From 2c1436f6cb3c563a90a76bc879ed7bb7a8c1b8d0 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Sat, 10 Dec 2016 23:10:08 +0100
Subject: [PATCH 1/4] Refactor `PDFViewerApplication.initialize` into two
 methods, one that reads the Preferences and one that initializes the various
 viewer components

---
 web/app.js | 212 +++++++++++++++++++++++++++++------------------------
 1 file changed, 115 insertions(+), 97 deletions(-)

diff --git a/web/app.js b/web/app.js
index 156367f5f..350f21b18 100644
--- a/web/app.js
+++ b/web/app.js
@@ -193,9 +193,124 @@ var PDFViewerApplication = {
 
   // called once when the document is loaded
   initialize: function pdfViewInitialize(appConfig) {
+    var self = this;
+    var PDFJS = pdfjsLib.PDFJS;
+
     configure(pdfjsLib.PDFJS);
     this.appConfig = appConfig;
 
+    Preferences.initialize();
+    this.preferences = Preferences;
+
+    this._initializeViewerComponents();
+
+    return this._readPreferences().then(function () {
+      if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
+        // Prevent external links from "replacing" the viewer,
+        // when it's embedded in e.g. an iframe or an object.
+        PDFJS.externalLinkTarget = PDFJS.LinkTarget.TOP;
+      }
+
+      self.initialized = true;
+    });
+  },
+
+  /**
+   * @private
+   */
+  _readPreferences: function () {
+    var self = this;
+    var PDFJS = pdfjsLib.PDFJS;
+
+    return Promise.all([
+      Preferences.get('enableWebGL').then(function resolved(value) {
+        PDFJS.disableWebGL = !value;
+      }),
+      Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
+        self.preferenceSidebarViewOnLoad = value;
+      }),
+      Preferences.get('pdfBugEnabled').then(function resolved(value) {
+        self.preferencePdfBugEnabled = value;
+      }),
+      Preferences.get('showPreviousViewOnLoad').then(function resolved(value) {
+        self.preferenceShowPreviousViewOnLoad = value;
+      }),
+      Preferences.get('defaultZoomValue').then(function resolved(value) {
+        self.preferenceDefaultZoomValue = value;
+      }),
+      Preferences.get('enhanceTextSelection').then(function resolved(value) {
+        // TODO: Move the initialization and fetching of `Preferences` to occur
+        //       before the various viewer components are initialized.
+        //
+        // This was attempted in: https://github.com/mozilla/pdf.js/pull/7586,
+        // but it had to be backed out since it violated implicit assumptions
+        // about some viewer components being synchronously available.
+        //
+        // NOTE: This hack works since the `enhanceTextSelection` option is not
+        //       needed until `PDFViewer.setDocument` has been called.
+        self.pdfViewer.enhanceTextSelection = value;
+      }),
+      Preferences.get('disableTextLayer').then(function resolved(value) {
+        if (PDFJS.disableTextLayer === true) {
+          return;
+        }
+        PDFJS.disableTextLayer = value;
+      }),
+      Preferences.get('disableRange').then(function resolved(value) {
+        if (PDFJS.disableRange === true) {
+          return;
+        }
+        PDFJS.disableRange = value;
+      }),
+      Preferences.get('disableStream').then(function resolved(value) {
+        if (PDFJS.disableStream === true) {
+          return;
+        }
+        PDFJS.disableStream = value;
+      }),
+      Preferences.get('disableAutoFetch').then(function resolved(value) {
+        PDFJS.disableAutoFetch = value;
+      }),
+      Preferences.get('disableFontFace').then(function resolved(value) {
+        if (PDFJS.disableFontFace === true) {
+          return;
+        }
+        PDFJS.disableFontFace = value;
+      }),
+      Preferences.get('useOnlyCssZoom').then(function resolved(value) {
+        PDFJS.useOnlyCssZoom = value;
+      }),
+      Preferences.get('externalLinkTarget').then(function resolved(value) {
+        if (PDFJS.isExternalLinkTargetSet()) {
+          return;
+        }
+        PDFJS.externalLinkTarget = value;
+      }),
+      Preferences.get('renderer').then(function resolved(value) {
+        // TODO: Like the `enhanceTextSelection` preference, move the
+        //       initialization and fetching of `Preferences` to occur
+        //       before the various viewer components are initialized.
+        self.pdfViewer.renderer = value;
+      }),
+      Preferences.get('renderInteractiveForms').then(function resolved(value) {
+        // TODO: Like the `enhanceTextSelection` preference, move the
+        //       initialization and fetching of `Preferences` to occur
+        //       before the various viewer components are initialized.
+        self.pdfViewer.renderInteractiveForms = value;
+      }),
+      Preferences.get('disablePageLabels').then(function resolved(value) {
+        self.preferenceDisablePageLabels = value;
+      }),
+      // TODO move more preferences and other async stuff here
+    ]).catch(function (reason) { });
+  },
+
+  /**
+   * @private
+   */
+  _initializeViewerComponents: function () {
+    var appConfig = this.appConfig;
+
     var eventBus = appConfig.eventBus || getGlobalEventBus();
     this.eventBus = eventBus;
     this.bindEvents();
@@ -235,9 +350,6 @@ var PDFViewerApplication = {
     });
     pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
 
