text: /* Copyright 2012 Mozilla Foundation
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  /* globals PDFBug, Stats */
  
  import {
    animationStarted,
    apiPageLayoutToSpreadMode,
    apiPageModeToSidebarView,
    AutoPrintRegExp,
    DEFAULT_SCALE_VALUE,
    EventBus,
    getActiveOrFocusedElement,
    isValidRotation,
    isValidScrollMode,
    isValidSpreadMode,
    MAX_SCALE,
    MIN_SCALE,
    noContextMenuHandler,
    normalizeWheelEventDirection,
    parseQueryString,
    ProgressBar,
    RendererType,
    ScrollMode,
    SidebarView,
    SpreadMode,
    TextLayerMode,
    debounce
  } from "./ui_utils.js";
  import { AppOptions, OptionKind } from "./app_options.js";
  import {
    build,
    createPromiseCapability,
    getDocument,
    getFilenameFromUrl,
    getPdfFilenameFromUrl,
    GlobalWorkerOptions,
    InvalidPDFException,
    isPdfFile,
    LinkTarget,
    loadScript,
    MissingPDFException,
    OPS,
    PDFWorker,
    PermissionFlag,
    shadow,
    UnexpectedResponseException,
    UNSUPPORTED_FEATURES,
    version,
  } from "pdfjs-lib";
  import { CursorTool, PDFCursorTools } from "./pdf_cursor_tools.js";
  import { PDFRenderingQueue, RenderingStates } from "./pdf_rendering_queue.js";
  import { OverlayManager } from "./overlay_manager.js";
  import { PasswordPrompt } from "./password_prompt.js";
  import { PDFAttachmentViewer } from "./pdf_attachment_viewer.js";
  import { PDFDocumentProperties } from "./pdf_document_properties.js";
  import { PDFFindBar } from "./pdf_find_bar.js";
  import { PDFFindController } from "./pdf_find_controller.js";
  import { PDFHistory } from "./pdf_history.js";
  import { PDFLayerViewer } from "./pdf_layer_viewer.js";
  import { PDFLinkService } from "./pdf_link_service.js";
  import { PDFOutlineViewer } from "./pdf_outline_viewer.js";
  import { PDFPresentationMode } from "./pdf_presentation_mode.js";
  import { PDFScriptingManager } from "./pdf_scripting_manager.js";
  import { PDFSidebar } from "./pdf_sidebar.js";
  import { PDFSidebarResizer } from "./pdf_sidebar_resizer.js";
  import { PDFThumbnailViewer } from "./pdf_thumbnail_viewer.js";
  import { PDFViewer } from "./pdf_viewer.js";
  import { SecondaryToolbar } from "./secondary_toolbar.js";
  import { SelectionPopUp } from "./selectionPopup.js";
  import { Toolbar } from "./toolbar.js";
  import { viewerCompatibilityParams } from "./viewer_compatibility.js";
  import { ViewHistory } from "./view_history.js";
  
  const DEFAULT_SCALE_DELTA = 1.1;
  const DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000; // ms
  const FORCE_PAGES_LOADED_TIMEOUT = 10000; // ms
  const WHEEL_ZOOM_DISABLED_TIMEOUT = 1000; // ms
  const ENABLE_PERMISSIONS_CLASS = "enablePermissions";
  
  const ViewOnLoad = {
    UNKNOWN: -1,
    PREVIOUS: 0, // Default value.
    INITIAL: 1,
  };
  
  const ViewerCssTheme = {
    AUTOMATIC: 0, // Default value.
    LIGHT: 1,
    DARK: 2,
  };
  
  // Keep these in sync with mozilla-central's Histograms.json.
  const KNOWN_VERSIONS = [
    "1.0",
    "1.1",
    "1.2",
    "1.3",
    "1.4",
    "1.5",
    "1.6",
    "1.7",
    "1.8",
    "1.9",
    "2.0",
    "2.1",
    "2.2",
    "2.3",
  ];
  // Keep these in sync with mozilla-central's Histograms.json.
  const KNOWN_GENERATORS = [
    "acrobat distiller",
    "acrobat pdfwriter",
    "adobe livecycle",
    "adobe pdf library",
    "adobe photoshop",
    "ghostscript",
    "tcpdf",
    "cairo",
    "dvipdfm",
    "dvips",
    "pdftex",
    "pdfkit",
    "itext",
    "prince",
    "quarkxpress",
    "mac os x",
    "microsoft",
    "openoffice",
    "oracle",
    "luradocument",
    "pdf-xchange",
    "antenna house",
    "aspose.cells",
    "fpdf",
  ];
  
  class DefaultExternalServices {
    constructor() {
      throw new Error("Cannot initialize DefaultExternalServices.");
    }
  
    static updateFindControlState(data) {}
  
    static updateFindMatchesCount(data) {}
  
    static initPassiveLoading(callbacks) {}
  
    static async fallback(data) {}
  
    static reportTelemetry(data) {}
  
    static createDownloadManager(options) {
      throw new Error("Not implemented: createDownloadManager");
    }
  
    static createPreferences() {
      throw new Error("Not implemented: createPreferences");
    }
  
    static createL10n(options) {
      throw new Error("Not implemented: createL10n");
    }
  
    static createScripting(options) {
      throw new Error("Not implemented: createScripting");
    }
  
    static get supportsIntegratedFind() {
      return shadow(this, "supportsIntegratedFind", false);
    }
  
    static get supportsDocumentFonts() {
      return shadow(this, "supportsDocumentFonts", true);
    }
  
    static get supportedMouseWheelZoomModifierKeys() {
      return shadow(this, "supportedMouseWheelZoomModifierKeys", {
        ctrlKey: true,
        metaKey: true,
      });
    }
  
    static get isInAutomation() {
      return shadow(this, "isInAutomation", false);
    }
  }
  
  const PDFViewerApplication = {
    initialBookmark: document.location.hash.substring(1),
    _initializedCapability: createPromiseCapability(),
    _fellback: false,
    appConfig: null,
    pdfDocument: null,
    pdfLoadingTask: null,
    printService: null,
    /** @type {PDFViewer} */
    pdfViewer: null,
    /** @type {PDFThumbnailViewer} */
    pdfThumbnailViewer: null,
    /** @type {PDFRenderingQueue} */
    pdfRenderingQueue: null,
    /** @type {PDFPresentationMode} */
    pdfPresentationMode: null,
    /** @type {PDFDocumentProperties} */
    pdfDocumentProperties: null,
    /** @type {PDFLinkService} */
    pdfLinkService: null,
    /** @type {PDFHistory} */
    pdfHistory: null,
    /** @type {PDFSidebar} */
    pdfSidebar: null,
    /** @type {PDFSidebarResizer} */
    pdfSidebarResizer: null,
    /** @type {PDFOutlineViewer} */
    pdfOutlineViewer: null,
    /** @type {PDFAttachmentViewer} */
    pdfAttachmentViewer: null,
    /** @type {PDFLayerViewer} */
    pdfLayerViewer: null,
    /** @type {PDFCursorTools} */
    pdfCursorTools: null,
    /** @type {PDFScriptingManager} */
    pdfScriptingManager: null,
    /** @type {ViewHistory} */
    store: null,
    /** @type {DownloadManager} */
    downloadManager: null,
    /** @type {OverlayManager} */
    overlayManager: null,
    /** @type {Preferences} */
    preferences: null,
    selectionPopUp: null,
    /** @type {Toolbar} */
    toolbar: null,
    /** @type {SecondaryToolbar} */
    secondaryToolbar: null,
    /** @type {EventBus} */
    eventBus: null,
    /** @type {IL10n} */
    l10n: null,
    isInitialViewSet: false,
    downloadComplete: false,
    isViewerEmbedded: window.parent !== window,
    url: "",
    baseUrl: "",
    externalServices: DefaultExternalServices,
    _boundEvents: Object.create(null),
    documentInfo: null,
    metadata: null,
    _contentDispositionFilename: null,
    _contentLength: null,
    _saveInProgress: false,
    _wheelUnusedTicks: 0,
    _idleCallbacks: new Set(),
  
    // Called once when the document is loaded.
    async initialize(appConfig) {
      this.preferences = this.externalServices.createPreferences();
      this.appConfig = appConfig;
  
      await this._readPreferences();
      await this._parseHashParameters();
      this._forceCssTheme();
      await this._initializeL10n();
  
      if (
        this.isViewerEmbedded &&
        AppOptions.get("externalLinkTarget") === LinkTarget.NONE
      ) {
        // Prevent external links from "replacing" the viewer,
        // when it's embedded in e.g. an <iframe> or an <object>.
        AppOptions.set("externalLinkTarget", LinkTarget.TOP);
      }
      await this._initializeViewerComponents();
  
      // Bind the various event handlers *after* the viewer has been
      // initialized, to prevent errors if an event arrives too soon.
      this.bindEvents();
      this.bindWindowEvents();
  
      // We can start UI localization now.
      const appContainer = appConfig.appContainer || document.documentElement;
      this.l10n.translate(appContainer).then(() => {
        // Dispatch the 'localized' event on the `eventBus` once the viewer
        // has been fully initialized and translated.
        this.eventBus.dispatch("localized", { source: this });
      });
  
      this._initializedCapability.resolve();
    },
  
    /**
     * @private
     */
    async _readPreferences() {
      if (
        (typeof PDFJSDev === "undefined" ||
          PDFJSDev.test("!PRODUCTION || GENERIC")) &&
        AppOptions.get("disablePreferences")
      ) {
        // Give custom implementations of the default viewer a simpler way to
        // opt-out of having the `Preferences` override existing `AppOptions`.
        return;
      }
      try {
        AppOptions.setAll(await this.preferences.getAll());
      } catch (reason) {
        console.error(`_readPreferences: "${reason?.message}".`);
      }
    },
  
    /**
     * Potentially parse special debugging flags in the hash section of the URL.
     * @private
     */
    async _parseHashParameters() {
      if (!AppOptions.get("pdfBugEnabled")) {
        return undefined;
      }
      const hash = document.location.hash.substring(1);
      if (!hash) {
        return undefined;
      }
      const hashParams = parseQueryString(hash),
        waitOn = [];
  
      if ("disableworker" in hashParams && hashParams.disableworker === "true") {
        waitOn.push(loadFakeWorker());
      }
      if ("disablerange" in hashParams) {
        AppOptions.set("disableRange", hashParams.disablerange === "true");
      }
      if ("disablestream" in hashParams) {
        AppOptions.set("disableStream", hashParams.disablestream === "true");
      }
      if ("disableautofetch" in hashParams) {
        AppOptions.set(
          "disableAutoFetch",
          hashParams.disableautofetch === "true"
        );
      }
      if ("disablefontface" in hashParams) {
        AppOptions.set("disableFontFace", hashParams.disablefontface === "true");
      }
      if ("disablehistory" in hashParams) {
        AppOptions.set("disableHistory", hashParams.disablehistory === "true");
      }
      if ("verbosity" in hashParams) {
        AppOptions.set("verbosity", hashParams.verbosity | 0);
      }
      if ("textlayer" in hashParams) {
        switch (hashParams.textlayer) {
          case "off":
            AppOptions.set("textLayerMode", TextLayerMode.DISABLE);
            break;
          case "visible":
          case "shadow":
          case "hover":
            const viewer = this.appConfig.viewerContainer;
            viewer.classList.add("textLayer-" + hashParams.textlayer);
            break;
        }
      }
      if ("pdfbug" in hashParams) {
        AppOptions.set("pdfBug", true);
        AppOptions.set("fontExtraProperties", true);
  
        const enabled = hashParams.pdfbug.split(",");
        waitOn.push(loadAndEnablePDFBug(enabled));
      }
      // It is not possible to change locale for the (various) extension builds.
      if (
        (typeof PDFJSDev === "undefined" ||
          PDFJSDev.test("!PRODUCTION || GENERIC")) &&
        "locale" in hashParams
      ) {
        AppOptions.set("locale", hashParams.locale);
      }
  
      if (waitOn.length === 0) {
        return undefined;
      }
      return Promise.all(waitOn).catch(reason => {
        console.error(`_parseHashParameters: "${reason.message}".`);
      });
    },
  
    /**
     * @private
     */
    async _initializeL10n() {
      this.l10n = this.externalServices.createL10n(
        typeof PDFJSDev === "undefined" || PDFJSDev.test("!PRODUCTION || GENERIC")
          ? { locale: AppOptions.get("locale") }
          : null
      );
      const dir = await this.l10n.getDirection();
      document.getElementsByTagName("html")[0].dir = dir;
    },
  
    /**
     * @private
     */
    _forceCssTheme() {
      const cssTheme = AppOptions.get("viewerCssTheme");
      if (
        cssTheme === ViewerCssTheme.AUTOMATIC ||
        !Object.values(ViewerCssTheme).includes(cssTheme)
      ) {
        return;
      }
      try {
        const styleSheet = document.styleSheets[0];
        const cssRules = styleSheet?.cssRules || [];
        const mediaMatcher =
          typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")
            ? "-moz-toolbar-prefers-color-scheme"
            : "prefers-color-scheme";
        const mediaRule = `(${mediaMatcher}: dark)`;
        const mediaRegex = new RegExp(
          `^@media \\(${mediaMatcher}: dark\\) {\\n\\s*([\\w\\s-.,:;/\\\\{}()]+)\\n}$`
        );
        for (let i = 0, ii = cssRules.length; i < ii; i++) {
          const rule = cssRules[i];
          if (rule instanceof CSSMediaRule && rule.media?.[0] === mediaRule) {
            if (cssTheme === ViewerCssTheme.LIGHT) {
              styleSheet.deleteRule(i);
              return;
            }
            // cssTheme === ViewerCssTheme.DARK
            const darkRules = mediaRegex.exec(rule.cssText);
            if (darkRules?.[1]) {
              styleSheet.deleteRule(i);
              styleSheet.insertRule(darkRules[1], i);
            }
            return;
          }
        }
      } catch (reason) {
        console.error(`_forceCssTheme: "${reason?.message}".`);
      }
    },
  
    /**
     * @private
     */
    async _initializeViewerComponents() {
      const appConfig = this.appConfig;
  
      const eventBus =
        appConfig.eventBus ||
        new EventBus({ isInAutomation: this.externalServices.isInAutomation });
      this.eventBus = eventBus;
  
      this.overlayManager = new OverlayManager();
  
      const pdfRenderingQueue = new PDFRenderingQueue();
      pdfRenderingQueue.onIdle = this._cleanup.bind(this);
      this.pdfRenderingQueue = pdfRenderingQueue;
  
      const pdfLinkService = new PDFLinkService({
        eventBus,
        externalLinkTarget: AppOptions.get("externalLinkTarget"),
        externalLinkRel: AppOptions.get("externalLinkRel"),
        ignoreDestinationZoom: AppOptions.get("ignoreDestinationZoom"),
      });
      this.pdfLinkService = pdfLinkService;
  
      const downloadManager = this.externalServices.createDownloadManager();
      this.downloadManager = downloadManager;
  
      const findController = new PDFFindController({
        linkService: pdfLinkService,
        eventBus,
      });
      this.findController = findController;
  
      const pdfScriptingManager = new PDFScriptingManager({
        eventBus,
        sandboxBundleSrc:
          typeof PDFJSDev === "undefined" ||
          PDFJSDev.test("!PRODUCTION || GENERIC || CHROME")
            ? AppOptions.get("sandboxBundleSrc")
            : null,
        scriptingFactory: this.externalServices,
        docPropertiesLookup: this._scriptingDocProperties.bind(this),
      });
      this.pdfScriptingManager = pdfScriptingManager;
  
      const container = appConfig.mainContainer;
      const viewer = appConfig.viewerContainer;
      this.pdfViewer = new PDFViewer({
        container,
        viewer,
        eventBus,
        renderingQueue: pdfRenderingQueue,
        linkService: pdfLinkService,
        downloadManager,
        findController,
        scriptingManager: pdfScriptingManager,
        renderer: AppOptions.get("renderer"),
        l10n: this.l10n,
        textLayerMode: AppOptions.get("textLayerMode"),
        imageResourcesPath: AppOptions.get("imageResourcesPath"),
        renderInteractiveForms: AppOptions.get("renderInteractiveForms"),
        enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"),
        useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"),
        maxCanvasPixels: AppOptions.get("maxCanvasPixels"),
        enableScripting: AppOptions.get("enableScripting"),
      });
      pdfRenderingQueue.setViewer(this.pdfViewer);
      pdfLinkService.setViewer(this.pdfViewer);
      pdfScriptingManager.setViewer(this.pdfViewer);
  
      this.pdfThumbnailViewer = new PDFThumbnailViewer({
        container: appConfig.sidebar.thumbnailView,
        eventBus,
        renderingQueue: pdfRenderingQueue,
        linkService: pdfLinkService,
        l10n: this.l10n,
      });
      pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
  
      this.pdfHistory = new PDFHistory({
        linkService: pdfLinkService,
        eventBus,
      });
      pdfLinkService.setHistory(this.pdfHistory);
  
      if (!this.supportsIntegratedFind) {
        this.findBar = new PDFFindBar(appConfig.findBar, eventBus, this.l10n);
      }
  
      this.pdfDocumentProperties = new PDFDocumentProperties(
        appConfig.documentProperties,
        this.overlayManager,
        eventBus,
        this.l10n
      );
  
      this.pdfCursorTools = new PDFCursorTools({
        container,
        eventBus,
        cursorToolOnLoad: AppOptions.get("cursorToolOnLoad"),
      });
  
      this.toolbar = new Toolbar(appConfig.toolbar, eventBus, this.l10n);
  
      this.selectionPopUp = new SelectionPopUp(appConfig.selectionPopUp, eventBus);
  
      this.secondaryToolbar = new SecondaryToolbar(
        appConfig.secondaryToolbar,
        container,
        eventBus
      );
  
      if (this.supportsFullscreen) {
        this.pdfPresentationMode = new PDFPresentationMode({
          container,
          pdfViewer: this.pdfViewer,
          eventBus,
        });
      }
  
      this.passwordPrompt = new PasswordPrompt(
        appConfig.passwordOverlay,
        this.overlayManager,
        this.l10n,
        this.isViewerEmbedded
      );
  
      this.pdfOutlineViewer = new PDFOutlineViewer({
        container: appConfig.sidebar.outlineView,
        eventBus,
        linkService: pdfLinkService,
      });
  
      this.pdfAttachmentViewer = new PDFAttachmentViewer({
        container: appConfig.sidebar.attachmentsView,
        eventBus,
        downloadManager,
      });
  
      this.pdfLayerViewer = new PDFLayerViewer({
        container: appConfig.sidebar.layersView,
        eventBus,
        l10n: this.l10n,
      });
  
      this.pdfSidebar = new PDFSidebar({
        elements: appConfig.sidebar,
        pdfViewer: this.pdfViewer,
        pdfThumbnailViewer: this.pdfThumbnailViewer,
        eventBus,
        l10n: this.l10n,
      });
      this.pdfSidebar.onToggled = this.forceRendering.bind(this);
  
      this.pdfSidebarResizer = new PDFSidebarResizer(
        appConfig.sidebarResizer,
        eventBus,
        this.l10n
      );
    },
  
    run(config) {
      this.initialize(config).then(webViewerInitialized);
    },
  
    get initialized() {
      return this._initializedCapability.settled;
    },
  
    get initializedPromise() {
      return this._initializedCapability.promise;
    },
  
    zoomIn(ticks) {
      if (this.pdfViewer.isInPresentationMode) {
        return;
      }
      let newScale = this.pdfViewer.currentScale;
      do {
        newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
        newScale = Math.ceil(newScale * 10) / 10;
        newScale = Math.min(MAX_SCALE, newScale);
      } while (--ticks > 0 && newScale < MAX_SCALE);
      this.pdfViewer.currentScaleValue = newScale;
    },
  
    zoomOut(ticks) {
      if (this.pdfViewer.isInPresentationMode) {
        return;
      }
      let newScale = this.pdfViewer.currentScale;
      do {
        newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
        newScale = Math.floor(newScale * 10) / 10;
        newScale = Math.max(MIN_SCALE, newScale);
      } while (--ticks > 0 && newScale > MIN_SCALE);
      this.pdfViewer.currentScaleValue = newScale;
    },
  
    zoomReset() {
      if (this.pdfViewer.isInPresentationMode) {
        return;
      }
      this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
    },
  
    get pagesCount() {
      return this.pdfDocument ? this.pdfDocument.numPages : 0;
    },
  
    get page() {
      return this.pdfViewer.currentPageNumber;
    },
  
    set page(val) {
      this.pdfViewer.currentPageNumber = val;
    },
  
    get supportsPrinting() {
      return PDFPrintServiceFactory.instance.supportsPrinting;
    },
  
    get supportsFullscreen() {
      if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
        return shadow(this, "supportsFullscreen", document.fullscreenEnabled);
      }
      const doc = document.documentElement;
      let support = !!(
        doc.requestFullscreen ||
        doc.mozRequestFullScreen ||
        doc.webkitRequestFullScreen
      );
  
      if (
        document.fullscreenEnabled === false ||
        document.mozFullScreenEnabled === false ||
        document.webkitFullscreenEnabled === false
      ) {
        support = false;
      }
      return shadow(this, "supportsFullscreen", support);
    },
  
    get supportsIntegratedFind() {
      return this.externalServices.supportsIntegratedFind;
    },
  
    get supportsDocumentFonts() {
      return this.externalServices.supportsDocumentFonts;
    },
  
    get loadingBar() {
      const bar = new ProgressBar("#loadingBar");
      return shadow(this, "loadingBar", bar);
    },
  
    get supportedMouseWheelZoomModifierKeys() {
      return this.externalServices.supportedMouseWheelZoomModifierKeys;
    },
  
    initPassiveLoading() {
      if (
        typeof PDFJSDev === "undefined" ||
        !PDFJSDev.test("MOZCENTRAL || CHROME")
      ) {
        throw new Error("Not implemented: initPassiveLoading");
      }
      this.externalServices.initPassiveLoading({
        onOpenWithTransport: (url, length, transport) => {
          this.open(url, { length, range: transport });
        },
        onOpenWithData: (data, contentDispositionFilename) => {
          if (isPdfFile(contentDispositionFilename)) {
            this._contentDispositionFilename = contentDispositionFilename;
          }
          this.open(data);
        },
        onOpenWithURL: (url, length, originalUrl) => {
          const file = originalUrl !== undefined ? { url, originalUrl } : url;
          const args = length !== undefined ? { length } : null;
  
          this.open(file, args);
        },
        onError: err => {
          this.l10n.get("loading_error").then(msg => {
            this._documentError(msg, err);
          });
        },
        onProgress: (loaded, total) => {
          this.progress(loaded / total);
        },
      });
    },
  
    setTitleUsingUrl(url = "") {
      this.url = url;
      this.baseUrl = url.split("#")[0];
      let title = getPdfFilenameFromUrl(url, "");
      if (!title) {
        try {
          title = decodeURIComponent(getFilenameFromUrl(url)) || url;
        } catch (ex) {
          // decodeURIComponent may throw URIError,
          // fall back to using the unprocessed url in that case
          title = url;
        }
      }
      this.setTitle(title);
    },
  
    setTitle(title) {
      if (this.isViewerEmbedded) {
        // Embedded PDF viewers should not be changing their parent page's title.
        return;
      }
      document.title = title;
    },
  
    get _docFilename() {
      // Use `this.url` instead of `this.baseUrl` to perform filename detection
      // based on the reference fragment as ultimate fallback if needed.
      return this._contentDispositionFilename || getPdfFilenameFromUrl(this.url);
    },
  
    /**
     * @private
     */
    _cancelIdleCallbacks() {
      if (!this._idleCallbacks.size) {
        return;
      }
      for (const callback of this._idleCallbacks) {
        window.cancelIdleCallback(callback);
      }
      this._idleCallbacks.clear();
    },
  
    /**
     * Closes opened PDF document.
     * @returns {Promise} - Returns the promise, which is resolved when all
     *                      destruction is completed.
     */
    async close() {
      this._unblockDocumentLoadEvent();
  
      if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
        const { container } = this.appConfig.errorWrapper;
        container.hidden = true;
      }
  
      if (!this.pdfLoadingTask) {
        return;
      }
      if (
        (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) &&
        this.pdfDocument?.annotationStorage.size > 0 &&
        this._annotationStorageModified
      ) {
        try {
          // Trigger saving, to prevent data loss in forms; see issue 12257.
          await this.save({ sourceEventType: "save" });
        } catch (reason) {
          // Ignoring errors, to ensure that document closing won't break.
        }
      }
      const promises = [];
  
      promises.push(this.pdfLoadingTask.destroy());
      this.pdfLoadingTask = null;
  
      if (this.pdfDocument) {
        this.pdfDocument = null;
  
        this.pdfThumbnailViewer.setDocument(null);
        this.pdfViewer.setDocument(null);
        this.pdfLinkService.setDocument(null);
        this.pdfDocumentProperties.setDocument(null);
      }
      webViewerResetPermissions();
      this.pdfLinkService.externalLinkEnabled = true;
      this._fellback = false;
      this.store = null;
      this.isInitialViewSet = false;
      this.downloadComplete = false;
      this.url = "";
      this.baseUrl = "";
      this.documentInfo = null;
      this.metadata = null;
      this._contentDispositionFilename = null;
      this._contentLength = null;
      this._saveInProgress = false;
  
      this._cancelIdleCallbacks();
      promises.push(this.pdfScriptingManager.destroyPromise);
  
      this.pdfSidebar.reset();
      this.pdfOutlineViewer.reset();
      this.pdfAttachmentViewer.reset();
      this.pdfLayerViewer.reset();
  
      if (this.pdfHistory) {
        this.pdfHistory.reset();
      }
      if (this.findBar) {
        this.findBar.reset();
      }
      this.toolbar.reset();
      this.secondaryToolbar.reset();
  
      if (typeof PDFBug !== "undefined") {
        PDFBug.cleanup();
      }
      await Promise.all(promises);
    },
  
    /**
     * Opens PDF document specified by URL or array with additional arguments.
     * @param {string|TypedArray|ArrayBuffer} file - PDF location or binary data.
     * @param {Object} [args] - Additional arguments for the getDocument call,
     *                          e.g. HTTP headers ('httpHeaders') or alternative
     *                          data transport ('range').
     * @returns {Promise} - Returns the promise, which is resolved when document
     *                      is opened.
     */
    async open(file, args) {
      if (this.pdfLoadingTask) {
        // We need to destroy already opened document.
        await this.close();
      }
      // Set the necessary global worker parameters, using the available options.
      const workerParameters = AppOptions.getAll(OptionKind.WORKER);
      for (const key in workerParameters) {
        GlobalWorkerOptions[key] = workerParameters[key];
      }
  
      const parameters = Object.create(null);
      if (typeof file === "string") {
        // URL
        this.setTitleUsingUrl(file);
        parameters.url = file;
      } else if (file && "byteLength" in file) {
        // ArrayBuffer
        parameters.data = file;
      } else if (file.url && file.originalUrl) {
        this.setTitleUsingUrl(file.originalUrl);
        parameters.url = file.url;
      }
      // Set the necessary API parameters, using the available options.
      const apiParameters = AppOptions.getAll(OptionKind.API);
      for (const key in apiParameters) {
        let value = apiParameters[key];
  
        if (key === "docBaseUrl" && !value) {
          if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
            value = document.URL.split("#")[0];
          } else if (PDFJSDev.test("MOZCENTRAL || CHROME")) {
            value = this.baseUrl;
          }
        }
        parameters[key] = value;
      }
      // Finally, update the API parameters with the arguments (if they exist).
      if (args) {
        for (const key in args) {
          parameters[key] = args[key];
        }
      }
  
      const loadingTask = getDocument(parameters);
      this.pdfLoadingTask = loadingTask;
  
      loadingTask.onPassword = (updateCallback, reason) => {
        this.pdfLinkService.externalLinkEnabled = false;
        this.passwordPrompt.setUpdateCallback(updateCallback, reason);
        this.passwordPrompt.open();
      };
  
      loadingTask.onProgress = ({ loaded, total }) => {
        this.progress(loaded / total);
      };
  
      // Listen for unsupported features to trigger the fallback UI.
      loadingTask.onUnsupportedFeature = this.fallback.bind(this);
  
      return loadingTask.promise.then(
        pdfDocument => {
          this.load(pdfDocument);
        },
        exception => {
          if (loadingTask !== this.pdfLoadingTask) {
            return undefined; // Ignore errors for previously opened PDF files.
          }
  
          let key = "loading_error";
          if (exception instanceof InvalidPDFException) {
            key = "invalid_file_error";
          } else if (exception instanceof MissingPDFException) {
            key = "missing_file_error";
          } else if (exception instanceof UnexpectedResponseException) {
            key = "unexpected_response_error";
          }
          return this.l10n.get(key).then(msg => {
            this._documentError(msg, { message: exception?.message });
            throw exception;
          });
        }
      );
    },
  
    /**
     * @private
     */
    _ensureDownloadComplete() {
      if (this.pdfDocument && this.downloadComplete) {
        return;
      }
      throw new Error("PDF document not downloaded.");
    },
  
    async download({ sourceEventType = "download" } = {}) {
      const url = this.baseUrl,
        filename = this._docFilename;
      try {
        this._ensureDownloadComplete();
  
        const data = await this.pdfDocument.getData();
        const blob = new Blob([data], { type: "application/pdf" });
  
        await this.downloadManager.download(blob, url, filename, sourceEventType);
      } catch (reason) {
        // When the PDF document isn't ready, or the PDF file is still
        // downloading, simply download using the URL.
        await this.downloadManager.downloadUrl(url, filename);
      }
    },
  
    async save({ sourceEventType = "download" } = {}) {
      if (this._saveInProgress) {
        return;
      }
      this._saveInProgress = true;
      await this.pdfScriptingManager.dispatchWillSave();
  
      const url = this.baseUrl,
        filename = this._docFilename;
      try {
        this._ensureDownloadComplete();
  
        const data = await this.pdfDocument.saveDocument();
        const blob = new Blob([data], { type: "application/pdf" });
  
        await this.downloadManager.download(blob, url, filename, sourceEventType);
      } catch (reason) {
        // When the PDF document isn't ready, or the PDF file is still
        // downloading, simply fallback to a "regular" download.
        await this.download({ sourceEventType });
      } finally {
        await this.pdfScriptingManager.dispatchDidSave();
        this._saveInProgress = false;
      }
    },
  
    downloadOrSave(options) {
      if (this.pdfDocument?.annotationStorage.size > 0) {
        this.save(options);
      } else {
        this.download(options);
      }
    },
  
    fallback(featureId) {
      this.externalServices.reportTelemetry({
        type: "unsupportedFeature",
        featureId,
      });
  
      // Only trigger the fallback once so we don't spam the user with messages
      // for one PDF.
      if (this._fellback) {
        return;
      }
      this._fellback = true;
  
      this.externalServices
        .fallback({
          featureId,
          url: this.baseUrl,
        })
        .then(download => {
          if (!download) {
            return;
          }
          this.download({ sourceEventType: "download" });
        });
    },
  
    /**
     * Show the error box; used for errors affecting loading and/or parsing of
     * the entire PDF document.
     */
    _documentError(message, moreInfo = null) {
      this._unblockDocumentLoadEvent();
  
      this._otherError(message, moreInfo);
    },
  
    /**
     * Show the error box; used for errors affecting e.g. only a single page.
     *
     * @param {string} message - A message that is human readable.
     * @param {Object} [moreInfo] - Further information about the error that is
     *                              more technical.  Should have a 'message' and
     *                              optionally a 'stack' property.
     */
    _otherError(message, moreInfo = null) {
      const moreInfoText = [
        this.l10n.get("error_version_info", {
          version: version || "?",
          build: build || "?",
        }),
      ];
      if (moreInfo) {
        moreInfoText.push(
          this.l10n.get("error_message", { message: moreInfo.message })
        );
        if (moreInfo.stack) {
          moreInfoText.push(
            this.l10n.get("error_stack", { stack: moreInfo.stack })
          );
        } else {
          if (moreInfo.filename) {
            moreInfoText.push(
              this.l10n.get("error_file", { file: moreInfo.filename })
            );
          }
          if (moreInfo.lineNumber) {
            moreInfoText.push(
              this.l10n.get("error_line", { line: moreInfo.lineNumber })
            );
          }
        }
      }
  
      if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
        const errorWrapperConfig = this.appConfig.errorWrapper;
        const errorWrapper = errorWrapperConfig.container;
        errorWrapper.hidden = false;
  
        const errorMessage = errorWrapperConfig.errorMessage;
        errorMessage.textContent = message;
  
        const closeButton = errorWrapperConfig.closeButton;
        closeButton.onclick = function () {
          errorWrapper.hidden = true;
        };
  
        const errorMoreInfo = errorWrapperConfig.errorMoreInfo;
        const moreInfoButton = errorWrapperConfig.moreInfoButton;
        const lessInfoButton = errorWrapperConfig.lessInfoButton;
        moreInfoButton.onclick = function () {
          errorMoreInfo.hidden = false;
          moreInfoButton.hidden = true;
          lessInfoButton.hidden = false;
          errorMoreInfo.style.height = errorMoreInfo.scrollHeight + "px";
        };
        lessInfoButton.onclick = function () {
          errorMoreInfo.hidden = true;
          moreInfoButton.hidden = false;
          lessInfoButton.hidden = true;
        };
        moreInfoButton.oncontextmenu = noContextMenuHandler;
        lessInfoButton.oncontextmenu = noContextMenuHandler;
        closeButton.oncontextmenu = noContextMenuHandler;
        moreInfoButton.hidden = false;
        lessInfoButton.hidden = true;
        Promise.all(moreInfoText).then(parts => {
          errorMoreInfo.value = parts.join("\n");
        });
      } else {
        Promise.all(moreInfoText).then(parts => {
          console.error(message + "\n" + parts.join("\n"));
        });
        this.fallback();
      }
    },
  
    progress(level) {
      if (this.downloadComplete) {
        // Don't accidentally show the loading bar again when the entire file has
        // already been fetched (only an issue when disableAutoFetch is enabled).
        return;
      }
      const percent = Math.round(level * 100);
      // When we transition from full request to range requests, it's possible
      // that we discard some of the loaded data. This can cause the loading
      // bar to move backwards. So prevent this by only updating the bar if it
      // increases.
      if (percent > this.loadingBar.percent || isNaN(percent)) {
        this.loadingBar.percent = percent;
  
        // When disableAutoFetch is enabled, it's not uncommon for the entire file
        // to never be fetched (depends on e.g. the file structure). In this case
        // the loading bar will not be completely filled, nor will it be hidden.
        // To prevent displaying a partially filled loading bar permanently, we
        // hide it when no data has been loaded during a certain amount of time.
        const disableAutoFetch = this.pdfDocument
          ? this.pdfDocument.loadingParams.disableAutoFetch
          : AppOptions.get("disableAutoFetch");
  
        if (disableAutoFetch && percent) {
          if (this.disableAutoFetchLoadingBarTimeout) {
            clearTimeout(this.disableAutoFetchLoadingBarTimeout);
            this.disableAutoFetchLoadingBarTimeout = null;
          }
          this.loadingBar.show();
  
          this.disableAutoFetchLoadingBarTimeout = setTimeout(() => {
            this.loadingBar.hide();
            this.disableAutoFetchLoadingBarTimeout = null;
          }, DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT);
        }
      }
    },
  
    load(pdfDocument) {
      this.pdfDocument = pdfDocument;
  
      pdfDocument.getDownloadInfo().then(({ length }) => {
        this._contentLength = length; // Ensure that the correct length is used.
        this.downloadComplete = true;
        this.loadingBar.hide();
  
        firstPagePromise.then(() => {
          this.eventBus.dispatch("documentloaded", { source: this });
        });
      });
  
      // Since the `setInitialView` call below depends on this being resolved,
      // fetch it early to avoid delaying initial rendering of the PDF document.
      const pageLayoutPromise = pdfDocument.getPageLayout().catch(function () {
        /* Avoid breaking initial rendering; ignoring errors. */
      });
      const pageModePromise = pdfDocument.getPageMode().catch(function () {
        /* Avoid breaking initial rendering; ignoring errors. */
      });
      const openActionPromise = pdfDocument.getOpenAction().catch(function () {
        /* Avoid breaking initial rendering; ignoring errors. */
      });
  
      this.toolbar.setPagesCount(pdfDocument.numPages, false);
      this.secondaryToolbar.setPagesCount(pdfDocument.numPages);
  
      let baseDocumentUrl;
      if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
        baseDocumentUrl = null;
      } else if (PDFJSDev.test("MOZCENTRAL")) {
        baseDocumentUrl = this.baseUrl;
      } else if (PDFJSDev.test("CHROME")) {
        baseDocumentUrl = location.href.split("#")[0];
      }
      this.pdfLinkService.setDocument(pdfDocument, baseDocumentUrl);
      this.pdfDocumentProperties.setDocument(pdfDocument, this.url);
  
      const pdfViewer = this.pdfViewer;
      pdfViewer.setDocument(pdfDocument);
      const { firstPagePromise, onePageRendered, pagesPromise } = pdfViewer;
  
      const pdfThumbnailViewer = this.pdfThumbnailViewer;
      pdfThumbnailViewer.setDocument(pdfDocument);
  
      const storedPromise = (this.store = new ViewHistory(
        pdfDocument.fingerprints[0]
      ))
        .getMultiple({
          page: null,
          zoom: DEFAULT_SCALE_VALUE,
          scrollLeft: "0",
          scrollTop: "0",
          rotation: null,
          sidebarView: SidebarView.UNKNOWN,
          scrollMode: ScrollMode.UNKNOWN,
          spreadMode: SpreadMode.UNKNOWN,
        })
        .catch(() => {
          /* Unable to read from storage; ignoring errors. */
          return Object.create(null);
        });
  
      firstPagePromise.then(pdfPage => {
        this.loadingBar.setWidth(this.appConfig.viewerContainer);
        this._initializeAnnotationStorageCallbacks(pdfDocument);
  
        Promise.all([
          animationStarted,
          storedPromise,
          pageLayoutPromise,
          pageModePromise,
          openActionPromise,
        ])
          .then(async ([timeStamp, stored, pageLayout, pageMode, openAction]) => {
            const viewOnLoad = AppOptions.get("viewOnLoad");
  
            this._initializePdfHistory({
              fingerprint: pdfDocument.fingerprints[0],
              viewOnLoad,
              initialDest: openAction?.dest,
            });
            const initialBookmark = this.initialBookmark;
  
            // Initialize the default values, from user preferences.
            const zoom = AppOptions.get("defaultZoomValue");
            let hash = zoom ? `zoom=${zoom}` : null;
  
            let rotation = null;
            let sidebarView = AppOptions.get("sidebarViewOnLoad");
            let scrollMode = AppOptions.get("scrollModeOnLoad");
            let spreadMode = AppOptions.get("spreadModeOnLoad");
  
            if (stored.page && viewOnLoad !== ViewOnLoad.INITIAL) {
              hash =
                `page=${stored.page}&zoom=${zoom || stored.zoom},` +
                `${stored.scrollLeft},${stored.scrollTop}`;
  
              rotation = parseInt(stored.rotation, 10);
              // Always let user preference take precedence over the view history.
              if (sidebarView === SidebarView.UNKNOWN) {
                sidebarView = stored.sidebarView | 0;
              }
              if (scrollMode === ScrollMode.UNKNOWN) {
                scrollMode = stored.scrollMode | 0;
              }
              if (spreadMode === SpreadMode.UNKNOWN) {
                spreadMode = stored.spreadMode | 0;
              }
            }
            // Always let the user preference/view history take precedence.
            if (pageMode && sidebarView === SidebarView.UNKNOWN) {
              sidebarView = apiPageModeToSidebarView(pageMode);
            }
            if (pageLayout && spreadMode === SpreadMode.UNKNOWN) {
              spreadMode = apiPageLayoutToSpreadMode(pageLayout);
            }
  
            this.setInitialView(hash, {
              rotation,
              sidebarView,
              scrollMode,
              spreadMode,
            });
            this.eventBus.dispatch("documentinit", { source: this });
            // Make all navigation keys work on document load,
            // unless the viewer is embedded in a web page.
            if (!this.isViewerEmbedded) {
              pdfViewer.focus();
            }
  
            // Currently only the "copy"-permission is supported, hence we delay
            // the `getPermissions` API call until *after* rendering has started.
            this._initializePermissions(pdfDocument);
  
            // For documents with different page sizes, once all pages are
            // resolved, ensure that the correct location becomes visible on load.
            // (To reduce the risk, in very large and/or slow loading documents,
            //  that the location changes *after* the user has started interacting
            //  with the viewer, wait for either `pagesPromise` or a timeout.)
            await Promise.race([
              pagesPromise,
              new Promise(resolve => {
                setTimeout(resolve, FORCE_PAGES_LOADED_TIMEOUT);
              }),
            ]);
            if (!initialBookmark && !hash) {
              return;
            }
            if (pdfViewer.hasEqualPageSizes) {
              return;
            }
            this.initialBookmark = initialBookmark;
  
            // eslint-disable-next-line no-self-assign
            pdfViewer.currentScaleValue = pdfViewer.currentScaleValue;
            // Re-apply the initial document location.
            this.setInitialView(hash);
          })
          .catch(() => {
            // Ensure that the document is always completely initialized,
            // even if there are any errors thrown above.
            this.setInitialView();
          })
          .then(function () {
            // At this point, rendering of the initial page(s) should always have
            // started (and may even have completed).
            // To prevent any future issues, e.g. the document being completely
            // blank on load, always trigger rendering here.
            pdfViewer.update();
          });
      });
  
      pagesPromise.then(() => {
        this._unblockDocumentLoadEvent();
  
        this._initializeAutoPrint(pdfDocument, openActionPromise);
      });
  
      onePageRendered.then(() => {
        pdfDocument.getOutline().then(outline => {
          if (pdfDocument !== this.pdfDocument) {
            return; // The document was closed while the outline resolved.
          }
          this.pdfOutlineViewer.render({ outline, pdfDocument });
        });
        pdfDocument.getAttachments().then(attachments => {
          if (pdfDocument !== this.pdfDocument) {
            return; // The document was closed while the attachments resolved.
          }
          this.pdfAttachmentViewer.render({ attachments });
        });
        // Ensure that the layers accurately reflects the current state in the
        // viewer itself, rather than the default state provided by the API.
        pdfViewer.optionalContentConfigPromise.then(optionalContentConfig => {
          if (pdfDocument !== this.pdfDocument) {
            return; // The document was closed while the layers resolved.
          }
          this.pdfLayerViewer.render({ optionalContentConfig, pdfDocument });
        });
        if (
          (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) ||
          "requestIdleCallback" in window
        ) {
          const callback = window.requestIdleCallback(
            () => {
              this._collectTelemetry(pdfDocument);
              this._idleCallbacks.delete(callback);
            },
            { timeout: 1000 }
          );
          this._idleCallbacks.add(callback);
        }
      });
  
      this._initializePageLabels(pdfDocument);
      this._initializeMetadata(pdfDocument);
    },
  
    /**
     * @private
     */
    async _scriptingDocProperties(pdfDocument) {
      if (!this.documentInfo) {
        // It should be *extremely* rare for metadata to not have been resolved
        // when this code runs, but ensure that we handle that case here.
        await new Promise(resolve => {
          this.eventBus._on("metadataloaded", resolve, { once: true });
        });
        if (pdfDocument !== this.pdfDocument) {
          return null; // The document was closed while the metadata resolved.
        }
      }
      if (!this._contentLength) {
        // Always waiting for the entire PDF document to be loaded will, most
        // likely, delay sandbox-creation too much in the general case for all
        // PDF documents which are not provided as binary data to the API.
        // Hence we'll simply have to trust that the `contentLength` (as provided
        // by the server), when it exists, is accurate enough here.
        await new Promise(resolve => {
          this.eventBus._on("documentloaded", resolve, { once: true });
        });
        if (pdfDocument !== this.pdfDocument) {
          return null; // The document was closed while the downloadInfo resolved.
        }
      }
  
      return {
        ...this.documentInfo,
        baseURL: this.baseUrl,
        filesize: this._contentLength,
        filename: this._docFilename,
        metadata: this.metadata?.getRaw(),
        authors: this.metadata?.get("dc:creator"),
        numPages: this.pagesCount,
        URL: this.url,
      };
    },
  
    /**
     * A place to fetch data for telemetry after one page is rendered and the
     * viewer is idle.
     * @private
     */
    async _collectTelemetry(pdfDocument) {
      const markInfo = await this.pdfDocument.getMarkInfo();
      if (pdfDocument !== this.pdfDocument) {
        return; // Document was closed while waiting for mark info.
      }
      const tagged = markInfo?.Marked || false;
      this.externalServices.reportTelemetry({
        type: "tagged",
        tagged,
      });
    },
  
    /**
     * @private
     */
    async _initializeAutoPrint(pdfDocument, openActionPromise) {
      const [openAction, javaScript] = await Promise.all([
        openActionPromise,
        !this.pdfViewer.enableScripting ? pdfDocument.getJavaScript() : null,
      ]);
  
      if (pdfDocument !== this.pdfDocument) {
        return; // The document was closed while the auto print data resolved.
      }
      let triggerAutoPrint = false;
  
      if (openAction?.action === "Print") {
        triggerAutoPrint = true;
      }
      if (javaScript) {
        javaScript.some(js => {
          if (!js) {
            // Don't warn/fallback for empty JavaScript actions.
            return false;
          }
          console.warn("Warning: JavaScript support is not enabled");
          this.fallback(UNSUPPORTED_FEATURES.javaScript);
          return true;
        });
  
        if (!triggerAutoPrint) {
          // Hack to support auto printing.
          for (const js of javaScript) {
            if (js && AutoPrintRegExp.test(js)) {
              triggerAutoPrint = true;
              break;
            }
          }
        }
      }
  
      if (triggerAutoPrint) {
        this.triggerPrinting();
      }
    },
  
    /**
     * @private
     */
    async _initializeMetadata(pdfDocument) {
      const { info, metadata, contentDispositionFilename, contentLength } =
        await pdfDocument.getMetadata();
  
      if (pdfDocument !== this.pdfDocument) {
        return; // The document was closed while the metadata resolved.
      }
      this.documentInfo = info;
      this.metadata = metadata;
      this._contentDispositionFilename ??= contentDispositionFilename;
      this._contentLength ??= contentLength; // See `getDownloadInfo`-call above.
  
      // Provides some basic debug information
      console.log(
        `PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
          `${(info.Producer || "-").trim()} / ${(info.Creator || "-").trim()}] ` +
          `(PDF.js: ${version || "-"})`
      );
  
      let pdfTitle = info?.Title;
  
      const metadataTitle = metadata?.get("dc:title");
      if (metadataTitle) {
        // Ghostscript can produce invalid 'dc:title' Metadata entries:
        //  - The title may be "Untitled" (fixes bug 1031612).
        //  - The title may contain incorrectly encoded characters, which thus
        //    looks broken, hence we ignore the Metadata entry when it
        //    contains characters from the Specials Unicode block
        //    (fixes bug 1605526).
        if (
          metadataTitle !== "Untitled" &&
          !/[\uFFF0-\uFFFF]/g.test(metadataTitle)
        ) {
          pdfTitle = metadataTitle;
        }
      }
      if (pdfTitle) {
        this.setTitle(
          `${pdfTitle} - ${contentDispositionFilename || document.title}`
        );
      } else if (contentDispositionFilename) {
        this.setTitle(contentDispositionFilename);
      }
  
      if (
        info.IsXFAPresent &&
        !info.IsAcroFormPresent &&
        // Note: `isPureXfa === true` implies that `enableXfa = true` was set.
        !pdfDocument.isPureXfa
      ) {
        console.warn("Warning: XFA is not enabled");
        this.fallback(UNSUPPORTED_FEATURES.forms);
      } else if (
        (info.IsAcroFormPresent || info.IsXFAPresent) &&
        !this.pdfViewer.renderInteractiveForms
      ) {
        console.warn("Warning: Interactive form support is not enabled");
        this.fallback(UNSUPPORTED_FEATURES.forms);
      }
  
      if (info.IsSignaturesPresent) {
        console.warn("Warning: Digital signatures validation is not supported");
        this.fallback(UNSUPPORTED_FEATURES.signatures);
      }
  
      // Telemetry labels must be C++ variable friendly.
      let versionId = "other";
      if (KNOWN_VERSIONS.includes(info.PDFFormatVersion)) {
        versionId = `v${info.PDFFormatVersion.replace(".", "_")}`;
      }
      let generatorId = "other";
      if (info.Producer) {
        const producer = info.Producer.toLowerCase();
        KNOWN_GENERATORS.some(function (generator) {
          if (!producer.includes(generator)) {
            return false;
          }
          generatorId = generator.replace(/[ .-]/g, "_");
          return true;
        });
      }
      let formType = null;
      if (info.IsXFAPresent) {
        formType = "xfa";
      } else if (info.IsAcroFormPresent) {
        formType = "acroform";
      }
      this.externalServices.reportTelemetry({
        type: "documentInfo",
        version: versionId,
        generator: generatorId,
        formType,
      });
  
      this.eventBus.dispatch("metadataloaded", { source: this });
    },
  
    /**
     * @private
     */
    async _initializePageLabels(pdfDocument) {
      const labels = await pdfDocument.getPageLabels();
  
      if (pdfDocument !== this.pdfDocument) {
        return; // The document was closed while the page labels resolved.
      }
      if (!labels || AppOptions.get("disablePageLabels")) {
        return;
      }
      const numLabels = labels.length;
      if (numLabels !== this.pagesCount) {
        console.error(
          "The number of Page Labels does not match the number of pages in the document."
        );
        return;
      }
      let i = 0;
      // Ignore page labels that correspond to standard page numbering.
      while (i < numLabels && labels[i] === (i + 1).toString()) {
        i++;
      }
      if (i === numLabels) {
        return;
      }
      const { pdfViewer, pdfThumbnailViewer, toolbar } = this;
  
      pdfViewer.setPageLabels(labels);
      pdfThumbnailViewer.setPageLabels(labels);
  
      // Changing toolbar page display to use labels and we need to set
      // the label of the current page.
      toolbar.setPagesCount(numLabels, true);
      toolbar.setPageNumber(
        pdfViewer.currentPageNumber,
        pdfViewer.currentPageLabel
      );
    },
  
    /**
     * @private
     */
    _initializePdfHistory({ fingerprint, viewOnLoad, initialDest = null }) {
      if (this.isViewerEmbedded || AppOptions.get("disableHistory")) {
        // The browsing history is only enabled when the viewer is standalone,
        // i.e. not when it is embedded in a web page.
        return;
      }
      this.pdfHistory.initialize({
        fingerprint,
        resetHistory: viewOnLoad === ViewOnLoad.INITIAL,
        updateUrl: AppOptions.get("historyUpdateUrl"),
      });
  
      if (this.pdfHistory.initialBookmark) {
        this.initialBookmark = this.pdfHistory.initialBookmark;
  
        this.initialRotation = this.pdfHistory.initialRotation;
      }
  
      // Always let the browser history/document hash take precedence.
      if (
        initialDest &&
        !this.initialBookmark &&
        viewOnLoad === ViewOnLoad.UNKNOWN
      ) {
        this.initialBookmark = JSON.stringify(initialDest);
        // TODO: Re-factor the `PDFHistory` initialization to remove this hack
        // that's currently necessary to prevent weird initial history state.
        this.pdfHistory.push({ explicitDest: initialDest, pageNumber: null });
      }
    },
  
    /**
     * @private
     */
    async _initializePermissions(pdfDocument) {
      const permissions = await pdfDocument.getPermissions();
  
      if (pdfDocument !== this.pdfDocument) {
        return; // The document was closed while the permissions resolved.
      }
      if (!permissions || !AppOptions.get("enablePermissions")) {
        return;
      }
      // Currently only the "copy"-permission is supported.
      if (!permissions.includes(PermissionFlag.COPY)) {
        this.appConfig.viewerContainer.classList.add(ENABLE_PERMISSIONS_CLASS);
      }
    },
  
    /**
     * @private
     */
    _initializeAnnotationStorageCallbacks(pdfDocument) {
      if (pdfDocument !== this.pdfDocument) {
        return;
      }
      const { annotationStorage } = pdfDocument;
  
      annotationStorage.onSetModified = () => {
        window.addEventListener("beforeunload", beforeUnload);
  
        if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
          this._annotationStorageModified = true;
        }
      };
      annotationStorage.onResetModified = () => {
        window.removeEventListener("beforeunload", beforeUnload);
  
        if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
          delete this._annotationStorageModified;
        }
      };
    },
  
    setInitialView(
      storedHash,
      { rotation, sidebarView, scrollMode, spreadMode } = {}
    ) {
      const setRotation = angle => {
        if (isValidRotation(angle)) {
          this.pdfViewer.pagesRotation = angle;
        }
      };
      const setViewerModes = (scroll, spread) => {
        if (isValidScrollMode(scroll)) {
          this.pdfViewer.scrollMode = scroll;
        }
        if (isValidSpreadMode(spread)) {
          this.pdfViewer.spreadMode = spread;
        }
      };
      this.isInitialViewSet = true;
      this.pdfSidebar.setInitialView(sidebarView);
  
      setViewerModes(scrollMode, spreadMode);
  
      if (this.initialBookmark) {
        setRotation(this.initialRotation);
        delete this.initialRotation;
  
        this.pdfLinkService.setHash(this.initialBookmark);
        this.initialBookmark = null;
      } else if (storedHash) {
        setRotation(rotation);
  
        this.pdfLinkService.setHash(storedHash);
      }
  
      // Ensure that the correct page number is displayed in the UI,
      // even if the active page didn't change during document load.
      this.toolbar.setPageNumber(
        this.pdfViewer.currentPageNumber,
        this.pdfViewer.currentPageLabel
      );
      this.secondaryToolbar.setPageNumber(this.pdfViewer.currentPageNumber);
  
      if (!this.pdfViewer.currentScaleValue) {
        // Scale was not initialized: invalid bookmark or scale was not specified.
        // Setting the default one.
        this.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
      }
    },
  
    /**
     * @private
     */
    _cleanup() {
      if (!this.pdfDocument) {
        return; // run cleanup when document is loaded
      }
      this.pdfViewer.cleanup();
      this.pdfThumbnailViewer.cleanup();
  
      // We don't want to remove fonts used by active page SVGs.
      this.pdfDocument.cleanup(
        /* keepLoadedFonts = */ this.pdfViewer.renderer === RendererType.SVG
      );
    },
  
    forceRendering() {
      this.pdfRenderingQueue.printing = !!this.printService;
      this.pdfRenderingQueue.isThumbnailViewEnabled =
        this.pdfSidebar.isThumbnailViewVisible;
      this.pdfRenderingQueue.renderHighestPriority();
    },
  
    beforePrint() {
      // Given that the "beforeprint" browser event is synchronous, we
      // unfortunately cannot await the scripting event dispatching here.
      this.pdfScriptingManager.dispatchWillPrint();
  
      if (this.printService) {
        // There is no way to suppress beforePrint/afterPrint events,
        // but PDFPrintService may generate double events -- this will ignore
        // the second event that will be coming from native window.print().
        return;
      }
  
      if (!this.supportsPrinting) {
        this.l10n.get("printing_not_supported").then(msg => {
          this._otherError(msg);
        });
        return;
      }
  
      // The beforePrint is a sync method and we need to know layout before
      // returning from this method. Ensure that we can get sizes of the pages.
      if (!this.pdfViewer.pageViewsReady) {
        this.l10n.get("printing_not_ready").then(msg => {
          // eslint-disable-next-line no-alert
          window.alert(msg);
        });
        return;
      }
  
      const pagesOverview = this.pdfViewer.getPagesOverview();
      const printContainer = this.appConfig.printContainer;
      const printResolution = AppOptions.get("printResolution");
      const optionalContentConfigPromise =
        this.pdfViewer.optionalContentConfigPromise;
  
      const printService = PDFPrintServiceFactory.instance.createPrintService(
        this.pdfDocument,
        pagesOverview,
        printContainer,
        printResolution,
        optionalContentConfigPromise,
        this.l10n
      );
      this.printService = printService;
      this.forceRendering();
  
      printService.layout();
  
      this.externalServices.reportTelemetry({
        type: "print",
      });
    },
  
    afterPrint() {
      // Given that the "afterprint" browser event is synchronous, we
      // unfortunately cannot await the scripting event dispatching here.
      this.pdfScriptingManager.dispatchDidPrint();
  
      if (this.printService) {
        this.printService.destroy();
        this.printService = null;
  
        if (this.pdfDocument) {
          this.pdfDocument.annotationStorage.resetModified();
        }
      }
      this.forceRendering();
    },
  
    rotatePages(delta) {
      this.pdfViewer.pagesRotation += delta;
      // Note that the thumbnail viewer is updated, and rendering is triggered,
      // in the 'rotationchanging' event handler.
    },
  
    requestPresentationMode() {
      if (!this.pdfPresentationMode) {
        return;
      }
      this.pdfPresentationMode.request();
    },
  
    triggerPrinting() {
      if (!this.supportsPrinting) {
        return;
      }
      window.print();
    },
  
    bindEvents() {
      const { eventBus, _boundEvents } = this;
  
      _boundEvents.beforePrint = this.beforePrint.bind(this);
      _boundEvents.afterPrint = this.afterPrint.bind(this);
  
      eventBus._on("resize", webViewerResize);
      eventBus._on("hashchange", webViewerHashchange);
      eventBus._on("beforeprint", _boundEvents.beforePrint);
      eventBus._on("afterprint", _boundEvents.afterPrint);
      eventBus._on("pagerendered", webViewerPageRendered);
      eventBus._on("updateviewarea", webViewerUpdateViewarea);
      eventBus._on("pagechanging", webViewerPageChanging);
      eventBus._on("scalechanging", webViewerScaleChanging);
      eventBus._on("rotationchanging", webViewerRotationChanging);
      eventBus._on("sidebarviewchanged", webViewerSidebarViewChanged);
      eventBus._on("pagemode", webViewerPageMode);
      eventBus._on("namedaction", webViewerNamedAction);
      eventBus._on("presentationmodechanged", webViewerPresentationModeChanged);
      eventBus._on("presentationmode", webViewerPresentationMode);
      eventBus._on("print", webViewerPrint);
      eventBus._on("download", webViewerDownload);
      eventBus._on("save", webViewerSave);
      eventBus._on("firstpage", webViewerFirstPage);
      eventBus._on("lastpage", webViewerLastPage);
      eventBus._on("nextpage", webViewerNextPage);
      eventBus._on("previouspage", webViewerPreviousPage);
      eventBus._on("zoomin", webViewerZoomIn);
      eventBus._on("zoomout", webViewerZoomOut);
      eventBus._on("zoomreset", webViewerZoomReset);
      eventBus._on("pagenumberchanged", webViewerPageNumberChanged);
      eventBus._on("scalechanged", webViewerScaleChanged);
      eventBus._on("rotatecw", webViewerRotateCw);
      eventBus._on("rotateccw", webViewerRotateCcw);
      eventBus._on("optionalcontentconfig", webViewerOptionalContentConfig);
      eventBus._on("switchscrollmode", webViewerSwitchScrollMode);
      eventBus._on("scrollmodechanged", webViewerScrollModeChanged);
      eventBus._on("switchspreadmode", webViewerSwitchSpreadMode);
      eventBus._on("spreadmodechanged", webViewerSpreadModeChanged);
      eventBus._on("documentproperties", webViewerDocumentProperties);
      eventBus._on("find", webViewerFind);
      eventBus._on("findfromurlhash", webViewerFindFromUrlHash);
      eventBus._on("updatefindmatchescount", webViewerUpdateFindMatchesCount);
      eventBus._on("updatefindcontrolstate", webViewerUpdateFindControlState);
      eventBus._on("hideSelectionPopUp", webViewerHideSelectionPopUp);
      eventBus._on("scrollCloseTextArea", webViewerScrollCloseTextArea);
  
      if (AppOptions.get("pdfBug")) {
        _boundEvents.reportPageStatsPDFBug = reportPageStatsPDFBug;
  
        eventBus._on("pagerendered", _boundEvents.reportPageStatsPDFBug);
        eventBus._on("pagechanging", _boundEvents.reportPageStatsPDFBug);
      }
      if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
        eventBus._on("fileinputchange", webViewerFileInputChange);
        eventBus._on("openfile", webViewerOpenFile);
      }
    },
  
    bindWindowEvents() {
      const { eventBus, _boundEvents } = this;
  
      _boundEvents.windowResize = () => {
        eventBus.dispatch("resize", { source: window });
      };
      _boundEvents.windowHashChange = () => {
        eventBus.dispatch("hashchange", {
          source: window,
          hash: document.location.hash.substring(1),
        });
      };
      _boundEvents.windowBeforePrint = () => {
        eventBus.dispatch("beforeprint", { source: window });
      };
      _boundEvents.windowAfterPrint = () => {
        eventBus.dispatch("afterprint", { source: window });
      };
      _boundEvents.windowUpdateFromSandbox = event => {
        eventBus.dispatch("updatefromsandbox", {
          source: window,
          detail: event.detail,
        });
      };
  
      window.addEventListener("visibilitychange", webViewerVisibilityChange);
      window.addEventListener("wheel", webViewerWheel, { passive: false });
      window.addEventListener("touchstart", webViewerTouchStart, {
        passive: false,
      });
      window.addEventListener("click", webViewerClick);
      window.addEventListener("keydown", webViewerKeyDown);
      window.addEventListener("resize", _boundEvents.windowResize);
      window.addEventListener("hashchange", _boundEvents.windowHashChange);
      window.addEventListener("beforeprint", _boundEvents.windowBeforePrint);
      window.addEventListener("afterprint", _boundEvents.windowAfterPrint);
      window.addEventListener(
        "updatefromsandbox",
        _boundEvents.windowUpdateFromSandbox
      );
    },
  
    unbindEvents() {
      const { eventBus, _boundEvents } = this;
  
      eventBus._off("resize", webViewerResize);
      eventBus._off("hashchange", webViewerHashchange);
      eventBus._off("beforeprint", _boundEvents.beforePrint);
      eventBus._off("afterprint", _boundEvents.afterPrint);
      eventBus._off("pagerendered", webViewerPageRendered);
      eventBus._off("updateviewarea", webViewerUpdateViewarea);
      eventBus._off("pagechanging", webViewerPageChanging);
      eventBus._off("scalechanging", webViewerScaleChanging);
      eventBus._off("rotationchanging", webViewerRotationChanging);
      eventBus._off("sidebarviewchanged", webViewerSidebarViewChanged);
      eventBus._off("pagemode", webViewerPageMode);
      eventBus._off("namedaction", webViewerNamedAction);
      eventBus._off("presentationmodechanged", webViewerPresentationModeChanged);
      eventBus._off("presentationmode", webViewerPresentationMode);
      eventBus._off("print", webViewerPrint);
      eventBus._off("download", webViewerDownload);
      eventBus._off("save", webViewerSave);
      eventBus._off("firstpage", webViewerFirstPage);
      eventBus._off("lastpage", webViewerLastPage);
      eventBus._off("nextpage", webViewerNextPage);
      eventBus._off("previouspage", webViewerPreviousPage);
      eventBus._off("zoomin", webViewerZoomIn);
      eventBus._off("zoomout", webViewerZoomOut);
      eventBus._off("zoomreset", webViewerZoomReset);
      eventBus._off("pagenumberchanged", webViewerPageNumberChanged);
      eventBus._off("scalechanged", webViewerScaleChanged);
      eventBus._off("rotatecw", webViewerRotateCw);
      eventBus._off("rotateccw", webViewerRotateCcw);
      eventBus._off("optionalcontentconfig", webViewerOptionalContentConfig);
      eventBus._off("switchscrollmode", webViewerSwitchScrollMode);
      eventBus._off("scrollmodechanged", webViewerScrollModeChanged);
      eventBus._off("switchspreadmode", webViewerSwitchSpreadMode);
      eventBus._off("spreadmodechanged", webViewerSpreadModeChanged);
      eventBus._off("documentproperties", webViewerDocumentProperties);
      eventBus._off("find", webViewerFind);
      eventBus._off("findfromurlhash", webViewerFindFromUrlHash);
      eventBus._off("updatefindmatchescount", webViewerUpdateFindMatchesCount);
      eventBus._off("updatefindcontrolstate", webViewerUpdateFindControlState);
      eventBus._off("hideSelectionPopUp", webViewerHideSelectionPopUp);
      eventBus._off("scrollCloseTextArea", webViewerScrollCloseTextArea);
  
      if (_boundEvents.reportPageStatsPDFBug) {
        eventBus._off("pagerendered", _boundEvents.reportPageStatsPDFBug);
        eventBus._off("pagechanging", _boundEvents.reportPageStatsPDFBug);
  
        _boundEvents.reportPageStatsPDFBug = null;
      }
      if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
        eventBus._off("fileinputchange", webViewerFileInputChange);
        eventBus._off("openfile", webViewerOpenFile);
      }
  
      _boundEvents.beforePrint = null;
      _boundEvents.afterPrint = null;
    },
  
    unbindWindowEvents() {
      const { _boundEvents } = this;
  
      window.removeEventListener("visibilitychange", webViewerVisibilityChange);
      window.removeEventListener("wheel", webViewerWheel, { passive: false });
      window.removeEventListener("touchstart", webViewerTouchStart, {
        passive: false,
      });
      window.removeEventListener("click", webViewerClick);
      window.removeEventListener("keydown", webViewerKeyDown);
      window.removeEventListener("resize", _boundEvents.windowResize);
      window.removeEventListener("hashchange", _boundEvents.windowHashChange);
      window.removeEventListener("beforeprint", _boundEvents.windowBeforePrint);
      window.removeEventListener("afterprint", _boundEvents.windowAfterPrint);
      window.removeEventListener(
        "updatefromsandbox",
        _boundEvents.windowUpdateFromSandbox
      );
  
      _boundEvents.windowResize = null;
      _boundEvents.windowHashChange = null;
      _boundEvents.windowBeforePrint = null;
      _boundEvents.windowAfterPrint = null;
      _boundEvents.windowUpdateFromSandbox = null;
    },
  
    accumulateWheelTicks(ticks) {
      // If the scroll direction changed, reset the accumulated wheel ticks.
      if (
        (this._wheelUnusedTicks > 0 && ticks < 0) ||
        (this._wheelUnusedTicks < 0 && ticks > 0)
      ) {
        this._wheelUnusedTicks = 0;
      }
      this._wheelUnusedTicks += ticks;
      const wholeTicks =
        Math.sign(this._wheelUnusedTicks) *
        Math.floor(Math.abs(this._wheelUnusedTicks));
      this._wheelUnusedTicks -= wholeTicks;
      return wholeTicks;
    },
  
    /**
     * Should be called *after* all pages have loaded, or if an error occurred,
     * to unblock the "load" event; see https://bugzilla.mozilla.org/show_bug.cgi?id=1618553
     * @private
     */
    _unblockDocumentLoadEvent() {
      if (document.blockUnblockOnload) {
        document.blockUnblockOnload(false);
      }
      // Ensure that this method is only ever run once.
      this._unblockDocumentLoadEvent = () => {};
    },
  
    /**
     * Used together with the integration-tests, to enable awaiting full
     * initialization of the scripting/sandbox.
     */
    get scriptingReady() {
      return this.pdfScriptingManager.ready;
    },
  };
  
  let validateFileURL;
  if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
    const HOSTED_VIEWER_ORIGINS = [
      "null",
      "http://mozilla.github.io",
      "https://mozilla.github.io",
    ];
    validateFileURL = function (file) {
      if (file === undefined) {
        return;
      }
      try {
        const viewerOrigin = new URL(window.location.href).origin || "null";
        if (HOSTED_VIEWER_ORIGINS.includes(viewerOrigin)) {
          // Hosted or local viewer, allow for any file locations
          return;
        }
        const { origin, protocol } = new URL(file, window.location.href);
        // Removing of the following line will not guarantee that the viewer will
        // start accepting URLs from foreign origin -- CORS headers on the remote
        // server must be properly configured.
        // IE10 / IE11 does not include an origin in `blob:`-URLs. So don't block
        // any blob:-URL. The browser's same-origin policy will block requests to
        // blob:-URLs from other origins, so this is safe.
        if (origin !== viewerOrigin && protocol !== "blob:") {
          throw new Error("file origin does not match viewer's");
        }
      } catch (ex) {
        PDFViewerApplication.l10n.get("loading_error").then(msg => {
          PDFViewerApplication._documentError(msg, { message: ex?.message });
        });
        throw ex;
      }
    };
  }
  
  async function loadFakeWorker() {
    if (!GlobalWorkerOptions.workerSrc) {
      GlobalWorkerOptions.workerSrc = AppOptions.get("workerSrc");
    }
    if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("PRODUCTION")) {
      window.pdfjsWorker = await import("pdfjs/core/worker.js");
      return undefined;
    }
    return loadScript(PDFWorker.getWorkerSrc());
  }
  
  function loadAndEnablePDFBug(enabledTabs) {
    const appConfig = PDFViewerApplication.appConfig;
    return loadScript(appConfig.debuggerScriptPath).then(function () {
      PDFBug.enable(enabledTabs);
      PDFBug.init({ OPS }, appConfig.mainContainer);
    });
  }
  
  function reportPageStatsPDFBug({ pageNumber }) {
    if (typeof Stats === "undefined" || !Stats.enabled) {
      return;
    }
    const pageView = PDFViewerApplication.pdfViewer.getPageView(
      /* index = */ pageNumber - 1
    );
    const pageStats = pageView?.pdfPage?.stats;
    if (!pageStats) {
      return;
    }
    Stats.add(pageNumber, pageStats);
  }
  
  function webViewerInitialized() {
    const appConfig = PDFViewerApplication.appConfig;
    let file;
    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
      const queryString = document.location.search.substring(1);
      const params = parseQueryString(queryString);
      file = "file" in params ? params.file : AppOptions.get("defaultUrl");
      // validateFileURL(file);
    } else if (PDFJSDev.test("MOZCENTRAL")) {
      file = window.location.href;
    } else if (PDFJSDev.test("CHROME")) {
      file = AppOptions.get("defaultUrl");
    }
  
    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
      const fileInput = document.createElement("input");
      fileInput.id = appConfig.openFileInputName;
      fileInput.className = "fileInput";
      fileInput.setAttribute("type", "file");
      fileInput.oncontextmenu = noContextMenuHandler;
      document.body.appendChild(fileInput);
  
      if (
        !window.File ||
        !window.FileReader ||
        !window.FileList ||
        !window.Blob
      ) {
        appConfig.toolbar.openFile.hidden = true;
        appConfig.secondaryToolbar.openFileButton.hidden = true;
      } else {
        fileInput.value = null;
      }
  
      fileInput.addEventListener("change", function (evt) {
        const files = evt.target.files;
        if (!files || files.length === 0) {
          return;
        }
        PDFViewerApplication.eventBus.dispatch("fileinputchange", {
          source: this,
          fileInput: evt.target,
        });
      });
  
      // Enable dragging-and-dropping a new PDF file onto the viewerContainer.
      appConfig.mainContainer.addEventListener("dragover", function (evt) {
        evt.preventDefault();
  
        evt.dataTransfer.dropEffect = "move";
      });
      appConfig.mainContainer.addEventListener("drop", function (evt) {
        evt.preventDefault();
  
        const files = evt.dataTransfer.files;
        if (!files || files.length === 0) {
          return;
        }
        PDFViewerApplication.eventBus.dispatch("fileinputchange", {
          source: this,
          fileInput: evt.dataTransfer,
        });
      });
    } else {
      appConfig.toolbar.openFile.hidden = true;
      appConfig.secondaryToolbar.openFileButton.hidden = true;
    }
  
    if (!PDFViewerApplication.supportsDocumentFonts) {
      AppOptions.set("disableFontFace", true);
      PDFViewerApplication.l10n.get("web_fonts_disabled").then(msg => {
        console.warn(msg);
      });
    }
  
    if (!PDFViewerApplication.supportsPrinting) {
      appConfig.toolbar.print.classList.add("hidden");
      appConfig.secondaryToolbar.printButton.classList.add("hidden");
    }
  
    if (!PDFViewerApplication.supportsFullscreen) {
      appConfig.toolbar.presentationModeButton.classList.add("hidden");
      appConfig.secondaryToolbar.presentationModeButton.classList.add("hidden");
    }
  
    if (PDFViewerApplication.supportsIntegratedFind) {
      appConfig.toolbar.viewFind.classList.add("hidden");
    }
  
    appConfig.mainContainer.addEventListener(
      "transitionend",
      function (evt) {
        if (evt.target === /* mainContainer */ this) {
          PDFViewerApplication.eventBus.dispatch("resize", { source: this });
        }
      },
      true
    );
  
    try {
      webViewerOpenFileViaURL(file);
    } catch (reason) {
      PDFViewerApplication.l10n.get("loading_error").then(msg => {
        PDFViewerApplication._documentError(msg, reason);
      });
    }
  }
  
  function webViewerOpenFileViaURL(file) {
    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
      if (file) {
        PDFViewerApplication.open(file);
      }
    } else if (PDFJSDev.test("MOZCENTRAL || CHROME")) {
      PDFViewerApplication.setTitleUsingUrl(file);
      PDFViewerApplication.initPassiveLoading();
    } else {
      if (file) {
        throw new Error("Not implemented: webViewerOpenFileViaURL");
      }
    }
  }
  
  function webViewerResetPermissions() {
    const { appConfig } = PDFViewerApplication;
    if (!appConfig) {
      return;
    }
    // Currently only the "copy"-permission is supported.
    appConfig.viewerContainer.classList.remove(ENABLE_PERMISSIONS_CLASS);
  }
  
  function webViewerPageRendered({ pageNumber, timestamp, error }) {
    // If the page is still visible when it has finished rendering,
    // ensure that the page number input loading indicator is hidden.
    if (pageNumber === PDFViewerApplication.page) {
      PDFViewerApplication.toolbar.updateLoadingIndicatorState(false);
    }
  
    // Use the rendered page to set the corresponding thumbnail image.
    if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {
      const pageView = PDFViewerApplication.pdfViewer.getPageView(
        /* index = */ pageNumber - 1
      );
      const thumbnailView = PDFViewerApplication.pdfThumbnailViewer.getThumbnail(
        /* index = */ pageNumber - 1
      );
      if (pageView && thumbnailView) {
        thumbnailView.setImage(pageView);
      }
    }
  
    if (error) {
      PDFViewerApplication.l10n.get("rendering_error").then(msg => {
        PDFViewerApplication._otherError(msg, error);
      });
    }
  
    PDFViewerApplication.externalServices.reportTelemetry({
      type: "pageInfo",
      timestamp,
    });
    // It is a good time to report stream and font types.
    PDFViewerApplication.pdfDocument.getStats().then(function (stats) {
      PDFViewerApplication.externalServices.reportTelemetry({
        type: "documentStats",
        stats,
      });
    });
  }
  
  function webViewerPageMode({ mode }) {
    // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`.
    let view;
    switch (mode) {
      case "thumbs":
        view = SidebarView.THUMBS;
        break;
      case "bookmarks":
      case "outline": // non-standard
        view = SidebarView.OUTLINE;
        break;
      case "attachments": // non-standard
        view = SidebarView.ATTACHMENTS;
        break;
      case "layers": // non-standard
        view = SidebarView.LAYERS;
        break;
      case "none":
        view = SidebarView.NONE;
        break;
      default:
        console.error('Invalid "pagemode" hash parameter: ' + mode);
        return;
    }
    PDFViewerApplication.pdfSidebar.switchView(view, /* forceOpen = */ true);
  }
  
  function webViewerNamedAction(evt) {
    // Processing a couple of named actions that might be useful, see also
    // `PDFLinkService.executeNamedAction`.
    switch (evt.action) {
      case "GoToPage":
        PDFViewerApplication.appConfig.toolbar.pageNumber.select();
        break;
  
      case "Find":
        if (!PDFViewerApplication.supportsIntegratedFind) {
          PDFViewerApplication.findBar.toggle();
        }
        break;
  
      case "Print":
        PDFViewerApplication.triggerPrinting();
        break;
  
      case "SaveAs":
        webViewerSave();
        break;
    }
  }
  
  function webViewerPresentationModeChanged(evt) {
    PDFViewerApplication.pdfViewer.presentationModeState = evt.state;
  }
  
  function webViewerSidebarViewChanged(evt) {
    PDFViewerApplication.pdfRenderingQueue.isThumbnailViewEnabled =
      PDFViewerApplication.pdfSidebar.isThumbnailViewVisible;
  
    const store = PDFViewerApplication.store;
    if (store && PDFViewerApplication.isInitialViewSet) {
      // Only update the storage when the document has been loaded *and* rendered.
      store.set("sidebarView", evt.view).catch(function () {});
    }
  }
  
  function webViewerUpdateViewarea(evt) {
    const location = evt.location,
      store = PDFViewerApplication.store;
  
    if (store && PDFViewerApplication.isInitialViewSet) {
      store
        .setMultiple({
          page: location.pageNumber,
          zoom: location.scale,
          scrollLeft: location.left,
          scrollTop: location.top,
          rotation: location.rotation,
        })
        .catch(function () {
          /* unable to write to storage */
        });
    }
    const href = PDFViewerApplication.pdfLinkService.getAnchorUrl(
      location.pdfOpenParams
    );
    PDFViewerApplication.appConfig.toolbar.viewBookmark.href = href;
    PDFViewerApplication.appConfig.secondaryToolbar.viewBookmarkButton.href =
      href;
  
    // Show/hide the loading indicator in the page number input element.
    const currentPage = PDFViewerApplication.pdfViewer.getPageView(
      /* index = */ PDFViewerApplication.page - 1
    );
    const loading = currentPage?.renderingState !== RenderingStates.FINISHED;
    PDFViewerApplication.toolbar.updateLoadingIndicatorState(loading);
  }
  
  function webViewerScrollModeChanged(evt) {
    const store = PDFViewerApplication.store;
    if (store && PDFViewerApplication.isInitialViewSet) {
      // Only update the storage when the document has been loaded *and* rendered.
      store.set("scrollMode", evt.mode).catch(function () {});
    }
  }
  
  function webViewerSpreadModeChanged(evt) {
    const store = PDFViewerApplication.store;
    if (store && PDFViewerApplication.isInitialViewSet) {
      // Only update the storage when the document has been loaded *and* rendered.
      store.set("spreadMode", evt.mode).catch(function () {});
    }
  }
  
  function webViewerResize() {
    const { pdfDocument, pdfViewer } = PDFViewerApplication;
    if (!pdfDocument) {
      return;
    }
    const currentScaleValue = pdfViewer.currentScaleValue;
    if (
      currentScaleValue === "auto" ||
      currentScaleValue === "page-fit" ||
      currentScaleValue === "page-width"
    ) {
      // Note: the scale is constant for 'page-actual'.
      pdfViewer.currentScaleValue = currentScaleValue;
    }
    pdfViewer.update();
  }
  
  function webViewerHashchange(evt) {
    const hash = evt.hash;
    if (!hash) {
      return;
    }
    if (!PDFViewerApplication.isInitialViewSet) {
      PDFViewerApplication.initialBookmark = hash;
    } else if (!PDFViewerApplication.pdfHistory.popStateInProgress) {
      PDFViewerApplication.pdfLinkService.setHash(hash);
    }
  }
  
  let webViewerFileInputChange, webViewerOpenFile;
  if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
    webViewerFileInputChange = function (evt) {
      if (PDFViewerApplication.pdfViewer?.isInPresentationMode) {
        return; // Opening a new PDF file isn't supported in Presentation Mode.
      }
      const file = evt.fileInput.files[0];
  
      if (!viewerCompatibilityParams.disableCreateObjectURL) {
        let url = URL.createObjectURL(file);
        if (file.name) {
          url = { url, originalUrl: file.name };
        }
        PDFViewerApplication.open(url);
      } else {
        PDFViewerApplication.setTitleUsingUrl(file.name);
        // Read the local file into a Uint8Array.
        const fileReader = new FileReader();
        fileReader.onload = function webViewerChangeFileReaderOnload(event) {
          const buffer = event.target.result;
          PDFViewerApplication.open(new Uint8Array(buffer));
        };
        fileReader.readAsArrayBuffer(file);
      }
  
      // URL does not reflect proper document location - hiding some icons.
      const appConfig = PDFViewerApplication.appConfig;
      appConfig.toolbar.viewBookmark.hidden = true;
      appConfig.secondaryToolbar.viewBookmarkButton.hidden = true;
      appConfig.toolbar.download.hidden = true;
      appConfig.secondaryToolbar.downloadButton.hidden = true;
    };
  
    webViewerOpenFile = function (evt) {
      const openFileInputName = PDFViewerApplication.appConfig.openFileInputName;
      document.getElementById(openFileInputName).click();
    };
  }
  
  function webViewerPresentationMode() {
    PDFViewerApplication.requestPresentationMode();
  }
  function webViewerPrint() {
    PDFViewerApplication.triggerPrinting();
  }
  function webViewerDownload() {
    PDFViewerApplication.downloadOrSave({ sourceEventType: "download" });
  }
  function webViewerSave() {
    PDFViewerApplication.downloadOrSave({ sourceEventType: "save" });
  }
  function webViewerFirstPage() {
    if (PDFViewerApplication.pdfDocument) {
      PDFViewerApplication.page = 1;
    }
  }
  function webViewerLastPage() {
    if (PDFViewerApplication.pdfDocument) {
      PDFViewerApplication.page = PDFViewerApplication.pagesCount;
    }
  }
  function webViewerNextPage() {
    PDFViewerApplication.pdfViewer.nextPage();
  }
  function webViewerPreviousPage() {
    PDFViewerApplication.pdfViewer.previousPage();
  }
  function webViewerZoomIn() {
    PDFViewerApplication.zoomIn();
  }
  function webViewerZoomOut() {
    PDFViewerApplication.zoomOut();
  }
  function webViewerZoomReset() {
    PDFViewerApplication.zoomReset();
  }
  function webViewerPageNumberChanged(evt) {
    const pdfViewer = PDFViewerApplication.pdfViewer;
    // Note that for `<input type="number">` HTML elements, an empty string will
    // be returned for non-number inputs; hence we simply do nothing in that case.
    if (evt.value !== "") {
      PDFViewerApplication.pdfLinkService.goToPage(evt.value);
    }
  
    // Ensure that the page number input displays the correct value, even if the
    // value entered by the user was invalid (e.g. a floating point number).
    if (
      evt.value !== pdfViewer.currentPageNumber.toString() &&
      evt.value !== pdfViewer.currentPageLabel
    ) {
      PDFViewerApplication.toolbar.setPageNumber(
        pdfViewer.currentPageNumber,
        pdfViewer.currentPageLabel
      );
    }
  }
  function webViewerScaleChanged(evt) {
    PDFViewerApplication.pdfViewer.currentScaleValue = evt.value;
  }
  function webViewerRotateCw() {
    PDFViewerApplication.rotatePages(90);
  }
  function webViewerRotateCcw() {
    PDFViewerApplication.rotatePages(-90);
  }
  function webViewerOptionalContentConfig(evt) {
    PDFViewerApplication.pdfViewer.optionalContentConfigPromise = evt.promise;
  }
  function webViewerSwitchScrollMode(evt) {
    PDFViewerApplication.pdfViewer.scrollMode = evt.mode;
  }
  function webViewerSwitchSpreadMode(evt) {
    PDFViewerApplication.pdfViewer.spreadMode = evt.mode;
  }
  function webViewerDocumentProperties() {
    PDFViewerApplication.pdfDocumentProperties.open();
  }
  
  function webViewerFind(evt) {
    PDFViewerApplication.findController.executeCommand("find" + evt.type, {
      query: evt.query,
      phraseSearch: evt.phraseSearch,
      caseSensitive: evt.caseSensitive,
      entireWord: evt.entireWord,
      highlightAll: evt.highlightAll,
      findPrevious: evt.findPrevious,
    });
  }
  
  function webViewerFindFromUrlHash(evt) {
    PDFViewerApplication.findController.executeCommand("find", {
      query: evt.query,
      phraseSearch: evt.phraseSearch,
      caseSensitive: false,
      entireWord: false,
      highlightAll: true,
      findPrevious: false,
    });
  }
  
  function webViewerUpdateFindMatchesCount({ matchesCount }) {
    if (PDFViewerApplication.supportsIntegratedFind) {
      PDFViewerApplication.externalServices.updateFindMatchesCount(matchesCount);
    } else {
      PDFViewerApplication.findBar.updateResultsCount(matchesCount);
    }
  }
  
  function webViewerHideSelectionPopUp() {
    PDFViewerApplication.selectionPopUp.hidePopBtn();
  }
  
  function webViewerScrollCloseTextArea(evt) {
    debounce(PDFViewerApplication.selectionPopUp.scrollCloseTextArea(evt.yPos));
  }
  
  function webViewerUpdateFindControlState({
    state,
    previous,
    matchesCount,
    rawQuery,
  }) {
    if (PDFViewerApplication.supportsIntegratedFind) {
      PDFViewerApplication.externalServices.updateFindControlState({
        result: state,
        findPrevious: previous,
        matchesCount,
        rawQuery,
      });
    } else {
      PDFViewerApplication.findBar.updateUIState(state, previous, matchesCount);
    }
  }
  
  function webViewerScaleChanging(evt) {
    PDFViewerApplication.toolbar.setPageScale(evt.presetValue, evt.scale);
  
    PDFViewerApplication.pdfViewer.update();
  }
  
  function webViewerRotationChanging(evt) {
    PDFViewerApplication.pdfThumbnailViewer.pagesRotation = evt.pagesRotation;
  
    PDFViewerApplication.forceRendering();
    // Ensure that the active page doesn't change during rotation.
    PDFViewerApplication.pdfViewer.currentPageNumber = evt.pageNumber;
  }
  
  function webViewerPageChanging({ pageNumber, pageLabel }) {
    PDFViewerApplication.toolbar.setPageNumber(pageNumber, pageLabel);
    PDFViewerApplication.secondaryToolbar.setPageNumber(pageNumber);
  
    if (PDFViewerApplication.pdfSidebar.isThumbnailViewVisible) {
      PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(pageNumber);
    }
  }
  
  function webViewerVisibilityChange(evt) {
    if (document.visibilityState === "visible") {
      // Ignore mouse wheel zooming during tab switches (bug 1503412).
      setZoomDisabledTimeout();
    }
  }
  
  let zoomDisabledTimeout = null;
  function setZoomDisabledTimeout() {
    if (zoomDisabledTimeout) {
      clearTimeout(zoomDisabledTimeout);
    }
    zoomDisabledTimeout = setTimeout(function () {
      zoomDisabledTimeout = null;
    }, WHEEL_ZOOM_DISABLED_TIMEOUT);
  }
  
  function webViewerWheel(evt) {
    const { pdfViewer, supportedMouseWheelZoomModifierKeys } =
      PDFViewerApplication;
  
    if (pdfViewer.isInPresentationMode) {
      return;
    }
  
    if (
      (evt.ctrlKey && supportedMouseWheelZoomModifierKeys.ctrlKey) ||
      (evt.metaKey && supportedMouseWheelZoomModifierKeys.metaKey)
    ) {
      // Only zoom the pages, not the entire viewer.
      evt.preventDefault();
      // NOTE: this check must be placed *after* preventDefault.
      if (zoomDisabledTimeout || document.visibilityState === "hidden") {
        return;
      }
  
      const previousScale = pdfViewer.currentScale;
  
      const delta = normalizeWheelEventDirection(evt);
      let ticks = 0;
      if (
        evt.deltaMode === WheelEvent.DOM_DELTA_LINE ||
        evt.deltaMode === WheelEvent.DOM_DELTA_PAGE
      ) {
        // For line-based devices, use one tick per event, because different
        // OSs have different defaults for the number lines. But we generally
        // want one "clicky" roll of the wheel (which produces one event) to
        // adjust the zoom by one step.
        if (Math.abs(delta) >= 1) {
          ticks = Math.sign(delta);
        } else {
          // If we're getting fractional lines (I can't think of a scenario
          // this might actually happen), be safe and use the accumulator.
          ticks = PDFViewerApplication.accumulateWheelTicks(delta);
        }
      } else {
        // pixel-based devices
        const PIXELS_PER_LINE_SCALE = 30;
        ticks = PDFViewerApplication.accumulateWheelTicks(
          delta / PIXELS_PER_LINE_SCALE
        );
      }
  
      if (ticks < 0) {
        PDFViewerApplication.zoomOut(-ticks);
      } else if (ticks > 0) {
        PDFViewerApplication.zoomIn(ticks);
      }
  
      const currentScale = pdfViewer.currentScale;
      if (previousScale !== currentScale) {
        // After scaling the page via zoomIn/zoomOut, the position of the upper-
        // left corner is restored. When the mouse wheel is used, the position
        // under the cursor should be restored instead.
        const scaleCorrectionFactor = currentScale / previousScale - 1;
        const rect = pdfViewer.container.getBoundingClientRect();
        const dx = evt.clientX - rect.left;
        const dy = evt.clientY - rect.top;
        pdfViewer.container.scrollLeft += dx * scaleCorrectionFactor;
        pdfViewer.container.scrollTop += dy * scaleCorrectionFactor;
      }
    } else {
      setZoomDisabledTimeout();
    }
  }
  
  function webViewerTouchStart(evt) {
    if (evt.touches.length > 1) {
      // Disable touch-based zooming, because the entire UI bits gets zoomed and
      // that doesn't look great. If we do want to have a good touch-based
      // zooming experience, we need to implement smooth zoom capability (probably
      // using a CSS transform for faster visual response, followed by async
      // re-rendering at the final zoom level) and do gesture detection on the
      // touchmove events to drive it. Or if we want to settle for a less good
      // experience we can make the touchmove events drive the existing step-zoom
      // behaviour that the ctrl+mousewheel path takes.
      evt.preventDefault();
    }
  }
  
  function webViewerClick(evt) {
    if (!PDFViewerApplication.secondaryToolbar.isOpen) {
      return;
    }
    const appConfig = PDFViewerApplication.appConfig;
    if (
      PDFViewerApplication.pdfViewer.containsElement(evt.target) ||
      (appConfig.toolbar.container.contains(evt.target) &&
        evt.target !== appConfig.secondaryToolbar.toggleButton)
    ) {
      PDFViewerApplication.secondaryToolbar.close();
    }
  }
  
  function webViewerKeyDown(evt) {
    if (PDFViewerApplication.overlayManager.active) {
      return;
    }
  
    let handled = false,
      ensureViewerFocused = false;
    const cmd =
      (evt.ctrlKey ? 1 : 0) |
      (evt.altKey ? 2 : 0) |
      (evt.shiftKey ? 4 : 0) |
      (evt.metaKey ? 8 : 0);
  
    const pdfViewer = PDFViewerApplication.pdfViewer;
    const isViewerInPresentationMode = pdfViewer?.isInPresentationMode;
  
    // First, handle the key bindings that are independent whether an input
    // control is selected or not.
    if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
      // either CTRL or META key with optional SHIFT.
      switch (evt.keyCode) {
        case 70: // f
          if (!PDFViewerApplication.supportsIntegratedFind && !evt.shiftKey) {
            PDFViewerApplication.findBar.open();
            handled = true;
          }
          break;
        case 71: // g
          if (!PDFViewerApplication.supportsIntegratedFind) {
            const findState = PDFViewerApplication.findController.state;
            if (findState) {
              PDFViewerApplication.findController.executeCommand("findagain", {
                query: findState.query,
                phraseSearch: findState.phraseSearch,
                caseSensitive: findState.caseSensitive,
                entireWord: findState.entireWord,
                highlightAll: findState.highlightAll,
                findPrevious: cmd === 5 || cmd === 12,
              });
            }
            handled = true;
          }
          break;
        case 61: // FF/Mac '='
        case 107: // FF '+' and '='
        case 187: // Chrome '+'
        case 171: // FF with German keyboard
          if (!isViewerInPresentationMode) {
            PDFViewerApplication.zoomIn();
          }
          handled = true;
          break;
        case 173: // FF/Mac '-'
        case 109: // FF '-'
        case 189: // Chrome '-'
          if (!isViewerInPresentationMode) {
            PDFViewerApplication.zoomOut();
          }
          handled = true;
          break;
        case 48: // '0'
        case 96: // '0' on Numpad of Swedish keyboard
          if (!isViewerInPresentationMode) {
            // keeping it unhandled (to restore page zoom to 100%)
            setTimeout(function () {
              // ... and resetting the scale after browser adjusts its scale
              PDFViewerApplication.zoomReset();
            });
            handled = false;
          }
          break;
  
        case 38: // up arrow
          if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
            PDFViewerApplication.page = 1;
            handled = true;
            ensureViewerFocused = true;
          }
          break;
        case 40: // down arrow
          if (
            isViewerInPresentationMode ||
            PDFViewerApplication.page < PDFViewerApplication.pagesCount
          ) {
            PDFViewerApplication.page = PDFViewerApplication.pagesCount;
            handled = true;
            ensureViewerFocused = true;
          }
          break;
      }
    }
  
    if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC || CHROME")) {
      const { eventBus } = PDFViewerApplication;
  
      // CTRL or META without shift
      if (cmd === 1 || cmd === 8) {
        switch (evt.keyCode) {
          case 83: // s
            eventBus.dispatch("download", { source: window });
            handled = true;
            break;
  
          case 79: // o
            if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
              eventBus.dispatch("openfile", { source: window });
              handled = true;
            }
            break;
        }
      }
    }
  
    // CTRL+ALT or Option+Command
    if (cmd === 3 || cmd === 10) {
      switch (evt.keyCode) {
        case 80: // p
          PDFViewerApplication.requestPresentationMode();
          handled = true;
          break;
        case 71: // g
          // focuses input#pageNumber field
          PDFViewerApplication.appConfig.toolbar.pageNumber.select();
          handled = true;
          break;
      }
    }
  
    if (handled) {
      if (ensureViewerFocused && !isViewerInPresentationMode) {
        pdfViewer.focus();
      }
      evt.preventDefault();
      return;
    }
  
    // Some shortcuts should not get handled if a control/input element
    // is selected.
    const curElement = getActiveOrFocusedElement();
    const curElementTagName = curElement?.tagName.toUpperCase();
    if (
      curElementTagName === "INPUT" ||
      curElementTagName === "TEXTAREA" ||
      curElementTagName === "SELECT" ||
      curElement?.isContentEditable
    ) {
      // Make sure that the secondary toolbar is closed when Escape is pressed.
      if (evt.keyCode !== /* Esc = */ 27) {
        return;
      }
    }
  
    // No control key pressed at all.
    if (cmd === 0) {
      let turnPage = 0,
        turnOnlyIfPageFit = false;
      switch (evt.keyCode) {
        case 38: // up arrow
        case 33: // pg up
          // vertical scrolling using arrow/pg keys
          if (pdfViewer.isVerticalScrollbarEnabled) {
            turnOnlyIfPageFit = true;
          }
          turnPage = -1;
          break;
        case 8: // backspace
          if (!isViewerInPresentationMode) {
            turnOnlyIfPageFit = true;
          }
          turnPage = -1;
          break;
        case 37: // left arrow
          // horizontal scrolling using arrow keys
          if (pdfViewer.isHorizontalScrollbarEnabled) {
            turnOnlyIfPageFit = true;
          }
        /* falls through */
        case 75: // 'k'
        case 80: // 'p'
          turnPage = -1;
          break;
        case 27: // esc key
          if (PDFViewerApplication.secondaryToolbar.isOpen) {
            PDFViewerApplication.secondaryToolbar.close();
            handled = true;
          }
          if (
            !PDFViewerApplication.supportsIntegratedFind &&
            PDFViewerApplication.findBar.opened
          ) {
            PDFViewerApplication.findBar.close();
            handled = true;
          }
          break;
        case 40: // down arrow
        case 34: // pg down
          // vertical scrolling using arrow/pg keys
          if (pdfViewer.isVerticalScrollbarEnabled) {
            turnOnlyIfPageFit = true;
          }
          turnPage = 1;
          break;
        case 13: // enter key
        case 32: // spacebar
          if (!isViewerInPresentationMode) {
            turnOnlyIfPageFit = true;
          }
          turnPage = 1;
          break;
        case 39: // right arrow
          // horizontal scrolling using arrow keys
          if (pdfViewer.isHorizontalScrollbarEnabled) {
            turnOnlyIfPageFit = true;
          }
        /* falls through */
        case 74: // 'j'
        case 78: // 'n'
          turnPage = 1;
          break;
  
        case 36: // home
          if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
            PDFViewerApplication.page = 1;
            handled = true;
            ensureViewerFocused = true;
          }
          break;
        case 35: // end
          if (
            isViewerInPresentationMode ||
            PDFViewerApplication.page < PDFViewerApplication.pagesCount
          ) {
            PDFViewerApplication.page = PDFViewerApplication.pagesCount;
            handled = true;
            ensureViewerFocused = true;
          }
          break;
  
        case 83: // 's'
          PDFViewerApplication.pdfCursorTools.switchTool(CursorTool.SELECT);
          break;
        case 72: // 'h'
          PDFViewerApplication.pdfCursorTools.switchTool(CursorTool.HAND);
          break;
  
        case 82: // 'r'
          PDFViewerApplication.rotatePages(90);
          break;
  
        case 115: // F4
          PDFViewerApplication.pdfSidebar.toggle();
          break;
      }
  
      if (
        turnPage !== 0 &&
        (!turnOnlyIfPageFit || pdfViewer.currentScaleValue === "page-fit")
      ) {
        if (turnPage > 0) {
          pdfViewer.nextPage();
        } else {
          pdfViewer.previousPage();
        }
        handled = true;
      }
    }
  
    // shift-key
    if (cmd === 4) {
      switch (evt.keyCode) {
        case 13: // enter key
        case 32: // spacebar
          if (
            !isViewerInPresentationMode &&
            pdfViewer.currentScaleValue !== "page-fit"
          ) {
            break;
          }
          if (PDFViewerApplication.page > 1) {
            PDFViewerApplication.page--;
          }
          handled = true;
          break;
  
        case 82: // 'r'
          PDFViewerApplication.rotatePages(-90);
          break;
      }
    }
  
    if (!handled && !isViewerInPresentationMode) {
      // 33=Page Up  34=Page Down  35=End    36=Home
      // 37=Left     38=Up         39=Right  40=Down
      // 32=Spacebar
      if (
        (evt.keyCode >= 33 && evt.keyCode <= 40) ||
        (evt.keyCode === 32 && curElementTagName !== "BUTTON")
      ) {
        ensureViewerFocused = true;
      }
    }
  
    if (ensureViewerFocused && !pdfViewer.containsElement(curElement)) {
      // The page container is not focused, but a page navigation key has been
      // pressed. Change the focus to the viewer container to make sure that
      // navigation by keyboard works as expected.
      pdfViewer.focus();
    }
  
    if (handled) {
      evt.preventDefault();
    }
  }
  
  function beforeUnload(evt) {
    evt.preventDefault();
    evt.returnValue = "";
    return false;
  }
  
  /* Abstract factory for the print service. */
  const PDFPrintServiceFactory = {
    instance: {
      supportsPrinting: false,
      createPrintService() {
        throw new Error("Not implemented: createPrintService");
      },
    },
  };
  
  export {
    DefaultExternalServices,
    PDFPrintServiceFactory,
    PDFViewerApplication,
  };
  