-    Preferences.initialize();
-    this.preferences = Preferences;
-
     this.pdfHistory = new PDFHistory({
       linkService: pdfLinkService,
       eventBus: this.eventBus
@@ -317,100 +429,6 @@ var PDFViewerApplication = {
     sidebarConfig.eventBus = this.eventBus;
     this.pdfSidebar = new PDFSidebar(sidebarConfig);
     this.pdfSidebar.onToggled = this.forceRendering.bind(this);
-
-    var self = this;
-    var PDFJS = pdfjsLib.PDFJS;
-    var initializedPromise = Promise.all([
-      Preferences.get('enableWebGL').then(function resolved(value) {
-        PDFJS.disableWebGL = !value;
-      }),
-      Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
-        self.preferenceSidebarViewOnLoad = value;
-      }),
-      Preferences.get('pdfBugEnabled').then(function resolved(value) {
-        self.preferencePdfBugEnabled = value;
-      }),
-      Preferences.get('showPreviousViewOnLoad').then(function resolved(value) {
-        self.preferenceShowPreviousViewOnLoad = value;
-      }),
-      Preferences.get('defaultZoomValue').then(function resolved(value) {
-        self.preferenceDefaultZoomValue = value;
-      }),
-      Preferences.get('enhanceTextSelection').then(function resolved(value) {
-        // TODO: Move the initialization and fetching of `Preferences` to occur
-        //       before the various viewer components are initialized.
-        //
-        // This was attempted in: https://github.com/mozilla/pdf.js/pull/7586,
-        // but it had to be backed out since it violated implicit assumptions
-        // about some viewer components being synchronously available.
-        //
-        // NOTE: This hack works since the `enhanceTextSelection` option is not
-        //       needed until `PDFViewer.setDocument` has been called.
-        self.pdfViewer.enhanceTextSelection = value;
-      }),
-      Preferences.get('disableTextLayer').then(function resolved(value) {
-        if (PDFJS.disableTextLayer === true) {
-          return;
-        }
-        PDFJS.disableTextLayer = value;
-      }),
-      Preferences.get('disableRange').then(function resolved(value) {
-        if (PDFJS.disableRange === true) {
-          return;
-        }
-        PDFJS.disableRange = value;
-      }),
-      Preferences.get('disableStream').then(function resolved(value) {
-        if (PDFJS.disableStream === true) {
-          return;
-        }
-        PDFJS.disableStream = value;
-      }),
-      Preferences.get('disableAutoFetch').then(function resolved(value) {
-        PDFJS.disableAutoFetch = value;
-      }),
-      Preferences.get('disableFontFace').then(function resolved(value) {
-        if (PDFJS.disableFontFace === true) {
-          return;
-        }
-        PDFJS.disableFontFace = value;
-      }),
-      Preferences.get('useOnlyCssZoom').then(function resolved(value) {
-        PDFJS.useOnlyCssZoom = value;
-      }),
-      Preferences.get('externalLinkTarget').then(function resolved(value) {
-        if (PDFJS.isExternalLinkTargetSet()) {
-          return;
-        }
-        PDFJS.externalLinkTarget = value;
-      }),
-      Preferences.get('renderer').then(function resolved(value) {
-        // TODO: Like the `enhanceTextSelection` preference, move the
-        //       initialization and fetching of `Preferences` to occur
-        //       before the various viewer components are initialized.
-        self.pdfViewer.renderer = value;
-      }),
-      Preferences.get('renderInteractiveForms').then(function resolved(value) {
-        // TODO: Like the `enhanceTextSelection` preference, move the
-        //       initialization and fetching of `Preferences` to occur
-        //       before the various viewer components are initialized.
-        self.pdfViewer.renderInteractiveForms = value;
-      }),
-      Preferences.get('disablePageLabels').then(function resolved(value) {
-        self.preferenceDisablePageLabels = value;
-      }),
-      // TODO move more preferences and other async stuff here
-    ]).catch(function (reason) { });
-
-    return initializedPromise.then(function () {
-      if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
-        // Prevent external links from "replacing" the viewer,
-        // when it's embedded in e.g. an iframe or an object.
-        PDFJS.externalLinkTarget = PDFJS.LinkTarget.TOP;
-      }
-
-      self.initialized = true;
-    });
   },
 
   run: function pdfViewRun(config) {

From 849f5dde9d608ec50c0b0a78345b19721e9edf6f Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Thu, 8 Dec 2016 13:30:21 +0100
Subject: [PATCH 2/4] Move the `Preferences` initialization/fetching code to
 the top of `PDFViewerApplication.initialize`, to enable using them when
 initializing e.g. the viewer components

Note that in quick testing using `console.time/timeEnd`, both locally and with the Firefox addon, the total run time of the *entire* `PDFViewerApplication.initialize` function does not seem to change with this patch.
---
 web/app.js | 280 ++++++++++++++++++++++++++---------------------------
 1 file changed, 138 insertions(+), 142 deletions(-)

diff --git a/web/app.js b/web/app.js
index 350f21b18..65bd2bd51 100644
--- a/web/app.js
+++ b/web/app.js
@@ -181,11 +181,16 @@ var PDFViewerApplication = {
   eventBus: null,
   pageRotation: 0,
   isInitialViewSet: false,
-  preferenceSidebarViewOnLoad: SidebarView.NONE,
-  preferencePdfBugEnabled: false,
-  preferenceShowPreviousViewOnLoad: true,
-  preferenceDefaultZoomValue: '',
-  preferenceDisablePageLabels: false,
+  viewerPrefs: {
+    sidebarViewOnLoad: SidebarView.NONE,
+    pdfBugEnabled: false,
+    showPreviousViewOnLoad: true,
+    defaultZoomValue: '',
+    disablePageLabels: false,
+    renderer: 'canvas',
+    enhanceTextSelection: false,
+    renderInteractiveForms: false,
+  },
   isViewerEmbedded: (window.parent !== window),
   url: '',
   baseUrl: '',
@@ -196,15 +201,15 @@ var PDFViewerApplication = {
     var self = this;
     var PDFJS = pdfjsLib.PDFJS;
 
-    configure(pdfjsLib.PDFJS);
-    this.appConfig = appConfig;
-
     Preferences.initialize();
     this.preferences = Preferences;
 
-    this._initializeViewerComponents();
+    configure(PDFJS);
+    this.appConfig = appConfig;
 
     return this._readPreferences().then(function () {
+      return self._initializeViewerComponents();
+    }).then(function () {
       if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
         // Prevent external links from "replacing" the viewer,
         // when it's embedded in e.g. an iframe or an object.
@@ -227,28 +232,19 @@ var PDFViewerApplication = {
         PDFJS.disableWebGL = !value;
       }),
       Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
-        self.preferenceSidebarViewOnLoad = value;
+        self.viewerPrefs['sidebarViewOnLoad'] = value;
       }),
       Preferences.get('pdfBugEnabled').then(function resolved(value) {
-        self.preferencePdfBugEnabled = value;
+        self.viewerPrefs['pdfBugEnabled'] = value;
       }),
       Preferences.get('showPreviousViewOnLoad').then(function resolved(value) {
-        self.preferenceShowPreviousViewOnLoad = value;
+        self.viewerPrefs['showPreviousViewOnLoad'] = value;
       }),
       Preferences.get('defaultZoomValue').then(function resolved(value) {
-        self.preferenceDefaultZoomValue = value;
+        self.viewerPrefs['defaultZoomValue'] = value;
       }),
       Preferences.get('enhanceTextSelection').then(function resolved(value) {
-        // TODO: Move the initialization and fetching of `Preferences` to occur
-        //       before the various viewer components are initialized.
-        //
-        // This was attempted in: https://github.com/mozilla/pdf.js/pull/7586,
-        // but it had to be backed out since it violated implicit assumptions
-        // about some viewer components being synchronously available.
-        //
-        // NOTE: This hack works since the `enhanceTextSelection` option is not
-        //       needed until `PDFViewer.setDocument` has been called.
-        self.pdfViewer.enhanceTextSelection = value;
+        self.viewerPrefs['enhanceTextSelection'] = value;
       }),
       Preferences.get('disableTextLayer').then(function resolved(value) {
         if (PDFJS.disableTextLayer === true) {
@@ -287,21 +283,14 @@ var PDFViewerApplication = {
         PDFJS.externalLinkTarget = value;
       }),
       Preferences.get('renderer').then(function resolved(value) {
-        // TODO: Like the `enhanceTextSelection` preference, move the
-        //       initialization and fetching of `Preferences` to occur
-        //       before the various viewer components are initialized.
-        self.pdfViewer.renderer = value;
+        self.viewerPrefs['renderer'] = value;
       }),
       Preferences.get('renderInteractiveForms').then(function resolved(value) {
-        // TODO: Like the `enhanceTextSelection` preference, move the
-        //       initialization and fetching of `Preferences` to occur
-        //       before the various viewer components are initialized.
-        self.pdfViewer.renderInteractiveForms = value;
+        self.viewerPrefs['renderInteractiveForms'] = value;
       }),
       Preferences.get('disablePageLabels').then(function resolved(value) {
-        self.preferenceDisablePageLabels = value;
+        self.viewerPrefs['disablePageLabels'] = value;
       }),
-      // TODO move more preferences and other async stuff here
     ]).catch(function (reason) { });
   },
 
@@ -309,126 +298,133 @@ var PDFViewerApplication = {
    * @private
    */
   _initializeViewerComponents: function () {
+    var self = this;
     var appConfig = this.appConfig;
 
-    var eventBus = appConfig.eventBus || getGlobalEventBus();
-    this.eventBus = eventBus;
-    this.bindEvents();
+    return new Promise(function (resolve, reject) {
+      var eventBus = appConfig.eventBus || getGlobalEventBus();
+      self.eventBus = eventBus;
+      self.bindEvents();
 
-    var pdfRenderingQueue = new PDFRenderingQueue();
-    pdfRenderingQueue.onIdle = this.cleanup.bind(this);
-    this.pdfRenderingQueue = pdfRenderingQueue;
+      var pdfRenderingQueue = new PDFRenderingQueue();
+      pdfRenderingQueue.onIdle = self.cleanup.bind(self);
+      self.pdfRenderingQueue = pdfRenderingQueue;
 
-    var pdfLinkService = new PDFLinkService({
-      eventBus: eventBus
-    });
-    this.pdfLinkService = pdfLinkService;
-
-    var downloadManager = this.externalServices.createDownloadManager();
-    this.downloadManager = downloadManager;
-
-    var container = appConfig.mainContainer;
-    var viewer = appConfig.viewerContainer;
-    this.pdfViewer = new PDFViewer({
-      container: container,
-      viewer: viewer,
-      eventBus: eventBus,
-      renderingQueue: pdfRenderingQueue,
-      linkService: pdfLinkService,
-      downloadManager: downloadManager,
-      enhanceTextSelection: false,
-      renderInteractiveForms: false,
-    });
-    pdfRenderingQueue.setViewer(this.pdfViewer);
-    pdfLinkService.setViewer(this.pdfViewer);
-
-    var thumbnailContainer = appConfig.sidebar.thumbnailView;
-    this.pdfThumbnailViewer = new PDFThumbnailViewer({
-      container: thumbnailContainer,
-      renderingQueue: pdfRenderingQueue,
-      linkService: pdfLinkService
-    });
-    pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
+      var pdfLinkService = new PDFLinkService({
+        eventBus: eventBus
+      });
+      self.pdfLinkService = pdfLinkService;
 
-    this.pdfHistory = new PDFHistory({
-      linkService: pdfLinkService,
-      eventBus: this.eventBus
-    });
-    pdfLinkService.setHistory(this.pdfHistory);
+      var downloadManager = self.externalServices.createDownloadManager();
+      self.downloadManager = downloadManager;
 
-    this.findController = new PDFFindController({
-      pdfViewer: this.pdfViewer
-    });
-    this.findController.onUpdateResultsCount = function (matchCount) {
-      if (this.supportsIntegratedFind) {
-        return;
-      }
-      this.findBar.updateResultsCount(matchCount);
-    }.bind(this);
-    this.findController.onUpdateState = function (state, previous, matchCount) {
-      if (this.supportsIntegratedFind) {
-        this.externalServices.updateFindControlState(
-          {result: state, findPrevious: previous});
-      } else {
-        this.findBar.updateUIState(state, previous, matchCount);
-      }
-    }.bind(this);
+      var container = appConfig.mainContainer;
+      var viewer = appConfig.viewerContainer;
+      self.pdfViewer = new PDFViewer({
+        container: container,
+        viewer: viewer,
+        eventBus: eventBus,
+        renderingQueue: pdfRenderingQueue,
+        linkService: pdfLinkService,
+        downloadManager: downloadManager,
+        renderer: self.viewerPrefs['renderer'],
+        enhanceTextSelection: self.viewerPrefs['enhanceTextSelection'],
+        renderInteractiveForms: self.viewerPrefs['renderInteractiveForms'],
+      });
+      pdfRenderingQueue.setViewer(self.pdfViewer);
+      pdfLinkService.setViewer(self.pdfViewer);
+
+      var thumbnailContainer = appConfig.sidebar.thumbnailView;
+      self.pdfThumbnailViewer = new PDFThumbnailViewer({
+        container: thumbnailContainer,
+        renderingQueue: pdfRenderingQueue,
+        linkService: pdfLinkService,
+      });
+      pdfRenderingQueue.setThumbnailViewer(self.pdfThumbnailViewer);
 
-    this.pdfViewer.setFindController(this.findController);
+      self.pdfHistory = new PDFHistory({
+        linkService: pdfLinkService,
+        eventBus: eventBus,
+      });
+      pdfLinkService.setHistory(self.pdfHistory);
 
-    // FIXME better PDFFindBar constructor parameters
-    var findBarConfig = Object.create(appConfig.findBar);
-    findBarConfig.findController = this.findController;
-    findBarConfig.eventBus = this.eventBus;
-    this.findBar = new PDFFindBar(findBarConfig);
+      self.findController = new PDFFindController({
+        pdfViewer: self.pdfViewer,
+      });
+      self.findController.onUpdateResultsCount = function (matchCount) {
+        if (self.supportsIntegratedFind) {
+          return;
+        }
+        self.findBar.updateResultsCount(matchCount);
+      };
+      self.findController.onUpdateState = function (state, previous,
+                                                    matchCount) {
+        if (self.supportsIntegratedFind) {
+          self.externalServices.updateFindControlState(
+            {result: state, findPrevious: previous});
+        } else {
+          self.findBar.updateUIState(state, previous, matchCount);
+        }
+      };
 
-    this.overlayManager = OverlayManager;
+      self.pdfViewer.setFindController(self.findController);
 
-    this.handTool = new HandTool({
-      container: container,
-      eventBus: this.eventBus,
-    });
+      // FIXME better PDFFindBar constructor parameters
+      var findBarConfig = Object.create(appConfig.findBar);
+      findBarConfig.findController = self.findController;
+      findBarConfig.eventBus = eventBus;
+      self.findBar = new PDFFindBar(findBarConfig);
 
-    this.pdfDocumentProperties =
-      new PDFDocumentProperties(appConfig.documentProperties);
+      self.overlayManager = OverlayManager;
 
-    this.toolbar = new Toolbar(appConfig.toolbar, container, eventBus);
+      self.handTool = new HandTool({
+        container: container,
+        eventBus: eventBus,
+      });
 
-    this.secondaryToolbar =
-      new SecondaryToolbar(appConfig.secondaryToolbar, container, eventBus);
+      self.pdfDocumentProperties =
+        new PDFDocumentProperties(appConfig.documentProperties);
 
-    if (this.supportsFullscreen) {
-      this.pdfPresentationMode = new PDFPresentationMode({
-        container: container,
-        viewer: viewer,
-        pdfViewer: this.pdfViewer,
-        eventBus: this.eventBus,
-        contextMenuItems: appConfig.fullscreen
+      self.toolbar = new Toolbar(appConfig.toolbar, container, eventBus);
+
+      self.secondaryToolbar =
+        new SecondaryToolbar(appConfig.secondaryToolbar, container, eventBus);
+
+      if (self.supportsFullscreen) {
+        self.pdfPresentationMode = new PDFPresentationMode({
+          container: container,
+          viewer: viewer,
+          pdfViewer: self.pdfViewer,
+          eventBus: eventBus,
+          contextMenuItems: appConfig.fullscreen
+        });
+      }
+
+      self.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay);
+
+      self.pdfOutlineViewer = new PDFOutlineViewer({
+        container: appConfig.sidebar.outlineView,
+        eventBus: eventBus,
+        linkService: pdfLinkService,
       });
-    }
 
-    this.passwordPrompt = new PasswordPrompt(appConfig.passwordOverlay);
+      self.pdfAttachmentViewer = new PDFAttachmentViewer({
+        container: appConfig.sidebar.attachmentsView,
+        eventBus: eventBus,
+        downloadManager: downloadManager,
+      });
 
-    this.pdfOutlineViewer = new PDFOutlineViewer({
-      container: appConfig.sidebar.outlineView,
-      eventBus: this.eventBus,
-      linkService: pdfLinkService,
-    });
+      // FIXME better PDFSidebar constructor parameters
+      var sidebarConfig = Object.create(appConfig.sidebar);
+      sidebarConfig.pdfViewer = self.pdfViewer;
+      sidebarConfig.pdfThumbnailViewer = self.pdfThumbnailViewer;
+      sidebarConfig.pdfOutlineViewer = self.pdfOutlineViewer;
+      sidebarConfig.eventBus = eventBus;
+      self.pdfSidebar = new PDFSidebar(sidebarConfig);
+      self.pdfSidebar.onToggled = self.forceRendering.bind(self);
 
-    this.pdfAttachmentViewer = new PDFAttachmentViewer({
-      container: appConfig.sidebar.attachmentsView,
-      eventBus: this.eventBus,
-      downloadManager: downloadManager
+      resolve(undefined);
     });
-
-    // FIXME better PDFSidebar constructor parameters
-    var sidebarConfig = Object.create(appConfig.sidebar);
-    sidebarConfig.pdfViewer = this.pdfViewer;
-    sidebarConfig.pdfThumbnailViewer = this.pdfThumbnailViewer;
-    sidebarConfig.pdfOutlineViewer = this.pdfOutlineViewer;
-    sidebarConfig.eventBus = this.eventBus;
-    this.pdfSidebar = new PDFSidebar(sidebarConfig);
-    this.pdfSidebar.onToggled = this.forceRendering.bind(this);
   },
 
   run: function pdfViewRun(config) {
@@ -930,7 +926,7 @@ var PDFViewerApplication = {
       if (!pdfjsLib.PDFJS.disableHistory && !self.isViewerEmbedded) {
         // The browsing history is only enabled when the viewer is standalone,
         // i.e. not when it is embedded in a web page.
-        if (!self.preferenceShowPreviousViewOnLoad) {
+        if (!self.viewerPrefs['showPreviousViewOnLoad']) {
           self.pdfHistory.clearHistoryState();
         }
         self.pdfHistory.initialize(self.documentFingerprint);
@@ -950,10 +946,10 @@ var PDFViewerApplication = {
 
       store.initializedPromise.then(function resolved() {
         var storedHash = null, sidebarView = null;
-        if (self.preferenceShowPreviousViewOnLoad &&
+        if (self.viewerPrefs['showPreviousViewOnLoad'] &&
             store.get('exists', false)) {
           var pageNum = store.get('page', '1');
-          var zoom = self.preferenceDefaultZoomValue ||
+          var zoom = self.viewerPrefs['defaultZoomValue'] ||
                      store.get('zoom', DEFAULT_SCALE_VALUE);
           var left = store.get('scrollLeft', '0');
           var top = store.get('scrollTop', '0');
@@ -962,8 +958,8 @@ var PDFViewerApplication = {
                        left + ',' + top;
 
           sidebarView = store.get('sidebarView', SidebarView.NONE);
-        } else if (self.preferenceDefaultZoomValue) {
-          storedHash = 'page=1&zoom=' + self.preferenceDefaultZoomValue;
+        } else if (self.viewerPrefs['defaultZoomValue']) {
+          storedHash = 'page=1&zoom=' + self.viewerPrefs['defaultZoomValue'];
         }
         self.setInitialView(storedHash,
           { scale: scale, sidebarView: sidebarView });
@@ -999,7 +995,7 @@ var PDFViewerApplication = {
     });
 
     pdfDocument.getPageLabels().then(function (labels) {
-      if (!labels || self.preferenceDisablePageLabels) {
+      if (!labels || self.viewerPrefs['disablePageLabels']) {
         return;
       }
       var i = 0, numLabels = labels.length;
@@ -1129,7 +1125,7 @@ var PDFViewerApplication = {
 
     this.isInitialViewSet = true;
 
-    this.pdfSidebar.setInitialView(this.preferenceSidebarViewOnLoad ||
+    this.pdfSidebar.setInitialView(this.viewerPrefs['sidebarViewOnLoad'] ||
                                    (sidebarView | 0));
 
     if (this.initialDestination) {
@@ -1386,7 +1382,7 @@ function webViewerInitialized() {
   var PDFJS = pdfjsLib.PDFJS;
 
   if ((typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION')) ||
-      PDFViewerApplication.preferencePdfBugEnabled) {
+      PDFViewerApplication.viewerPrefs['pdfBugEnabled']) {
     // Special debugging flags in the hash section of the URL.
     var hash = document.location.hash.substring(1);
     var hashParams = parseQueryString(hash);

From 648024f5d028f1650838fd62ac9f5c1e1892e417 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Thu, 8 Dec 2016 16:28:40 +0100
Subject: [PATCH 3/4] Don't call `bindEvents()` until `PDFViewerApplication`
 has been initialized, and move binding of `window` event listeners to a
 helper method, to prevent errors if an event manages to arrive too soon

With `bindEvents()` now being called after the viewer has been initialized, we no longer need to have `PDFViewerApplication.initialized` checks in the event handler functions.
Furthermore by moving the `window.addEventListener`s to a helper method, `PDFViewerApplication.initialized` checks are no longer necessary in the event handlers, hence we thus address part of issue 7797 here as well.
---
 web/app.js | 126 ++++++++++++++++++++++++-----------------------------
 1 file changed, 58 insertions(+), 68 deletions(-)

diff --git a/web/app.js b/web/app.js
index 65bd2bd51..d2fe73230 100644
--- a/web/app.js
+++ b/web/app.js
@@ -210,6 +210,11 @@ var PDFViewerApplication = {
     return this._readPreferences().then(function () {
       return self._initializeViewerComponents();
     }).then(function () {
+      // Bind the various event handlers *after* the viewer has been
+      // initialized, to prevent errors if an event arrives too soon.
+      self.bindEvents();
+      self.bindWindowEvents();
+
       if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
         // Prevent external links from "replacing" the viewer,
         // when it's embedded in e.g. an iframe or an object.
@@ -304,7 +309,6 @@ var PDFViewerApplication = {
     return new Promise(function (resolve, reject) {
       var eventBus = appConfig.eventBus || getGlobalEventBus();
       self.eventBus = eventBus;
-      self.bindEvents();
 
       var pdfRenderingQueue = new PDFRenderingQueue();
       pdfRenderingQueue.onIdle = self.cleanup.bind(self);
@@ -1291,7 +1295,41 @@ var PDFViewerApplication = {
     if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
       eventBus.on('fileinputchange', webViewerFileInputChange);
     }
-  }
+  },
+
+  bindWindowEvents: function pdfViewBindWindowEvents() {
+    var eventBus = this.eventBus;
+
+    window.addEventListener('wheel', webViewerWheel);
+    window.addEventListener('click', webViewerClick);
+    window.addEventListener('keydown', webViewerKeyDown);
+
+    window.addEventListener('resize', function windowResize() {
+      eventBus.dispatch('resize');
+    });
+    window.addEventListener('hashchange', function windowHashChange() {
+      eventBus.dispatch('hashchange', {
+        hash: document.location.hash.substring(1),
+      });
+    });
+    window.addEventListener('beforeprint', function windowBeforePrint() {
+      eventBus.dispatch('beforeprint');
+    });
+    window.addEventListener('afterprint', function windowAfterPrint() {
+      eventBus.dispatch('afterprint');
+    });
+    if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
+      window.addEventListener('change', function windowChange(evt) {
+        var files = evt.target.files;
+        if (!files || files.length === 0) {
+          return;
+        }
+        eventBus.dispatch('fileinputchange', {
+          fileInput: evt.target,
+        });
+      });
+    }
+  },
 };
 
 var validateFileURL;
@@ -1596,9 +1634,6 @@ function webViewerTextLayerRendered(e) {
 }
 
 function webViewerPageMode(e) {
-  if (!PDFViewerApplication.initialized) {
-    return;
-  }
   // Handle the 'pagemode' hash parameter, see also `PDFLinkService_setHash`.
   var mode = e.mode, view;
   switch (mode) {
@@ -1623,9 +1658,6 @@ function webViewerPageMode(e) {
 }
 
 function webViewerNamedAction(e) {
-  if (!PDFViewerApplication.initialized) {
-    return;
-  }
   // Processing couple of named actions that might be useful.
   // See also PDFLinkService.executeNamedAction
   var action = e.action;
@@ -1651,9 +1683,6 @@ function webViewerPresentationModeChanged(e) {
 }
 
 function webViewerSidebarViewChanged(e) {
-  if (!PDFViewerApplication.initialized) {
-    return;
-  }
   PDFViewerApplication.pdfRenderingQueue.isThumbnailViewEnabled =
     PDFViewerApplication.pdfSidebar.isThumbnailViewVisible;
 
@@ -1668,9 +1697,6 @@ function webViewerSidebarViewChanged(e) {
 }
 
 function webViewerUpdateViewarea(e) {
-  if (!PDFViewerApplication.initialized) {
-    return;
-  }
   var location = e.location, store = PDFViewerApplication.store;
 
   if (store) {
@@ -1701,36 +1727,22 @@ function webViewerUpdateViewarea(e) {
   PDFViewerApplication.toolbar.updateLoadingIndicatorState(loading);
 }
 
-window.addEventListener('resize', function webViewerResize(evt) {
-  if (!PDFViewerApplication.eventBus) {
-    return;
-  }
-  PDFViewerApplication.eventBus.dispatch('resize');
-});
-
 function webViewerResize() {
-  if (PDFViewerApplication.initialized) {
-    var currentScaleValue = PDFViewerApplication.pdfViewer.currentScaleValue;
-    if (currentScaleValue === 'auto' ||
-        currentScaleValue === 'page-fit' ||
-        currentScaleValue === 'page-width') {
-      // Note: the scale is constant for 'page-actual'.
-      PDFViewerApplication.pdfViewer.currentScaleValue = currentScaleValue;
-    } else if (!currentScaleValue) {
-      // Normally this shouldn't happen, but if the scale wasn't initialized
-      // we set it to the default value in order to prevent any issues.
-      // (E.g. the document being rendered with the wrong scale on load.)
-      PDFViewerApplication.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
-    }
-    PDFViewerApplication.pdfViewer.update();
+  var currentScaleValue = PDFViewerApplication.pdfViewer.currentScaleValue;
+  if (currentScaleValue === 'auto' ||
+      currentScaleValue === 'page-fit' ||
+      currentScaleValue === 'page-width') {
+    // Note: the scale is constant for 'page-actual'.
+    PDFViewerApplication.pdfViewer.currentScaleValue = currentScaleValue;
+  } else if (!currentScaleValue) {
+    // Normally this shouldn't happen, but if the scale wasn't initialized
+    // we set it to the default value in order to prevent any issues.
+    // (E.g. the document being rendered with the wrong scale on load.)
+    PDFViewerApplication.pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
   }
+  PDFViewerApplication.pdfViewer.update();
 }
 
-window.addEventListener('hashchange', function webViewerHashchange(evt) {
-  var hash = document.location.hash.substring(1);
-  PDFViewerApplication.eventBus.dispatch('hashchange', {hash: hash});
-});
-
 function webViewerHashchange(e) {
   if (PDFViewerApplication.pdfHistory.isHashChangeUnlocked) {
     var hash = e.hash;
@@ -1747,15 +1759,6 @@ function webViewerHashchange(e) {
 
 var webViewerFileInputChange;
 if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
-  window.addEventListener('change', function webViewerChange(evt) {
-    var files = evt.target.files;
-    if (!files || files.length === 0) {
-      return;
-    }
-    PDFViewerApplication.eventBus.dispatch('fileinputchange',
-      {fileInput: evt.target});
-  }, true);
-
   webViewerFileInputChange = function webViewerFileInputChange(e) {
     var file = e.fileInput.files[0];
 
@@ -1873,9 +1876,6 @@ function webViewerFindFromUrlHash(e) {
 function webViewerScaleChanging(e) {
   PDFViewerApplication.toolbar.setPageScale(e.presetValue, e.scale);
 
-  if (!PDFViewerApplication.initialized) {
-    return;
-  }
   PDFViewerApplication.pdfViewer.update();
 }
 
@@ -1899,9 +1899,9 @@ function webViewerPageChanging(e) {
 }
 
 var zoomDisabled = false, zoomDisabledTimeout;
-function handleMouseWheel(evt) {
+function webViewerWheel(evt) {
   var pdfViewer = PDFViewerApplication.pdfViewer;
-  if (!pdfViewer || pdfViewer.isInPresentationMode) {
+  if (pdfViewer.isInPresentationMode) {
     return;
   }
 
@@ -1951,9 +1951,7 @@ function handleMouseWheel(evt) {
   }
 }
 
-window.addEventListener('wheel', handleMouseWheel);
-
-window.addEventListener('click', function click(evt) {
+function webViewerClick(evt) {
   if (!PDFViewerApplication.secondaryToolbar.isOpen) {
     return;
   }
@@ -1963,9 +1961,9 @@ window.addEventListener('click', function click(evt) {
        evt.target !== appConfig.secondaryToolbar.toggleButton)) {
     PDFViewerApplication.secondaryToolbar.close();
   }
-}, true);
+}
 
-window.addEventListener('keydown', function keydown(evt) {
+function webViewerKeyDown(evt) {
   if (OverlayManager.active) {
     return;
   }
@@ -2241,15 +2239,7 @@ window.addEventListener('keydown', function keydown(evt) {
   if (handled) {
     evt.preventDefault();
   }
-});
-
-window.addEventListener('beforeprint', function beforePrint(evt) {
-  PDFViewerApplication.eventBus.dispatch('beforeprint');
-});
-
-window.addEventListener('afterprint', function afterPrint(evt) {
-  PDFViewerApplication.eventBus.dispatch('afterprint');
-});
+}
 
 localized.then(webViewerLocalized);
 

From a96b0f80dd6883b9da366cbb430135827434aa24 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Thu, 8 Dec 2016 14:44:12 +0100
Subject: [PATCH 4/4] Convert the only remaining consumer (in `hand_tool.js`)
 of the 'localized' event to use the `localized` Promise instead, and only
 re-dispatch the 'localized' event on the `eventBus` for `GENERIC` builds

Ideally we'd remove the 'localized' event from the `eventBus`, but for backwards compatibility we keep it in `GENERIC` builds.
Note that while we want to ensure that the direction attribute of the HTML is updated as soon as the `localized` Promise is resolved, we purposely wait until the viewer has been initialized to ensure that the 'localized' event will always be dispatched.
---
 web/app.js       | 17 +++++++++++------
 web/hand_tool.js | 19 ++++++++++---------
 2 files changed, 21 insertions(+), 15 deletions(-)

diff --git a/web/app.js b/web/app.js
index d2fe73230..f7e76b3e9 100644
--- a/web/app.js
+++ b/web/app.js
@@ -215,6 +215,14 @@ var PDFViewerApplication = {
       self.bindEvents();
       self.bindWindowEvents();
 
+      if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
+        // For backwards compatibility, we dispatch the 'localized' event on
+        // the `eventBus` once the viewer has been initialized.
+        localized.then(function () {
+          self.eventBus.dispatch('localized');
+        });
+      }
+
       if (self.isViewerEmbedded && !PDFJS.isExternalLinkTargetSet()) {
         // Prevent external links from "replacing" the viewer,
         // when it's embedded in e.g. an iframe or an object.
@@ -1788,11 +1796,6 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
   };
 }
 
-function webViewerLocalized() {
-  document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
-  PDFViewerApplication.eventBus.dispatch('localized');
-}
-
 function webViewerPresentationMode() {
   PDFViewerApplication.requestPresentationMode();
 }
@@ -2241,7 +2244,9 @@ function webViewerKeyDown(evt) {
   }
 }
 
-localized.then(webViewerLocalized);
+localized.then(function webViewerLocalized() {
+  document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
+});
 
 /* Abstract factory for the print service. */
 var PDFPrintServiceFactory = {
diff --git a/web/hand_tool.js b/web/hand_tool.js
index e0db5ac6c..e4e401284 100644
--- a/web/hand_tool.js
+++ b/web/hand_tool.js
@@ -18,17 +18,19 @@
 (function (root, factory) {
   if (typeof define === 'function' && define.amd) {
     define('pdfjs-web/hand_tool', ['exports', 'pdfjs-web/grab_to_pan',
-      'pdfjs-web/preferences'], factory);
+      'pdfjs-web/preferences', 'pdfjs-web/ui_utils'], factory);
   } else if (typeof exports !== 'undefined') {
-    factory(exports, require('./grab_to_pan.js'), require('./preferences.js'));
+    factory(exports, require('./grab_to_pan.js'), require('./preferences.js'),
+      require('./ui_utils.js'));
   } else {
     factory((root.pdfjsWebHandTool = {}), root.pdfjsWebGrabToPan,
-      root.pdfjsWebPreferences);
+      root.pdfjsWebPreferences, root.pdfjsWebUIUtils);
   }
-}(this, function (exports, grabToPan, preferences) {
+}(this, function (exports, grabToPan, preferences, uiUtils) {
 
 var GrabToPan = grabToPan.GrabToPan;
 var Preferences = preferences.Preferences;
+var localized = uiUtils.localized;
 
 /**
  * @typedef {Object} HandToolOptions
@@ -59,13 +61,12 @@ var HandTool = (function HandToolClosure() {
 
     this.eventBus.on('togglehandtool', this.toggle.bind(this));
 
-    this.eventBus.on('localized', function (e) {
-      Preferences.get('enableHandToolOnLoad').then(function resolved(value) {
-        if (value) {
+    Promise.all([localized, Preferences.get('enableHandToolOnLoad')]).then(
+      function resolved(values) {
+        if (values[1] === true) {
           this.handTool.activate();
         }
-      }.bind(this), function rejected(reason) {});
-    }.bind(this));
+      }.bind(this)).catch(function rejected(reason) { });
 
     this.eventBus.on('presentationmodechanged', function (e) {
       if (e.switchInProgress) {