Bug 1084158 - Update pdf.js to version 1.0.907. r=bdahl, r=Mossop, a=lsblakk
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 17 Oct 2014 14:15:43 -0400
changeset 233429 c4b405e8f11278ce853c4f0c7b65742c96fb1533
parent 233428 bf823df545e3d3e0eca8be59c3107c4109aee286
child 233430 c75229ca139e01a6c41cf1e3a026583a7fd95015
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbdahl, Mossop, lsblakk
bugs1084158
milestone35.0a2
Bug 1084158 - Update pdf.js to version 1.0.907. r=bdahl, r=Mossop, a=lsblakk
browser/extensions/pdfjs/README.mozilla
browser/extensions/pdfjs/content/PdfJs.jsm
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
browser/extensions/pdfjs/content/build/pdf.js
browser/extensions/pdfjs/content/build/pdf.worker.js
browser/extensions/pdfjs/content/network.js
browser/extensions/pdfjs/content/pdfjschildbootstrap.js
browser/extensions/pdfjs/content/web/debugger.js
browser/extensions/pdfjs/content/web/images/loading-small.png
browser/extensions/pdfjs/content/web/images/loading-small@2x.png
browser/extensions/pdfjs/content/web/viewer.css
browser/extensions/pdfjs/content/web/viewer.html
browser/extensions/pdfjs/content/web/viewer.js
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.0.801
+Current extension version is: 1.0.907
 
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -73,17 +73,19 @@ function isDefaultHandler() {
 function initializeDefaultPreferences() {
 
 var DEFAULT_PREFERENCES = {
   showPreviousViewOnLoad: true,
   defaultZoomValue: '',
   sidebarViewOnLoad: 0,
   enableHandToolOnLoad: false,
   enableWebGL: false,
+  pdfBugEnabled: false,
   disableRange: false,
+  disableStream: false,
   disableAutoFetch: false,
   disableFontFace: false,
   disableTextLayer: false,
   useOnlyCssZoom: false
 };
 
 
   var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -72,17 +72,18 @@ function getChromeWindow(domWindow) {
 
 function getFindBar(domWindow) {
   if (PdfjsContentUtils.isRemote) {
     return PdfjsContentUtils.getFindBar(domWindow);
   }
   var browser = getContainingBrowser(domWindow);
   try {
     var tabbrowser = browser.getTabBrowser();
-    var tab = tabbrowser.getTabForBrowser(browser);
+    var tab;
+    tab = tabbrowser.getTabForBrowser(browser);
     return tabbrowser.getFindBar(tab);
   } catch (e) {
     // FF22 has no _getTabForBrowser, and FF24 has no getFindBar
     var chromeWindow = browser.ownerDocument.defaultView;
     return chromeWindow.gFindBar;
   }
 }
 
@@ -157,66 +158,64 @@ function getLocalizedString(strings, id,
 
 function makeContentReadable(obj, window) {
   return Cu.cloneInto(obj, window);
 }
 
 // PDF data storage
 function PdfDataListener(length) {
   this.length = length; // less than 0, if length is unknown
-  this.data = new Uint8Array(length >= 0 ? length : 0x10000);
+  this.buffer = null;
   this.loaded = 0;
 }
 
 PdfDataListener.prototype = {
   append: function PdfDataListener_append(chunk) {
-    var willBeLoaded = this.loaded + chunk.length;
-    if (this.length >= 0 && this.length < willBeLoaded) {
+    // In most of the cases we will pass data as we receive it, but at the
+    // beginning of the loading we may accumulate some data.
+    if (!this.buffer) {
+      this.buffer = new Uint8Array(chunk);
+    } else {
+      var buffer = this.buffer;
+      var newBuffer = new Uint8Array(buffer.length + chunk.length);
+      newBuffer.set(buffer);
+      newBuffer.set(chunk, buffer.length);
+      this.buffer = newBuffer;
+    }
+    this.loaded += chunk.length;
+    if (this.length >= 0 && this.length < this.loaded) {
       this.length = -1; // reset the length, server is giving incorrect one
     }
-    if (this.length < 0 && this.data.length < willBeLoaded) {
-      // data length is unknown and new chunk will not fit in the existing
-      // buffer, resizing the buffer by doubling the its last length
-      var newLength = this.data.length;
-      for (; newLength < willBeLoaded; newLength *= 2) {}
-      var newData = new Uint8Array(newLength);
-      newData.set(this.data);
-      this.data = newData;
-    }
-    this.data.set(chunk, this.loaded);
-    this.loaded = willBeLoaded;
     this.onprogress(this.loaded, this.length >= 0 ? this.length : void(0));
   },
-  getData: function PdfDataListener_getData() {
-    var data = this.data;
-    if (this.loaded != data.length)
-      data = data.subarray(0, this.loaded);
-    delete this.data; // releasing temporary storage
-    return data;
+  readData: function PdfDataListener_readData() {
+    var result = this.buffer;
+    this.buffer = null;
+    return result;
   },
   finish: function PdfDataListener_finish() {
     this.isDataReady = true;
     if (this.oncompleteCallback) {
-      this.oncompleteCallback(this.getData());
+      this.oncompleteCallback(this.readData());
     }
   },
   error: function PdfDataListener_error(errorCode) {
     this.errorCode = errorCode;
     if (this.oncompleteCallback) {
       this.oncompleteCallback(null, errorCode);
     }
   },
   onprogress: function() {},
   get oncomplete() {
     return this.oncompleteCallback;
   },
   set oncomplete(value) {
     this.oncompleteCallback = value;
     if (this.isDataReady) {
-      value(this.getData());
+      value(this.readData());
     }
     if (this.errorCode) {
       value(null, this.errorCode);
     }
   }
 };
 
 // All the priviledged actions.
@@ -229,17 +228,17 @@ function ChromeActions(domWindow, conten
     streamTypesUsed: [],
     fontTypesUsed: [],
     startAt: Date.now()
   };
 }
 
 ChromeActions.prototype = {
   isInPrivateBrowsing: function() {
-    return PrivateBrowsingUtils.isWindowPrivate(this.domWindow);
+    return PrivateBrowsingUtils.isContentWindowPrivate(this.domWindow);
   },
   download: function(data, sendResponse) {
     var self = this;
     var originalUrl = data.originalUrl;
     // The data may not be downloaded so we need just retry getting the pdf with
     // the original url.
     var originalUri = NetUtil.newURI(data.originalUrl);
     var filename = data.filename;
@@ -324,19 +323,16 @@ ChromeActions.prototype = {
 
       var result = this.localizedStrings[data];
       return JSON.stringify(result || null);
     } catch (e) {
       log('Unable to retrive localized strings: ' + e);
       return 'null';
     }
   },
-  pdfBugEnabled: function() {
-    return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
-  },
   supportsIntegratedFind: function() {
     // Integrated find is only supported when we're not in a frame
     if (this.domWindow.frameElement !== null) {
       return false;
     }
     // ... and when the new find events code exists.
     var findBar = getFindBar(this.domWindow);
     return findBar && ('updateControlState' in findBar);
@@ -506,21 +502,23 @@ ChromeActions.prototype = {
 };
 
 var RangedChromeActions = (function RangedChromeActionsClosure() {
   /**
    * This is for range requests
    */
   function RangedChromeActions(
               domWindow, contentDispositionFilename, originalRequest,
-              dataListener) {
+              rangeEnabled, streamingEnabled, dataListener) {
 
     ChromeActions.call(this, domWindow, contentDispositionFilename);
     this.dataListener = dataListener;
     this.originalRequest = originalRequest;
+    this.rangeEnabled = rangeEnabled;
+    this.streamingEnabled = streamingEnabled;
 
     this.pdfUrl = originalRequest.URI.spec;
     this.contentLength = originalRequest.contentLength;
 
     // Pass all the headers from the original request through
     var httpHeaderVisitor = {
       headers: {},
       visitHeader: function(aHeader, aValue) {
@@ -529,17 +527,19 @@ var RangedChromeActions = (function Rang
           // request header to fetch only the unfetched portions of the file
           // (e.g. 'Range: bytes=1024-'). However, we want to set this header
           // manually to fetch the PDF in chunks.
           return;
         }
         this.headers[aHeader] = aValue;
       }
     };
-    originalRequest.visitRequestHeaders(httpHeaderVisitor);
+    if (originalRequest.visitRequestHeaders) {
+      originalRequest.visitRequestHeaders(httpHeaderVisitor);
+    }
 
     var self = this;
     var xhr_onreadystatechange = function xhr_onreadystatechange() {
       if (this.readyState === 1) { // LOADING
         var netChannel = this.channel;
         if ('nsIPrivateBrowsingChannel' in Ci &&
             netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
           var docIsPrivate = self.isInPrivateBrowsing();
@@ -568,30 +568,56 @@ var RangedChromeActions = (function Rang
     });
   }
 
   RangedChromeActions.prototype = Object.create(ChromeActions.prototype);
   var proto = RangedChromeActions.prototype;
   proto.constructor = RangedChromeActions;
 
   proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
-    this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
-    this.originalRequest = null;
+    var self = this;
+    var data;
+    if (!this.streamingEnabled) {
+      this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
+      this.originalRequest = null;
+      data = this.dataListener.readData();
+      this.dataListener = null;
+    } else {
+      data = this.dataListener.readData();
+
+      this.dataListener.onprogress = function (loaded, total) {
+        self.domWindow.postMessage({
+          pdfjsLoadAction: 'progressiveRead',
+          loaded: loaded,
+          total: total,
+          chunk: self.dataListener.readData()
+        }, '*');
+      };
+      this.dataListener.oncomplete = function () {
+        delete self.dataListener;
+      };
+    }
+
     this.domWindow.postMessage({
       pdfjsLoadAction: 'supportsRangedLoading',
+      rangeEnabled: this.rangeEnabled,
+      streamingEnabled: this.streamingEnabled,
       pdfUrl: this.pdfUrl,
       length: this.contentLength,
-      data: this.dataListener.getData()
+      data: data
     }, '*');
-    this.dataListener = null;
 
     return true;
   };
 
   proto.requestDataRange = function RangedChromeActions_requestDataRange(args) {
+    if (!this.rangeEnabled) {
+      return;
+    }
+
     var begin = args.begin;
     var end = args.end;
     var domWindow = this.domWindow;
     // TODO(mack): Support error handler. We're not currently not handling
     // errors from chrome code for non-range requests, so this doesn't
     // seem high-pri
     this.networkManager.requestRange(begin, end, {
       onDone: function RangedChromeActions_onDone(args) {
@@ -823,32 +849,41 @@ PdfStreamConverter.prototype = {
     // Setup the request so we can use it below.
     var isHttpRequest = false;
     try {
       aRequest.QueryInterface(Ci.nsIHttpChannel);
       isHttpRequest = true;
     } catch (e) {}
 
     var rangeRequest = false;
+    var streamRequest = false;
     if (isHttpRequest) {
       var contentEncoding = 'identity';
       try {
         contentEncoding = aRequest.getResponseHeader('Content-Encoding');
       } catch (e) {}
 
       var acceptRanges;
       try {
         acceptRanges = aRequest.getResponseHeader('Accept-Ranges');
       } catch (e) {}
 
       var hash = aRequest.URI.ref;
+      var isPDFBugEnabled = getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
       rangeRequest = contentEncoding === 'identity' &&
                      acceptRanges === 'bytes' &&
                      aRequest.contentLength >= 0 &&
-                     hash.indexOf('disableRange=true') < 0;
+                     !getBoolPref(PREF_PREFIX + '.disableRange', false) &&
+                     (!isPDFBugEnabled ||
+                      hash.toLowerCase().indexOf('disablerange=true') < 0);
+      streamRequest = contentEncoding === 'identity' &&
+                      aRequest.contentLength >= 0 &&
+                      !getBoolPref(PREF_PREFIX + '.disableStream', false) &&
+                      (!isPDFBugEnabled ||
+                       hash.toLowerCase().indexOf('disablestream=true') < 0);
     }
 
     aRequest.QueryInterface(Ci.nsIChannel);
 
     aRequest.QueryInterface(Ci.nsIWritablePropertyBag);
 
     var contentDispositionFilename;
     try {
@@ -892,22 +927,23 @@ PdfStreamConverter.prototype = {
       onDataAvailable: function(request, context, inputStream, offset, count) {
         listener.onDataAvailable(aRequest, context, inputStream, offset, count);
       },
       onStopRequest: function(request, context, statusCode) {
         // We get the DOM window here instead of before the request since it
         // may have changed during a redirect.
         var domWindow = getDOMWindow(channel);
         var actions;
-        if (rangeRequest) {
+        if (rangeRequest || streamRequest) {
           actions = new RangedChromeActions(
-              domWindow, contentDispositionFilename, aRequest, dataListener);
+            domWindow, contentDispositionFilename, aRequest,
+            rangeRequest, streamRequest, dataListener);
         } else {
           actions = new StandardChromeActions(
-              domWindow, contentDispositionFilename, dataListener);
+            domWindow, contentDispositionFilename, dataListener);
         }
         var requestListener = new RequestListener(actions);
         domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
           requestListener.receive(event);
         }, false, true);
         if (actions.supportsIntegratedFind()) {
           var chromeWindow = getChromeWindow(domWindow);
           var findBar = getFindBar(domWindow);
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -38,17 +38,19 @@ XPCOMUtils.defineLazyServiceGetter(Svc, 
 
 
 var DEFAULT_PREFERENCES = {
   showPreviousViewOnLoad: true,
   defaultZoomValue: '',
   sidebarViewOnLoad: 0,
   enableHandToolOnLoad: false,
   enableWebGL: false,
+  pdfBugEnabled: false,
   disableRange: false,
+  disableStream: false,
   disableAutoFetch: false,
   disableFontFace: false,
   disableTextLayer: false,
   useOnlyCssZoom: false
 };
 
 
 let PdfjsChromeUtils = {
@@ -280,17 +282,18 @@ let PdfjsChromeUtils = {
 /*
  * CPOW security features require chrome objects declare exposed
  * properties via __exposedProps__. We don't want to expose things
  * directly on the findbar, so we wrap the findbar in a smaller
  * object here that supports the features pdf.js needs.
  */
 function PdfjsFindbarWrapper(aBrowser) {
   let tabbrowser = aBrowser.getTabBrowser();
-  let tab = tabbrowser.getTabForBrowser(aBrowser);
+  let tab;
+  tab = tabbrowser.getTabForBrowser(aBrowser);
   this._findbar = tabbrowser.getFindBar(tab);
 };
 
 PdfjsFindbarWrapper.prototype = {
   __exposedProps__: {
     addEventListener: "r",
     removeEventListener: "r",
     updateControlState: "r",
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.801';
-PDFJS.build = 'e77e5c4';
+PDFJS.version = '1.0.907';
+PDFJS.build = 'e9072ac';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -1358,16 +1358,24 @@ PDFJS.workerSrc = (PDFJS.workerSrc === u
  * supports partial content requests then the PDF will be fetched in chunks.
  * Enabled (false) by default.
  * @var {boolean}
  */
 PDFJS.disableRange = (PDFJS.disableRange === undefined ?
                       false : PDFJS.disableRange);
 
 /**
+ * Disable streaming of PDF file data. By default PDF.js attempts to load PDF
+ * in chunks. This default behavior can be disabled.
+ * @var {boolean}
+ */
+PDFJS.disableStream = (PDFJS.disableStream === undefined ?
+                       false : PDFJS.disableStream);
+
+/**
  * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
  * will automatically keep fetching more data even if it isn't needed to display
  * the current page. This default behavior can be disabled.
  * @var {boolean}
  */
 PDFJS.disableAutoFetch = (PDFJS.disableAutoFetch === undefined ?
                           false : PDFJS.disableAutoFetch);
 
@@ -1555,21 +1563,31 @@ var PDFDocumentProxy = (function PDFDocu
      * associated with the reference.
      */
     getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
       return this.transport.getPageIndex(ref);
     },
     /**
      * @return {Promise} A promise that is resolved with a lookup table for
      * mapping named destinations to reference numbers.
+     *
+     * This can be slow for large documents: use getDestination instead
      */
     getDestinations: function PDFDocumentProxy_getDestinations() {
       return this.transport.getDestinations();
     },
     /**
+     * @param {string} id The named destination to get.
+     * @return {Promise} A promise that is resolved with all information
+     * of the given named destination.
+     */
+    getDestination: function PDFDocumentProxy_getDestination(id) {
+      return this.transport.getDestination(id);
+    },
+    /**
      * @return {Promise} A promise that is resolved with a lookup table for
      * mapping named attachments to their content.
      */
     getAttachments: function PDFDocumentProxy_getAttachments() {
       return this.transport.getAttachments();
     },
     /**
      * @return {Promise} A promise that is resolved with an array of all the
@@ -2112,16 +2130,22 @@ var WorkerTransport = (function WorkerTr
         });
 
         pdfDataRangeTransport.addProgressListener(function(loaded) {
           messageHandler.send('OnDataProgress', {
             loaded: loaded
           });
         });
 
+        pdfDataRangeTransport.addProgressiveReadListener(function(chunk) {
+          messageHandler.send('OnDataRange', {
+            chunk: chunk
+          });
+        });
+
         messageHandler.on('RequestDataRange',
           function transportDataRange(data) {
             pdfDataRangeTransport.requestDataRange(data.begin, data.end);
           }, this);
       }
 
       messageHandler.on('GetDoc', function transportDoc(data) {
         var pdfInfo = data.pdfInfo;
@@ -2172,16 +2196,22 @@ var WorkerTransport = (function WorkerTr
         this.workerReadyCapability.reject(
           new UnknownErrorException(exception.message, exception.details));
       }, this);
 
       messageHandler.on('DataLoaded', function transportPage(data) {
         this.downloadInfoCapability.resolve(data);
       }, this);
 
+      messageHandler.on('PDFManagerReady', function transportPage(data) {
+        if (this.pdfDataRangeTransport) {
+          this.pdfDataRangeTransport.transportReady();
+        }
+      }, this);
+
       messageHandler.on('StartRenderPage', function transportRender(data) {
         var page = this.pageCache[data.pageIndex];
 
         page.stats.timeEnd('Page Request');
         page._startRenderPage(data.transparency, data.intent);
       }, this);
 
       messageHandler.on('RenderPageChunk', function transportRender(data) {
@@ -2203,17 +2233,17 @@ var WorkerTransport = (function WorkerTr
 
             var font;
             if ('error' in exportedData) {
               var error = exportedData.error;
               warn('Error during font loading: ' + error);
               this.commonObjs.resolve(id, error);
               break;
             } else {
-              font = new FontFace(exportedData);
+              font = new FontFaceObject(exportedData);
             }
 
             FontLoader.bind(
               [font],
               function fontReady(fontObjs) {
                 this.commonObjs.resolve(id, font);
               }.bind(this)
             );
@@ -2316,16 +2346,17 @@ var WorkerTransport = (function WorkerTr
           };
           img.src = imageUrl;
         });
       });
     },
 
     fetchDocument: function WorkerTransport_fetchDocument(source) {
       source.disableAutoFetch = PDFJS.disableAutoFetch;
+      source.disableStream = PDFJS.disableStream;
       source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
       this.messageHandler.send('GetDocRequest', {
         source: source,
         disableRange: PDFJS.disableRange,
         maxImageSize: PDFJS.maxImageSize,
         cMapUrl: PDFJS.cMapUrl,
         cMapPacked: PDFJS.cMapPacked,
         disableFontFace: PDFJS.disableFontFace,
@@ -2367,16 +2398,20 @@ var WorkerTransport = (function WorkerTr
       return this.messageHandler.sendWithPromise('GetAnnotations',
         { pageIndex: pageIndex });
     },
 
     getDestinations: function WorkerTransport_getDestinations() {
       return this.messageHandler.sendWithPromise('GetDestinations', null);
     },
 
+    getDestination: function WorkerTransport_getDestination(id) {
+      return this.messageHandler.sendWithPromise('GetDestination', { id: id } );
+    },
+
     getAttachments: function WorkerTransport_getAttachments() {
       return this.messageHandler.sendWithPromise('GetAttachments', null);
     },
 
     getJavaScript: function WorkerTransport_getJavaScript() {
       return this.messageHandler.sendWithPromise('GetJavaScript', null);
     },
 
@@ -5726,30 +5761,31 @@ var FontLoader = {
       font.attached = true;
       font.bindDOM()
     }
   
     setTimeout(callback);
   }
 };
 
-var FontFace = (function FontFaceClosure() {
-  function FontFace(name, file, properties) {
+var FontFaceObject = (function FontFaceObjectClosure() {
+  function FontFaceObject(name, file, properties) {
     this.compiledGlyphs = {};
     if (arguments.length === 1) {
       // importing translated data
       var data = arguments[0];
       for (var i in data) {
         this[i] = data[i];
       }
       return;
     }
   }
-  FontFace.prototype = {
-    bindDOM: function FontFace_bindDOM() {
+  FontFaceObject.prototype = {
+
+    bindDOM: function FontFaceObject_bindDOM() {
       if (!this.data) {
         return null;
       }
 
       if (PDFJS.disableFontFace) {
         this.disableFontFace = true;
         return null;
       }
@@ -5766,26 +5802,26 @@ var FontFace = (function FontFaceClosure
       if (PDFJS.pdfBug && 'FontInspector' in globalScope &&
           globalScope['FontInspector'].enabled) {
         globalScope['FontInspector'].fontAdded(this, url);
       }
 
       return rule;
     },
 
-    getPathGenerator: function (objs, character) {
+    getPathGenerator: function FontLoader_getPathGenerator(objs, character) {
       if (!(character in this.compiledGlyphs)) {
         var js = objs.get(this.loadedName + '_path_' + character);
         /*jshint -W054 */
         this.compiledGlyphs[character] = new Function('c', 'size', js);
       }
       return this.compiledGlyphs[character];
     }
   };
-  return FontFace;
+  return FontFaceObject;
 })();
 
 
 var HIGHLIGHT_OFFSET = 4; // px
 var ANNOT_MIN_SIZE = 10; // px
 
 var AnnotationUtils = (function AnnotationUtilsClosure() {
   // TODO(mack): This dupes some of the logic in CanvasGraphics.setFont()
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.801';
-PDFJS.build = 'e77e5c4';
+PDFJS.version = '1.0.907';
+PDFJS.build = 'e9072ac';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -1304,28 +1304,28 @@ var ChunkedStream = (function ChunkedStr
     this.start = 0;
     this.pos = 0;
     this.end = length;
     this.chunkSize = chunkSize;
     this.loadedChunks = [];
     this.numChunksLoaded = 0;
     this.numChunks = Math.ceil(length / chunkSize);
     this.manager = manager;
-    this.initialDataLength = 0;
+    this.progressiveDataLength = 0;
     this.lastSuccessfulEnsureByteChunk = -1;  // a single-entry cache
   }
 
   // required methods for a stream. if a particular stream does not
   // implement these, an error should be thrown
   ChunkedStream.prototype = {
 
     getMissingChunks: function ChunkedStream_getMissingChunks() {
       var chunks = [];
       for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
-        if (!(chunk in this.loadedChunks)) {
+        if (!this.loadedChunks[chunk]) {
           chunks.push(chunk);
         }
       }
       return chunks;
     },
 
     getBaseStreams: function ChunkedStream_getBaseStreams() {
       return [this];
@@ -1347,83 +1347,91 @@ var ChunkedStream = (function ChunkedStr
 
       this.bytes.set(new Uint8Array(chunk), begin);
       var chunkSize = this.chunkSize;
       var beginChunk = Math.floor(begin / chunkSize);
       var endChunk = Math.floor((end - 1) / chunkSize) + 1;
       var curChunk;
 
       for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
-        if (!(curChunk in this.loadedChunks)) {
+        if (!this.loadedChunks[curChunk]) {
           this.loadedChunks[curChunk] = true;
           ++this.numChunksLoaded;
         }
       }
     },
 
-    onReceiveInitialData: function ChunkedStream_onReceiveInitialData(data) {
-      this.bytes.set(data);
-      this.initialDataLength = data.length;
-      var endChunk = (this.end === data.length ?
-        this.numChunks : Math.floor(data.length / this.chunkSize));
-      for (var i = 0; i < endChunk; i++) {
-        this.loadedChunks[i] = true;
-        ++this.numChunksLoaded;
+    onReceiveProgressiveData:
+        function ChunkedStream_onReceiveProgressiveData(data) {
+      var position = this.progressiveDataLength;
+      var beginChunk = Math.floor(position / this.chunkSize);
+
+      this.bytes.set(new Uint8Array(data), position);
+      position += data.byteLength;
+      this.progressiveDataLength = position;
+      var endChunk = position >= this.end ? this.numChunks :
+                     Math.floor(position / this.chunkSize);
+      var curChunk;
+      for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
+        if (!this.loadedChunks[curChunk]) {
+          this.loadedChunks[curChunk] = true;
+          ++this.numChunksLoaded;
+        }
       }
     },
 
     ensureByte: function ChunkedStream_ensureByte(pos) {
       var chunk = Math.floor(pos / this.chunkSize);
       if (chunk === this.lastSuccessfulEnsureByteChunk) {
         return;
       }
 
-      if (!(chunk in this.loadedChunks)) {
+      if (!this.loadedChunks[chunk]) {
         throw new MissingDataException(pos, pos + 1);
       }
       this.lastSuccessfulEnsureByteChunk = chunk;
     },
 
     ensureRange: function ChunkedStream_ensureRange(begin, end) {
       if (begin >= end) {
         return;
       }
 
-      if (end <= this.initialDataLength) {
+      if (end <= this.progressiveDataLength) {
         return;
       }
 
       var chunkSize = this.chunkSize;
       var beginChunk = Math.floor(begin / chunkSize);
       var endChunk = Math.floor((end - 1) / chunkSize) + 1;
       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
-        if (!(chunk in this.loadedChunks)) {
+        if (!this.loadedChunks[chunk]) {
           throw new MissingDataException(begin, end);
         }
       }
     },
 
     nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
       var chunk, n;
       for (chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
-        if (!(chunk in this.loadedChunks)) {
+        if (!this.loadedChunks[chunk]) {
           return chunk;
         }
       }
       // Wrap around to beginning
       for (chunk = 0; chunk < beginChunk; ++chunk) {
-        if (!(chunk in this.loadedChunks)) {
+        if (!this.loadedChunks[chunk]) {
           return chunk;
         }
       }
       return null;
     },
 
     hasChunk: function ChunkedStream_hasChunk(chunk) {
-      return chunk in this.loadedChunks;
+      return !!this.loadedChunks[chunk];
     },
 
     get length() {
       return this.end - this.start;
     },
 
     get isEmpty() {
       return this.length === 0;
@@ -1512,17 +1520,17 @@ var ChunkedStream = (function ChunkedStr
       function ChunkedStreamSubstream() {}
       ChunkedStreamSubstream.prototype = Object.create(this);
       ChunkedStreamSubstream.prototype.getMissingChunks = function() {
         var chunkSize = this.chunkSize;
         var beginChunk = Math.floor(this.start / chunkSize);
         var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
         var missingChunks = [];
         for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
-          if (!(chunk in this.loadedChunks)) {
+          if (!this.loadedChunks[chunk]) {
             missingChunks.push(chunk);
           }
         }
         return missingChunks;
       };
       var subStream = new ChunkedStreamSubstream();
       subStream.pos = subStream.start = start;
       subStream.end = start + length || this.end;
@@ -1570,38 +1578,26 @@ var ChunkedStreamManager = (function Chu
       };
     }
 
     this.currRequestId = 0;
 
     this.chunksNeededByRequest = {};
     this.requestsByChunk = {};
     this.callbacksByRequest = {};
+    this.progressiveDataLength = 0;
 
     this._loadedStreamCapability = createPromiseCapability();
 
     if (args.initialData) {
-      this.setInitialData(args.initialData);
+      this.onReceiveData({chunk: args.initialData});
     }
   }
 
   ChunkedStreamManager.prototype = {
-
-    setInitialData: function ChunkedStreamManager_setInitialData(data) {
-      this.stream.onReceiveInitialData(data);
-      if (this.stream.allChunksLoaded()) {
-        this._loadedStreamCapability.resolve(this.stream);
-      } else if (this.msgHandler) {
-        this.msgHandler.send('DocProgress', {
-          loaded: data.length,
-          total: this.length
-        });
-      }
-    },
-
     onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
       return this._loadedStreamCapability.promise;
     },
 
     // Get all the chunks that are not yet loaded and groups them into
     // contiguous ranges to load in as few requests as possible
     requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
       var missingChunks = this.stream.getMissingChunks();
@@ -1729,23 +1725,31 @@ var ChunkedStreamManager = (function Chu
       this.msgHandler.send('DocProgress', {
         loaded: bytesLoaded,
         total: this.length
       });
     },
 
     onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
       var chunk = args.chunk;
-      var begin = args.begin;
+      var isProgressive = args.begin === undefined;
+      var begin = isProgressive ? this.progressiveDataLength : args.begin;
       var end = begin + chunk.byteLength;
 
-      var beginChunk = this.getBeginChunk(begin);
-      var endChunk = this.getEndChunk(end);
-
-      this.stream.onReceiveData(begin, chunk);
+      var beginChunk = Math.floor(begin / this.chunkSize);
+      var endChunk = end < this.length ? Math.floor(end / this.chunkSize) :
+                                         Math.ceil(end / this.chunkSize);
+
+      if (isProgressive) {
+        this.stream.onReceiveProgressiveData(chunk);
+        this.progressiveDataLength = end;
+      } else {
+        this.stream.onReceiveData(begin, chunk);
+      }
+
       if (this.stream.allChunksLoaded()) {
         this._loadedStreamCapability.resolve(this.stream);
       }
 
       var loadedRequests = [];
       var i, requestId;
       for (chunk = beginChunk; chunk < endChunk; ++chunk) {
         // The server might return more chunks than requested
@@ -1872,16 +1876,20 @@ var BasePdfManager = (function BasePdfMa
     requestRange: function BasePdfManager_ensure(begin, end) {
       return new NotImplementedException();
     },
 
     requestLoadedStream: function BasePdfManager_requestLoadedStream() {
       return new NotImplementedException();
     },
 
+    sendProgressiveData: function BasePdfManager_sendProgressiveData(chunk) {
+      return new NotImplementedException();
+    },
+
     updatePassword: function BasePdfManager_updatePassword(password) {
       this.pdfDocument.xref.password = this.password = password;
       if (this._passwordChangedCapability) {
         this._passwordChangedCapability.resolve();
       }
     },
 
     passwordChanged: function BasePdfManager_passwordChanged() {
@@ -2008,16 +2016,21 @@ var NetworkPdfManager = (function Networ
     }.bind(this));
   };
 
   NetworkPdfManager.prototype.requestLoadedStream =
       function NetworkPdfManager_requestLoadedStream() {
     this.streamManager.requestAllChunks();
   };
 
+  NetworkPdfManager.prototype.sendProgressiveData =
+      function NetworkPdfManager_sendProgressiveData(chunk) {
+    this.streamManager.onReceiveData({ chunk: chunk });
+  };
+
   NetworkPdfManager.prototype.onLoadedStream =
       function NetworkPdfManager_getLoadedStream() {
     return this.streamManager.onLoadedStream();
   };
 
   NetworkPdfManager.prototype.terminate =
       function NetworkPdfManager_terminate() {
     this.streamManager.networkManager.abortAllRequests();
@@ -2954,16 +2967,48 @@ var Catalog = (function CatalogClosure()
           if (!names.hasOwnProperty(name)) {
             continue;
           }
           dests[name] = fetchDestination(names[name]);
         }
       }
       return shadow(this, 'destinations', dests);
     },
+    getDestination: function Catalog_getDestination(destinationId) {
+      function fetchDestination(dest) {
+        return isDict(dest) ? dest.get('D') : dest;
+      }
+
+      var xref = this.xref;
+      var dest, nameTreeRef, nameDictionaryRef;
+      var obj = this.catDict.get('Names');
+      if (obj && obj.has('Dests')) {
+        nameTreeRef = obj.getRaw('Dests');
+      } else if (this.catDict.has('Dests')) {
+        nameDictionaryRef = this.catDict.get('Dests');
+      }
+
+      if (nameDictionaryRef) {
+        // reading simple destination dictionary
+        obj = nameDictionaryRef;
+        obj.forEach(function catalogForEach(key, value) {
+          if (!value) {
+            return;
+          }
+          if (key === destinationId) {
+            dest = fetchDestination(value);
+          }
+        });
+      }
+      if (nameTreeRef) {
+        var nameTree = new NameTree(nameTreeRef, xref);
+        dest = fetchDestination(nameTree.get(destinationId));
+      }
+      return dest;
+    },
     get attachments() {
       var xref = this.xref;
       var attachments = null, nameTreeRef;
       var obj = this.catDict.get('Names');
       if (obj) {
         nameTreeRef = obj.getRaw('EmbeddedFiles');
       }
 
@@ -3855,16 +3900,86 @@ var NameTree = (function NameTreeClosure
         var names = obj.get('Names');
         if (names) {
           for (i = 0, n = names.length; i < n; i += 2) {
             dict[names[i]] = xref.fetchIfRef(names[i + 1]);
           }
         }
       }
       return dict;
+    },
+
+    get: function NameTree_get(destinationId) {
+      if (!this.root) {
+        return null;
+      }
+
+      var xref = this.xref;
+      var kidsOrNames = xref.fetchIfRef(this.root);
+      var loopCount = 0;
+      var MAX_NAMES_LEVELS = 10;
+      var l, r, m;
+
+      // Perform a binary search to quickly find the entry that
+      // contains the named destination we are looking for.
+      while (kidsOrNames.has('Kids')) {
+        loopCount++;
+        if (loopCount > MAX_NAMES_LEVELS) {
+          warn('Search depth limit for named destionations has been reached.');
+          return null;
+        }
+        
+        var kids = kidsOrNames.get('Kids');
+        if (!isArray(kids)) {
+          return null;
+        }
+
+        l = 0;
+        r = kids.length - 1;
+        while (l <= r) {
+          m = (l + r) >> 1;
+          var kid = xref.fetchIfRef(kids[m]);
+          var limits = kid.get('Limits');
+
+          if (destinationId < limits[0]) {
+            r = m - 1;
+          } else if (destinationId > limits[1]) {
+            l = m + 1;
+          } else {
+            kidsOrNames = xref.fetchIfRef(kids[m]);
+            break;
+          }
+        }
+        if (l > r) {
+          return null;
+        }
+      }
+
+      // If we get here, then we have found the right entry. Now
+      // go through the named destinations in the Named dictionary
+      // until we find the exact destination we're looking for.
+      var names = kidsOrNames.get('Names');
+      if (isArray(names)) {
+        // Perform a binary search to reduce the lookup time.
+        l = 0;
+        r = names.length - 2;
+        while (l <= r) {
+          // Check only even indices (0, 2, 4, ...) because the
+          // odd indices contain the actual D array.
+          m = (l + r) & ~1;
+          if (destinationId < names[m]) {
+            r = m - 2;
+          } else if (destinationId > names[m]) {
+            l = m + 2;
+          } else {
+            return xref.fetchIfRef(names[m + 1]);
+          }
+        }
+      }
+      return null;
     }
   };
   return NameTree;
 })();
 
 /**
  * "A PDF file can refer to the contents of another file by using a File 
  * Specification (PDF 1.1)", see the spec (7.11) for more details.
@@ -15852,27 +15967,40 @@ var Font = (function FontClosure() {
       if (isStandardFont && type === 'CIDFontType2' &&
           properties.cidEncoding.indexOf('Identity-') === 0) {
         // Standard fonts might be embedded as CID font without glyph mapping.
         // Building one based on GlyphMapForStandardFonts.
         var map = [];
         for (var code in GlyphMapForStandardFonts) {
           map[+code] = GlyphMapForStandardFonts[code];
         }
+        var isIdentityUnicode = this.toUnicode instanceof IdentityToUnicodeMap;
+        if (!isIdentityUnicode) {
+          this.toUnicode.forEach(function(charCode, unicodeCharCode) {
+            map[+charCode] = unicodeCharCode;
+          });
+        }
         this.toFontChar = map;
         this.toUnicode = new ToUnicodeMap(map);
       } else if (/Symbol/i.test(fontName)) {
         var symbols = Encodings.SymbolSetEncoding;
         for (charCode in symbols) {
           fontChar = GlyphsUnicode[symbols[charCode]];
           if (!fontChar) {
             continue;
           }
           this.toFontChar[charCode] = fontChar;
         }
+        for (charCode in properties.differences) {
+          fontChar = GlyphsUnicode[properties.differences[charCode]];
+          if (!fontChar) {
+            continue;
+          }
+          this.toFontChar[charCode] = fontChar;
+        }
       } else if (/Dingbats/i.test(fontName)) {
         var dingbats = Encodings.ZapfDingbatsEncoding;
         for (charCode in dingbats) {
           fontChar = DingbatsGlyphsUnicode[dingbats[charCode]];
           if (!fontChar) {
             continue;
           }
           this.toFontChar[charCode] = fontChar;
@@ -15923,16 +16051,19 @@ var Font = (function FontClosure() {
     // XXX: Temporarily change the type for open type so we trigger a warning.
     // This should be removed when we add support for open type.
     if (subtype === 'OpenType') {
       type = 'OpenType';
     }
 
     var data;
     switch (type) {
+      case 'MMType1':
+        info('MMType1 font (' + name + '), falling back to Type1.');
+        /* falls through */
       case 'Type1':
       case 'CIDFontType0':
         this.mimetype = 'font/opentype';
 
         var cff = (subtype === 'Type1C' || subtype === 'CIDFontType0C') ?
           new CFFFont(file, properties) : new Type1Font(name, file, properties);
 
         adjustWidths(properties);
@@ -31201,17 +31332,17 @@ var JpegStream = (function JpegStreamClo
   JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) {
     if (this.bufferLength) {
       return;
     }
     try {
       var jpegImage = new JpegImage();
 
       // checking if values needs to be transformed before conversion
-      if (this.dict && isArray(this.dict.get('Decode'))) {
+      if (this.forceRGB && this.dict && isArray(this.dict.get('Decode'))) {
         var decodeArr = this.dict.get('Decode');
         var bitsPerComponent = this.dict.get('BitsPerComponent') || 8;
         var decodeArrLength = decodeArr.length;
         var transform = new Int32Array(decodeArrLength);
         var transformNeeded = false;
         var maxValue = (1 << bitsPerComponent) - 1;
         for (var i = 0; i < decodeArrLength; i += 2) {
           transform[i] = ((decodeArr[i + 1] - decodeArr[i]) * 256) | 0;
@@ -32835,16 +32966,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
         }
         return pdfManagerCapability.promise;
       }
 
       var networkManager = new NetworkManager(source.url, {
         httpHeaders: source.httpHeaders,
         withCredentials: source.withCredentials
       });
+      var cachedChunks = [];
       var fullRequestXhrId = networkManager.requestFull({
         onHeadersReceived: function onHeadersReceived() {
           if (disableRange) {
             return;
           }
 
           var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
           if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
@@ -32865,34 +32997,75 @@ var WorkerMessageHandler = PDFJS.WorkerM
           source.length = length;
           if (length <= 2 * RANGE_CHUNK_SIZE) {
             // The file size is smaller than the size of two chunks, so it does
             // not make any sense to abort the request and retry with a range
             // request.
             return;
           }
 
-          // NOTE: by cancelling the full request, and then issuing range
-          // requests, there will be an issue for sites where you can only
-          // request the pdf once. However, if this is the case, then the
-          // server should not be returning that it can support range requests.
-          networkManager.abortRequest(fullRequestXhrId);
+          if (networkManager.isStreamingRequest(fullRequestXhrId)) {
+            // We can continue fetching when progressive loading is enabled,
+            // and we don't need the autoFetch feature.
+            source.disableAutoFetch = true;
+          } else {
+            // NOTE: by cancelling the full request, and then issuing range
+            // requests, there will be an issue for sites where you can only
+            // request the pdf once. However, if this is the case, then the
+            // server should not be returning that it can support range
+            // requests.
+            networkManager.abortRequest(fullRequestXhrId);
+          }
 
           try {
             pdfManager = new NetworkPdfManager(source, handler);
             pdfManagerCapability.resolve(pdfManager);
           } catch (ex) {
             pdfManagerCapability.reject(ex);
           }
         },
 
+        onProgressiveData: source.disableStream ? null :
+            function onProgressiveData(chunk) {
+          if (!pdfManager) {
+            cachedChunks.push(chunk);
+            return;
+          }
+          pdfManager.sendProgressiveData(chunk);
+        },
+
         onDone: function onDone(args) {
+          if (pdfManager) {
+            return; // already processed
+          }
+
+          var pdfFile;
+          if (args === null) {
+            // TODO add some streaming manager, e.g. for unknown length files.
+            // The data was returned in the onProgressiveData, combining...
+            var pdfFileLength = 0, pos = 0;
+            cachedChunks.forEach(function (chunk) {
+              pdfFileLength += chunk.byteLength;
+            });
+            if (source.length && pdfFileLength !== source.length) {
+              warn('reported HTTP length is different from actual');
+            }
+            var pdfFileArray = new Uint8Array(pdfFileLength);
+            cachedChunks.forEach(function (chunk) {
+              pdfFileArray.set(new Uint8Array(chunk), pos);
+              pos += chunk.byteLength;
+            });
+            pdfFile = pdfFileArray.buffer;
+          } else {
+            pdfFile = args.chunk;
+          }
+
           // the data is array, instantiating directly from it
           try {
-            pdfManager = new LocalPdfManager(args.chunk, source.password);
+            pdfManager = new LocalPdfManager(pdfFile, source.password);
             pdfManagerCapability.resolve();
           } catch (ex) {
             pdfManagerCapability.reject(ex);
           }
         },
 
         onError: function onError(status) {
           var exception;
@@ -32977,16 +33150,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
       PDFJS.disableFontFace = data.disableFontFace;
       PDFJS.disableCreateObjectURL = data.disableCreateObjectURL;
       PDFJS.verbosity = data.verbosity;
       PDFJS.cMapUrl = data.cMapUrl === undefined ?
                            null : data.cMapUrl;
       PDFJS.cMapPacked = data.cMapPacked === true;
 
       getPdfManager(data).then(function () {
+        handler.send('PDFManagerReady', null);
         pdfManager.onLoadedStream().then(function(stream) {
           handler.send('DataLoaded', { length: stream.bytes.byteLength });
         });
       }).then(function pdfManagerReady() {
         loadDocument(false).then(onSuccess, function loadFailure(ex) {
           // Try again with recoveryMode == true
           if (!(ex instanceof XRefParseException)) {
             if (ex instanceof PasswordException) {
@@ -33031,16 +33205,22 @@ var WorkerMessageHandler = PDFJS.WorkerM
     });
 
     handler.on('GetDestinations',
       function wphSetupGetDestinations(data) {
         return pdfManager.ensureCatalog('destinations');
       }
     );
 
+    handler.on('GetDestination',
+      function wphSetupGetDestination(data) {
+        return pdfManager.ensureCatalog('getDestination', [ data.id ]);
+      }
+    );
+
     handler.on('GetAttachments',
       function wphSetupGetAttachments(data) {
         return pdfManager.ensureCatalog('attachments');
       }
     );
 
     handler.on('GetJavaScript',
       function wphSetupGetJavaScript(data) {
--- a/browser/extensions/pdfjs/content/network.js
+++ b/browser/extensions/pdfjs/content/network.js
@@ -57,40 +57,40 @@ var NetworkManager = (function NetworkMa
   }
 
   function getArrayBuffer(xhr) {
     var data = xhr.response;
     if (typeof data !== 'string') {
       return data;
     }
     var length = data.length;
-    var buffer = new Uint8Array(length);
+    var array = new Uint8Array(length);
     for (var i = 0; i < length; i++) {
-      buffer[i] = data.charCodeAt(i) & 0xFF;
+      array[i] = data.charCodeAt(i) & 0xFF;
     }
-    return buffer;
+    return array.buffer;
   }
 
   NetworkManager.prototype = {
     requestRange: function NetworkManager_requestRange(begin, end, listeners) {
       var args = {
         begin: begin,
         end: end
       };
       for (var prop in listeners) {
         args[prop] = listeners[prop];
       }
       return this.request(args);
     },
 
-    requestFull: function NetworkManager_requestRange(listeners) {
+    requestFull: function NetworkManager_requestFull(listeners) {
       return this.request(listeners);
     },
 
-    request: function NetworkManager_requestRange(args) {
+    request: function NetworkManager_request(args) {
       var xhr = this.getXhr();
       var xhrId = this.currXhrId++;
       var pendingRequest = this.pendingRequests[xhrId] = {
         xhr: xhr
       };
 
       xhr.open('GET', this.url);
       xhr.withCredentials = this.withCredentials;
@@ -104,37 +104,64 @@ var NetworkManager = (function NetworkMa
       if (this.isHttp && 'begin' in args && 'end' in args) {
         var rangeStr = args.begin + '-' + (args.end - 1);
         xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
         pendingRequest.expectedStatus = 206;
       } else {
         pendingRequest.expectedStatus = 200;
       }
 
-      xhr.responseType = 'arraybuffer';
+      if (args.onProgressiveData) {
+        xhr.responseType = 'moz-chunked-arraybuffer';
+        if (xhr.responseType === 'moz-chunked-arraybuffer') {
+          pendingRequest.onProgressiveData = args.onProgressiveData;
+          pendingRequest.mozChunked = true;
+        } else {
+          xhr.responseType = 'arraybuffer';
+        }
+      } else {
+        xhr.responseType = 'arraybuffer';
+      }
 
-      if (args.onProgress) {
-        xhr.onprogress = args.onProgress;
-      }
       if (args.onError) {
         xhr.onerror = function(evt) {
           args.onError(xhr.status);
         };
       }
       xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
+      xhr.onprogress = this.onProgress.bind(this, xhrId);
 
       pendingRequest.onHeadersReceived = args.onHeadersReceived;
       pendingRequest.onDone = args.onDone;
       pendingRequest.onError = args.onError;
+      pendingRequest.onProgress = args.onProgress;
 
       xhr.send(null);
 
       return xhrId;
     },
 
+    onProgress: function NetworkManager_onProgress(xhrId, evt) {
+      var pendingRequest = this.pendingRequests[xhrId];
+      if (!pendingRequest) {
+        // Maybe abortRequest was called...
+        return;
+      }
+
+      if (pendingRequest.mozChunked) {
+        var chunk = getArrayBuffer(pendingRequest.xhr);
+        pendingRequest.onProgressiveData(chunk);
+      }
+
+      var onProgress = pendingRequest.onProgress;
+      if (onProgress) {
+        onProgress(evt);
+      }
+    },
+
     onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
       var pendingRequest = this.pendingRequests[xhrId];
       if (!pendingRequest) {
         // Maybe abortRequest was called...
         return;
       }
 
       var xhr = pendingRequest.xhr;
@@ -185,16 +212,18 @@ var NetworkManager = (function NetworkMa
       if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
         var rangeHeader = xhr.getResponseHeader('Content-Range');
         var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
         var begin = parseInt(matches[1], 10);
         pendingRequest.onDone({
           begin: begin,
           chunk: chunk
         });
+      } else if (pendingRequest.onProgressiveData) {
+        pendingRequest.onDone(null);
       } else {
         pendingRequest.onDone({
           begin: 0,
           chunk: chunk
         });
       }
     },
 
@@ -204,16 +233,20 @@ var NetworkManager = (function NetworkMa
       }
       return false;
     },
 
     getRequestXhr: function NetworkManager_getXhr(xhrId) {
       return this.pendingRequests[xhrId].xhr;
     },
 
+    isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
+      return !!(this.pendingRequests[xhrId].onProgressiveData);
+    },
+
     isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
       return xhrId in this.pendingRequests;
     },
 
     isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
       return xhrId in this.loadedRequests;
     },
 
--- a/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
+++ b/browser/extensions/pdfjs/content/pdfjschildbootstrap.js
@@ -10,17 +10,17 @@
 *
 * 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.
 */
 /* jshint esnext:true */
-/* globals Components, PdfjsContentUtils, PdfJs */
+/* globals Components, PdfjsContentUtils, PdfJs, Services */
 
 'use strict';
 
 /*
  * pdfjschildbootstrap.js loads into the content process to take care of
  * initializing our built-in version of pdfjs when running remote.
  */
 
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -107,23 +107,30 @@ var FontInspector = (function FontInspec
           var td2 = document.createElement('td');
           td2.textContent = obj[list[i]].toString();
           tr.appendChild(td2);
           moreInfo.appendChild(tr);
         }
         return moreInfo;
       }
       var moreInfo = properties(fontObj, ['name', 'type']);
-      var m = /url\(['"]?([^\)"']+)/.exec(url);
       var fontName = fontObj.loadedName;
       var font = document.createElement('div');
       var name = document.createElement('span');
       name.textContent = fontName;
       var download = document.createElement('a');
-      download.href = m[1];
+      if (url) {
+        url = /url\(['"]?([^\)"']+)/.exec(url);
+        download.href = url[1];
+      } else if (fontObj.data) {
+        url = URL.createObjectURL(new Blob([fontObj.data], {
+          type: fontObj.mimeType
+        }));
+      }
+      download.href = url;
       download.textContent = 'Download';
       var logIt = document.createElement('a');
       logIt.href = '';
       logIt.textContent = 'Log';
       logIt.addEventListener('click', function(event) {
         event.preventDefault();
         console.log(fontObj);
       });
@@ -206,16 +213,17 @@ var StepperManager = (function StepperMa
       steppers.push(stepper);
       if (steppers.length === 1) {
         this.selectStepper(pageIndex, false);
       }
       return stepper;
     },
     selectStepper: function selectStepper(pageIndex, selectPanel) {
       var i;
+      pageIndex = pageIndex | 0;
       if (selectPanel) {
         this.manager.selectPanel(this);
       }
       for (i = 0; i < steppers.length; ++i) {
         var stepper = steppers[i];
         if (stepper.pageIndex === pageIndex) {
           stepper.panel.removeAttribute('hidden');
         } else {
@@ -414,17 +422,17 @@ var Stepper = (function StepperClosure()
       };
       dom.addEventListener('keydown', listener, false);
       self.goTo(idx);
     },
     goTo: function goTo(idx) {
       var allRows = this.panel.getElementsByClassName('line');
       for (var x = 0, xx = allRows.length; x < xx; ++x) {
         var row = allRows[x];
-        if (parseInt(row.dataset.idx, 10) === idx) {
+        if ((row.dataset.idx | 0) === idx) {
           row.style.backgroundColor = 'rgb(251,250,207)';
           row.scrollIntoView();
         } else {
           row.style.backgroundColor = null;
         }
       }
     }
   };
index 51848a70fe3928e0e22f65be8a70a593211c4a22..8831a80588dcaffe9a0e075f3d00066faed3d482
GIT binary patch
literal 7402
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
WE-oSF-FmY>&=h1<Wj;%rhWsC!VsDxN
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b25b4452aa1a52181c324020c39175e0f8f425a2
GIT binary patch
literal 16131
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
CzxRv)
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -1,23 +1,158 @@
-/* Copyright 2012 Mozilla Foundation
+/* Copyright 2014 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.
  */
 
+.textLayer {
+  position: absolute;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  overflow: hidden;
+}
+
+.textLayer > div {
+  color: transparent;
+  position: absolute;
+  white-space: pre;
+  cursor: text;
+  -moz-transform-origin: 0% 0%;
+  transform-origin: 0% 0%;
+}
+
+.textLayer .highlight {
+  margin: -1px;
+  padding: 1px;
+
+  background-color: rgb(180, 0, 170);
+  border-radius: 4px;
+}
+
+.textLayer .highlight.begin {
+  border-radius: 4px 0px 0px 4px;
+}
+
+.textLayer .highlight.end {
+  border-radius: 0px 4px 4px 0px;
+}
+
+.textLayer .highlight.middle {
+  border-radius: 0px;
+}
+
+.textLayer .highlight.selected {
+  background-color: rgb(0, 100, 0);
+}
+
+.pdfViewer .canvasWrapper {
+  overflow: hidden;
+}
+
+.pdfViewer .page {
+  direction: ltr;
+  width: 816px;
+  height: 1056px;
+  margin: 1px auto -8px auto;
+  position: relative;
+  overflow: visible;
+  border: 9px solid transparent;
+  background-clip: content-box;
+  border-image: url(images/shadow.png) 9 9 repeat;
+  background-color: white;
+}
+
+.pdfViewer .page canvas {
+  margin: 0;
+  display: block;
+}
+
+.pdfViewer .page .loadingIcon {
+  position: absolute;
+  display: block;
+  left: 0;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  background: url('images/loading-icon.gif') center no-repeat;
+}
+
+.pdfViewer .page .annotLink > a:hover {
+  opacity: 0.2;
+  background: #ff0;
+  box-shadow: 0px 2px 10px #ff0;
+}
+
+:-moz-full-screen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0;
+}
+
+:fullscreen .pdfViewer .page {
+  margin-bottom: 100%;
+  border: 0;
+}
+
+.pdfViewer .page .annotationHighlight {
+  position: absolute;
+  border: 2px #FFFF99 solid;
+}
+
+.pdfViewer .page .annotText > img {
+  position: absolute;
+  cursor: pointer;
+}
+
+.pdfViewer .page .annotTextContentWrapper {
+  position: absolute;
+  width: 20em;
+}
+
+.pdfViewer .page .annotTextContent {
+  z-index: 200;
+  float: left;
+  max-width: 20em;
+  background-color: #FFFF99;
+  box-shadow: 0px 2px 5px #333;
+  border-radius: 2px;
+  padding: 0.6em;
+  cursor: pointer;
+}
+
+.pdfViewer .page .annotTextContent > h1 {
+  font-size: 1em;
+  border-bottom: 1px solid #000000;
+  padding-bottom: 0.2em;
+}
+
+.pdfViewer .page .annotTextContent > p {
+  padding-top: 0.2em;
+}
+
+.pdfViewer .page .annotLink > a {
+  position: absolute;
+  font-size: 1em;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+}
+
 * {
   padding: 0;
   margin: 0;
 }
 
 html {
   height: 100%;
   /* Font size is needed to make the activity bar the correct size. */
@@ -60,26 +195,16 @@ select {
   border-top: 2px solid transparent;
   background-color: #000;
   width: 100%;
   height: 100%;
   overflow: hidden;
   cursor: none;
 }
 
-:-moz-full-screen .page {
-  margin-bottom: 100%;
-  border: 0;
-}
-
-:fullscreen .page {
-  margin-bottom: 100%;
-  border: 0;
-}
-
 :-moz-full-screen a:not(.internalLink) {
   display: none;
 }
 
 :fullscreen a:not(.internalLink) {
   display: none;
 }
 
@@ -979,16 +1104,22 @@ html[dir='rtl'] .verticalToolbarSeparato
 
 .toolbarField.pageNumber {
   -moz-appearance: textfield; /* hides the spinner in moz */
   min-width: 16px;
   text-align: right;
   width: 40px;
 }
 
+.toolbarField.pageNumber.visiblePageIsLoading {
+  background-image: url(images/loading-small.png);
+  background-repeat: no-repeat;
+  background-position: 1px;
+}
+
 .toolbarField:hover {
   background-color: hsla(0,0%,100%,.11);
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
 }
 
 .toolbarField:focus {
   background-color: hsla(0,0%,100%,.15);
   border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9);
@@ -1162,145 +1293,27 @@ html[dir='rtl'] .attachmentsItem > butto
 
 .noResults {
   font-size: 12px;
   color: hsla(0,0%,100%,.8);
   font-style: italic;
   cursor: default;
 }
 
-.canvasWrapper {
-  overflow: hidden;
-}
-
-canvas {
-  margin: 0;
-  display: block;
-}
-
-.page {
-  direction: ltr;
-  width: 816px;
-  height: 1056px;
-  margin: 1px auto -8px auto;
-  position: relative;
-  overflow: visible;
-  border: 9px solid transparent;
-  background-clip: content-box;
-  border-image: url(images/shadow.png) 9 9 repeat;
-  background-color: white;
-}
-
-.annotLink > a:hover {
-  opacity: 0.2;
-  background: #ff0;
-  box-shadow: 0px 2px 10px #ff0;
-}
-
-.loadingIcon {
-  position: absolute;
-  display: block;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  background: url('images/loading-icon.gif') center no-repeat;
-}
-
-.textLayer {
-  position: absolute;
-  left: 0;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  overflow: hidden;
-}
-
-.textLayer > div {
-  color: transparent;
-  position: absolute;
-  white-space: pre;
-  cursor: text;
-  -moz-transform-origin: 0% 0%;
-  transform-origin: 0% 0%;
-}
-
-.textLayer .highlight {
-  margin: -1px;
-  padding: 1px;
-
-  background-color: rgba(180, 0, 170, 0.2);
-  border-radius: 4px;
-}
-
-.textLayer .highlight.begin {
-  border-radius: 4px 0px 0px 4px;
-}
-
-.textLayer .highlight.end {
-  border-radius: 0px 4px 4px 0px;
-}
-
-.textLayer .highlight.middle {
-  border-radius: 0px;
-}
-
-.textLayer .highlight.selected {
-  background-color: rgba(0, 100, 0, 0.2);
-}
 
 /* TODO: file FF bug to support ::-moz-selection:window-inactive
    so we can override the opaque grey background when the window is inactive;
    see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
-::selection { background:rgba(0,0,255,0.3); }
-::-moz-selection { background:rgba(0,0,255,0.3); }
-
-.annotationHighlight {
-  position: absolute;
-  border: 2px #FFFF99 solid;
-}
-
-.annotText > img {
-  position: absolute;
-  cursor: pointer;
-}
-
-.annotTextContentWrapper {
-  position: absolute;
-  width: 20em;
-}
+::selection { background: rgba(0,0,255,0.3); }
+::-moz-selection { background: rgba(0,0,255,0.3); }
 
-.annotTextContent {
-  z-index: 200;
-  float: left;
-  max-width: 20em;
-  background-color: #FFFF99;
-  box-shadow: 0px 2px 5px #333;
-  border-radius: 2px;
-  padding: 0.6em;
-  cursor: pointer;
-}
-
-.annotTextContent > h1 {
-  font-size: 1em;
-  border-bottom: 1px solid #000000;
-  padding-bottom: 0.2em;
-}
-
-.annotTextContent > p {
-  padding-top: 0.2em;
-}
-
-.annotLink > a {
-  position: absolute;
-  font-size: 1em;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
+.textLayer ::selection { background: rgb(0,0,255); }
+.textLayer ::-moz-selection { background: rgb(0,0,255); }
+.textLayer {
+  opacity: 0.2;
 }
 
 #errorWrapper {
   background: none repeat scroll 0 0 #FF5555;
   color: white;
   left: 0;
   position: absolute;
   right: 0;
@@ -1472,21 +1485,19 @@ html[dir='rtl'] #documentPropertiesOverl
   top: 27px;
 }
 #PDFBug button.active {
   font-weight: bold;
 }
 .debuggerShowText {
   background: none repeat scroll 0 0 yellow;
   color: blue;
-  opacity: 0.3;
 }
 .debuggerHideText:hover {
   background: none repeat scroll 0 0 yellow;
-  opacity: 0.3;
 }
 #PDFBug .stats {
   font-family: courier;
   font-size: 10px;
   white-space: pre;
 }
 #PDFBug .stats .title {
     font-weight: bold;
@@ -1556,16 +1567,22 @@ html[dir='rtl'] #documentPropertiesOverl
 
   html[dir='ltr'] .secondaryToolbarButton::before {
     left: -2px;
   }
   html[dir='rtl'] .secondaryToolbarButton::before {
     left: 186px;
   }
 
+  .toolbarField.pageNumber.visiblePageIsLoading,
+  #findInput[data-status="pending"] {
+    background-image: url(images/loading-small@2x.png);
+    background-size: 16px 17px;
+  }
+
   .dropdownToolbarButton {
     background: url(images/toolbarButton-menuArrows@2x.png) no-repeat;
     background-size: 7px 16px;
   }
 
   html[dir='ltr'] .toolbarButton#sidebarToggle::before {
     content: url(images/toolbarButton-sidebarToggle@2x.png);
   }
@@ -1705,16 +1722,18 @@ html[dir='rtl'] #documentPropertiesOverl
     margin: 0;
   }
 
   .page {
     float: left;
     display: none;
     border: none;
     box-shadow: none;
+    background-clip: content-box;
+    background-color: white;
   }
 
   .page[data-loaded] {
     display: block;
   }
 
   .fileInput {
     display: none;
@@ -1726,16 +1745,17 @@ html[dir='rtl'] #documentPropertiesOverl
   }
   body[data-mozPrintCallback] #printContainer {
     display: block;
   }
   #printContainer canvas {
     position: relative;
     top: 0;
     left: 0;
+    display: block;
   }
 }
 
 .visibleLargeView,
 .visibleMediumView,
 .visibleSmallView {
   display: none;
 }
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -240,17 +240,17 @@ http://sourceforge.net/adobe/cmap/wiki/L
                     data-l10n-id="last_page"></menuitem>
           <menuitem id="contextPageRotateCw" label="Rotate Clockwise"
                     data-l10n-id="page_rotate_cw"></menuitem>
           <menuitem id="contextPageRotateCcw" label="Rotate Counter-Clockwise"
                     data-l10n-id="page_rotate_ccw"></menuitem>
         </menu>
 
         <div id="viewerContainer" tabindex="0">
-          <div id="viewer"></div>
+          <div id="viewer" class="pdfViewer"></div>
         </div>
 
         <div id="errorWrapper" hidden='true'>
           <div id="errorMessageLeft">
             <span id="errorMessage"></span>
             <button id="errorShowMore" data-l10n-id="error_more_info">
               More Information
             </button>
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -12,61 +12,51 @@
  * 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 PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
            DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
            PDFHistory, Preferences, SidebarView, ViewHistory, PageView,
-           ThumbnailView, URL, noContextMenuHandler, SecondaryToolbar,
+           PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
            PasswordPrompt, PresentationMode, HandTool, Promise,
            DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
-           OverlayManager, PDFFindController, PDFFindBar */
+           OverlayManager, PDFFindController, PDFFindBar, getVisibleElements,
+           watchScroll, PDFViewer, PDFRenderingQueue, PresentationModeState,
+           RenderingStates, DEFAULT_SCALE, UNKNOWN_SCALE,
+           IGNORE_CURRENT_POSITION_ON_ZOOM: true */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
-var DEFAULT_SCALE = 'auto';
 var DEFAULT_SCALE_DELTA = 1.1;
-var UNKNOWN_SCALE = 0;
-var DEFAULT_CACHE_SIZE = 10;
-var CSS_UNITS = 96.0 / 72.0;
-var SCROLLBAR_PADDING = 40;
-var VERTICAL_PADDING = 5;
-var MAX_AUTO_SCALE = 1.25;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 10.0;
 var VIEW_HISTORY_MEMORY = 20;
 var SCALE_SELECT_CONTAINER_PADDING = 8;
 var SCALE_SELECT_PADDING = 22;
-var THUMBNAIL_SCROLL_MARGIN = -19;
-var CLEANUP_TIMEOUT = 30000;
-var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
-var RenderingStates = {
-  INITIAL: 0,
-  RUNNING: 1,
-  PAUSED: 2,
-  FINISHED: 3
-};
-var FindStates = {
-  FIND_FOUND: 0,
-  FIND_NOTFOUND: 1,
-  FIND_WRAPPED: 2,
-  FIND_PENDING: 3
-};
+var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
 
 PDFJS.imageResourcesPath = './images/';
   PDFJS.workerSrc = '../build/pdf.worker.js';
   PDFJS.cMapUrl = '../web/cmaps/';
   PDFJS.cMapPacked = true;
 
 var mozL10n = document.mozL10n || document.webL10n;
 
 
+var CSS_UNITS = 96.0 / 72.0;
+var DEFAULT_SCALE = 'auto';
+var UNKNOWN_SCALE = 0;
+var MAX_AUTO_SCALE = 1.25;
+var SCROLLBAR_PADDING = 40;
+var VERTICAL_PADDING = 5;
+var DEFAULT_CACHE_SIZE = 10;
+
 // optimised CSS custom property getter/setter
 var CustomStyle = (function CustomStyleClosure() {
 
   // As noted on: http://www.zachstronaut.com/posts/2009/02/17/
   //              animate-css-transforms-firefox-webkit.html
   // in some versions of IE9 it is critical that ms appear in this list
   // before Moz
   var prefixes = ['ms', 'Moz', 'Webkit', 'O'];
@@ -180,16 +170,101 @@ function scrollIntoView(element, spot) {
       offsetX += spot.left;
       parent.scrollLeft = offsetX;
     }
   }
   parent.scrollTop = offsetY;
 }
 
 /**
+ * Helper function to start monitoring the scroll event and converting them into
+ * PDF.js friendly one: with scroll debounce and scroll direction.
+ */
+function watchScroll(viewAreaElement, callback) {
+  var debounceScroll = function debounceScroll(evt) {
+    if (rAF) {
+      return;
+    }
+    // schedule an invocation of scroll for next animation frame.
+    rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
+      rAF = null;
+
+      var currentY = viewAreaElement.scrollTop;
+      var lastY = state.lastY;
+      if (currentY > lastY) {
+        state.down = true;
+      } else if (currentY < lastY) {
+        state.down = false;
+      }
+      state.lastY = currentY;
+      // else do nothing and use previous value
+      callback(state);
+    });
+  };
+
+  var state = {
+    down: true,
+    lastY: viewAreaElement.scrollTop,
+    _eventHandler: debounceScroll
+  };
+
+  var rAF = null;
+  viewAreaElement.addEventListener('scroll', debounceScroll, true);
+  return state;
+}
+
+/**
+ * Generic helper to find out what elements are visible within a scroll pane.
+ */
+function getVisibleElements(scrollEl, views, sortByVisibility) {
+  var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
+  var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
+
+  var visible = [], view;
+  var currentHeight, viewHeight, hiddenHeight, percentHeight;
+  var currentWidth, viewWidth;
+  for (var i = 0, ii = views.length; i < ii; ++i) {
+    view = views[i];
+    currentHeight = view.el.offsetTop + view.el.clientTop;
+    viewHeight = view.el.clientHeight;
+    if ((currentHeight + viewHeight) < top) {
+      continue;
+    }
+    if (currentHeight > bottom) {
+      break;
+    }
+    currentWidth = view.el.offsetLeft + view.el.clientLeft;
+    viewWidth = view.el.clientWidth;
+    if ((currentWidth + viewWidth) < left || currentWidth > right) {
+      continue;
+    }
+    hiddenHeight = Math.max(0, top - currentHeight) +
+      Math.max(0, currentHeight + viewHeight - bottom);
+    percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
+
+    visible.push({ id: view.id, x: currentWidth, y: currentHeight,
+      view: view, percent: percentHeight });
+  }
+
+  var first = visible[0];
+  var last = visible[visible.length - 1];
+
+  if (sortByVisibility) {
+    visible.sort(function(a, b) {
+      var pc = a.percent - b.percent;
+      if (Math.abs(pc) > 0.001) {
+        return -pc;
+      }
+      return a.id - b.id; // ensure stability
+    });
+  }
+  return {first: first, last: last, views: visible};
+}
+
+/**
  * Event handler to suppress context menu.
  */
 function noContextMenuHandler(e) {
   e.preventDefault();
 }
 
 /**
  * Returns the filename or guessed filename from the url (see issue 3455).
@@ -306,24 +381,25 @@ var Cache = function cacheCache(size) {
     while (data.length > size) {
       data.shift().destroy();
     }
   };
 };
 
 
 
-
 var DEFAULT_PREFERENCES = {
   showPreviousViewOnLoad: true,
   defaultZoomValue: '',
   sidebarViewOnLoad: 0,
   enableHandToolOnLoad: false,
   enableWebGL: false,
+  pdfBugEnabled: false,
   disableRange: false,
+  disableStream: false,
   disableAutoFetch: false,
   disableFontFace: false,
   disableTextLayer: false,
   useOnlyCssZoom: false
 };
 
 
 var SidebarView = {
@@ -587,19 +663,16 @@ Preferences._readFromStorage = function 
       var readPrefs = JSON.parse(prefStr);
       resolve(readPrefs);
     });
   });
 };
 
 
 
-var cache = new Cache(DEFAULT_CACHE_SIZE);
-var currentPageNumber = 1;
-
 
 /**
  * View History - This is a utility for saving various view parameters for
  *                recently opened files.
  *
  * The way that the view parameters are stored depends on how PDF.js is built,
  * for 'node make <flag>' the following cases exist:
  *  - FIREFOX or MOZCENTRAL - uses sessionStorage.
@@ -831,16 +904,23 @@ var PDFFindBar = (function PDFFindBarClo
       }
     }
   };
   return PDFFindBar;
 })();
 
 
 
+var FindStates = {
+  FIND_FOUND: 0,
+  FIND_NOTFOUND: 1,
+  FIND_WRAPPED: 2,
+  FIND_PENDING: 3
+};
+
 /**
  * Provides "search" or "find" functionality for the PDF.
  * This object actually performs the search for a given string.
  */
 var PDFFindController = (function PDFFindControllerClosure() {
   function PDFFindController(options) {
     this.startedTextExtraction = false;
     this.extractTextPromises = [];
@@ -851,21 +931,22 @@ var PDFFindController = (function PDFFin
     this.selected = { // Currently selected match.
       pageIdx: -1,
       matchIdx: -1
     };
     this.offset = { // Where the find algorithm currently is in the document.
       pageIdx: null,
       matchIdx: null
     };
+    this.pagesToSearch = null;
     this.resumePageIdx = null;
     this.state = null;
     this.dirtyMatch = false;
     this.findTimeout = null;
-    this.pdfPageSource = options.pdfPageSource || null;
+    this.pdfViewer = options.pdfViewer || null;
     this.integratedFind = options.integratedFind || false;
     this.charactersToNormalize = {
       '\u2018': '\'', // Left single quotation mark
       '\u2019': '\'', // Right single quotation mark
       '\u201A': '\'', // Single low-9 quotation mark
       '\u201B': '\'', // Single high-reversed-9 quotation mark
       '\u201C': '"', // Left double quotation mark
       '\u201D': '"', // Right double quotation mark
@@ -951,39 +1032,39 @@ var PDFFindController = (function PDFFin
     extractText: function PDFFindController_extractText() {
       if (this.startedTextExtraction) {
         return;
       }
       this.startedTextExtraction = true;
 
       this.pageContents = [];
       var extractTextPromisesResolves = [];
-      var numPages = this.pdfPageSource.pdfDocument.numPages;
+      var numPages = this.pdfViewer.pagesCount;
       for (var i = 0; i < numPages; i++) {
         this.extractTextPromises.push(new Promise(function (resolve) {
           extractTextPromisesResolves.push(resolve);
         }));
       }
 
       var self = this;
       function extractPageText(pageIndex) {
-        self.pdfPageSource.pages[pageIndex].getTextContent().then(
+        self.pdfViewer.getPageTextContent(pageIndex).then(
           function textContentResolved(textContent) {
             var textItems = textContent.items;
             var str = [];
 
             for (var i = 0, len = textItems.length; i < len; i++) {
               str.push(textItems[i].str);
             }
 
             // Store the pageContent as a string.
             self.pageContents.push(str.join(''));
 
             extractTextPromisesResolves[pageIndex](pageIndex);
-            if ((pageIndex + 1) < self.pdfPageSource.pages.length) {
+            if ((pageIndex + 1) < self.pdfViewer.pagesCount) {
               extractPageText(pageIndex + 1);
             }
           }
         );
       }
       extractPageText(0);
     },
 
@@ -1003,34 +1084,34 @@ var PDFFindController = (function PDFFin
           this.findTimeout = setTimeout(this.nextMatch.bind(this), 250);
         } else {
           this.nextMatch();
         }
       }.bind(this));
     },
 
     updatePage: function PDFFindController_updatePage(index) {
-      var page = this.pdfPageSource.pages[index];
+      var page = this.pdfViewer.getPageView(index);
 
       if (this.selected.pageIdx === index) {
         // If the page is selected, scroll the page into view, which triggers
         // rendering the page, which adds the textLayer. Once the textLayer is
         // build, it will scroll onto the selected match.
-        page.scrollIntoView();
+        this.pdfViewer.scrollPageIntoView(index + 1);
       }
 
       if (page.textLayer) {
         page.textLayer.updateMatches();
       }
     },
 
     nextMatch: function PDFFindController_nextMatch() {
       var previous = this.state.findPrevious;
-      var currentPageIndex = this.pdfPageSource.page - 1;
-      var numPages = this.pdfPageSource.pages.length;
+      var currentPageIndex = this.pdfViewer.currentPageNumber - 1;
+      var numPages = this.pdfViewer.pagesCount;
 
       this.active = true;
 
       if (this.dirtyMatch) {
         // Need to recalculate the matches, reset everything.
         this.dirtyMatch = false;
         this.selected.pageIdx = this.selected.matchIdx = -1;
         this.offset.pageIdx = currentPageIndex;
@@ -1062,16 +1143,18 @@ var PDFFindController = (function PDFFin
       }
 
       // If we're waiting on a page, we return since we can't do anything else.
       if (this.resumePageIdx) {
         return;
       }
 
       var offset = this.offset;
+      // Keep track of how many pages we should maximally iterate through.
+      this.pagesToSearch = numPages;
       // If there's already a matchIdx that means we are iterating through a
       // page's matches.
       if (offset.matchIdx !== null) {
         var numPageMatches = this.pageMatches[offset.pageIdx].length;
         if ((!previous && offset.matchIdx + 1 < numPageMatches) ||
             (previous && offset.matchIdx > 0)) {
           // The simple case; we just have advance the matchIdx to select
           // the next match on the page.
@@ -1100,18 +1183,18 @@ var PDFFindController = (function PDFFin
         offset.matchIdx = (previous ? numMatches - 1 : 0);
         this.updateMatch(true);
         return true;
       } else {
         // No matches, so attempt to search the next page.
         this.advanceOffsetPage(previous);
         if (offset.wrapped) {
           offset.matchIdx = null;
-          if (!this.hadMatch) {
-            // No point in wrapping, there were no matches.
+          if (this.pagesToSearch < 0) {
+            // No point in wrapping again, there were no matches.
             this.updateMatch(false);
             // while matches were not found, searching for a page 
             // with matches should nevertheless halt.
             return true;
           }
         }
         // Matches were not found (and searching is not done).
         return false;
@@ -1134,21 +1217,22 @@ var PDFFindController = (function PDFFin
       } while (!this.matchesReady(matches));
     },
 
     advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
       var offset = this.offset;
       var numPages = this.extractTextPromises.length;
       offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
       offset.matchIdx = null;
+
+      this.pagesToSearch--;
       
       if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
         offset.pageIdx = (previous ? numPages - 1 : 0);
         offset.wrapped = true;
-        return;
       }
     },
 
     updateMatch: function PDFFindController_updateMatch(found) {
       var state = FindStates.FIND_NOTFOUND;
       var wrapped = this.offset.wrapped;
       this.offset.wrapped = false;
     
@@ -1160,17 +1244,17 @@ var PDFFindController = (function PDFFin
         // Update the currently selected page to wipe out any selected matches.
         if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
           this.updatePage(previousPage);
         }
       }
     
       this.updateUIState(state, this.state.findPrevious);
       if (this.selected.pageIdx !== -1) {
-        this.updatePage(this.selected.pageIdx, true);
+        this.updatePage(this.selected.pageIdx);
       }
     },
 
     updateUIState: function PDFFindController_updateUIState(state, previous) {
       if (this.integratedFind) {
         FirefoxCom.request('updateFindControlState',
                            { result: state, findPrevious: previous });
         return;
@@ -1186,47 +1270,47 @@ var PDFFindController = (function PDFFin
 })();
 
 
 
 var PDFHistory = {
   initialized: false,
   initialDestination: null,
 
-  initialize: function pdfHistoryInitialize(fingerprint) {
-    if (PDFJS.disableHistory || PDFView.isViewerEmbedded) {
-      // The browsing history is only enabled when the viewer is standalone,
-      // i.e. not when it is embedded in a web page.
-      return;
-    }
+  /**
+   * @param {string} fingerprint
+   * @param {IPDFLinkService} linkService
+   */
+  initialize: function pdfHistoryInitialize(fingerprint, linkService) {
     this.initialized = true;
     this.reInitialized = false;
     this.allowHashChange = true;
     this.historyUnlocked = true;
 
     this.previousHash = window.location.hash.substring(1);
     this.currentBookmark = '';
     this.currentPage = 0;
     this.updatePreviousBookmark = false;
     this.previousBookmark = '';
     this.previousPage = 0;
     this.nextHashParam = '';
 
     this.fingerprint = fingerprint;
+    this.linkService = linkService;
     this.currentUid = this.uid = 0;
     this.current = {};
 
     var state = window.history.state;
     if (this._isStateObjectDefined(state)) {
       // This corresponds to navigating back to the document
       // from another page in the browser history.
       if (state.target.dest) {
         this.initialDestination = state.target.dest;
       } else {
-        PDFView.initialBookmark = state.target.hash;
+        linkService.setHash(state.target.hash);
       }
       this.currentUid = state.uid;
       this.uid = state.uid + 1;
       this.current = state.target;
     } else {
       // This corresponds to the loading of a new document.
       if (state && state.fingerprint &&
           this.fingerprint !== state.fingerprint) {
@@ -1354,26 +1438,26 @@ var PDFHistory = {
   push: function pdfHistoryPush(params, isInitialBookmark) {
     if (!(this.initialized && this.historyUnlocked)) {
       return;
     }
     if (params.dest && !params.hash) {
       params.hash = (this.current.hash && this.current.dest &&
                      this.current.dest === params.dest) ?
         this.current.hash :
-        PDFView.getDestinationHash(params.dest).split('#')[1];
+        this.linkService.getDestinationHash(params.dest).split('#')[1];
     }
     if (params.page) {
       params.page |= 0;
     }
     if (isInitialBookmark) {
       var target = window.history.state.target;
       if (!target) {
         // Invoked when the user specifies an initial bookmark,
-        // thus setting PDFView.initialBookmark, when the document is loaded.
+        // thus setting initialBookmark, when the document is loaded.
         this._pushToHistory(params, false);
         this.previousHash = window.location.hash.substring(1);
       }
       this.updatePreviousBookmark = this.nextHashParam ? false : true;
       if (target) {
         // If the current document is reloaded,
         // avoid creating duplicate entries in the history.
         this._updatePreviousBookmark();
@@ -1488,19 +1572,19 @@ var PDFHistory = {
         this.currentUid = state.uid;
         window.history.back();
         return;
       }
     }
     this.historyUnlocked = false;
 
     if (state.target.dest) {
-      PDFView.navigateTo(state.target.dest);
+      this.linkService.navigateTo(state.target.dest);
     } else {
-      PDFView.setHash(state.target.hash);
+      this.linkService.setHash(state.target.hash);
     }
     this.currentUid = state.uid;
     if (state.uid > this.uid) {
       this.uid = state.uid;
     }
     this.current = state.target;
     this.updatePreviousBookmark = true;
 
@@ -1598,42 +1682,42 @@ var SecondaryToolbar = {
   },
 
   printClick: function secondaryToolbarPrintClick(evt) {
     window.print();
     this.close();
   },
 
   downloadClick: function secondaryToolbarDownloadClick(evt) {
-    PDFView.download();
+    PDFViewerApplication.download();
     this.close();
   },
 
   viewBookmarkClick: function secondaryToolbarViewBookmarkClick(evt) {
     this.close();
   },
 
   firstPageClick: function secondaryToolbarFirstPageClick(evt) {
-    PDFView.page = 1;
+    PDFViewerApplication.page = 1;
     this.close();
   },
 
   lastPageClick: function secondaryToolbarLastPageClick(evt) {
-    if (PDFView.pdfDocument) {
-      PDFView.page = PDFView.pdfDocument.numPages;
+    if (PDFViewerApplication.pdfDocument) {
+      PDFViewerApplication.page = PDFViewerApplication.pagesCount;
     }
     this.close();
   },
 
   pageRotateCwClick: function secondaryToolbarPageRotateCwClick(evt) {
-    PDFView.rotatePages(90);
+    PDFViewerApplication.rotatePages(90);
   },
 
   pageRotateCcwClick: function secondaryToolbarPageRotateCcwClick(evt) {
-    PDFView.rotatePages(-90);
+    PDFViewerApplication.rotatePages(-90);
   },
 
   documentPropertiesClick: function secondaryToolbarDocumentPropsClick(evt) {
     this.documentProperties.open();
     this.close();
   },
 
   // Misc. functions for interacting with the toolbar.
@@ -1722,109 +1806,121 @@ var PresentationMode = {
   get isFullscreen() {
     return (document.fullscreenElement ||
             document.mozFullScreen ||
             document.webkitIsFullScreen ||
             document.msFullscreenElement);
   },
 
   /**
-   * Initialize a timeout that is used to reset PDFView.currentPosition when the
+   * Initialize a timeout that is used to specify switchInProgress when the
    * browser transitions to fullscreen mode. Since resize events are triggered
    * multiple times during the switch to fullscreen mode, this is necessary in
    * order to prevent the page from being scrolled partially, or completely,
    * out of view when Presentation Mode is enabled.
    * Note: This is only an issue at certain zoom levels, e.g. 'page-width'.
    */
   _setSwitchInProgress: function presentationMode_setSwitchInProgress() {
     if (this.switchInProgress) {
       clearTimeout(this.switchInProgress);
     }
     this.switchInProgress = setTimeout(function switchInProgressTimeout() {
       delete this.switchInProgress;
+      this._notifyStateChange();
     }.bind(this), DELAY_BEFORE_RESETTING_SWITCH_IN_PROGRESS);
-
-    PDFView.currentPosition = null;
   },
 
   _resetSwitchInProgress: function presentationMode_resetSwitchInProgress() {
     if (this.switchInProgress) {
       clearTimeout(this.switchInProgress);
       delete this.switchInProgress;
     }
   },
 
   request: function presentationModeRequest() {
-    if (!PDFView.supportsFullscreen || this.isFullscreen ||
+    if (!PDFViewerApplication.supportsFullscreen || this.isFullscreen ||
         !this.viewer.hasChildNodes()) {
       return false;
     }
     this._setSwitchInProgress();
+    this._notifyStateChange();
 
     if (this.container.requestFullscreen) {
       this.container.requestFullscreen();
     } else if (this.container.mozRequestFullScreen) {
       this.container.mozRequestFullScreen();
     } else if (this.container.webkitRequestFullScreen) {
       this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
     } else if (this.container.msRequestFullscreen) {
       this.container.msRequestFullscreen();
     } else {
       return false;
     }
 
     this.args = {
-      page: PDFView.page,
-      previousScale: PDFView.currentScaleValue
+      page: PDFViewerApplication.page,
+      previousScale: PDFViewerApplication.currentScaleValue
     };
 
     return true;
   },
 
+  _notifyStateChange: function presentationModeNotifyStateChange() {
+    var event = document.createEvent('CustomEvent');
+    event.initCustomEvent('presentationmodechanged', true, true, {
+      active: PresentationMode.active,
+      switchInProgress: !!PresentationMode.switchInProgress
+    });
+    window.dispatchEvent(event);
+  },
+
   enter: function presentationModeEnter() {
     this.active = true;
     this._resetSwitchInProgress();
+    this._notifyStateChange();
 
     // Ensure that the correct page is scrolled into view when entering
     // Presentation Mode, by waiting until fullscreen mode in enabled.
     // Note: This is only necessary in non-Mozilla browsers.
     setTimeout(function enterPresentationModeTimeout() {
-      PDFView.page = this.args.page;
-      PDFView.setScale('page-fit', true);
+      PDFViewerApplication.page = this.args.page;
+      PDFViewerApplication.setScale('page-fit', true);
     }.bind(this), 0);
 
     window.addEventListener('mousemove', this.mouseMove, false);
     window.addEventListener('mousedown', this.mouseDown, false);
     window.addEventListener('contextmenu', this.contextMenu, false);
 
     this.showControls();
     HandTool.enterPresentationMode();
     this.contextMenuOpen = false;
     this.container.setAttribute('contextmenu', 'viewerContextMenu');
   },
 
   exit: function presentationModeExit() {
-    var page = PDFView.page;
+    var page = PDFViewerApplication.page;
 
     // Ensure that the correct page is scrolled into view when exiting
     // Presentation Mode, by waiting until fullscreen mode is disabled.
     // Note: This is only necessary in non-Mozilla browsers.
     setTimeout(function exitPresentationModeTimeout() {
       this.active = false;
-      PDFView.setScale(this.args.previousScale);
-      PDFView.page = page;
+      this._notifyStateChange();
+
+      PDFViewerApplication.setScale(this.args.previousScale, true);
+      PDFViewerApplication.page = page;
       this.args = null;
     }.bind(this), 0);
 
     window.removeEventListener('mousemove', this.mouseMove, false);
     window.removeEventListener('mousedown', this.mouseDown, false);
     window.removeEventListener('contextmenu', this.contextMenu, false);
 
     this.hideControls();
-    PDFView.clearMouseScrollState();
+    PDFViewerApplication.clearMouseScrollState();
     HandTool.exitPresentationMode();
     this.container.removeAttribute('contextmenu');
     this.contextMenuOpen = false;
 
     // Ensure that the thumbnail of the current page is visible
     // when exiting presentation mode.
     scrollIntoView(document.getElementById('thumbnailContainer' + page));
   },
@@ -1865,17 +1961,17 @@ var PresentationMode = {
     if (evt.button === 0) {
       // Enable clicking of links in presentation mode. Please note:
       // Only links pointing to destinations in the current PDF document work.
       var isInternalLink = (evt.target.href &&
                             evt.target.classList.contains('internalLink'));
       if (!isInternalLink) {
         // Unless an internal link was clicked, advance one page.
         evt.preventDefault();
-        PDFView.page += (evt.shiftKey ? -1 : 1);
+        PDFViewerApplication.page += (evt.shiftKey ? -1 : 1);
       }
     }
   },
 
   contextMenu: function presentationModeContextMenu(evt) {
     PresentationMode.contextMenuOpen = true;
   }
 };
@@ -2378,16 +2474,18 @@ var DocumentProperties = {
   subjectField: null,
   keywordsField: null,
   creationDateField: null,
   modificationDateField: null,
   creatorField: null,
   producerField: null,
   versionField: null,
   pageCountField: null,
+  url: null,
+  pdfDocument: null,
 
   initialize: function documentPropertiesInitialize(options) {
     this.overlayName = options.overlayName;
 
     // Set the document property fields.
     this.fileNameField = options.fileNameField;
     this.fileSizeField = options.fileSizeField;
     this.titleField = options.titleField;
@@ -2415,42 +2513,42 @@ var DocumentProperties = {
 
   getProperties: function documentPropertiesGetProperties() {
     if (!OverlayManager.active) {
       // If the dialog was closed before dataAvailablePromise was resolved,
       // don't bother updating the properties.
       return;
     }
     // Get the file size (if it hasn't already been set).
-    PDFView.pdfDocument.getDownloadInfo().then(function(data) {
+    this.pdfDocument.getDownloadInfo().then(function(data) {
       if (data.length === this.rawFileSize) {
         return;
       }
       this.setFileSize(data.length);
       this.updateUI(this.fileSizeField, this.parseFileSize());
     }.bind(this));
 
     // Get the document properties.
-    PDFView.pdfDocument.getMetadata().then(function(data) {
+    this.pdfDocument.getMetadata().then(function(data) {
       var fields = [
         { field: this.fileNameField,
-          content: getPDFFileNameFromURL(PDFView.url) },
+          content: getPDFFileNameFromURL(this.url) },
         { field: this.fileSizeField, content: this.parseFileSize() },
         { field: this.titleField, content: data.info.Title },
         { field: this.authorField, content: data.info.Author },
         { field: this.subjectField, content: data.info.Subject },
         { field: this.keywordsField, content: data.info.Keywords },
         { field: this.creationDateField,
           content: this.parseDate(data.info.CreationDate) },
         { field: this.modificationDateField,
           content: this.parseDate(data.info.ModDate) },
         { field: this.creatorField, content: data.info.Creator },
         { field: this.producerField, content: data.info.Producer },
         { field: this.versionField, content: data.info.PDFFormatVersion },
-        { field: this.pageCountField, content: PDFView.pdfDocument.numPages }
+        { field: this.pageCountField, content: this.pdfDocument.numPages }
       ];
 
       // Show the properties in the dialog.
       for (var item in fields) {
         var element = fields[item];
         this.updateUI(element.field, element.content);
       }
     }.bind(this));
@@ -2541,1560 +2639,217 @@ var DocumentProperties = {
     var timeString = date.toLocaleTimeString();
     return mozL10n.get('document_properties_date_string',
                        {date: dateString, time: timeString},
                        '{{date}}, {{time}}');
   }
 };
 
 
-var PDFView = {
-  pages: [],
-  thumbnails: [],
-  currentScale: UNKNOWN_SCALE,
-  currentScaleValue: null,
-  initialBookmark: document.location.hash.substring(1),
-  container: null,
-  thumbnailContainer: null,
-  initialized: false,
-  fellback: false,
-  pdfDocument: null,
-  sidebarOpen: false,
-  printing: false,
-  pageViewScroll: null,
-  thumbnailViewScroll: null,
-  pageRotation: 0,
-  mouseScrollTimeStamp: 0,
-  mouseScrollDelta: 0,
-  lastScroll: 0,
-  previousPageNumber: 1,
-  isViewerEmbedded: (window.parent !== window),
-  idleTimeout: null,
-  currentPosition: null,
-  url: '',
-
-  // called once when the document is loaded
-  initialize: function pdfViewInitialize() {
-    var self = this;
-    var container = this.container = document.getElementById('viewerContainer');
-    this.pageViewScroll = {};
-    this.watchScroll(container, this.pageViewScroll, updateViewarea);
-
-    var thumbnailContainer = this.thumbnailContainer =
-                             document.getElementById('thumbnailView');
-    this.thumbnailViewScroll = {};
-    this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
-                     this.renderHighestPriority.bind(this));
-
-    Preferences.initialize();
-
-    this.findController = new PDFFindController({
-      pdfPageSource: this,
-      integratedFind: this.supportsIntegratedFind
-    });
-
-    this.findBar = new PDFFindBar({
-      bar: document.getElementById('findbar'),
-      toggleButton: document.getElementById('viewFind'),
-      findField: document.getElementById('findInput'),
-      highlightAllCheckbox: document.getElementById('findHighlightAll'),
-      caseSensitiveCheckbox: document.getElementById('findMatchCase'),
-      findMsg: document.getElementById('findMsg'),
-      findStatusIcon: document.getElementById('findStatusIcon'),
-      findPreviousButton: document.getElementById('findPrevious'),
-      findNextButton: document.getElementById('findNext'),
-      findController: this.findController
-    });
-
-    this.findController.setFindBar(this.findBar);
-
-    HandTool.initialize({
-      container: container,
-      toggleHandTool: document.getElementById('toggleHandTool')
-    });
-
-    SecondaryToolbar.initialize({
-      toolbar: document.getElementById('secondaryToolbar'),
-      presentationMode: PresentationMode,
-      toggleButton: document.getElementById('secondaryToolbarToggle'),
-      presentationModeButton:
-        document.getElementById('secondaryPresentationMode'),
-      openFile: document.getElementById('secondaryOpenFile'),
-      print: document.getElementById('secondaryPrint'),
-      download: document.getElementById('secondaryDownload'),
-      viewBookmark: document.getElementById('secondaryViewBookmark'),
-      firstPage: document.getElementById('firstPage'),
-      lastPage: document.getElementById('lastPage'),
-      pageRotateCw: document.getElementById('pageRotateCw'),
-      pageRotateCcw: document.getElementById('pageRotateCcw'),
-      documentProperties: DocumentProperties,
-      documentPropertiesButton: document.getElementById('documentProperties')
-    });
-
-    PresentationMode.initialize({
-      container: container,
-      secondaryToolbar: SecondaryToolbar,
-      firstPage: document.getElementById('contextFirstPage'),
-      lastPage: document.getElementById('contextLastPage'),
-      pageRotateCw: document.getElementById('contextPageRotateCw'),
-      pageRotateCcw: document.getElementById('contextPageRotateCcw')
-    });
-
-    PasswordPrompt.initialize({
-      overlayName: 'passwordOverlay',
-      passwordField: document.getElementById('password'),
-      passwordText: document.getElementById('passwordText'),
-      passwordSubmit: document.getElementById('passwordSubmit'),
-      passwordCancel: document.getElementById('passwordCancel')
-    });
-
-    DocumentProperties.initialize({
-      overlayName: 'documentPropertiesOverlay',
-      closeButton: document.getElementById('documentPropertiesClose'),
-      fileNameField: document.getElementById('fileNameField'),
-      fileSizeField: document.getElementById('fileSizeField'),
-      titleField: document.getElementById('titleField'),
-      authorField: document.getElementById('authorField'),
-      subjectField: document.getElementById('subjectField'),
-      keywordsField: document.getElementById('keywordsField'),
-      creationDateField: document.getElementById('creationDateField'),
-      modificationDateField: document.getElementById('modificationDateField'),
-      creatorField: document.getElementById('creatorField'),
-      producerField: document.getElementById('producerField'),
-      versionField: document.getElementById('versionField'),
-      pageCountField: document.getElementById('pageCountField')
-    });
-
-    container.addEventListener('scroll', function() {
-      self.lastScroll = Date.now();
-    }, false);
-
-    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('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('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;
-      })
-      // TODO move more preferences and other async stuff here
-    ]).catch(function (reason) { });
-
-    return initializedPromise.then(function () {
-      PDFView.initialized = true;
-    });
-  },
-
-  getPage: function pdfViewGetPage(n) {
-    return this.pdfDocument.getPage(n);
-  },
-
-  // Helper function to keep track whether a div was scrolled up or down and
-  // then call a callback.
-  watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
-    state.down = true;
-    state.lastY = viewAreaElement.scrollTop;
-    state.rAF = null;
-    viewAreaElement.addEventListener('scroll', function debounceScroll(evt) {
-      if (state.rAF) {
-        return;
-      }
-      // schedule an invocation of webViewerScrolled for next animation frame.
-      state.rAF = window.requestAnimationFrame(function webViewerScrolled() {
-        state.rAF = null;
-        if (!PDFView.pdfDocument) {
-          return;
-        }
-        var currentY = viewAreaElement.scrollTop;
-        var lastY = state.lastY;
-        if (currentY > lastY) {
-          state.down = true;
-        } else if (currentY < lastY) {
-          state.down = false;
-        }
-        // else do nothing and use previous value
-        state.lastY = currentY;
-        callback();
-      });
-    }, true);
-  },
-
-  _setScaleUpdatePages: function pdfView_setScaleUpdatePages(
-      newScale, newValue, resetAutoSettings, noScroll) {
-    this.currentScaleValue = newValue;
-    if (newScale === this.currentScale) {
-      return;
-    }
-    for (var i = 0, ii = this.pages.length; i < ii; i++) {
-      this.pages[i].update(newScale);
-    }
-    this.currentScale = newScale;
-
-    if (!noScroll) {
-      var page = this.page, dest;
-      if (this.currentPosition && !IGNORE_CURRENT_POSITION_ON_ZOOM) {
-        page = this.currentPosition.page;
-        dest = [null, { name: 'XYZ' }, this.currentPosition.left,
-                this.currentPosition.top, null];
-      }
-      this.pages[page - 1].scrollIntoView(dest);
-    }
-    var event = document.createEvent('UIEvents');
-    event.initUIEvent('scalechange', false, false, window, 0);
-    event.scale = newScale;
-    event.resetAutoSettings = resetAutoSettings;
-    window.dispatchEvent(event);
-  },
-
-  setScale: function pdfViewSetScale(value, resetAutoSettings, noScroll) {
-    if (value === 'custom') {
-      return;
-    }
-    var scale = parseFloat(value);
-
-    if (scale > 0) {
-      this._setScaleUpdatePages(scale, value, true, noScroll);
-    } else {
-      var currentPage = this.pages[this.page - 1];
-      if (!currentPage) {
+var PresentationModeState = {
+  UNKNOWN: 0,
+  NORMAL: 1,
+  CHANGING: 2,
+  FULLSCREEN: 3,
+};
+
+var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
+
+
+var CLEANUP_TIMEOUT = 30000;
+
+var RenderingStates = {
+  INITIAL: 0,
+  RUNNING: 1,
+  PAUSED: 2,
+  FINISHED: 3
+};
+
+/**
+ * Controls rendering of the views for pages and thumbnails.
+ * @class
+ */
+var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
+  /**
+   * @constructs
+   */
+  function PDFRenderingQueue() {
+    this.pdfViewer = null;
+    this.pdfThumbnailViewer = null;
+    this.onIdle = null;
+
+    this.highestPriorityPage = null;
+    this.idleTimeout = null;
+    this.printing = false;
+    this.isThumbnailViewEnabled = false;
+  }
+
+  PDFRenderingQueue.prototype = /** @lends PDFRenderingQueue.prototype */ {
+    /**
+     * @param {PDFViewer} pdfViewer
+     */
+    setViewer: function PDFRenderingQueue_setViewer(pdfViewer) {
+      this.pdfViewer = pdfViewer;
+    },
+
+    /**
+     * @param {PDFThumbnailViewer} pdfThumbnailViewer
+     */
+    setThumbnailViewer:
+        function PDFRenderingQueue_setThumbnailViewer(pdfThumbnailViewer) {
+      this.pdfThumbnailViewer = pdfThumbnailViewer;
+    },
+
+    /**
+     * @param {IRenderableView} view
+     * @returns {boolean}
+     */
+    isHighestPriority: function PDFRenderingQueue_isHighestPriority(view) {
+      return this.highestPriorityPage === view.renderingId;
+    },
+
+    renderHighestPriority: function
+        PDFRenderingQueue_renderHighestPriority(currentlyVisiblePages) {
+      if (this.idleTimeout) {
+        clearTimeout(this.idleTimeout);
+        this.idleTimeout = null;
+      }
+
+      // Pages have a higher priority than thumbnails, so check them first.
+      if (this.pdfViewer.forceRendering(currentlyVisiblePages)) {
         return;
       }
-      var hPadding = PresentationMode.active ? 0 : SCROLLBAR_PADDING;
-      var vPadding = PresentationMode.active ? 0 : VERTICAL_PADDING;
-      var pageWidthScale = (this.container.clientWidth - hPadding) /
-                            currentPage.width * currentPage.scale;
-      var pageHeightScale = (this.container.clientHeight - vPadding) /
-                             currentPage.height * currentPage.scale;
-      switch (value) {
-        case 'page-actual':
-          scale = 1;
-          break;
-        case 'page-width':
-          scale = pageWidthScale;
-          break;
-        case 'page-height':
-          scale = pageHeightScale;
-          break;
-        case 'page-fit':
-          scale = Math.min(pageWidthScale, pageHeightScale);
-          break;
-        case 'auto':
-          var isLandscape = (currentPage.width > currentPage.height);
-          var horizontalScale = isLandscape ? pageHeightScale : pageWidthScale;
-          scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
-          break;
-        default:
-          console.error('pdfViewSetScale: \'' + value +
-                        '\' is an unknown zoom value.');
-          return;
-      }
-      this._setScaleUpdatePages(scale, value, resetAutoSettings, noScroll);
-
-      selectScaleOption(value);
-    }
-  },
-
-  zoomIn: function pdfViewZoomIn(ticks) {
-    var newScale = this.currentScale;
-    do {
-      newScale = (newScale * DEFAULT_SCALE_DELTA).toFixed(2);
-      newScale = Math.ceil(newScale * 10) / 10;
-      newScale = Math.min(MAX_SCALE, newScale);
-    } while (--ticks && newScale < MAX_SCALE);
-    this.setScale(newScale, true);
-  },
-
-  zoomOut: function pdfViewZoomOut(ticks) {
-    var newScale = this.currentScale;
-    do {
-      newScale = (newScale / DEFAULT_SCALE_DELTA).toFixed(2);
-      newScale = Math.floor(newScale * 10) / 10;
-      newScale = Math.max(MIN_SCALE, newScale);
-    } while (--ticks && newScale > MIN_SCALE);
-    this.setScale(newScale, true);
-  },
-
-  set page(val) {
-    var pages = this.pages;
-    var event = document.createEvent('UIEvents');
-    event.initUIEvent('pagechange', false, false, window, 0);
-
-    if (!(0 < val && val <= pages.length)) {
-      this.previousPageNumber = val;
-      event.pageNumber = this.page;
-      window.dispatchEvent(event);
-      return;
-    }
-
-    pages[val - 1].updateStats();
-    this.previousPageNumber = currentPageNumber;
-    currentPageNumber = val;
-    event.pageNumber = val;
-    window.dispatchEvent(event);
-
-    // checking if the this.page was called from the updateViewarea function:
-    // avoiding the creation of two "set page" method (internal and public)
-    if (updateViewarea.inProgress) {
-      return;
-    }
-    // Avoid scrolling the first page during loading
-    if (this.loading && val === 1) {
-      return;
-    }
-    pages[val - 1].scrollIntoView();
-  },
-
-  get page() {
-    return currentPageNumber;
-  },
-
-  get supportsPrinting() {
-    var canvas = document.createElement('canvas');
-    var value = 'mozPrintCallback' in canvas;
-    // shadow
-    Object.defineProperty(this, 'supportsPrinting', { value: value,
-                                                      enumerable: true,
-                                                      configurable: true,
-                                                      writable: false });
-    return value;
-  },
-
-  get supportsFullscreen() {
-    var doc = document.documentElement;
-    var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
-                  doc.webkitRequestFullScreen || doc.msRequestFullscreen;
-
-    if (document.fullscreenEnabled === false ||
-        document.mozFullScreenEnabled === false ||
-        document.webkitFullscreenEnabled === false ||
-        document.msFullscreenEnabled === false) {
-      support = false;
-    }
-
-    Object.defineProperty(this, 'supportsFullscreen', { value: support,
-                                                        enumerable: true,
-                                                        configurable: true,
-                                                        writable: false });
-    return support;
-  },
-
-  get supportsIntegratedFind() {
-    var support = false;
-    support = FirefoxCom.requestSync('supportsIntegratedFind');
-    Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
-                                                            enumerable: true,
-                                                            configurable: true,
-                                                            writable: false });
-    return support;
-  },
-
-  get supportsDocumentFonts() {
-    var support = true;
-    support = FirefoxCom.requestSync('supportsDocumentFonts');
-    Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
-                                                           enumerable: true,
-                                                           configurable: true,
-                                                           writable: false });
-    return support;
-  },
-
-  get supportsDocumentColors() {
-    var support = true;
-    support = FirefoxCom.requestSync('supportsDocumentColors');
-    Object.defineProperty(this, 'supportsDocumentColors', { value: support,
-                                                            enumerable: true,
-                                                            configurable: true,
-                                                            writable: false });
-    return support;
-  },
-
-  get loadingBar() {
-    var bar = new ProgressBar('#loadingBar', {});
-    Object.defineProperty(this, 'loadingBar', { value: bar,
-                                                enumerable: true,
-                                                configurable: true,
-                                                writable: false });
-    return bar;
-  },
-
-  get isHorizontalScrollbarEnabled() {
-    return (PresentationMode.active ? false :
-            (this.container.scrollWidth > this.container.clientWidth));
-  },
-
-  initPassiveLoading: function pdfViewInitPassiveLoading() {
-    var pdfDataRangeTransport = {
-      rangeListeners: [],
-      progressListeners: [],
-
-      addRangeListener: function PdfDataRangeTransport_addRangeListener(
-                                   listener) {
-        this.rangeListeners.push(listener);
-      },
-
-      addProgressListener: function PdfDataRangeTransport_addProgressListener(
-                                      listener) {
-        this.progressListeners.push(listener);
-      },
-
-      onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
-        var listeners = this.rangeListeners;
-        for (var i = 0, n = listeners.length; i < n; ++i) {
-          listeners[i](begin, chunk);
-        }
-      },
-
-      onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
-        var listeners = this.progressListeners;
-        for (var i = 0, n = listeners.length; i < n; ++i) {
-          listeners[i](loaded);
-        }
-      },
-
-      requestDataRange: function PdfDataRangeTransport_requestDataRange(
-                                  begin, end) {
-        FirefoxCom.request('requestDataRange', { begin: begin, end: end });
-      }
-    };
-
-    window.addEventListener('message', function windowMessage(e) {
-      if (e.source !== null) {
-        // The message MUST originate from Chrome code.
-        console.warn('Rejected untrusted message from ' + e.origin);
-        return;
-      }
-      var args = e.data;
-
-      if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
-        return;
-      }
-      switch (args.pdfjsLoadAction) {
-        case 'supportsRangedLoading':
-          PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
-            length: args.length,
-            initialData: args.data
-          });
-          break;
-        case 'range':
-          pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
-          break;
-        case 'rangeProgress':
-          pdfDataRangeTransport.onDataProgress(args.loaded);
-          break;
-        case 'progress':
-          PDFView.progress(args.loaded / args.total);
-          break;
-        case 'complete':
-          if (!args.data) {
-            PDFView.error(mozL10n.get('loading_error', null,
-                          'An error occurred while loading the PDF.'), e);
-            break;
-          }
-          PDFView.open(args.data, 0);
-          break;
-      }
-    });
-    FirefoxCom.requestSync('initPassiveLoading', null);
-  },
-
-  setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
-    this.url = url;
-    try {
-      this.setTitle(decodeURIComponent(getFileName(url)) || url);
-    } catch (e) {
-      // decodeURIComponent may throw URIError,
-      // fall back to using the unprocessed url in that case
-      this.setTitle(url);
-    }
-  },
-
-  setTitle: function pdfViewSetTitle(title) {
-    document.title = title;
-  },
-
-  close: function pdfViewClose() {
-    var errorWrapper = document.getElementById('errorWrapper');
-    errorWrapper.setAttribute('hidden', 'true');
-
-    if (!this.pdfDocument) {
-      return;
-    }
-
-    this.pdfDocument.destroy();
-    this.pdfDocument = null;
-
-    var thumbsView = document.getElementById('thumbnailView');
-    while (thumbsView.hasChildNodes()) {
-      thumbsView.removeChild(thumbsView.lastChild);
-    }
-
-    var container = document.getElementById('viewer');
-    while (container.hasChildNodes()) {
-      container.removeChild(container.lastChild);
-    }
-
-    if (typeof PDFBug !== 'undefined') {
-      PDFBug.cleanup();
-    }
-  },
-
-  // TODO(mack): This function signature should really be pdfViewOpen(url, args)
-  open: function pdfViewOpen(file, scale, password,
-                             pdfDataRangeTransport, args) {
-    if (this.pdfDocument) {
-      // Reload the preferences if a document was previously opened.
-      Preferences.reload();
-    }
-    this.close();
-
-    var parameters = {password: password};
-    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;
-    }
-    if (args) {
-      for (var prop in args) {
-        parameters[prop] = args[prop];
-      }
-    }
-
-    var self = this;
-    self.loading = true;
-    self.downloadComplete = false;
-
-    var passwordNeeded = function passwordNeeded(updatePassword, reason) {
-      PasswordPrompt.updatePassword = updatePassword;
-      PasswordPrompt.reason = reason;
-      PasswordPrompt.open();
-    };
-
-    function getDocumentProgress(progressData) {
-      self.progress(progressData.loaded / progressData.total);
-    }
-
-    PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
-                      getDocumentProgress).then(
-      function getDocumentCallback(pdfDocument) {
-        self.load(pdfDocument, scale);
-        self.loading = false;
-      },
-      function getDocumentError(exception) {
-        var message = exception && exception.message;
-        var loadingErrorMessage = mozL10n.get('loading_error', null,
-          'An error occurred while loading the PDF.');
-
-        if (exception instanceof PDFJS.InvalidPDFException) {
-          // change error message also for other builds
-          loadingErrorMessage = mozL10n.get('invalid_file_error', null,
-                                            'Invalid or corrupted PDF file.');
-        } else if (exception instanceof PDFJS.MissingPDFException) {
-          // special message for missing PDF's
-          loadingErrorMessage = mozL10n.get('missing_file_error', null,
-                                            'Missing PDF file.');
-        } else if (exception instanceof PDFJS.UnexpectedResponseException) {
-          loadingErrorMessage = mozL10n.get('unexpected_response_error', null,
-                                            'Unexpected server response.');
-        }
-
-        var moreInfo = {
-          message: message
-        };
-        self.error(loadingErrorMessage, moreInfo);
-        self.loading = false;
-      }
-    );
-
-    if (args && args.length) {
-      DocumentProperties.setFileSize(args.length);
-    }
-  },
-
-  download: function pdfViewDownload() {
-    function downloadByUrl() {
-      downloadManager.downloadUrl(url, filename);
-    }
-
-    var url = this.url.split('#')[0];
-    var filename = getPDFFileNameFromURL(url);
-    var downloadManager = new DownloadManager();
-    downloadManager.onerror = function (err) {
-      // This error won't really be helpful because it's likely the
-      // fallback won't work either (or is already open).
-      PDFView.error('PDF failed to download.');
-    };
-
-    if (!this.pdfDocument) { // the PDF is not ready yet
-      downloadByUrl();
-      return;
-    }
-
-    if (!this.downloadComplete) { // the PDF is still downloading
-      downloadByUrl();
-      return;
-    }
-
-    this.pdfDocument.getData().then(
-      function getDataSuccess(data) {
-        var blob = PDFJS.createBlob(data, 'application/pdf');
-        downloadManager.download(blob, url, filename);
-      },
-      downloadByUrl // Error occurred try downloading with just the url.
-    ).then(null, downloadByUrl);
-  },
-
-  fallback: function pdfViewFallback(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;
-    var url = this.url.split('#')[0];
-    FirefoxCom.request('fallback', { featureId: featureId, url: url },
-      function response(download) {
-        if (!download) {
+      // No pages needed rendering so check thumbnails.
+      if (this.pdfThumbnailViewer && this.isThumbnailViewEnabled) {
+        if (this.pdfThumbnailViewer.forceRendering()) {
           return;
         }
-        PDFView.download();
-      });
-  },
-
-  navigateTo: function pdfViewNavigateTo(dest) {
-    var destString = '';
-    var self = this;
-
-    var goToDestination = function(destRef) {
-      self.pendingRefStr = null;
-      // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
-      var pageNumber = destRef instanceof Object ?
-        self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
-        (destRef + 1);
-      if (pageNumber) {
-        if (pageNumber > self.pages.length) {
-          pageNumber = self.pages.length;
-        }
-        var currentPage = self.pages[pageNumber - 1];
-        currentPage.scrollIntoView(dest);
-
-        // Update the browsing history.
-        PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
-      } else {
-        self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
-          var pageNum = pageIndex + 1;
-          self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
-          goToDestination(destRef);
-        });
-      }
-    };
-
-    this.destinationsPromise.then(function() {
-      if (typeof dest === 'string') {
-        destString = dest;
-        dest = self.destinations[dest];
-      }
-      if (!(dest instanceof Array)) {
-        return; // invalid destination
-      }
-      goToDestination(dest[0]);
-    });
-  },
-
-  getDestinationHash: function pdfViewGetDestinationHash(dest) {
-    if (typeof dest === 'string') {
-      return PDFView.getAnchorUrl('#' + escape(dest));
-    }
-    if (dest instanceof Array) {
-      var destRef = dest[0]; // see navigateTo method for dest format
-      var pageNumber = destRef instanceof Object ?
-        this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
-        (destRef + 1);
-      if (pageNumber) {
-        var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
-        var destKind = dest[1];
-        if (typeof destKind === 'object' && 'name' in destKind &&
-            destKind.name === 'XYZ') {
-          var scale = (dest[4] || this.currentScaleValue);
-          var scaleNumber = parseFloat(scale);
-          if (scaleNumber) {
-            scale = scaleNumber * 100;
-          }
-          pdfOpenParams += '&zoom=' + scale;
-          if (dest[2] || dest[3]) {
-            pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
-          }
-        }
-        return pdfOpenParams;
-      }
-    }
-    return '';
-  },
-
-  /**
-   * Prefix the full url on anchor links to make sure that links are resolved
-   * relative to the current URL instead of the one defined in <base href>.
-   * @param {String} anchor The anchor hash, including the #.
-   */
-  getAnchorUrl: function getAnchorUrl(anchor) {
-    return this.url.split('#')[0] + anchor;
-  },
-
-  /**
-   * Show the error box.
-   * @param {String} message A message that is human readable.
-   * @param {Object} moreInfo (optional) Further information about the error
-   *                            that is more technical.  Should have a 'message'
-   *                            and optionally a 'stack' property.
-   */
-  error: function pdfViewError(message, moreInfo) {
-    var moreInfoText = mozL10n.get('error_version_info',
-      {version: PDFJS.version || '?', build: PDFJS.build || '?'},
-      'PDF.js v{{version}} (build: {{build}})') + '\n';
-    if (moreInfo) {
-      moreInfoText +=
-        mozL10n.get('error_message', {message: moreInfo.message},
-        'Message: {{message}}');
-      if (moreInfo.stack) {
-        moreInfoText += '\n' +
-          mozL10n.get('error_stack', {stack: moreInfo.stack},
-          'Stack: {{stack}}');
-      } else {
-        if (moreInfo.filename) {
-          moreInfoText += '\n' +
-            mozL10n.get('error_file', {file: moreInfo.filename},
-            'File: {{file}}');
-        }
-        if (moreInfo.lineNumber) {
-          moreInfoText += '\n' +
-            mozL10n.get('error_line', {line: moreInfo.lineNumber},
-            'Line: {{line}}');
-        }
-      }
-    }
-
-    console.error(message + '\n' + moreInfoText);
-    this.fallback();
-  },
-
-  progress: function pdfViewProgress(level) {
-    var 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 > PDFView.loadingBar.percent || isNaN(percent)) {
-      PDFView.loadingBar.percent = percent;
-    }
-  },
-
-  load: function pdfViewLoad(pdfDocument, scale) {
-    var self = this;
-    var isOnePageRenderedResolved = false;
-    var resolveOnePageRendered = null;
-    var onePageRendered = new Promise(function (resolve) {
-      resolveOnePageRendered = resolve;
-    });
-    function bindOnAfterDraw(pageView, thumbnailView) {
-      // when page is painted, using the image as thumbnail base
-      pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
-        if (!isOnePageRenderedResolved) {
-          isOnePageRenderedResolved = true;
-          resolveOnePageRendered();
+      }
+
+      if (this.printing) {
+        // If printing is currently ongoing do not reschedule cleanup.
+        return;
+      }
+
+      if (this.onIdle) {
+        this.idleTimeout = setTimeout(this.onIdle.bind(this), CLEANUP_TIMEOUT);
+      }
+    },
+
+    getHighestPriority: function
+        PDFRenderingQueue_getHighestPriority(visible, views, scrolledDown) {
+      // The state has changed figure out which page has the highest priority to
+      // render next (if any).
+      // Priority:
+      // 1 visible pages
+      // 2 if last scrolled down page after the visible pages
+      // 2 if last scrolled up page before the visible pages
+      var visibleViews = visible.views;
+
+      var numVisible = visibleViews.length;
+      if (numVisible === 0) {
+        return false;
+      }
+      for (var i = 0; i < numVisible; ++i) {
+        var view = visibleViews[i].view;
+        if (!this.isViewFinished(view)) {
+          return view;
         }
-        thumbnailView.setImage(pageView.canvas);
-      };
-    }
-
-    PDFView.findController.reset();
-
-    this.pdfDocument = pdfDocument;
-
-    DocumentProperties.resolveDataAvailable();
-
-    var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
-      self.downloadComplete = true;
-      PDFView.loadingBar.hide();
-      var outerContainer = document.getElementById('outerContainer');
-      outerContainer.classList.remove('loadingInProgress');
-    });
-
-    var pagesCount = pdfDocument.numPages;
-
-    var id = pdfDocument.fingerprint;
-    document.getElementById('numPages').textContent =
-      mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
-    document.getElementById('pageNumber').max = pagesCount;
-
-    PDFView.documentFingerprint = id;
-    var store = PDFView.store = new ViewHistory(id);
-
-    this.pageRotation = 0;
-
-    var pages = this.pages = [];
-    var pagesRefMap = this.pagesRefMap = {};
-    var thumbnails = this.thumbnails = [];
-
-    var resolvePagesPromise;
-    var pagesPromise = new Promise(function (resolve) {
-      resolvePagesPromise = resolve;
-    });
-    this.pagesPromise = pagesPromise;
-
-    var firstPagePromise = pdfDocument.getPage(1);
-    var container = document.getElementById('viewer');
-    var thumbsView = document.getElementById('thumbnailView');
-
-    // Fetch a single page so we can get a viewport that will be the default
-    // viewport for all pages
-    firstPagePromise.then(function(pdfPage) {
-      var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
-      for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-        var viewportClone = viewport.clone();
-        var pageView = new PageView(container, pageNum, scale,
-                                    self.navigateTo.bind(self),
-                                    viewportClone);
-        var thumbnailView = new ThumbnailView(thumbsView, pageNum,
-                                              viewportClone);
-        bindOnAfterDraw(pageView, thumbnailView);
-        pages.push(pageView);
-        thumbnails.push(thumbnailView);
-      }
-
-      // Fetch all the pages since the viewport is needed before printing
-      // starts to create the correct size canvas. Wait until one page is
-      // rendered so we don't tie up too many resources early on.
-      onePageRendered.then(function () {
-        if (!PDFJS.disableAutoFetch) {
-          var getPagesLeft = pagesCount;
-          for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-            pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
-              var pageView = pages[pageNum - 1];
-              if (!pageView.pdfPage) {
-                pageView.setPdfPage(pdfPage);
-              }
-              var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
-              pagesRefMap[refStr] = pageNum;
-              getPagesLeft--;
-              if (!getPagesLeft) {
-                resolvePagesPromise();
-              }
-            }.bind(null, pageNum));
-          }
-        } else {
-          // XXX: Printing is semi-broken with auto fetch disabled.
-          resolvePagesPromise();
+      }
+
+      // All the visible views have rendered, try to render next/previous pages.
+      if (scrolledDown) {
+        var nextPageIndex = visible.last.id;
+        // ID's start at 1 so no need to add 1.
+        if (views[nextPageIndex] &&
+            !this.isViewFinished(views[nextPageIndex])) {
+          return views[nextPageIndex];
         }
-      });
-
-      downloadedPromise.then(function () {
-        var event = document.createEvent('CustomEvent');
-        event.initCustomEvent('documentload', true, true, {});
-        window.dispatchEvent(event);
-      });
-
-      PDFView.loadingBar.setWidth(container);
-
-      PDFView.findController.resolveFirstPage();
-
-      // Initialize the browsing history.
-      PDFHistory.initialize(self.documentFingerprint);
-    });
-
-    // Fetch the necessary preference values.
-    var showPreviousViewOnLoad;
-    var showPreviousViewOnLoadPromise =
-      Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
-        showPreviousViewOnLoad = prefValue;
-      });
-    var defaultZoomValue;
-    var defaultZoomValuePromise =
-      Preferences.get('defaultZoomValue').then(function (prefValue) {
-        defaultZoomValue = prefValue;
-      });
-
-    var storePromise = store.initializedPromise;
-    Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
-                 defaultZoomValuePromise]).then(function resolved() {
-      var storedHash = null;
-      if (showPreviousViewOnLoad && store.get('exists', false)) {
-        var pageNum = store.get('page', '1');
-        var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale);
-        var left = store.get('scrollLeft', '0');
-        var top = store.get('scrollTop', '0');
-
-        storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
-                     left + ',' + top;
-      } else if (defaultZoomValue) {
-        storedHash = 'page=1&zoom=' + defaultZoomValue;
-      }
-      self.setInitialView(storedHash, scale);
-
-      // Make all navigation keys work on document load,
-      // unless the viewer is embedded in a web page.
-      if (!self.isViewerEmbedded) {
-        self.container.focus();
-        self.container.blur();
-      }
-    }, function rejected(reason) {
-      console.error(reason);
-
-      firstPagePromise.then(function () {
-        self.setInitialView(null, scale);
-      });
-    });
-
-    pagesPromise.then(function() {
-      if (PDFView.supportsPrinting) {
-        pdfDocument.getJavaScript().then(function(javaScript) {
-          if (javaScript.length) {
-            console.warn('Warning: JavaScript is not supported');
-            PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
-          }
-          // Hack to support auto printing.
-          var regex = /\bprint\s*\(/g;
-          for (var i = 0, ii = javaScript.length; i < ii; i++) {
-            var js = javaScript[i];
-            if (js && regex.test(js)) {
-              setTimeout(function() {
-                window.print();
-              });
-              return;
-            }
-          }
-        });
-      }
-    });
-
-    var destinationsPromise =
-      this.destinationsPromise = pdfDocument.getDestinations();
-    destinationsPromise.then(function(destinations) {
-      self.destinations = destinations;
-    });
-
-    // outline depends on destinations and pagesRefMap
-    var promises = [pagesPromise, destinationsPromise,
-                    PDFView.animationStartedPromise];
-    Promise.all(promises).then(function() {
-      pdfDocument.getOutline().then(function(outline) {
-        self.outline = new DocumentOutlineView(outline);
-        document.getElementById('viewOutline').disabled = !outline;
-
-        if (outline &&
-            self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) {
-          self.switchSidebarView('outline', true);
-        }
-      });
-      pdfDocument.getAttachments().then(function(attachments) {
-        self.attachments = new DocumentAttachmentsView(attachments);
-        document.getElementById('viewAttachments').disabled = !attachments;
-
-        if (attachments &&
-            self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) {
-          self.switchSidebarView('attachments', true);
-        }
-      });
-    });
-
-    if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) {
-      Promise.all([firstPagePromise, onePageRendered]).then(function () {
-        self.switchSidebarView('thumbs', true);
-      });
-    }
-
-    pdfDocument.getMetadata().then(function(data) {
-      var info = data.info, metadata = data.metadata;
-      self.documentInfo = info;
-      self.metadata = metadata;
-
-      // Provides some basic debug information
-      console.log('PDF ' + pdfDocument.fingerprint + ' [' +
-                  info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() +
-                  ' / ' + (info.Creator || '-').trim() + ']' +
-                  ' (PDF.js: ' + (PDFJS.version || '-') +
-                  (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
-
-      var pdfTitle;
-      if (metadata && metadata.has('dc:title')) {
-        var title = metadata.get('dc:title');
-        // Ghostscript sometimes return 'Untitled', sets the title to 'Untitled'
-        if (title !== 'Untitled') {
-          pdfTitle = title;
+      } else {
+        var previousPageIndex = visible.first.id - 2;
+        if (views[previousPageIndex] &&
+          !this.isViewFinished(views[previousPageIndex])) {
+          return views[previousPageIndex];
         }
       }
-
-      if (!pdfTitle && info && info['Title']) {
-        pdfTitle = info['Title'];
-      }
-
-      if (pdfTitle) {
-        self.setTitle(pdfTitle + ' - ' + document.title);
-      }
-
-      if (info.IsAcroFormPresent) {
-        console.warn('Warning: AcroForm/XFA is not supported');
-        PDFView.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
-      }
-
-      var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
-      var generatorId = 0;
-      var KNOWN_GENERATORS = ["acrobat distiller", "acrobat pdfwritter",
-       "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"];
-      var generatorId = 0;
-      if (info.Producer) {
-        KNOWN_GENERATORS.some(function (generator, s, i) {
-          if (generator.indexOf(s) < 0) {
-            return false;
-          }
-          generatorId = i + 1;
-          return true;
-        }.bind(null, info.Producer.toLowerCase()));
-      }
-      var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ?
-                     'xfa' : 'acroform';
-      FirefoxCom.request('reportTelemetry', JSON.stringify({
-        type: 'documentInfo',
-        version: versionId,
-        generator: generatorId,
-        formType: formType
-      }));
-    });
-  },
-
-  setInitialView: function pdfViewSetInitialView(storedHash, scale) {
-    // Reset the current scale, as otherwise the page's scale might not get
-    // updated if the zoom level stayed the same.
-    this.currentScale = UNKNOWN_SCALE;
-    this.currentScaleValue = null;
-    // When opening a new file (when one is already loaded in the viewer):
-    // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
-    // if 'currentPageNumber' is larger than the number of pages in the file.
-    document.getElementById('pageNumber').value = currentPageNumber = 1;
-    // Reset the current position when loading a new file,
-    // to prevent displaying the wrong position in the document.
-    this.currentPosition = null;
-
-    if (PDFHistory.initialDestination) {
-      this.navigateTo(PDFHistory.initialDestination);
-      PDFHistory.initialDestination = null;
-    } else if (this.initialBookmark) {
-      this.setHash(this.initialBookmark);
-      PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
-      this.initialBookmark = null;
-    } else if (storedHash) {
-      this.setHash(storedHash);
-    } else if (scale) {
-      this.setScale(scale, true);
-      this.page = 1;
-    }
-
-    if (PDFView.currentScale === UNKNOWN_SCALE) {
-      // Scale was not initialized: invalid bookmark or scale was not specified.
-      // Setting the default one.
-      this.setScale(DEFAULT_SCALE, true);
-    }
-  },
-
-  renderHighestPriority:
-      function pdfViewRenderHighestPriority(currentlyVisiblePages) {
-    if (PDFView.idleTimeout) {
-      clearTimeout(PDFView.idleTimeout);
-      PDFView.idleTimeout = null;
-    }
-
-    // Pages have a higher priority than thumbnails, so check them first.
-    var visiblePages = currentlyVisiblePages || this.getVisiblePages();
-    var pageView = this.getHighestPriority(visiblePages, this.pages,
-                                           this.pageViewScroll.down);
-    if (pageView) {
-      this.renderView(pageView, 'page');
-      return;
-    }
-    // No pages needed rendering so check thumbnails.
-    if (this.sidebarOpen) {
-      var visibleThumbs = this.getVisibleThumbs();
-      var thumbView = this.getHighestPriority(visibleThumbs,
-                                              this.thumbnails,
-                                              this.thumbnailViewScroll.down);
-      if (thumbView) {
-        this.renderView(thumbView, 'thumbnail');
-        return;
-      }
-    }
-
-    if (this.printing) {
-      // If printing is currently ongoing do not reschedule cleanup.
-      return;
-    }
-
-    PDFView.idleTimeout = setTimeout(function () {
-      PDFView.cleanup();
-    }, CLEANUP_TIMEOUT);
-  },
-
-  cleanup: function pdfViewCleanup() {
-    for (var i = 0, ii = this.pages.length; i < ii; i++) {
-      if (this.pages[i] &&
-          this.pages[i].renderingState !== RenderingStates.FINISHED) {
-        this.pages[i].reset();
-      }
-    }
-    this.pdfDocument.cleanup();
-
-    ThumbnailView.tempImageCache = null;
-  },
-
-  getHighestPriority: function pdfViewGetHighestPriority(visible, views,
-                                                         scrolledDown) {
-    // The state has changed figure out which page has the highest priority to
-    // render next (if any).
-    // Priority:
-    // 1 visible pages
-    // 2 if last scrolled down page after the visible pages
-    // 2 if last scrolled up page before the visible pages
-    var visibleViews = visible.views;
-
-    var numVisible = visibleViews.length;
-    if (numVisible === 0) {
-      return false;
-    }
-    for (var i = 0; i < numVisible; ++i) {
-      var view = visibleViews[i].view;
-      if (!this.isViewFinished(view)) {
-        return view;
-      }
-    }
-
-    // All the visible views have rendered, try to render next/previous pages.
-    if (scrolledDown) {
-      var nextPageIndex = visible.last.id;
-      // ID's start at 1 so no need to add 1.
-      if (views[nextPageIndex] && !this.isViewFinished(views[nextPageIndex])) {
-        return views[nextPageIndex];
-      }
-    } else {
-      var previousPageIndex = visible.first.id - 2;
-      if (views[previousPageIndex] &&
-          !this.isViewFinished(views[previousPageIndex])) {
-        return views[previousPageIndex];
-      }
-    }
-    // Everything that needs to be rendered has been.
-    return false;
-  },
-
-  isViewFinished: function pdfViewIsViewFinished(view) {
-    return view.renderingState === RenderingStates.FINISHED;
-  },
-
-  // Render a page or thumbnail view. This calls the appropriate function based
-  // on the views state. If the view is already rendered it will return false.
-  renderView: function pdfViewRender(view, type) {
-    var state = view.renderingState;
-    switch (state) {
-      case RenderingStates.FINISHED:
-        return false;
-      case RenderingStates.PAUSED:
-        PDFView.highestPriorityPage = type + view.id;
-        view.resume();
-        break;
-      case RenderingStates.RUNNING:
-        PDFView.highestPriorityPage = type + view.id;
-        break;
-      case RenderingStates.INITIAL:
-        PDFView.highestPriorityPage = type + view.id;
-        view.draw(this.renderHighestPriority.bind(this));
-        break;
-    }
-    return true;
-  },
-
-  setHash: function pdfViewSetHash(hash) {
-    var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
-      'FitV','FitBV','FitR'];
-
-    if (!hash) {
-      return;
-    }
-
-    if (hash.indexOf('=') >= 0) {
-      var params = PDFView.parseQueryString(hash);
-      // borrowing syntax from "Parameters for Opening PDF Files"
-      if ('nameddest' in params) {
-        PDFHistory.updateNextHashParam(params.nameddest);
-        PDFView.navigateTo(params.nameddest);
-        return;
-      }
-      var pageNumber, dest;
-      if ('page' in params) {
-        pageNumber = (params.page | 0) || 1;
-      }
-      if ('zoom' in params) {
-        var zoomArgs = params.zoom.split(','); // scale,left,top
-        // building destination array
-
-        // If the zoom value, it has to get divided by 100. If it is a string,
-        // it should stay as it is.
-        var zoomArg = zoomArgs[0];
-        var zoomArgNumber = parseFloat(zoomArg);
-        var destName = 'XYZ';
-        if (zoomArgNumber) {
-          zoomArg = zoomArgNumber / 100;
-        } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
-          destName = zoomArg;
-        }
-        dest = [null, { name: destName },
-                zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
-                zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
-                zoomArg];
-      }
-      if (dest) {
-        var currentPage = this.pages[(pageNumber || this.page) - 1];
-        currentPage.scrollIntoView(dest);
-      } else if (pageNumber) {
-        this.page = pageNumber; // simple page
-      }
-      if ('pagemode' in params) {
-        if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
-            params.pagemode === 'attachments') {
-          this.switchSidebarView((params.pagemode === 'bookmarks' ?
-                                  'outline' : params.pagemode), true);
-        } else if (params.pagemode === 'none' && this.sidebarOpen) {
-          document.getElementById('sidebarToggle').click();
-        }
-      }
-    } else if (/^\d+$/.test(hash)) { // page number
-      this.page = hash;
-    } else { // named destination
-      PDFHistory.updateNextHashParam(unescape(hash));
-      PDFView.navigateTo(unescape(hash));
-    }
-  },
-
-  switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
-    if (openSidebar && !this.sidebarOpen) {
-      document.getElementById('sidebarToggle').click();
-    }
-    var thumbsView = document.getElementById('thumbnailView');
-    var outlineView = document.getElementById('outlineView');
-    var attachmentsView = document.getElementById('attachmentsView');
-
-    var thumbsButton = document.getElementById('viewThumbnail');
-    var outlineButton = document.getElementById('viewOutline');
-    var attachmentsButton = document.getElementById('viewAttachments');
-
-    switch (view) {
-      case 'thumbs':
-        var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
-
-        thumbsButton.classList.add('toggled');
-        outlineButton.classList.remove('toggled');
-        attachmentsButton.classList.remove('toggled');
-        thumbsView.classList.remove('hidden');
-        outlineView.classList.add('hidden');
-        attachmentsView.classList.add('hidden');
-
-        PDFView.renderHighestPriority();
-
-        if (wasAnotherViewVisible) {
-          // Ensure that the thumbnail of the current page is visible
-          // when switching from another view.
-          scrollIntoView(document.getElementById('thumbnailContainer' +
-                                                 this.page));
-        }
-        break;
-
-      case 'outline':
-        thumbsButton.classList.remove('toggled');
-        outlineButton.classList.add('toggled');
-        attachmentsButton.classList.remove('toggled');
-        thumbsView.classList.add('hidden');
-        outlineView.classList.remove('hidden');
-        attachmentsView.classList.add('hidden');
-
-        if (outlineButton.getAttribute('disabled')) {
-          return;
-        }
-        break;
-
-      case 'attachments':
-        thumbsButton.classList.remove('toggled');
-        outlineButton.classList.remove('toggled');
-        attachmentsButton.classList.add('toggled');
-        thumbsView.classList.add('hidden');
-        outlineView.classList.add('hidden');
-        attachmentsView.classList.remove('hidden');
-
-        if (attachmentsButton.getAttribute('disabled')) {
-          return;
-        }
-        break;
-    }
-  },
-
-  getVisiblePages: function pdfViewGetVisiblePages() {
-    if (!PresentationMode.active) {
-      return this.getVisibleElements(this.container, this.pages, true);
-    } else {
-      // The algorithm in getVisibleElements doesn't work in all browsers and
-      // configurations when presentation mode is active.
-      var visible = [];
-      var currentPage = this.pages[this.page - 1];
-      visible.push({ id: currentPage.id, view: currentPage });
-      return { first: currentPage, last: currentPage, views: visible };
-    }
-  },
-
-  getVisibleThumbs: function pdfViewGetVisibleThumbs() {
-    return this.getVisibleElements(this.thumbnailContainer, this.thumbnails);
-  },
-
-  // Generic helper to find out what elements are visible within a scroll pane.
-  getVisibleElements: function pdfViewGetVisibleElements(
-      scrollEl, views, sortByVisibility) {
-    var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
-    var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
-
-    var visible = [], view;
-    var currentHeight, viewHeight, hiddenHeight, percentHeight;
-    var currentWidth, viewWidth;
-    for (var i = 0, ii = views.length; i < ii; ++i) {
-      view = views[i];
-      currentHeight = view.el.offsetTop + view.el.clientTop;
-      viewHeight = view.el.clientHeight;
-      if ((currentHeight + viewHeight) < top) {
-        continue;
-      }
-      if (currentHeight > bottom) {
-        break;
-      }
-      currentWidth = view.el.offsetLeft + view.el.clientLeft;
-      viewWidth = view.el.clientWidth;
-      if ((currentWidth + viewWidth) < left || currentWidth > right) {
-        continue;
-      }
-      hiddenHeight = Math.max(0, top - currentHeight) +
-                     Math.max(0, currentHeight + viewHeight - bottom);
-      percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
-
-      visible.push({ id: view.id, x: currentWidth, y: currentHeight,
-                     view: view, percent: percentHeight });
-    }
-
-    var first = visible[0];
-    var last = visible[visible.length - 1];
-
-    if (sortByVisibility) {
-      visible.sort(function(a, b) {
-        var pc = a.percent - b.percent;
-        if (Math.abs(pc) > 0.001) {
-          return -pc;
-        }
-        return a.id - b.id; // ensure stability
-      });
-    }
-    return {first: first, last: last, views: visible};
-  },
-
-  // Helper function to parse query string (e.g. ?param1=value&parm2=...).
-  parseQueryString: function pdfViewParseQueryString(query) {
-    var parts = query.split('&');
-    var params = {};
-    for (var i = 0, ii = parts.length; i < ii; ++i) {
-      var param = parts[i].split('=');
-      var key = param[0];
-      var value = param.length > 1 ? param[1] : null;
-      params[decodeURIComponent(key)] = decodeURIComponent(value);
-    }
-    return params;
-  },
-
-  beforePrint: function pdfViewSetupBeforePrint() {
-    if (!this.supportsPrinting) {
-      var printMessage = mozL10n.get('printing_not_supported', null,
-          'Warning: Printing is not fully supported by this browser.');
-      this.error(printMessage);
-      return;
-    }
-
-    var alertNotReady = false;
-    var i, ii;
-    if (!this.pages.length) {
-      alertNotReady = true;
-    } else {
-      for (i = 0, ii = this.pages.length; i < ii; ++i) {
-        if (!this.pages[i].pdfPage) {
-          alertNotReady = true;
+      // Everything that needs to be rendered has been.
+      return null;
+    },
+
+    /**
+     * @param {IRenderableView} view
+     * @returns {boolean}
+     */
+    isViewFinished: function PDFRenderingQueue_isViewFinished(view) {
+      return view.renderingState === RenderingStates.FINISHED;
+    },
+
+    /**
+     * Render a page or thumbnail view. This calls the appropriate function
+     * based on the views state. If the view is already rendered it will return
+     * false.
+     * @param {IRenderableView} view
+     */
+    renderView: function PDFRenderingQueue_renderView(view) {
+      var state = view.renderingState;
+      switch (state) {
+        case RenderingStates.FINISHED:
+          return false;
+        case RenderingStates.PAUSED:
+          this.highestPriorityPage = view.renderingId;
+          view.resume();
+          break;
+        case RenderingStates.RUNNING:
+          this.highestPriorityPage = view.renderingId;
           break;
-        }
-      }
-    }
-    if (alertNotReady) {
-      var notReadyMessage = mozL10n.get('printing_not_ready', null,
-          'Warning: The PDF is not fully loaded for printing.');
-      window.alert(notReadyMessage);
-      return;
-    }
-
-    this.printing = true;
-    this.renderHighestPriority();
-
-    var body = document.querySelector('body');
-    body.setAttribute('data-mozPrintCallback', true);
-    for (i = 0, ii = this.pages.length; i < ii; ++i) {
-      this.pages[i].beforePrint();
-    }
-
-      FirefoxCom.request('reportTelemetry', JSON.stringify({
-        type: 'print'
-      }));
-  },
-
-  afterPrint: function pdfViewSetupAfterPrint() {
-    var div = document.getElementById('printContainer');
-    while (div.hasChildNodes()) {
-      div.removeChild(div.lastChild);
-    }
-
-    this.printing = false;
-    this.renderHighestPriority();
-  },
-
-  rotatePages: function pdfViewRotatePages(delta) {
-    var currentPage = this.pages[this.page - 1];
-    var i, l;
-    this.pageRotation = (this.pageRotation + 360 + delta) % 360;
-
-    for (i = 0, l = this.pages.length; i < l; i++) {
-      var page = this.pages[i];
-      page.update(page.scale, this.pageRotation);
-    }
-
-    for (i = 0, l = this.thumbnails.length; i < l; i++) {
-      var thumb = this.thumbnails[i];
-      thumb.update(this.pageRotation);
-    }
-
-    this.setScale(this.currentScaleValue, true, true);
-
-    this.renderHighestPriority();
-
-    if (currentPage) {
-      currentPage.scrollIntoView();
-    }
-  },
-
-  /**
-   * This function flips the page in presentation mode if the user scrolls up
-   * or down with large enough motion and prevents page flipping too often.
-   *
-   * @this {PDFView}
-   * @param {number} mouseScrollDelta The delta value from the mouse event.
-   */
-  mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
-    var MOUSE_SCROLL_COOLDOWN_TIME = 50;
-
-    var currentTime = (new Date()).getTime();
-    var storedTime = this.mouseScrollTimeStamp;
-
-    // In case one page has already been flipped there is a cooldown time
-    // which has to expire before next page can be scrolled on to.
-    if (currentTime > storedTime &&
-        currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
-      return;
-    }
-
-    // In case the user decides to scroll to the opposite direction than before
-    // clear the accumulated delta.
-    if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
-        (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
-      this.clearMouseScrollState();
-    }
-
-    this.mouseScrollDelta += mouseScrollDelta;
-
-    var PAGE_FLIP_THRESHOLD = 120;
-    if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
-
-      var PageFlipDirection = {
-        UP: -1,
-        DOWN: 1
-      };
-
-      // In presentation mode scroll one page at a time.
-      var pageFlipDirection = (this.mouseScrollDelta > 0) ?
-                                PageFlipDirection.UP :
-                                PageFlipDirection.DOWN;
-      this.clearMouseScrollState();
-      var currentPage = this.page;
-
-      // In case we are already on the first or the last page there is no need
-      // to do anything.
-      if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) ||
-          (currentPage === this.pages.length &&
-           pageFlipDirection === PageFlipDirection.DOWN)) {
-        return;
-      }
-
-      this.page += pageFlipDirection;
-      this.mouseScrollTimeStamp = currentTime;
-    }
-  },
-
-  /**
-   * This function clears the member attributes used with mouse scrolling in
-   * presentation mode.
-   *
-   * @this {PDFView}
-   */
-  clearMouseScrollState: function pdfViewClearMouseScrollState() {
-    this.mouseScrollTimeStamp = 0;
-    this.mouseScrollDelta = 0;
-  }
-};
-
-
-var PageView = function pageView(container, id, scale,
-                                 navigateTo, defaultViewport) {
+        case RenderingStates.INITIAL:
+          this.highestPriorityPage = view.renderingId;
+          view.draw(this.renderHighestPriority.bind(this));
+          break;
+      }
+      return true;
+    },
+  };
+
+  return PDFRenderingQueue;
+})();
+
+
+/**
+ * @constructor
+ * @param {HTMLDivElement} container - The viewer element.
+ * @param {number} id - The page unique ID (normally its number).
+ * @param {number} scale - The page scale display.
+ * @param {PageViewport} defaultViewport - The page viewport.
+ * @param {IPDFLinkService} linkService - The navigation/linking service.
+ * @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @param {Cache} cache - The page cache.
+ * @param {PDFPageSource} pageSource
+ * @param {PDFViewer} viewer
+ *
+ * @implements {IRenderableView}
+ */
+var PageView = function pageView(container, id, scale, defaultViewport,
+                                 linkService, renderingQueue, cache,
+                                 pageSource, viewer) {
   this.id = id;
+  this.renderingId = 'page' + id;
 
   this.rotation = 0;
   this.scale = scale || 1.0;
   this.viewport = defaultViewport;
   this.pdfPageRotate = defaultViewport.rotation;
   this.hasRestrictedScaling = false;
 
+  this.linkService = linkService;
+  this.renderingQueue = renderingQueue;
+  this.cache = cache;
+  this.pageSource = pageSource;
+  this.viewer = viewer;
+
   this.renderingState = RenderingStates.INITIAL;
   this.resume = null;
 
   this.textLayer = null;
 
   this.zoomLayer = null;
 
   this.annotationLayer = null;
@@ -4295,70 +3050,32 @@ var PageView = function pageView(contain
     enumerable: true
   });
 
   var self = this;
 
   function setupAnnotations(pageDiv, pdfPage, viewport) {
 
     function bindLink(link, dest) {
-      link.href = PDFView.getDestinationHash(dest);
+      link.href = linkService.getDestinationHash(dest);
       link.onclick = function pageViewSetupLinksOnclick() {
         if (dest) {
-          PDFView.navigateTo(dest);
+          linkService.navigateTo(dest);
         }
         return false;
       };
       if (dest) {
         link.className = 'internalLink';
       }
     }
 
     function bindNamedAction(link, action) {
-      link.href = PDFView.getAnchorUrl('');
+      link.href = linkService.getAnchorUrl('');
       link.onclick = function pageViewSetupNamedActionOnClick() {
-        // See PDF reference, table 8.45 - Named action
-        switch (action) {
-          case 'GoToPage':
-            document.getElementById('pageNumber').focus();
-            break;
-
-          case 'GoBack':
-            PDFHistory.back();
-            break;
-
-          case 'GoForward':
-            PDFHistory.forward();
-            break;
-
-          case 'Find':
-            if (!PDFView.supportsIntegratedFind) {
-              PDFView.findBar.toggle();
-            }
-            break;
-
-          case 'NextPage':
-            PDFView.page++;
-            break;
-
-          case 'PrevPage':
-            PDFView.page--;
-            break;
-
-          case 'LastPage':
-            PDFView.page = PDFView.pages.length;
-            break;
-
-          case 'FirstPage':
-            PDFView.page = 1;
-            break;
-
-          default:
-            break; // No action according to spec
-        }
+        linkService.executeNamedAction(action);
         return false;
       };
       link.className = 'internalLink';
     }
 
     pdfPage.getAnnotations().then(function(annotationsData) {
       viewport = viewport.clone({ dontFlip: true });
       var transform = viewport.transform;
@@ -4429,117 +3146,24 @@ var PageView = function pageView(contain
       }
     });
   }
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
     return this.viewport.convertToPdfPoint(x, y);
   };
 
-  this.scrollIntoView = function pageViewScrollIntoView(dest) {
-    if (PresentationMode.active) {
-      if (PDFView.page !== this.id) {
-        // Avoid breaking PDFView.getVisiblePages in presentation mode.
-        PDFView.page = this.id;
-        return;
-      }
-      dest = null;
-      PDFView.setScale(PDFView.currentScaleValue, true, true);
-    }
-    if (!dest) {
-      scrollIntoView(div);
-      return;
-    }
-
-    var x = 0, y = 0;
-    var width = 0, height = 0, widthScale, heightScale;
-    var changeOrientation = (this.rotation % 180 === 0 ? false : true);
-    var pageWidth = (changeOrientation ? this.height : this.width) /
-      this.scale / CSS_UNITS;
-    var pageHeight = (changeOrientation ? this.width : this.height) /
-      this.scale / CSS_UNITS;
-    var scale = 0;
-    switch (dest[1].name) {
-      case 'XYZ':
-        x = dest[2];
-        y = dest[3];
-        scale = dest[4];
-        // If x and/or y coordinates are not supplied, default to
-        // _top_ left of the page (not the obvious bottom left,
-        // since aligning the bottom of the intended page with the
-        // top of the window is rarely helpful).
-        x = x !== null ? x : 0;
-        y = y !== null ? y : pageHeight;
-        break;
-      case 'Fit':
-      case 'FitB':
-        scale = 'page-fit';
-        break;
-      case 'FitH':
-      case 'FitBH':
-        y = dest[2];
-        scale = 'page-width';
-        break;
-      case 'FitV':
-      case 'FitBV':
-        x = dest[2];
-        width = pageWidth;
-        height = pageHeight;
-        scale = 'page-height';
-        break;
-      case 'FitR':
-        x = dest[2];
-        y = dest[3];
-        width = dest[4] - x;
-        height = dest[5] - y;
-        widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
-          width / CSS_UNITS;
-        heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
-          height / CSS_UNITS;
-        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
-        break;
-      default:
-        return;
-    }
-
-    if (scale && scale !== PDFView.currentScale) {
-      PDFView.setScale(scale, true, true);
-    } else if (PDFView.currentScale === UNKNOWN_SCALE) {
-      PDFView.setScale(DEFAULT_SCALE, true, true);
-    }
-
-    if (scale === 'page-fit' && !dest[4]) {
-      scrollIntoView(div);
-      return;
-    }
-
-    var boundingRect = [
-      this.viewport.convertToViewportPoint(x, y),
-      this.viewport.convertToViewportPoint(x + width, y + height)
-    ];
-    var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
-    var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
-
-    scrollIntoView(div, { left: left, top: top });
-  };
-
-  this.getTextContent = function pageviewGetTextContent() {
-    return PDFView.getPage(this.id).then(function(pdfPage) {
-      return pdfPage.getTextContent();
-    });
-  };
-
   this.draw = function pageviewDraw(callback) {
     var pdfPage = this.pdfPage;
 
     if (this.pagePdfPromise) {
       return;
     }
     if (!pdfPage) {
-      var promise = PDFView.getPage(this.id);
+      var promise = this.pageSource.getPage();
       promise.then(function(pdfPage) {
         delete this.pagePdfPromise;
         this.setPdfPage(pdfPage);
         this.draw(callback);
       }.bind(this));
       this.pagePdfPromise = promise;
       return;
     }
@@ -4597,37 +3221,34 @@ var PageView = function pageView(contain
     canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
     canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
     canvas.style.width = Math.floor(viewport.width) + 'px';
     canvas.style.height = Math.floor(viewport.height) + 'px';
     // Add the viewport so it's known what it was originally drawn with.
     canvas._viewport = viewport;
 
     var textLayerDiv = null;
+    var textLayer = null;
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
       textLayerDiv.className = 'textLayer';
       textLayerDiv.style.width = canvas.style.width;
       textLayerDiv.style.height = canvas.style.height;
       if (this.annotationLayer) {
         // annotationLayer needs to stay on top
         div.insertBefore(textLayerDiv, this.annotationLayer);
       } else {
         div.appendChild(textLayerDiv);
       }
-    }
-    var textLayer = this.textLayer =
-      textLayerDiv ? new TextLayerBuilder({
-        textLayerDiv: textLayerDiv,
-        pageIndex: this.id - 1,
-        lastScrollSource: PDFView,
-        viewport: this.viewport,
-        isViewerInPresentationMode: PresentationMode.active,
-        findController: PDFView.findController
-      }) : null;
+
+      textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
+                                                     this.viewport);
+    }
+    this.textLayer = textLayer;
+
     // TODO(mack): use data attributes to store these
     ctx._scaleX = outputScale.sx;
     ctx._scaleY = outputScale.sy;
     if (outputScale.scaled) {
       ctx.scale(outputScale.sx, outputScale.sy);
     }
 
     // Rendering area
@@ -4652,79 +3273,55 @@ var PageView = function pageView(contain
         delete self.loadingIconDiv;
       }
 
       if (self.zoomLayer) {
         div.removeChild(self.zoomLayer);
         self.zoomLayer = null;
       }
 
-      if (self.textLayer && self.textLayer.textDivs &&
-          self.textLayer.textDivs.length > 0 &&
-          !PDFView.supportsDocumentColors) {
-        console.error(mozL10n.get('document_colors_disabled', null,
-          'PDF documents are not allowed to use their own colors: ' +
-          '\'Allow pages to choose their own colors\' ' +
-          'is deactivated in the browser.'));
-        PDFView.fallback();
-      }
-      if (error) {
-        PDFView.error(mozL10n.get('rendering_error', null,
-          'An error occurred while rendering the page.'), error);
-      }
-
+      self.error = error;
       self.stats = pdfPage.stats;
       self.updateStats();
       if (self.onAfterDraw) {
         self.onAfterDraw();
       }
 
       var event = document.createEvent('CustomEvent');
       event.initCustomEvent('pagerender', true, true, {
         pageNumber: pdfPage.pageNumber
       });
       div.dispatchEvent(event);
 
-      FirefoxCom.request('reportTelemetry', JSON.stringify({
-        type: 'pageInfo'
-      }));
-      // It is a good time to report stream and font types
-      PDFView.pdfDocument.getStats().then(function (stats) {
-        FirefoxCom.request('reportTelemetry', JSON.stringify({
-          type: 'documentStats',
-          stats: stats
-        }));
-      });
       callback();
     }
 
     var renderContext = {
       canvasContext: ctx,
       viewport: this.viewport,
-      textLayer: textLayer,
       // intent: 'default', // === 'display'
       continueCallback: function pdfViewcContinueCallback(cont) {
-        if (PDFView.highestPriorityPage !== 'page' + self.id) {
+        if (!self.renderingQueue.isHighestPriority(self)) {
           self.renderingState = RenderingStates.PAUSED;
           self.resume = function resumeCallback() {
             self.renderingState = RenderingStates.RUNNING;
             cont();
           };
           return;
         }
         cont();
       }
     };
     var renderTask = this.renderTask = this.pdfPage.render(renderContext);
 
     this.renderTask.promise.then(
       function pdfPageRenderCallback() {
         pageViewDrawCallback(null);
         if (textLayer) {
-          self.getTextContent().then(
+          self.pdfPage.getTextContent().then(
             function textContentResolved(textContent) {
               textLayer.setTextContent(textContent);
             }
           );
         }
       },
       function pdfPageRenderError(error) {
         pageViewDrawCallback(error);
@@ -4802,248 +3399,44 @@ var PageView = function pageView(contain
     if (PDFJS.pdfBug && Stats.enabled) {
       var stats = this.stats;
       Stats.add(this.id, stats);
     }
   };
 };
 
 
-var ThumbnailView = function thumbnailView(container, id, defaultViewport) {
-  var anchor = document.createElement('a');
-  anchor.href = PDFView.getAnchorUrl('#page=' + id);
-  anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
-  anchor.onclick = function stopNavigation() {
-    PDFView.page = id;
-    return false;
-  };
-
-  this.pdfPage = undefined;
-  this.viewport = defaultViewport;
-  this.pdfPageRotate = defaultViewport.rotation;
-
-  this.rotation = 0;
-  this.pageWidth = this.viewport.width;
-  this.pageHeight = this.viewport.height;
-  this.pageRatio = this.pageWidth / this.pageHeight;
-  this.id = id;
-
-  this.canvasWidth = 98;
-  this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
-  this.scale = (this.canvasWidth / this.pageWidth);
-
-  var div = this.el = document.createElement('div');
-  div.id = 'thumbnailContainer' + id;
-  div.className = 'thumbnail';
-
-  if (id === 1) {
-    // Highlight the thumbnail of the first page when no page number is
-    // specified (or exists in cache) when the document is loaded.
-    div.classList.add('selected');
-  }
-
-  var ring = document.createElement('div');
-  ring.className = 'thumbnailSelectionRing';
-  ring.style.width = this.canvasWidth + 'px';
-  ring.style.height = this.canvasHeight + 'px';
-
-  div.appendChild(ring);
-  anchor.appendChild(div);
-  container.appendChild(anchor);
-
-  this.hasImage = false;
-  this.renderingState = RenderingStates.INITIAL;
-
-  this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
-    this.pdfPage = pdfPage;
-    this.pdfPageRotate = pdfPage.rotate;
-    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-    this.viewport = pdfPage.getViewport(1, totalRotation);
-    this.update();
-  };
-
-  this.update = function thumbnailViewUpdate(rotation) {
-    if (rotation !== undefined) {
-      this.rotation = rotation;
-    }
-    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-    this.viewport = this.viewport.clone({
-      scale: 1,
-      rotation: totalRotation
-    });
-    this.pageWidth = this.viewport.width;
-    this.pageHeight = this.viewport.height;
-    this.pageRatio = this.pageWidth / this.pageHeight;
-
-    this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
-    this.scale = (this.canvasWidth / this.pageWidth);
-
-    div.removeAttribute('data-loaded');
-    ring.textContent = '';
-    ring.style.width = this.canvasWidth + 'px';
-    ring.style.height = this.canvasHeight + 'px';
-
-    this.hasImage = false;
-    this.renderingState = RenderingStates.INITIAL;
-    this.resume = null;
-  };
-
-  this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
-    var canvas = document.createElement('canvas');
-    canvas.id = 'thumbnail' + id;
-
-    canvas.width = this.canvasWidth;
-    canvas.height = this.canvasHeight;
-    canvas.className = 'thumbnailImage';
-    canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
-      {page: id}, 'Thumbnail of Page {{page}}'));
-
-    div.setAttribute('data-loaded', true);
-
-    ring.appendChild(canvas);
-
-    var ctx = canvas.getContext('2d');
-    ctx.save();
-    ctx.fillStyle = 'rgb(255, 255, 255)';
-    ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
-    ctx.restore();
-    return ctx;
-  };
-
-  this.drawingRequired = function thumbnailViewDrawingRequired() {
-    return !this.hasImage;
-  };
-
-  this.draw = function thumbnailViewDraw(callback) {
-    if (!this.pdfPage) {
-      var promise = PDFView.getPage(this.id);
-      promise.then(function(pdfPage) {
-        this.setPdfPage(pdfPage);
-        this.draw(callback);
-      }.bind(this));
-      return;
-    }
-
-    if (this.renderingState !== RenderingStates.INITIAL) {
-      console.error('Must be in new state before drawing');
-    }
-
-    this.renderingState = RenderingStates.RUNNING;
-    if (this.hasImage) {
-      callback();
-      return;
-    }
-
-    var self = this;
-    var ctx = this.getPageDrawContext();
-    var drawViewport = this.viewport.clone({ scale: this.scale });
-    var renderContext = {
-      canvasContext: ctx,
-      viewport: drawViewport,
-      continueCallback: function(cont) {
-        if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
-          self.renderingState = RenderingStates.PAUSED;
-          self.resume = function() {
-            self.renderingState = RenderingStates.RUNNING;
-            cont();
-          };
-          return;
-        }
-        cont();
-      }
-    };
-    this.pdfPage.render(renderContext).promise.then(
-      function pdfPageRenderCallback() {
-        self.renderingState = RenderingStates.FINISHED;
-        callback();
-      },
-      function pdfPageRenderError(error) {
-        self.renderingState = RenderingStates.FINISHED;
-        callback();
-      }
-    );
-    this.hasImage = true;
-  };
-
-  function getTempCanvas(width, height) {
-    var tempCanvas = ThumbnailView.tempImageCache;
-    if (!tempCanvas) {
-      tempCanvas = document.createElement('canvas');
-      ThumbnailView.tempImageCache = tempCanvas;
-    }
-    tempCanvas.width = width;
-    tempCanvas.height = height;
-    return tempCanvas;
-  }
-
-  this.setImage = function thumbnailViewSetImage(img) {
-    if (!this.pdfPage) {
-      var promise = PDFView.getPage(this.id);
-      promise.then(function(pdfPage) {
-        this.setPdfPage(pdfPage);
-        this.setImage(img);
-      }.bind(this));
-      return;
-    }
-    if (this.hasImage || !img) {
-      return;
-    }
-    this.renderingState = RenderingStates.FINISHED;
-    var ctx = this.getPageDrawContext();
-
-    var reducedImage = img;
-    var reducedWidth = img.width;
-    var reducedHeight = img.height;
-
-    // drawImage does an awful job of rescaling the image, doing it gradually
-    var MAX_SCALE_FACTOR = 2.0;
-    if (Math.max(img.width / ctx.canvas.width,
-                 img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
-      reducedWidth >>= 1;
-      reducedHeight >>= 1;
-      reducedImage = getTempCanvas(reducedWidth, reducedHeight);
-      var reducedImageCtx = reducedImage.getContext('2d');
-      reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
-                                0, 0, reducedWidth, reducedHeight);
-      while (Math.max(reducedWidth / ctx.canvas.width,
-                      reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
-        reducedImageCtx.drawImage(reducedImage,
-                                  0, 0, reducedWidth, reducedHeight,
-                                  0, 0, reducedWidth >> 1, reducedHeight >> 1);
-        reducedWidth >>= 1;
-        reducedHeight >>= 1;
-      }
-    }
-
-    ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
-                  0, 0, ctx.canvas.width, ctx.canvas.height);
-
-    this.hasImage = true;
-  };
-};
-
-ThumbnailView.tempImageCache = null;
-
-
 var FIND_SCROLL_OFFSET_TOP = -50;
 var FIND_SCROLL_OFFSET_LEFT = -400;
 var MAX_TEXT_DIVS_TO_RENDER = 100000;
 var RENDER_DELAY = 200; // ms
 
 var NonWhitespaceRegexp = /\S/;
 
 function isAllWhitespace(str) {
   return !NonWhitespaceRegexp.test(str);
 }
 
 /**
+ * @typedef {Object} TextLayerBuilderOptions
+ * @property {HTMLDivElement} textLayerDiv - The text layer container.
+ * @property {number} pageIndex - The page index.
+ * @property {PageViewport} viewport - The viewport of the text layer.
+ * @property {ILastScrollSource} lastScrollSource - The object that records when
+ *   last time scroll happened.
+ * @property {boolean} isViewerInPresentationMode
+ * @property {PDFFindController} findController
+ */
+
+/**
  * TextLayerBuilder provides text-selection functionality for the PDF.
  * It does this by creating overlay divs over the PDF text. These divs
  * contain text that matches the PDF text they are overlaying. This object
  * also provides a way to highlight text that is being searched for.
+ * @class
  */
 var TextLayerBuilder = (function TextLayerBuilderClosure() {
   function TextLayerBuilder(options) {
     this.textLayerDiv = options.textLayerDiv;
     this.layoutDone = false;
     this.divContentDone = false;
     this.pageIdx = options.pageIndex;
     this.matches = [];
@@ -5378,33 +3771,2350 @@ var TextLayerBuilder = (function TextLay
         [] : (this.findController.pageMatches[this.pageIdx] || []));
       this.renderMatches(this.matches);
     }
   };
   return TextLayerBuilder;
 })();
 
 
-var DocumentOutlineView = function documentOutlineView(outline) {
-  var outlineView = document.getElementById('outlineView');
+/**
+ * @typedef {Object} PDFViewerOptions
+ * @property {HTMLDivElement} container - The container for the viewer element.
+ * @property {HTMLDivElement} viewer - (optional) The viewer element.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
+ *   queue object.
+ */
+
+/**
+ * Simple viewer control to display PDF content/pages.
+ * @class
+ * @implements {ILastScrollSource}
+ * @implements {IRenderableView}
+ */
+var PDFViewer = (function pdfViewer() {
+  /**
+   * @constructs PDFViewer
+   * @param {PDFViewerOptions} options
+   */
+  function PDFViewer(options) {
+    this.container = options.container;
+    this.viewer = options.viewer || options.container.firstElementChild;
+    this.linkService = options.linkService || new SimpleLinkService(this);
+
+    this.defaultRenderingQueue = !options.renderingQueue;
+    if (this.defaultRenderingQueue) {
+      // Custom rendering queue is not specified, using default one
+      this.renderingQueue = new PDFRenderingQueue();
+      this.renderingQueue.setViewer(this);
+    } else {
+      this.renderingQueue = options.renderingQueue;
+    }
+
+    this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
+    this.lastScroll = 0;
+    this.updateInProgress = false;
+    this.presentationModeState = PresentationModeState.UNKNOWN;
+    this._resetView();
+  }
+
+  PDFViewer.prototype = /** @lends PDFViewer.prototype */{
+    get pagesCount() {
+      return this.pages.length;
+    },
+
+    getPageView: function (index) {
+      return this.pages[index];
+    },
+
+    get currentPageNumber() {
+      return this._currentPageNumber;
+    },
+
+    set currentPageNumber(val) {
+      if (!this.pdfDocument) {
+        this._currentPageNumber = val;
+        return;
+      }
+
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('pagechange', true, true, window, 0);
+      event.updateInProgress = this.updateInProgress;
+
+      if (!(0 < val && val <= this.pagesCount)) {
+        event.pageNumber = this._currentPageNumber;
+        event.previousPageNumber = val;
+        this.container.dispatchEvent(event);
+        return;
+      }
+
+      this.pages[val - 1].updateStats();
+      event.previousPageNumber = this._currentPageNumber;
+      this._currentPageNumber = val;
+      event.pageNumber = val;
+      this.container.dispatchEvent(event);
+    },
+
+    /**
+     * @returns {number}
+     */
+    get currentScale() {
+      return this._currentScale;
+    },
+
+    /**
+     * @param {number} val - Scale of the pages in percents.
+     */
+    set currentScale(val) {
+      if (isNaN(val))  {
+        throw new Error('Invalid numeric scale');
+      }
+      if (!this.pdfDocument) {
+        this._currentScale = val;
+        this._currentScaleValue = val.toString();
+        return;
+      }
+      this._setScale(val, false);
+    },
+
+    /**
+     * @returns {string}
+     */
+    get currentScaleValue() {
+      return this._currentScaleValue;
+    },
+
+    /**
+     * @param val - The scale of the pages (in percent or predefined value).
+     */
+    set currentScaleValue(val) {
+      if (!this.pdfDocument) {
+        this._currentScale = isNaN(val) ? UNKNOWN_SCALE : val;
+        this._currentScaleValue = val;
+        return;
+      }
+      this._setScale(val, false);
+    },
+
+    /**
+     * @returns {number}
+     */
+    get pagesRotation() {
+      return this._pagesRotation;
+    },
+
+    /**
+     * @param {number} rotation - The rotation of the pages (0, 90, 180, 270).
+     */
+    set pagesRotation(rotation) {
+      this._pagesRotation = rotation;
+
+      for (var i = 0, l = this.pages.length; i < l; i++) {
+        var page = this.pages[i];
+        page.update(page.scale, rotation);
+      }
+
+      this._setScale(this._currentScaleValue, true);
+    },
+
+    /**
+     * @param pdfDocument {PDFDocument}
+     */
+    setDocument: function (pdfDocument) {
+      if (this.pdfDocument) {
+        this._resetView();
+      }
+
+      this.pdfDocument = pdfDocument;
+      if (!pdfDocument) {
+        return;
+      }
+
+      var pagesCount = pdfDocument.numPages;
+      var pagesRefMap = this.pagesRefMap = {};
+      var self = this;
+
+      var resolvePagesPromise;
+      var pagesPromise = new Promise(function (resolve) {
+        resolvePagesPromise = resolve;
+      });
+      this.pagesPromise = pagesPromise;
+      pagesPromise.then(function () {
+        var event = document.createEvent('CustomEvent');
+        event.initCustomEvent('pagesloaded', true, true, {
+          pagesCount: pagesCount
+        });
+        self.container.dispatchEvent(event);
+      });
+
+      var isOnePageRenderedResolved = false;
+      var resolveOnePageRendered = null;
+      var onePageRendered = new Promise(function (resolve) {
+        resolveOnePageRendered = resolve;
+      });
+      this.onePageRendered = onePageRendered;
+
+      var bindOnAfterDraw = function (pageView) {
+        // when page is painted, using the image as thumbnail base
+        pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+          if (!isOnePageRenderedResolved) {
+            isOnePageRenderedResolved = true;
+            resolveOnePageRendered();
+          }
+          var event = document.createEvent('CustomEvent');
+          event.initCustomEvent('pagerendered', true, true, {
+            pageNumber: pageView.id
+          });
+          self.container.dispatchEvent(event);
+        };
+      };
+
+      var firstPagePromise = pdfDocument.getPage(1);
+      this.firstPagePromise = firstPagePromise;
+
+      // Fetch a single page so we can get a viewport that will be the default
+      // viewport for all pages
+      return firstPagePromise.then(function(pdfPage) {
+        var scale = this._currentScale || 1.0;
+        var viewport = pdfPage.getViewport(scale * CSS_UNITS);
+        for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+          var pageSource = new PDFPageSource(pdfDocument, pageNum);
+          var pageView = new PageView(this.viewer, pageNum, scale,
+                                      viewport.clone(), this.linkService,
+                                      this.renderingQueue, this.cache,
+                                      pageSource, this);
+          bindOnAfterDraw(pageView);
+          this.pages.push(pageView);
+        }
+
+        // Fetch all the pages since the viewport is needed before printing
+        // starts to create the correct size canvas. Wait until one page is
+        // rendered so we don't tie up too many resources early on.
+        onePageRendered.then(function () {
+          if (!PDFJS.disableAutoFetch) {
+            var getPagesLeft = pagesCount;
+            for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+              pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
+                var pageView = self.pages[pageNum - 1];
+                if (!pageView.pdfPage) {
+                  pageView.setPdfPage(pdfPage);
+                }
+                var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
+                pagesRefMap[refStr] = pageNum;
+                getPagesLeft--;
+                if (!getPagesLeft) {
+                  resolvePagesPromise();
+                }
+              }.bind(null, pageNum));
+            }
+          } else {
+            // XXX: Printing is semi-broken with auto fetch disabled.
+            resolvePagesPromise();
+          }
+        });
+
+        var event = document.createEvent('CustomEvent');
+        event.initCustomEvent('pagesinit', true, true, null);
+        self.container.dispatchEvent(event);
+
+        if (this.defaultRenderingQueue) {
+          this.update();
+        }
+      }.bind(this));
+    },
+
+    _resetView: function () {
+      this.cache = new Cache(DEFAULT_CACHE_SIZE);
+      this.pages = [];
+      this._currentPageNumber = 1;
+      this._currentScale = UNKNOWN_SCALE;
+      this._currentScaleValue = null;
+      this.location = null;
+      this._pagesRotation = 0;
+
+      var container = this.viewer;
+      while (container.hasChildNodes()) {
+        container.removeChild(container.lastChild);
+      }
+    },
+
+    _scrollUpdate: function () {
+      this.lastScroll = Date.now();
+
+      if (this.pagesCount === 0) {
+        return;
+      }
+      this.update();
+    },
+
+    _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
+        newScale, newValue, noScroll, preset) {
+      this._currentScaleValue = newValue;
+      if (newScale === this._currentScale) {
+        return;
+      }
+      for (var i = 0, ii = this.pages.length; i < ii; i++) {
+        this.pages[i].update(newScale);
+      }
+      this._currentScale = newScale;
+
+      if (!noScroll) {
+        var page = this._currentPageNumber, dest;
+        var inPresentationMode =
+          this.presentationModeState === PresentationModeState.CHANGING ||
+          this.presentationModeState === PresentationModeState.FULLSCREEN;
+        if (this.location && !inPresentationMode &&
+            !IGNORE_CURRENT_POSITION_ON_ZOOM) {
+          page = this.location.pageNumber;
+          dest = [null, { name: 'XYZ' }, this.location.left,
+            this.location.top, null];
+        }
+        this.scrollPageIntoView(page, dest);
+      }
+
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('scalechange', true, true, window, 0);
+      event.scale = newScale;
+      if (preset) {
+        event.presetValue = newValue;
+      }
+      this.container.dispatchEvent(event);
+    },
+
+    _setScale: function pdfViewer_setScale(value, noScroll) {
+      if (value === 'custom') {
+        return;
+      }
+      var scale = parseFloat(value);
+
+      if (scale > 0) {
+        this._setScaleUpdatePages(scale, value, noScroll, false);
+      } else {
+        var currentPage = this.pages[this._currentPageNumber - 1];
+        if (!currentPage) {
+          return;
+        }
+        var inPresentationMode =
+          this.presentationModeState === PresentationModeState.FULLSCREEN;
+        var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
+        var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
+        var pageWidthScale = (this.container.clientWidth - hPadding) /
+                             currentPage.width * currentPage.scale;
+        var pageHeightScale = (this.container.clientHeight - vPadding) /
+                              currentPage.height * currentPage.scale;
+        switch (value) {
+          case 'page-actual':
+            scale = 1;
+            break;
+          case 'page-width':
+            scale = pageWidthScale;
+            break;
+          case 'page-height':
+            scale = pageHeightScale;
+            break;
+          case 'page-fit':
+            scale = Math.min(pageWidthScale, pageHeightScale);
+            break;
+          case 'auto':
+            var isLandscape = (currentPage.width > currentPage.height);
+            // For pages in landscape mode, fit the page height to the viewer
+            // *unless* the page would thus become too wide to fit horizontally.
+            var horizontalScale = isLandscape ?
+              Math.min(pageHeightScale, pageWidthScale) : pageWidthScale;
+            scale = Math.min(MAX_AUTO_SCALE, horizontalScale);
+            break;
+          default:
+            console.error('pdfViewSetScale: \'' + value +
+              '\' is an unknown zoom value.');
+            return;
+        }
+        this._setScaleUpdatePages(scale, value, noScroll, true);
+      }
+    },
+
+    /**
+     * Scrolls page into view.
+     * @param {number} pageNumber
+     * @param {Array} dest - (optional) original PDF destination array:
+     *   <page-ref> </XYZ|FitXXX> <args..>
+     */
+    scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
+                                                              dest) {
+      var pageView = this.pages[pageNumber - 1];
+      var pageViewDiv = pageView.el;
+
+      if (this.presentationModeState ===
+        PresentationModeState.FULLSCREEN) {
+        if (this.linkService.page !== pageView.id) {
+          // Avoid breaking getVisiblePages in presentation mode.
+          this.linkService.page = pageView.id;
+          return;
+        }
+        dest = null;
+        // Fixes the case when PDF has different page sizes.
+        this._setScale(this.currentScaleValue, true);
+      }
+      if (!dest) {
+        scrollIntoView(pageViewDiv);
+        return;
+      }
+
+      var x = 0, y = 0;
+      var width = 0, height = 0, widthScale, heightScale;
+      var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
+      var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
+        pageView.scale / CSS_UNITS;
+      var pageHeight = (changeOrientation ? pageView.width : pageView.height) /
+        pageView.scale / CSS_UNITS;
+      var scale = 0;
+      switch (dest[1].name) {
+        case 'XYZ':
+          x = dest[2];
+          y = dest[3];
+          scale = dest[4];
+          // If x and/or y coordinates are not supplied, default to
+          // _top_ left of the page (not the obvious bottom left,
+          // since aligning the bottom of the intended page with the
+          // top of the window is rarely helpful).
+          x = x !== null ? x : 0;
+          y = y !== null ? y : pageHeight;
+          break;
+        case 'Fit':
+        case 'FitB':
+          scale = 'page-fit';
+          break;
+        case 'FitH':
+        case 'FitBH':
+          y = dest[2];
+          scale = 'page-width';
+          break;
+        case 'FitV':
+        case 'FitBV':
+          x = dest[2];
+          width = pageWidth;
+          height = pageHeight;
+          scale = 'page-height';
+          break;
+        case 'FitR':
+          x = dest[2];
+          y = dest[3];
+          width = dest[4] - x;
+          height = dest[5] - y;
+          var viewerContainer = this.container;
+          widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
+            width / CSS_UNITS;
+          heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
+            height / CSS_UNITS;
+          scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
+          break;
+        default:
+          return;
+      }
+
+      if (scale && scale !== this.currentScale) {
+        this.currentScaleValue = scale;
+      } else if (this.currentScale === UNKNOWN_SCALE) {
+        this.currentScaleValue = DEFAULT_SCALE;
+      }
+
+      if (scale === 'page-fit' && !dest[4]) {
+        scrollIntoView(pageViewDiv);
+        return;
+      }
+
+      var boundingRect = [
+        pageView.viewport.convertToViewportPoint(x, y),
+        pageView.viewport.convertToViewportPoint(x + width, y + height)
+      ];
+      var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
+      var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
+
+      scrollIntoView(pageViewDiv, { left: left, top: top });
+    },
+
+    _updateLocation: function (firstPage) {
+      var currentScale = this._currentScale;
+      var currentScaleValue = this._currentScaleValue;
+      var normalizedScaleValue =
+        parseFloat(currentScaleValue) === currentScale ?
+        Math.round(currentScale * 10000) / 100 : currentScaleValue;
+
+      var pageNumber = firstPage.id;
+      var pdfOpenParams = '#page=' + pageNumber;
+      pdfOpenParams += '&zoom=' + normalizedScaleValue;
+      var currentPageView = this.pages[pageNumber - 1];
+      var container = this.container;
+      var topLeft = currentPageView.getPagePoint(
+        (container.scrollLeft - firstPage.x),
+        (container.scrollTop - firstPage.y));
+      var intLeft = Math.round(topLeft[0]);
+      var intTop = Math.round(topLeft[1]);
+      pdfOpenParams += ',' + intLeft + ',' + intTop;
+
+      this.location = {
+        pageNumber: pageNumber,
+        scale: normalizedScaleValue,
+        top: intTop,
+        left: intLeft,
+        pdfOpenParams: pdfOpenParams
+      };
+    },
+
+    update: function () {
+      var visible = this._getVisiblePages();
+      var visiblePages = visible.views;
+      if (visiblePages.length === 0) {
+        return;
+      }
+
+      this.updateInProgress = true;
+
+      var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
+          2 * visiblePages.length + 1);
+      this.cache.resize(suggestedCacheSize);
+
+      this.renderingQueue.renderHighestPriority(visible);
+
+      var currentId = this.currentPageNumber;
+      var firstPage = visible.first;
+
+      for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
+           i < ii; ++i) {
+        var page = visiblePages[i];
+
+        if (page.percent < 100) {
+          break;
+        }
+        if (page.id === currentId) {
+          stillFullyVisible = true;
+          break;
+        }
+      }
+
+      if (!stillFullyVisible) {
+        currentId = visiblePages[0].id;
+      }
+
+      if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
+        this.currentPageNumber = currentId;
+      }
+
+      this._updateLocation(firstPage);
+
+      this.updateInProgress = false;
+
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('updateviewarea', true, true, window, 0);
+      this.container.dispatchEvent(event);
+    },
+
+    containsElement: function (element) {
+      return this.container.contains(element);
+    },
+
+    focus: function () {
+      this.container.focus();
+    },
+
+    blur: function () {
+      this.container.blur();
+    },
+
+    get isHorizontalScrollbarEnabled() {
+      return (this.presentationModeState === PresentationModeState.FULLSCREEN ?
+        false : (this.container.scrollWidth > this.container.clientWidth));
+    },
+
+    _getVisiblePages: function () {
+      if (this.presentationModeState !== PresentationModeState.FULLSCREEN) {
+        return getVisibleElements(this.container, this.pages, true);
+      } else {
+        // The algorithm in getVisibleElements doesn't work in all browsers and
+        // configurations when presentation mode is active.
+        var visible = [];
+        var currentPage = this.pages[this._currentPageNumber - 1];
+        visible.push({ id: currentPage.id, view: currentPage });
+        return { first: currentPage, last: currentPage, views: visible };
+      }
+    },
+
+    cleanup: function () {
+      for (var i = 0, ii = this.pages.length; i < ii; i++) {
+        if (this.pages[i] &&
+          this.pages[i].renderingState !== RenderingStates.FINISHED) {
+          this.pages[i].reset();
+        }
+      }
+    },
+
+    forceRendering: function (currentlyVisiblePages) {
+      var visiblePages = currentlyVisiblePages || this._getVisiblePages();
+      var pageView = this.renderingQueue.getHighestPriority(visiblePages,
+                                                            this.pages,
+                                                            this.scroll.down);
+      if (pageView) {
+        this.renderingQueue.renderView(pageView);
+        return true;
+      }
+      return false;
+    },
+
+    getPageTextContent: function (pageIndex) {
+      return this.pdfDocument.getPage(pageIndex + 1).then(function (page) {
+        return page.getTextContent();
+      });
+    },
+
+    /**
+     * @param textLayerDiv {HTMLDivElement}
+     * @param pageIndex {number}
+     * @param viewport {PageViewport}
+     * @returns {TextLayerBuilder}
+     */
+    createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
+      var isViewerInPresentationMode =
+        this.presentationModeState === PresentationModeState.FULLSCREEN;
+      return new TextLayerBuilder({
+        textLayerDiv: textLayerDiv,
+        pageIndex: pageIndex,
+        viewport: viewport,
+        lastScrollSource: this,
+        isViewerInPresentationMode: isViewerInPresentationMode,
+        findController: this.findController
+      });
+    },
+
+    setFindController: function (findController) {
+      this.findController = findController;
+    },
+  };
+
+  return PDFViewer;
+})();
+
+var SimpleLinkService = (function SimpleLinkServiceClosure() {
+  function SimpleLinkService(pdfViewer) {
+    this.pdfViewer = pdfViewer;
+  }
+  SimpleLinkService.prototype = {
+    /**
+     * @returns {number}
+     */
+    get page() {
+      return this.pdfViewer.currentPageNumber;
+    },
+    /**
+     * @param {number} value
+     */
+    set page(value) {
+      this.pdfViewer.currentPageNumber = value;
+    },
+    /**
+     * @param dest - The PDF destination object.
+     */
+    navigateTo: function (dest) {},
+    /**
+     * @param dest - The PDF destination object.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getDestinationHash: function (dest) {
+      return '#';
+    },
+    /**
+     * @param hash - The PDF parameters/hash.
+     * @returns {string} The hyperlink to the PDF object.
+     */
+    getAnchorUrl: function (hash) {
+      return '#';
+    },
+    /**
+     * @param {string} hash
+     */
+    setHash: function (hash) {},
+    /**
+     * @param {string} action
+     */
+    executeNamedAction: function (action) {},
+  };
+  return SimpleLinkService;
+})();
+
+/**
+ * PDFPage object source.
+ * @class
+ */
+var PDFPageSource = (function PDFPageSourceClosure() {
+  /**
+   * @constructs
+   * @param {PDFDocument} pdfDocument
+   * @param {number} pageNumber
+   * @constructor
+   */
+  function PDFPageSource(pdfDocument, pageNumber) {
+    this.pdfDocument = pdfDocument;
+    this.pageNumber = pageNumber;
+  }
+
+  PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
+    /**
+     * @returns {Promise<PDFPage>}
+     */
+    getPage: function () {
+      return this.pdfDocument.getPage(this.pageNumber);
+    }
+  };
+
+  return PDFPageSource;
+})();
+
+
+var PDFViewerApplication = {
+  initialBookmark: document.location.hash.substring(1),
+  initialized: false,
+  fellback: false,
+  pdfDocument: null,
+  sidebarOpen: false,
+  printing: false,
+  /** @type {PDFViewer} */
+  pdfViewer: null,
+  /** @type {PDFThumbnailViewer} */
+  pdfThumbnailViewer: null,
+  /** @type {PDFRenderingQueue} */
+  pdfRenderingQueue: null,
+  pageRotation: 0,
+  updateScaleControls: true,
+  isInitialViewSet: false,
+  animationStartedPromise: null,
+  mouseScrollTimeStamp: 0,
+  mouseScrollDelta: 0,
+  preferenceSidebarViewOnLoad: SidebarView.NONE,
+  preferencePdfBugEnabled: false,
+  isViewerEmbedded: (window.parent !== window),
+  url: '',
+
+  // called once when the document is loaded
+  initialize: function pdfViewInitialize() {
+    var pdfRenderingQueue = new PDFRenderingQueue();
+    pdfRenderingQueue.onIdle = this.cleanup.bind(this);
+    this.pdfRenderingQueue = pdfRenderingQueue;
+
+    var container = document.getElementById('viewerContainer');
+    var viewer = document.getElementById('viewer');
+    this.pdfViewer = new PDFViewer({
+      container: container,
+      viewer: viewer,
+      renderingQueue: pdfRenderingQueue,
+      linkService: this
+    });
+    pdfRenderingQueue.setViewer(this.pdfViewer);
+
+    var thumbnailContainer = document.getElementById('thumbnailView');
+    this.pdfThumbnailViewer = new PDFThumbnailViewer({
+      container: thumbnailContainer,
+      renderingQueue: pdfRenderingQueue,
+      linkService: this
+    });
+    pdfRenderingQueue.setThumbnailViewer(this.pdfThumbnailViewer);
+
+    Preferences.initialize();
+
+    this.findController = new PDFFindController({
+      pdfViewer: this.pdfViewer,
+      integratedFind: this.supportsIntegratedFind
+    });
+    this.pdfViewer.setFindController(this.findController);
+
+    this.findBar = new PDFFindBar({
+      bar: document.getElementById('findbar'),
+      toggleButton: document.getElementById('viewFind'),
+      findField: document.getElementById('findInput'),
+      highlightAllCheckbox: document.getElementById('findHighlightAll'),
+      caseSensitiveCheckbox: document.getElementById('findMatchCase'),
+      findMsg: document.getElementById('findMsg'),
+      findStatusIcon: document.getElementById('findStatusIcon'),
+      findPreviousButton: document.getElementById('findPrevious'),
+      findNextButton: document.getElementById('findNext'),
+      findController: this.findController
+    });
+
+    this.findController.setFindBar(this.findBar);
+
+    HandTool.initialize({
+      container: container,
+      toggleHandTool: document.getElementById('toggleHandTool')
+    });
+
+    SecondaryToolbar.initialize({
+      toolbar: document.getElementById('secondaryToolbar'),
+      presentationMode: PresentationMode,
+      toggleButton: document.getElementById('secondaryToolbarToggle'),
+      presentationModeButton:
+        document.getElementById('secondaryPresentationMode'),
+      openFile: document.getElementById('secondaryOpenFile'),
+      print: document.getElementById('secondaryPrint'),
+      download: document.getElementById('secondaryDownload'),
+      viewBookmark: document.getElementById('secondaryViewBookmark'),
+      firstPage: document.getElementById('firstPage'),
+      lastPage: document.getElementById('lastPage'),
+      pageRotateCw: document.getElementById('pageRotateCw'),
+      pageRotateCcw: document.getElementById('pageRotateCcw'),
+      documentProperties: DocumentProperties,
+      documentPropertiesButton: document.getElementById('documentProperties')
+    });
+
+    PresentationMode.initialize({
+      container: container,
+      secondaryToolbar: SecondaryToolbar,
+      firstPage: document.getElementById('contextFirstPage'),
+      lastPage: document.getElementById('contextLastPage'),
+      pageRotateCw: document.getElementById('contextPageRotateCw'),
+      pageRotateCcw: document.getElementById('contextPageRotateCcw')
+    });
+
+    PasswordPrompt.initialize({
+      overlayName: 'passwordOverlay',
+      passwordField: document.getElementById('password'),
+      passwordText: document.getElementById('passwordText'),
+      passwordSubmit: document.getElementById('passwordSubmit'),
+      passwordCancel: document.getElementById('passwordCancel')
+    });
+
+    DocumentProperties.initialize({
+      overlayName: 'documentPropertiesOverlay',
+      closeButton: document.getElementById('documentPropertiesClose'),
+      fileNameField: document.getElementById('fileNameField'),
+      fileSizeField: document.getElementById('fileSizeField'),
+      titleField: document.getElementById('titleField'),
+      authorField: document.getElementById('authorField'),
+      subjectField: document.getElementById('subjectField'),
+      keywordsField: document.getElementById('keywordsField'),
+      creationDateField: document.getElementById('creationDateField'),
+      modificationDateField: document.getElementById('modificationDateField'),
+      creatorField: document.getElementById('creatorField'),
+      producerField: document.getElementById('producerField'),
+      versionField: document.getElementById('versionField'),
+      pageCountField: document.getElementById('pageCountField')
+    });
+
+    var self = this;
+    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('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('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;
+      })
+      // TODO move more preferences and other async stuff here
+    ]).catch(function (reason) { });
+
+    return initializedPromise.then(function () {
+      PDFViewerApplication.initialized = true;
+    });
+  },
+
+  zoomIn: function pdfViewZoomIn(ticks) {
+    var 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 && newScale < MAX_SCALE);
+    this.setScale(newScale, true);
+  },
+
+  zoomOut: function pdfViewZoomOut(ticks) {
+    var 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 && newScale > MIN_SCALE);
+    this.setScale(newScale, true);
+  },
+
+  get currentScaleValue() {
+    return this.pdfViewer.currentScaleValue;
+  },
+
+  get pagesCount() {
+    return this.pdfDocument.numPages;
+  },
+
+  set page(val) {
+    this.pdfViewer.currentPageNumber = val;
+  },
+
+  get page() {
+    return this.pdfViewer.currentPageNumber;
+  },
+
+  get supportsPrinting() {
+    var canvas = document.createElement('canvas');
+    var value = 'mozPrintCallback' in canvas;
+    // shadow
+    Object.defineProperty(this, 'supportsPrinting', { value: value,
+                                                      enumerable: true,
+                                                      configurable: true,
+                                                      writable: false });
+    return value;
+  },
+
+  get supportsFullscreen() {
+    var doc = document.documentElement;
+    var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
+                  doc.webkitRequestFullScreen || doc.msRequestFullscreen;
+
+    if (document.fullscreenEnabled === false ||
+        document.mozFullScreenEnabled === false ||
+        document.webkitFullscreenEnabled === false ||
+        document.msFullscreenEnabled === false) {
+      support = false;
+    }
+
+    Object.defineProperty(this, 'supportsFullscreen', { value: support,
+                                                        enumerable: true,
+                                                        configurable: true,
+                                                        writable: false });
+    return support;
+  },
+
+  get supportsIntegratedFind() {
+    var support = false;
+    support = FirefoxCom.requestSync('supportsIntegratedFind');
+    Object.defineProperty(this, 'supportsIntegratedFind', { value: support,
+                                                            enumerable: true,
+                                                            configurable: true,
+                                                            writable: false });
+    return support;
+  },
+
+  get supportsDocumentFonts() {
+    var support = true;
+    support = FirefoxCom.requestSync('supportsDocumentFonts');
+    Object.defineProperty(this, 'supportsDocumentFonts', { value: support,
+                                                           enumerable: true,
+                                                           configurable: true,
+                                                           writable: false });
+    return support;
+  },
+
+  get supportsDocumentColors() {
+    var support = true;
+    support = FirefoxCom.requestSync('supportsDocumentColors');
+    Object.defineProperty(this, 'supportsDocumentColors', { value: support,
+                                                            enumerable: true,
+                                                            configurable: true,
+                                                            writable: false });
+    return support;
+  },
+
+  get loadingBar() {
+    var bar = new ProgressBar('#loadingBar', {});
+    Object.defineProperty(this, 'loadingBar', { value: bar,
+                                                enumerable: true,
+                                                configurable: true,
+                                                writable: false });
+    return bar;
+  },
+
+  initPassiveLoading: function pdfViewInitPassiveLoading() {
+    var pdfDataRangeTransportReadyResolve;
+    var pdfDataRangeTransportReady = new Promise(function (resolve) {
+      pdfDataRangeTransportReadyResolve = resolve;
+    });
+    var pdfDataRangeTransport = {
+      rangeListeners: [],
+      progressListeners: [],
+      progressiveReadListeners: [],
+      ready: pdfDataRangeTransportReady,
+
+      addRangeListener: function PdfDataRangeTransport_addRangeListener(
+                                   listener) {
+        this.rangeListeners.push(listener);
+      },
+
+      addProgressListener: function PdfDataRangeTransport_addProgressListener(
+                                      listener) {
+        this.progressListeners.push(listener);
+      },
+
+      addProgressiveReadListener:
+          function PdfDataRangeTransport_addProgressiveReadListener(listener) {
+        this.progressiveReadListeners.push(listener);
+      },
+
+      onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
+        var listeners = this.rangeListeners;
+        for (var i = 0, n = listeners.length; i < n; ++i) {
+          listeners[i](begin, chunk);
+        }
+      },
+
+      onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
+        this.ready.then(function () {
+          var listeners = this.progressListeners;
+          for (var i = 0, n = listeners.length; i < n; ++i) {
+            listeners[i](loaded);
+          }
+        }.bind(this));
+      },
+
+      onDataProgressiveRead:
+          function PdfDataRangeTransport_onDataProgress(chunk) {
+        this.ready.then(function () {
+          var listeners = this.progressiveReadListeners;
+          for (var i = 0, n = listeners.length; i < n; ++i) {
+            listeners[i](chunk);
+          }
+        }.bind(this));
+      },
+
+      transportReady: function PdfDataRangeTransport_transportReady() {
+        pdfDataRangeTransportReadyResolve();
+      },
+
+      requestDataRange: function PdfDataRangeTransport_requestDataRange(
+                                  begin, end) {
+        FirefoxCom.request('requestDataRange', { begin: begin, end: end });
+      }
+    };
+
+    window.addEventListener('message', function windowMessage(e) {
+      if (e.source !== null) {
+        // The message MUST originate from Chrome code.
+        console.warn('Rejected untrusted message from ' + e.origin);
+        return;
+      }
+      var args = e.data;
+
+      if (typeof args !== 'object' || !('pdfjsLoadAction' in args)) {
+        return;
+      }
+      switch (args.pdfjsLoadAction) {
+        case 'supportsRangedLoading':
+          PDFViewerApplication.open(args.pdfUrl, 0, undefined,
+                                    pdfDataRangeTransport, {
+            length: args.length,
+            initialData: args.data
+          });
+          break;
+        case 'range':
+          pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
+          break;
+        case 'rangeProgress':
+          pdfDataRangeTransport.onDataProgress(args.loaded);
+          break;
+        case 'progressiveRead':
+          pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
+          break;
+        case 'progress':
+          PDFViewerApplication.progress(args.loaded / args.total);
+          break;
+        case 'complete':
+          if (!args.data) {
+            PDFViewerApplication.error(mozL10n.get('loading_error', null,
+              'An error occurred while loading the PDF.'), e);
+            break;
+          }
+          PDFViewerApplication.open(args.data, 0);
+          break;
+      }
+    });
+    FirefoxCom.requestSync('initPassiveLoading', null);
+  },
+
+  setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
+    this.url = url;
+    try {
+      this.setTitle(decodeURIComponent(getFileName(url)) || url);
+    } catch (e) {
+      // decodeURIComponent may throw URIError,
+      // fall back to using the unprocessed url in that case
+      this.setTitle(url);
+    }
+  },
+
+  setTitle: function pdfViewSetTitle(title) {
+    document.title = title;
+  },
+
+  close: function pdfViewClose() {
+    var errorWrapper = document.getElementById('errorWrapper');
+    errorWrapper.setAttribute('hidden', 'true');
+
+    if (!this.pdfDocument) {
+      return;
+    }
+
+    this.pdfDocument.destroy();
+    this.pdfDocument = null;
+
+    this.pdfThumbnailViewer.setDocument(null);
+    this.pdfViewer.setDocument(null);
+
+    if (typeof PDFBug !== 'undefined') {
+      PDFBug.cleanup();
+    }
+  },
+
+  // TODO(mack): This function signature should really be pdfViewOpen(url, args)
+  open: function pdfViewOpen(file, scale, password,
+                             pdfDataRangeTransport, args) {
+    if (this.pdfDocument) {
+      // Reload the preferences if a document was previously opened.
+      Preferences.reload();
+    }
+    this.close();
+
+    var parameters = {password: password};
+    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;
+    }
+    if (args) {
+      for (var prop in args) {
+        parameters[prop] = args[prop];
+      }
+    }
+
+    var self = this;
+    self.loading = true;
+    self.downloadComplete = false;
+
+    var passwordNeeded = function passwordNeeded(updatePassword, reason) {
+      PasswordPrompt.updatePassword = updatePassword;
+      PasswordPrompt.reason = reason;
+      PasswordPrompt.open();
+    };
+
+    function getDocumentProgress(progressData) {
+      self.progress(progressData.loaded / progressData.total);
+    }
+
+    PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
+                      getDocumentProgress).then(
+      function getDocumentCallback(pdfDocument) {
+        self.load(pdfDocument, scale);
+        self.loading = false;
+      },
+      function getDocumentError(exception) {
+        var message = exception && exception.message;
+        var loadingErrorMessage = mozL10n.get('loading_error', null,
+          'An error occurred while loading the PDF.');
+
+        if (exception instanceof PDFJS.InvalidPDFException) {
+          // change error message also for other builds
+          loadingErrorMessage = mozL10n.get('invalid_file_error', null,
+                                            'Invalid or corrupted PDF file.');
+        } else if (exception instanceof PDFJS.MissingPDFException) {
+          // special message for missing PDF's
+          loadingErrorMessage = mozL10n.get('missing_file_error', null,
+                                            'Missing PDF file.');
+        } else if (exception instanceof PDFJS.UnexpectedResponseException) {
+          loadingErrorMessage = mozL10n.get('unexpected_response_error', null,
+                                            'Unexpected server response.');
+        }
+
+        var moreInfo = {
+          message: message
+        };
+        self.error(loadingErrorMessage, moreInfo);
+        self.loading = false;
+      }
+    );
+
+    if (args && args.length) {
+      DocumentProperties.setFileSize(args.length);
+    }
+  },
+
+  download: function pdfViewDownload() {
+    function downloadByUrl() {
+      downloadManager.downloadUrl(url, filename);
+    }
+
+    var url = this.url.split('#')[0];
+    var filename = getPDFFileNameFromURL(url);
+    var downloadManager = new DownloadManager();
+    downloadManager.onerror = function (err) {
+      // This error won't really be helpful because it's likely the
+      // fallback won't work either (or is already open).
+      PDFViewerApplication.error('PDF failed to download.');
+    };
+
+    if (!this.pdfDocument) { // the PDF is not ready yet
+      downloadByUrl();
+      return;
+    }
+
+    if (!this.downloadComplete) { // the PDF is still downloading
+      downloadByUrl();
+      return;
+    }
+
+    this.pdfDocument.getData().then(
+      function getDataSuccess(data) {
+        var blob = PDFJS.createBlob(data, 'application/pdf');
+        downloadManager.download(blob, url, filename);
+      },
+      downloadByUrl // Error occurred try downloading with just the url.
+    ).then(null, downloadByUrl);
+  },
+
+  fallback: function pdfViewFallback(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;
+    var url = this.url.split('#')[0];
+    FirefoxCom.request('fallback', { featureId: featureId, url: url },
+      function response(download) {
+        if (!download) {
+          return;
+        }
+        PDFViewerApplication.download();
+      });
+  },
+
+  navigateTo: function pdfViewNavigateTo(dest) {
+    var destString = '';
+    var self = this;
+
+    var goToDestination = function(destRef) {
+      self.pendingRefStr = null;
+      // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+      var pageNumber = destRef instanceof Object ?
+        self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+        (destRef + 1);
+      if (pageNumber) {
+        if (pageNumber > self.pagesCount) {
+          pageNumber = self.pagesCount;
+        }
+        self.pdfViewer.scrollPageIntoView(pageNumber, dest);
+
+        // Update the browsing history.
+        PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
+      } else {
+        self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+          var pageNum = pageIndex + 1;
+          self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
+          goToDestination(destRef);
+        });
+      }
+    };
+
+    var destinationPromise;
+    if (typeof dest === 'string') {
+      destString = dest;
+      destinationPromise = this.pdfDocument.getDestination(dest);
+    } else {
+      destinationPromise = Promise.resolve(dest);
+    }
+    destinationPromise.then(function(destination) {
+      dest = destination;
+      if (!(destination instanceof Array)) {
+        return; // invalid destination
+      }
+      goToDestination(destination[0]);
+    });
+  },
+
+  executeNamedAction: function pdfViewExecuteNamedAction(action) {
+    // See PDF reference, table 8.45 - Named action
+    switch (action) {
+      case 'GoToPage':
+        document.getElementById('pageNumber').focus();
+        break;
+
+      case 'GoBack':
+        PDFHistory.back();
+        break;
+
+      case 'GoForward':
+        PDFHistory.forward();
+        break;
+
+      case 'Find':
+        if (!this.supportsIntegratedFind) {
+          this.findBar.toggle();
+        }
+        break;
+
+      case 'NextPage':
+        this.page++;
+        break;
+
+      case 'PrevPage':
+        this.page--;
+        break;
+
+      case 'LastPage':
+        this.page = this.pagesCount;
+        break;
+
+      case 'FirstPage':
+        this.page = 1;
+        break;
+
+      default:
+        break; // No action according to spec
+    }
+  },
+
+  getDestinationHash: function pdfViewGetDestinationHash(dest) {
+    if (typeof dest === 'string') {
+      return this.getAnchorUrl('#' + escape(dest));
+    }
+    if (dest instanceof Array) {
+      var destRef = dest[0]; // see navigateTo method for dest format
+      var pageNumber = destRef instanceof Object ?
+        this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+        (destRef + 1);
+      if (pageNumber) {
+        var pdfOpenParams = this.getAnchorUrl('#page=' + pageNumber);
+        var destKind = dest[1];
+        if (typeof destKind === 'object' && 'name' in destKind &&
+            destKind.name === 'XYZ') {
+          var scale = (dest[4] || this.currentScaleValue);
+          var scaleNumber = parseFloat(scale);
+          if (scaleNumber) {
+            scale = scaleNumber * 100;
+          }
+          pdfOpenParams += '&zoom=' + scale;
+          if (dest[2] || dest[3]) {
+            pdfOpenParams += ',' + (dest[2] || 0) + ',' + (dest[3] || 0);
+          }
+        }
+        return pdfOpenParams;
+      }
+    }
+    return '';
+  },
+
+  /**
+   * Prefix the full url on anchor links to make sure that links are resolved
+   * relative to the current URL instead of the one defined in <base href>.
+   * @param {String} anchor The anchor hash, including the #.
+   */
+  getAnchorUrl: function getAnchorUrl(anchor) {
+    return this.url.split('#')[0] + anchor;
+  },
+
+  /**
+   * Show the error box.
+   * @param {String} message A message that is human readable.
+   * @param {Object} moreInfo (optional) Further information about the error
+   *                            that is more technical.  Should have a 'message'
+   *                            and optionally a 'stack' property.
+   */
+  error: function pdfViewError(message, moreInfo) {
+    var moreInfoText = mozL10n.get('error_version_info',
+      {version: PDFJS.version || '?', build: PDFJS.build || '?'},
+      'PDF.js v{{version}} (build: {{build}})') + '\n';
+    if (moreInfo) {
+      moreInfoText +=
+        mozL10n.get('error_message', {message: moreInfo.message},
+        'Message: {{message}}');
+      if (moreInfo.stack) {
+        moreInfoText += '\n' +
+          mozL10n.get('error_stack', {stack: moreInfo.stack},
+          'Stack: {{stack}}');
+      } else {
+        if (moreInfo.filename) {
+          moreInfoText += '\n' +
+            mozL10n.get('error_file', {file: moreInfo.filename},
+            'File: {{file}}');
+        }
+        if (moreInfo.lineNumber) {
+          moreInfoText += '\n' +
+            mozL10n.get('error_line', {line: moreInfo.lineNumber},
+            'Line: {{line}}');
+        }
+      }
+    }
+
+    console.error(message + '\n' + moreInfoText);
+    this.fallback();
+  },
+
+  progress: function pdfViewProgress(level) {
+    var 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;
+    }
+  },
+
+  load: function pdfViewLoad(pdfDocument, scale) {
+    var self = this;
+    scale = scale || UNKNOWN_SCALE;
+
+    this.findController.reset();
+
+    this.pdfDocument = pdfDocument;
+
+    DocumentProperties.url = this.url;
+    DocumentProperties.pdfDocument = pdfDocument;
+    DocumentProperties.resolveDataAvailable();
+
+    var downloadedPromise = pdfDocument.getDownloadInfo().then(function() {
+      self.downloadComplete = true;
+      self.loadingBar.hide();
+      var outerContainer = document.getElementById('outerContainer');
+      outerContainer.classList.remove('loadingInProgress');
+    });
+
+    var pagesCount = pdfDocument.numPages;
+    document.getElementById('numPages').textContent =
+      mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
+    document.getElementById('pageNumber').max = pagesCount;
+
+    var id = this.documentFingerprint = pdfDocument.fingerprint;
+    var store = this.store = new ViewHistory(id);
+
+    var pdfViewer = this.pdfViewer;
+    pdfViewer.currentScale = scale;
+    pdfViewer.setDocument(pdfDocument);
+    var firstPagePromise = pdfViewer.firstPagePromise;
+    var pagesPromise = pdfViewer.pagesPromise;
+    var onePageRendered = pdfViewer.onePageRendered;
+
+    this.pageRotation = 0;
+    this.isInitialViewSet = false;
+    this.pagesRefMap = pdfViewer.pagesRefMap;
+
+    this.pdfThumbnailViewer.setDocument(pdfDocument);
+
+    firstPagePromise.then(function(pdfPage) {
+      downloadedPromise.then(function () {
+        var event = document.createEvent('CustomEvent');
+        event.initCustomEvent('documentload', true, true, {});
+        window.dispatchEvent(event);
+      });
+
+      self.loadingBar.setWidth(document.getElementById('viewer'));
+
+      self.findController.resolveFirstPage();
+
+      if (!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.
+        PDFHistory.initialize(self.documentFingerprint, self);
+      }
+    });
+
+    // Fetch the necessary preference values.
+    var showPreviousViewOnLoad;
+    var showPreviousViewOnLoadPromise =
+      Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
+        showPreviousViewOnLoad = prefValue;
+      });
+    var defaultZoomValue;
+    var defaultZoomValuePromise =
+      Preferences.get('defaultZoomValue').then(function (prefValue) {
+        defaultZoomValue = prefValue;
+      });
+
+    var storePromise = store.initializedPromise;
+    Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
+                 defaultZoomValuePromise]).then(function resolved() {
+      var storedHash = null;
+      if (showPreviousViewOnLoad && store.get('exists', false)) {
+        var pageNum = store.get('page', '1');
+        var zoom = defaultZoomValue ||
+                   store.get('zoom', self.pdfViewer.currentScale);
+        var left = store.get('scrollLeft', '0');
+        var top = store.get('scrollTop', '0');
+
+        storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
+                     left + ',' + top;
+      } else if (defaultZoomValue) {
+        storedHash = 'page=1&zoom=' + defaultZoomValue;
+      }
+      self.setInitialView(storedHash, scale);
+
+      // Make all navigation keys work on document load,
+      // unless the viewer is embedded in a web page.
+      if (!self.isViewerEmbedded) {
+        self.pdfViewer.focus();
+        self.pdfViewer.blur();
+      }
+    }, function rejected(reason) {
+      console.error(reason);
+
+      firstPagePromise.then(function () {
+        self.setInitialView(null, scale);
+      });
+    });
+
+    pagesPromise.then(function() {
+      if (self.supportsPrinting) {
+        pdfDocument.getJavaScript().then(function(javaScript) {
+          if (javaScript.length) {
+            console.warn('Warning: JavaScript is not supported');
+            self.fallback(PDFJS.UNSUPPORTED_FEATURES.javaScript);
+          }
+          // Hack to support auto printing.
+          var regex = /\bprint\s*\(/g;
+          for (var i = 0, ii = javaScript.length; i < ii; i++) {
+            var js = javaScript[i];
+            if (js && regex.test(js)) {
+              setTimeout(function() {
+                window.print();
+              });
+              return;
+            }
+          }
+        });
+      }
+    });
+
+    // outline depends on pagesRefMap
+    var promises = [pagesPromise, this.animationStartedPromise];
+    Promise.all(promises).then(function() {
+      pdfDocument.getOutline().then(function(outline) {
+        var outlineView = document.getElementById('outlineView');
+        self.outline = new DocumentOutlineView({
+          outline: outline,
+          outlineView: outlineView,
+          linkService: self
+        });
+        document.getElementById('viewOutline').disabled = !outline;
+
+        if (!outline && !outlineView.classList.contains('hidden')) {
+          self.switchSidebarView('thumbs');
+        }
+        if (outline &&
+            self.preferenceSidebarViewOnLoad === SidebarView.OUTLINE) {
+          self.switchSidebarView('outline', true);
+        }
+      });
+      pdfDocument.getAttachments().then(function(attachments) {
+        var attachmentsView = document.getElementById('attachmentsView');
+        self.attachments = new DocumentAttachmentsView({
+          attachments: attachments,
+          attachmentsView: attachmentsView
+        });
+        document.getElementById('viewAttachments').disabled = !attachments;
+
+        if (!attachments && !attachmentsView.classList.contains('hidden')) {
+          self.switchSidebarView('thumbs');
+        }
+        if (attachments &&
+            self.preferenceSidebarViewOnLoad === SidebarView.ATTACHMENTS) {
+          self.switchSidebarView('attachments', true);
+        }
+      });
+    });
+
+    if (self.preferenceSidebarViewOnLoad === SidebarView.THUMBS) {
+      Promise.all([firstPagePromise, onePageRendered]).then(function () {
+        self.switchSidebarView('thumbs', true);
+      });
+    }
+
+    pdfDocument.getMetadata().then(function(data) {
+      var info = data.info, metadata = data.metadata;
+      self.documentInfo = info;
+      self.metadata = metadata;
+
+      // Provides some basic debug information
+      console.log('PDF ' + pdfDocument.fingerprint + ' [' +
+                  info.PDFFormatVersion + ' ' + (info.Producer || '-').trim() +
+                  ' / ' + (info.Creator || '-').trim() + ']' +
+                  ' (PDF.js: ' + (PDFJS.version || '-') +
+                  (!PDFJS.disableWebGL ? ' [WebGL]' : '') + ')');
+
+      var pdfTitle;
+      if (metadata && metadata.has('dc:title')) {
+        var title = metadata.get('dc:title');
+        // Ghostscript sometimes return 'Untitled', sets the title to 'Untitled'
+        if (title !== 'Untitled') {
+          pdfTitle = title;
+        }
+      }
+
+      if (!pdfTitle && info && info['Title']) {
+        pdfTitle = info['Title'];
+      }
+
+      if (pdfTitle) {
+        self.setTitle(pdfTitle + ' - ' + document.title);
+      }
+
+      if (info.IsAcroFormPresent) {
+        console.warn('Warning: AcroForm/XFA is not supported');
+        self.fallback(PDFJS.UNSUPPORTED_FEATURES.forms);
+      }
+
+      var versionId = String(info.PDFFormatVersion).slice(-1) | 0;
+      var generatorId = 0;
+      var KNOWN_GENERATORS = ["acrobat distiller", "acrobat pdfwritter",
+       "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"];
+      var generatorId = 0;
+      if (info.Producer) {
+        KNOWN_GENERATORS.some(function (generator, s, i) {
+          if (generator.indexOf(s) < 0) {
+            return false;
+          }
+          generatorId = i + 1;
+          return true;
+        }.bind(null, info.Producer.toLowerCase()));
+      }
+      var formType = !info.IsAcroFormPresent ? null : info.IsXFAPresent ?
+                     'xfa' : 'acroform';
+      FirefoxCom.request('reportTelemetry', JSON.stringify({
+        type: 'documentInfo',
+        version: versionId,
+        generator: generatorId,
+        formType: formType
+      }));
+    });
+  },
+
+  setInitialView: function pdfViewSetInitialView(storedHash, scale) {
+    this.isInitialViewSet = true;
+
+    // When opening a new file (when one is already loaded in the viewer):
+    // Reset 'currentPageNumber', since otherwise the page's scale will be wrong
+    // if 'currentPageNumber' is larger than the number of pages in the file.
+    document.getElementById('pageNumber').value =
+      this.pdfViewer.currentPageNumber = 1;
+
+    if (PDFHistory.initialDestination) {
+      this.navigateTo(PDFHistory.initialDestination);
+      PDFHistory.initialDestination = null;
+    } else if (this.initialBookmark) {
+      this.setHash(this.initialBookmark);
+      PDFHistory.push({ hash: this.initialBookmark }, !!this.initialBookmark);
+      this.initialBookmark = null;
+    } else if (storedHash) {
+      this.setHash(storedHash);
+    } else if (scale) {
+      this.setScale(scale, true);
+      this.page = 1;
+    }
+
+    if (this.pdfViewer.currentScale === UNKNOWN_SCALE) {
+      // Scale was not initialized: invalid bookmark or scale was not specified.
+      // Setting the default one.
+      this.setScale(DEFAULT_SCALE, true);
+    }
+  },
+
+  cleanup: function pdfViewCleanup() {
+    this.pdfViewer.cleanup();
+    this.pdfThumbnailViewer.cleanup();
+    this.pdfDocument.cleanup();
+  },
+
+  forceRendering: function pdfViewForceRendering() {
+    this.pdfRenderingQueue.printing = this.printing;
+    this.pdfRenderingQueue.isThumbnailViewEnabled = this.sidebarOpen;
+    this.pdfRenderingQueue.renderHighestPriority();
+  },
+
+  setHash: function pdfViewSetHash(hash) {
+    if (!this.isInitialViewSet) {
+      this.initialBookmark = hash;
+      return;
+    }
+
+    var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
+      'FitV','FitBV','FitR'];
+
+    if (!hash) {
+      return;
+    }
+
+    if (hash.indexOf('=') >= 0) {
+      var params = this.parseQueryString(hash);
+      // borrowing syntax from "Parameters for Opening PDF Files"
+      if ('nameddest' in params) {
+        PDFHistory.updateNextHashParam(params.nameddest);
+        this.navigateTo(params.nameddest);
+        return;
+      }
+      var pageNumber, dest;
+      if ('page' in params) {
+        pageNumber = (params.page | 0) || 1;
+      }
+      if ('zoom' in params) {
+        var zoomArgs = params.zoom.split(','); // scale,left,top
+        // building destination array
+
+        // If the zoom value, it has to get divided by 100. If it is a string,
+        // it should stay as it is.
+        var zoomArg = zoomArgs[0];
+        var zoomArgNumber = parseFloat(zoomArg);
+        var destName = 'XYZ';
+        if (zoomArgNumber) {
+          zoomArg = zoomArgNumber / 100;
+        } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
+          destName = zoomArg;
+        }
+        dest = [null, { name: destName },
+                zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+                zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+                zoomArg];
+      }
+      if (dest) {
+        this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
+      } else if (pageNumber) {
+        this.page = pageNumber; // simple page
+      }
+      if ('pagemode' in params) {
+        if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
+            params.pagemode === 'attachments') {
+          this.switchSidebarView((params.pagemode === 'bookmarks' ?
+                                  'outline' : params.pagemode), true);
+        } else if (params.pagemode === 'none' && this.sidebarOpen) {
+          document.getElementById('sidebarToggle').click();
+        }
+      }
+    } else if (/^\d+$/.test(hash)) { // page number
+      this.page = hash;
+    } else { // named destination
+      PDFHistory.updateNextHashParam(unescape(hash));
+      this.navigateTo(unescape(hash));
+    }
+  },
+
+  switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
+    if (openSidebar && !this.sidebarOpen) {
+      document.getElementById('sidebarToggle').click();
+    }
+    var thumbsView = document.getElementById('thumbnailView');
+    var outlineView = document.getElementById('outlineView');
+    var attachmentsView = document.getElementById('attachmentsView');
+
+    var thumbsButton = document.getElementById('viewThumbnail');
+    var outlineButton = document.getElementById('viewOutline');
+    var attachmentsButton = document.getElementById('viewAttachments');
+
+    switch (view) {
+      case 'thumbs':
+        var wasAnotherViewVisible = thumbsView.classList.contains('hidden');
+
+        thumbsButton.classList.add('toggled');
+        outlineButton.classList.remove('toggled');
+        attachmentsButton.classList.remove('toggled');
+        thumbsView.classList.remove('hidden');
+        outlineView.classList.add('hidden');
+        attachmentsView.classList.add('hidden');
+
+        this.forceRendering();
+
+        if (wasAnotherViewVisible) {
+          this.pdfThumbnailViewer.ensureThumbnailVisible(this.page);
+        }
+        break;
+
+      case 'outline':
+        thumbsButton.classList.remove('toggled');
+        outlineButton.classList.add('toggled');
+        attachmentsButton.classList.remove('toggled');
+        thumbsView.classList.add('hidden');
+        outlineView.classList.remove('hidden');
+        attachmentsView.classList.add('hidden');
+
+        if (outlineButton.getAttribute('disabled')) {
+          return;
+        }
+        break;
+
+      case 'attachments':
+        thumbsButton.classList.remove('toggled');
+        outlineButton.classList.remove('toggled');
+        attachmentsButton.classList.add('toggled');
+        thumbsView.classList.add('hidden');
+        outlineView.classList.add('hidden');
+        attachmentsView.classList.remove('hidden');
+
+        if (attachmentsButton.getAttribute('disabled')) {
+          return;
+        }
+        break;
+    }
+  },
+
+  // Helper function to parse query string (e.g. ?param1=value&parm2=...).
+  parseQueryString: function pdfViewParseQueryString(query) {
+    var parts = query.split('&');
+    var params = {};
+    for (var i = 0, ii = parts.length; i < ii; ++i) {
+      var param = parts[i].split('=');
+      var key = param[0].toLowerCase();
+      var value = param.length > 1 ? param[1] : null;
+      params[decodeURIComponent(key)] = decodeURIComponent(value);
+    }
+    return params;
+  },
+
+  beforePrint: function pdfViewSetupBeforePrint() {
+    if (!this.supportsPrinting) {
+      var printMessage = mozL10n.get('printing_not_supported', null,
+          'Warning: Printing is not fully supported by this browser.');
+      this.error(printMessage);
+      return;
+    }
+
+    var alertNotReady = false;
+    var i, ii;
+    if (!this.pagesCount) {
+      alertNotReady = true;
+    } else {
+      for (i = 0, ii = this.pagesCount; i < ii; ++i) {
+        if (!this.pdfViewer.getPageView(i).pdfPage) {
+          alertNotReady = true;
+          break;
+        }
+      }
+    }
+    if (alertNotReady) {
+      var notReadyMessage = mozL10n.get('printing_not_ready', null,
+          'Warning: The PDF is not fully loaded for printing.');
+      window.alert(notReadyMessage);
+      return;
+    }
+
+    this.printing = true;
+    this.forceRendering();
+
+    var body = document.querySelector('body');
+    body.setAttribute('data-mozPrintCallback', true);
+    for (i = 0, ii = this.pagesCount; i < ii; ++i) {
+      this.pdfViewer.getPageView(i).beforePrint();
+    }
+
+      FirefoxCom.request('reportTelemetry', JSON.stringify({
+        type: 'print'
+      }));
+  },
+
+  afterPrint: function pdfViewSetupAfterPrint() {
+    var div = document.getElementById('printContainer');
+    while (div.hasChildNodes()) {
+      div.removeChild(div.lastChild);
+    }
+
+    this.printing = false;
+    this.forceRendering();
+  },
+
+  setScale: function (value, resetAutoSettings) {
+    this.updateScaleControls = !!resetAutoSettings;
+    this.pdfViewer.currentScaleValue = value;
+    this.updateScaleControls = true;
+  },
+
+  rotatePages: function pdfViewRotatePages(delta) {
+    var pageNumber = this.page;
+
+    this.pageRotation = (this.pageRotation + 360 + delta) % 360;
+    this.pdfViewer.pagesRotation = this.pageRotation;
+    this.pdfThumbnailViewer.pagesRotation = this.pageRotation;
+
+    this.forceRendering();
+
+    this.pdfViewer.scrollPageIntoView(pageNumber);
+  },
+
+  /**
+   * This function flips the page in presentation mode if the user scrolls up
+   * or down with large enough motion and prevents page flipping too often.
+   *
+   * @this {PDFView}
+   * @param {number} mouseScrollDelta The delta value from the mouse event.
+   */
+  mouseScroll: function pdfViewMouseScroll(mouseScrollDelta) {
+    var MOUSE_SCROLL_COOLDOWN_TIME = 50;
+
+    var currentTime = (new Date()).getTime();
+    var storedTime = this.mouseScrollTimeStamp;
+
+    // In case one page has already been flipped there is a cooldown time
+    // which has to expire before next page can be scrolled on to.
+    if (currentTime > storedTime &&
+        currentTime - storedTime < MOUSE_SCROLL_COOLDOWN_TIME) {
+      return;
+    }
+
+    // In case the user decides to scroll to the opposite direction than before
+    // clear the accumulated delta.
+    if ((this.mouseScrollDelta > 0 && mouseScrollDelta < 0) ||
+        (this.mouseScrollDelta < 0 && mouseScrollDelta > 0)) {
+      this.clearMouseScrollState();
+    }
+
+    this.mouseScrollDelta += mouseScrollDelta;
+
+    var PAGE_FLIP_THRESHOLD = 120;
+    if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
+
+      var PageFlipDirection = {
+        UP: -1,
+        DOWN: 1
+      };
+
+      // In presentation mode scroll one page at a time.
+      var pageFlipDirection = (this.mouseScrollDelta > 0) ?
+                                PageFlipDirection.UP :
+                                PageFlipDirection.DOWN;
+      this.clearMouseScrollState();
+      var currentPage = this.page;
+
+      // In case we are already on the first or the last page there is no need
+      // to do anything.
+      if ((currentPage === 1 && pageFlipDirection === PageFlipDirection.UP) ||
+          (currentPage === this.pagesCount &&
+           pageFlipDirection === PageFlipDirection.DOWN)) {
+        return;
+      }
+
+      this.page += pageFlipDirection;
+      this.mouseScrollTimeStamp = currentTime;
+    }
+  },
+
+  /**
+   * This function clears the member attributes used with mouse scrolling in
+   * presentation mode.
+   *
+   * @this {PDFView}
+   */
+  clearMouseScrollState: function pdfViewClearMouseScrollState() {
+    this.mouseScrollTimeStamp = 0;
+    this.mouseScrollDelta = 0;
+  }
+};
+
+
+var THUMBNAIL_SCROLL_MARGIN = -19;
+
+/**
+ * @constructor
+ * @param container
+ * @param id
+ * @param defaultViewport
+ * @param linkService
+ * @param renderingQueue
+ * @param pageSource
+ *
+ * @implements {IRenderableView}
+ */
+var ThumbnailView = function thumbnailView(container, id, defaultViewport,
+                                           linkService, renderingQueue,
+                                           pageSource) {
+  var anchor = document.createElement('a');
+  anchor.href = linkService.getAnchorUrl('#page=' + id);
+  anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
+  anchor.onclick = function stopNavigation() {
+    linkService.page = id;
+    return false;
+  };
+
+  this.pdfPage = undefined;
+  this.viewport = defaultViewport;
+  this.pdfPageRotate = defaultViewport.rotation;
+
+  this.rotation = 0;
+  this.pageWidth = this.viewport.width;
+  this.pageHeight = this.viewport.height;
+  this.pageRatio = this.pageWidth / this.pageHeight;
+  this.id = id;
+  this.renderingId = 'thumbnail' + id;
+
+  this.canvasWidth = 98;
+  this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
+  this.scale = (this.canvasWidth / this.pageWidth);
+
+  var div = this.el = document.createElement('div');
+  div.id = 'thumbnailContainer' + id;
+  div.className = 'thumbnail';
+
+  if (id === 1) {
+    // Highlight the thumbnail of the first page when no page number is
+    // specified (or exists in cache) when the document is loaded.
+    div.classList.add('selected');
+  }
+
+  var ring = document.createElement('div');
+  ring.className = 'thumbnailSelectionRing';
+  ring.style.width = this.canvasWidth + 'px';
+  ring.style.height = this.canvasHeight + 'px';
+
+  div.appendChild(ring);
+  anchor.appendChild(div);
+  container.appendChild(anchor);
+
+  this.hasImage = false;
+  this.renderingState = RenderingStates.INITIAL;
+  this.renderingQueue = renderingQueue;
+  this.pageSource = pageSource;
+
+  this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
+    this.pdfPage = pdfPage;
+    this.pdfPageRotate = pdfPage.rotate;
+    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+    this.viewport = pdfPage.getViewport(1, totalRotation);
+    this.update();
+  };
+
+  this.update = function thumbnailViewUpdate(rotation) {
+    if (rotation !== undefined) {
+      this.rotation = rotation;
+    }
+    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+    this.viewport = this.viewport.clone({
+      scale: 1,
+      rotation: totalRotation
+    });
+    this.pageWidth = this.viewport.width;
+    this.pageHeight = this.viewport.height;
+    this.pageRatio = this.pageWidth / this.pageHeight;
+
+    this.canvasHeight = this.canvasWidth / this.pageWidth * this.pageHeight;
+    this.scale = (this.canvasWidth / this.pageWidth);
+
+    div.removeAttribute('data-loaded');
+    ring.textContent = '';
+    ring.style.width = this.canvasWidth + 'px';
+    ring.style.height = this.canvasHeight + 'px';
+
+    this.hasImage = false;
+    this.renderingState = RenderingStates.INITIAL;
+    this.resume = null;
+  };
+
+  this.getPageDrawContext = function thumbnailViewGetPageDrawContext() {
+    var canvas = document.createElement('canvas');
+    canvas.id = 'thumbnail' + id;
+
+    canvas.width = this.canvasWidth;
+    canvas.height = this.canvasHeight;
+    canvas.className = 'thumbnailImage';
+    canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
+      {page: id}, 'Thumbnail of Page {{page}}'));
+
+    div.setAttribute('data-loaded', true);
+
+    ring.appendChild(canvas);
+
+    var ctx = canvas.getContext('2d');
+    ctx.save();
+    ctx.fillStyle = 'rgb(255, 255, 255)';
+    ctx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
+    ctx.restore();
+    return ctx;
+  };
+
+  this.drawingRequired = function thumbnailViewDrawingRequired() {
+    return !this.hasImage;
+  };
+
+  this.draw = function thumbnailViewDraw(callback) {
+    if (!this.pdfPage) {
+      var promise = this.pageSource.getPage(this.id);
+      promise.then(function(pdfPage) {
+        this.setPdfPage(pdfPage);
+        this.draw(callback);
+      }.bind(this));
+      return;
+    }
+
+    if (this.renderingState !== RenderingStates.INITIAL) {
+      console.error('Must be in new state before drawing');
+    }
+
+    this.renderingState = RenderingStates.RUNNING;
+    if (this.hasImage) {
+      callback();
+      return;
+    }
+
+    var self = this;
+    var ctx = this.getPageDrawContext();
+    var drawViewport = this.viewport.clone({ scale: this.scale });
+    var renderContext = {
+      canvasContext: ctx,
+      viewport: drawViewport,
+      continueCallback: function(cont) {
+        if (!self.renderingQueue.isHighestPriority(self)) {
+          self.renderingState = RenderingStates.PAUSED;
+          self.resume = function() {
+            self.renderingState = RenderingStates.RUNNING;
+            cont();
+          };
+          return;
+        }
+        cont();
+      }
+    };
+    this.pdfPage.render(renderContext).promise.then(
+      function pdfPageRenderCallback() {
+        self.renderingState = RenderingStates.FINISHED;
+        callback();
+      },
+      function pdfPageRenderError(error) {
+        self.renderingState = RenderingStates.FINISHED;
+        callback();
+      }
+    );
+    this.hasImage = true;
+  };
+
+  function getTempCanvas(width, height) {
+    var tempCanvas = ThumbnailView.tempImageCache;
+    if (!tempCanvas) {
+      tempCanvas = document.createElement('canvas');
+      ThumbnailView.tempImageCache = tempCanvas;
+    }
+    tempCanvas.width = width;
+    tempCanvas.height = height;
+    return tempCanvas;
+  }
+
+  this.setImage = function thumbnailViewSetImage(img) {
+    if (!this.pdfPage) {
+      var promise = this.pageSource.getPage();
+      promise.then(function(pdfPage) {
+        this.setPdfPage(pdfPage);
+        this.setImage(img);
+      }.bind(this));
+      return;
+    }
+    if (this.hasImage || !img) {
+      return;
+    }
+    this.renderingState = RenderingStates.FINISHED;
+    var ctx = this.getPageDrawContext();
+
+    var reducedImage = img;
+    var reducedWidth = img.width;
+    var reducedHeight = img.height;
+
+    // drawImage does an awful job of rescaling the image, doing it gradually
+    var MAX_SCALE_FACTOR = 2.0;
+    if (Math.max(img.width / ctx.canvas.width,
+                 img.height / ctx.canvas.height) > MAX_SCALE_FACTOR) {
+      reducedWidth >>= 1;
+      reducedHeight >>= 1;
+      reducedImage = getTempCanvas(reducedWidth, reducedHeight);
+      var reducedImageCtx = reducedImage.getContext('2d');
+      reducedImageCtx.drawImage(img, 0, 0, img.width, img.height,
+                                0, 0, reducedWidth, reducedHeight);
+      while (Math.max(reducedWidth / ctx.canvas.width,
+                      reducedHeight / ctx.canvas.height) > MAX_SCALE_FACTOR) {
+        reducedImageCtx.drawImage(reducedImage,
+                                  0, 0, reducedWidth, reducedHeight,
+                                  0, 0, reducedWidth >> 1, reducedHeight >> 1);
+        reducedWidth >>= 1;
+        reducedHeight >>= 1;
+      }
+    }
+
+    ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
+                  0, 0, ctx.canvas.width, ctx.canvas.height);
+
+    this.hasImage = true;
+  };
+};
+
+ThumbnailView.tempImageCache = null;
+
+/**
+ * @typedef {Object} PDFThumbnailViewerOptions
+ * @property {HTMLDivElement} container - The container for the thumbs elements.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ */
+
+/**
+ * Simple viewer control to display thumbs for pages.
+ * @class
+ */
+var PDFThumbnailViewer = (function pdfThumbnailViewer() {
+  /**
+   * @constructs
+   * @param {PDFThumbnailViewerOptions} options
+   */
+  function PDFThumbnailViewer(options) {
+    this.container = options.container;
+    this.renderingQueue = options.renderingQueue;
+    this.linkService = options.linkService;
+
+    this.scroll = watchScroll(this.container, this._scrollUpdated.bind(this));
+    this._resetView();
+  }
+
+  PDFThumbnailViewer.prototype = {
+    _scrollUpdated: function PDFThumbnailViewer_scrollUpdated() {
+      this.renderingQueue.renderHighestPriority();
+    },
+
+    getThumbnail: function PDFThumbnailViewer_getThumbnail(index) {
+      return this.thumbnails[index];
+    },
+
+    _getVisibleThumbs: function PDFThumbnailViewer_getVisibleThumbs() {
+      return getVisibleElements(this.container, this.thumbnails);
+    },
+
+    scrollThumbnailIntoView: function (page) {
+      var selected = document.querySelector('.thumbnail.selected');
+      if (selected) {
+        selected.classList.remove('selected');
+      }
+      var thumbnail = document.getElementById('thumbnailContainer' + page);
+      thumbnail.classList.add('selected');
+      var visibleThumbs = this._getVisibleThumbs();
+      var numVisibleThumbs = visibleThumbs.views.length;
+
+      // If the thumbnail isn't currently visible, scroll it into view.
+      if (numVisibleThumbs > 0) {
+        var first = visibleThumbs.first.id;
+        // Account for only one thumbnail being visible.
+        var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
+        if (page <= first || page >= last) {
+          scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
+        }
+      }
+    },
+
+    get pagesRotation() {
+      return this._pagesRotation;
+    },
+
+    set pagesRotation(rotation) {
+      this._pagesRotation = rotation;
+      for (var i = 0, l = this.thumbnails.length; i < l; i++) {
+        var thumb = this.thumbnails[i];
+        thumb.update(rotation);
+      }
+    },
+
+    cleanup: function PDFThumbnailViewer_cleanup() {
+      ThumbnailView.tempImageCache = null;
+    },
+
+    _resetView: function () {
+      this.thumbnails = [];
+      this._pagesRotation = 0;
+    },
+
+    setDocument: function (pdfDocument) {
+      if (this.pdfDocument) {
+        // cleanup of the elements and views
+        var thumbsView = this.container;
+        while (thumbsView.hasChildNodes()) {
+          thumbsView.removeChild(thumbsView.lastChild);
+        }
+        this._resetView();
+      }
+
+      this.pdfDocument = pdfDocument;
+      if (!pdfDocument) {
+        return Promise.resolve();
+      }
+
+      return pdfDocument.getPage(1).then(function (firstPage) {
+        var pagesCount = pdfDocument.numPages;
+        var viewport = firstPage.getViewport(1.0);
+        for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+          var pageSource = new PDFPageSource(pdfDocument, pageNum);
+          var thumbnail = new ThumbnailView(this.container, pageNum,
+                                            viewport.clone(), this.linkService,
+                                            this.renderingQueue, pageSource);
+          this.thumbnails.push(thumbnail);
+        }
+      }.bind(this));
+    },
+
+    ensureThumbnailVisible:
+        function PDFThumbnailViewer_ensureThumbnailVisible(page) {
+      // Ensure that the thumbnail of the current page is visible
+      // when switching from another view.
+      scrollIntoView(document.getElementById('thumbnailContainer' + page));
+    },
+
+    forceRendering: function () {
+      var visibleThumbs = this._getVisibleThumbs();
+      var thumbView = this.renderingQueue.getHighestPriority(visibleThumbs,
+                                                             this.thumbnails,
+                                                             this.scroll.down);
+      if (thumbView) {
+        this.renderingQueue.renderView(thumbView);
+        return true;
+      }
+      return false;
+    }
+  };
+
+  return PDFThumbnailViewer;
+})();
+
+
+var DocumentOutlineView = function documentOutlineView(options) {
+  var outline = options.outline;
+  var outlineView = options.outlineView;
   while (outlineView.firstChild) {
     outlineView.removeChild(outlineView.firstChild);
   }
 
   if (!outline) {
-    if (!outlineView.classList.contains('hidden')) {
-      PDFView.switchSidebarView('thumbs');
-    }
     return;
   }
 
+  var linkService = options.linkService;
+
   function bindItemLink(domObj, item) {
-    domObj.href = PDFView.getDestinationHash(item.dest);
+    domObj.href = linkService.getDestinationHash(item.dest);
     domObj.onclick = function documentOutlineViewOnclick(e) {
-      PDFView.navigateTo(item.dest);
+      linkService.navigateTo(item.dest);
       return false;
     };
   }
 
   var queue = [{parent: outlineView, items: outline}];
   while (queue.length > 0) {
     var levelData = queue.shift();
     var i, n = levelData.items.length;
@@ -5425,26 +6135,24 @@ var DocumentOutlineView = function docum
       }
 
       levelData.parent.appendChild(div);
     }
   }
 };
 
 
-var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
-  var attachmentsView = document.getElementById('attachmentsView');
+var DocumentAttachmentsView = function documentAttachmentsView(options) {
+  var attachments = options.attachments;
+  var attachmentsView = options.attachmentsView;
   while (attachmentsView.firstChild) {
     attachmentsView.removeChild(attachmentsView.firstChild);
   }
 
   if (!attachments) {
-    if (!attachmentsView.classList.contains('hidden')) {
-      PDFView.switchSidebarView('thumbs');
-    }
     return;
   }
 
   function bindItemLink(domObj, item) {
     domObj.onclick = function documentAttachmentsViewOnclick(e) {
       var downloadManager = new DownloadManager();
       downloadManager.downloadData(item.content, getFileName(item.filename),
                                    '');
@@ -5465,113 +6173,108 @@ var DocumentAttachmentsView = function d
     div.appendChild(button);
     attachmentsView.appendChild(div);
   }
 };
 
 
 
 function webViewerLoad(evt) {
-  PDFView.initialize().then(webViewerInitialized);
+  PDFViewerApplication.initialize().then(webViewerInitialized);
 }
 
 function webViewerInitialized() {
   var file = window.location.href.split('#')[0];
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
   document.getElementById('secondaryOpenFile').setAttribute('hidden', 'true');
 
-  // Special debugging flags in the hash section of the URL.
-  var hash = document.location.hash.substring(1);
-  var hashParams = PDFView.parseQueryString(hash);
-
-  if ('disableWorker' in hashParams) {
-    PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
-  }
-
-  if ('disableRange' in hashParams) {
-    PDFJS.disableRange = (hashParams['disableRange'] === 'true');
-  }
-
-  if ('disableAutoFetch' in hashParams) {
-    PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
-  }
-
-  if ('disableFontFace' in hashParams) {
-    PDFJS.disableFontFace = (hashParams['disableFontFace'] === 'true');
+
+  if (PDFViewerApplication.preferencePdfBugEnabled) {
+    // Special debugging flags in the hash section of the URL.
+    var hash = document.location.hash.substring(1);
+    var hashParams = PDFViewerApplication.parseQueryString(hash);
+
+    if ('disableworker' in hashParams) {
+      PDFJS.disableWorker = (hashParams['disableworker'] === 'true');
+    }
+    if ('disablerange' in hashParams) {
+      PDFJS.disableRange = (hashParams['disablerange'] === 'true');
+    }
+    if ('disablestream' in hashParams) {
+      PDFJS.disableStream = (hashParams['disablestream'] === 'true');
+    }
+    if ('disableautofetch' in hashParams) {
+      PDFJS.disableAutoFetch = (hashParams['disableautofetch'] === 'true');
+    }
+    if ('disablefontface' in hashParams) {
+      PDFJS.disableFontFace = (hashParams['disablefontface'] === 'true');
+    }
+    if ('disablehistory' in hashParams) {
+      PDFJS.disableHistory = (hashParams['disablehistory'] === 'true');
+    }
+    if ('webgl' in hashParams) {
+      PDFJS.disableWebGL = (hashParams['webgl'] !== 'true');
+    }
+    if ('useonlycsszoom' in hashParams) {
+      PDFJS.useOnlyCssZoom = (hashParams['useonlycsszoom'] === 'true');
+    }
+    if ('verbosity' in hashParams) {
+      PDFJS.verbosity = hashParams['verbosity'] | 0;
+    }
+    if ('ignorecurrentpositiononzoom' in hashParams) {
+      IGNORE_CURRENT_POSITION_ON_ZOOM =
+        (hashParams['ignorecurrentpositiononzoom'] === 'true');
+    }
+    if ('textlayer' in hashParams) {
+      switch (hashParams['textlayer']) {
+        case 'off':
+          PDFJS.disableTextLayer = true;
+          break;
+        case 'visible':
+        case 'shadow':
+        case 'hover':
+          var viewer = document.getElementById('viewer');
+          viewer.classList.add('textLayer-' + hashParams['textlayer']);
+          break;
+      }
+    }
+    if ('pdfbug' in hashParams) {
+      PDFJS.pdfBug = true;
+      var pdfBug = hashParams['pdfbug'];
+      var enabled = pdfBug.split(',');
+      PDFBug.enable(enabled);
+      PDFBug.init();
+    }
   }
 
-  if ('disableHistory' in hashParams) {
-    PDFJS.disableHistory = (hashParams['disableHistory'] === 'true');
-  }
-
-  if ('webgl' in hashParams) {
-    PDFJS.disableWebGL = (hashParams['webgl'] !== 'true');
-  }
-
-  if ('useOnlyCssZoom' in hashParams) {
-    PDFJS.useOnlyCssZoom = (hashParams['useOnlyCssZoom'] === 'true');
-  }
-
-  if ('verbosity' in hashParams) {
-    PDFJS.verbosity = hashParams['verbosity'] | 0;
-  }
-
-  if ('ignoreCurrentPositionOnZoom' in hashParams) {
-    IGNORE_CURRENT_POSITION_ON_ZOOM =
-      (hashParams['ignoreCurrentPositionOnZoom'] === 'true');
-  }
-
-
-
-  if (!PDFView.supportsDocumentFonts) {
+  if (!PDFViewerApplication.supportsDocumentFonts) {
     PDFJS.disableFontFace = true;
     console.warn(mozL10n.get('web_fonts_disabled', null,
       'Web fonts are disabled: unable to use embedded PDF fonts.'));
   }
 
-  if ('textLayer' in hashParams) {
-    switch (hashParams['textLayer']) {
-      case 'off':
-        PDFJS.disableTextLayer = true;
-        break;
-      case 'visible':
-      case 'shadow':
-      case 'hover':
-        var viewer = document.getElementById('viewer');
-        viewer.classList.add('textLayer-' + hashParams['textLayer']);
-        break;
-    }
-  }
-
-  if ('pdfBug' in hashParams && FirefoxCom.requestSync('pdfBugEnabled')) {
-    PDFJS.pdfBug = true;
-    var pdfBug = hashParams['pdfBug'];
-    var enabled = pdfBug.split(',');
-    PDFBug.enable(enabled);
-    PDFBug.init();
-  }
-
-  if (!PDFView.supportsPrinting) {
+  if (!PDFViewerApplication.supportsPrinting) {
     document.getElementById('print').classList.add('hidden');
     document.getElementById('secondaryPrint').classList.add('hidden');
   }
 
-  if (!PDFView.supportsFullscreen) {
+  if (!PDFViewerApplication.supportsFullscreen) {
     document.getElementById('presentationMode').classList.add('hidden');
     document.getElementById('secondaryPresentationMode').
       classList.add('hidden');
   }
 
-  if (PDFView.supportsIntegratedFind) {
+  if (PDFViewerApplication.supportsIntegratedFind) {
     document.getElementById('viewFind').classList.add('hidden');
   }
 
   // Listen for unsupported features to trigger the fallback UI.
-  PDFJS.UnsupportedManager.listen(PDFView.fallback.bind(PDFView));
+  PDFJS.UnsupportedManager.listen(
+    PDFViewerApplication.fallback.bind(PDFViewerApplication));
 
   // Suppress context menus for some controls
   document.getElementById('scaleSelect').oncontextmenu = noContextMenuHandler;
 
   var mainContainer = document.getElementById('mainContainer');
   var outerContainer = document.getElementById('outerContainer');
   mainContainer.addEventListener('transitionend', function(e) {
     if (e.target === mainContainer) {
@@ -5582,199 +6285,208 @@ function webViewerInitialized() {
     }
   }, true);
 
   document.getElementById('sidebarToggle').addEventListener('click',
     function() {
       this.classList.toggle('toggled');
       outerContainer.classList.add('sidebarMoving');
       outerContainer.classList.toggle('sidebarOpen');
-      PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
-      PDFView.renderHighestPriority();
+      PDFViewerApplication.sidebarOpen =
+        outerContainer.classList.contains('sidebarOpen');
+      PDFViewerApplication.forceRendering();
     });
 
   document.getElementById('viewThumbnail').addEventListener('click',
     function() {
-      PDFView.switchSidebarView('thumbs');
+      PDFViewerApplication.switchSidebarView('thumbs');
     });
 
   document.getElementById('viewOutline').addEventListener('click',
     function() {
-      PDFView.switchSidebarView('outline');
+      PDFViewerApplication.switchSidebarView('outline');
     });
 
   document.getElementById('viewAttachments').addEventListener('click',
     function() {
-      PDFView.switchSidebarView('attachments');
+      PDFViewerApplication.switchSidebarView('attachments');
     });
 
   document.getElementById('previous').addEventListener('click',
     function() {
-      PDFView.page--;
+      PDFViewerApplication.page--;
     });
 
   document.getElementById('next').addEventListener('click',
     function() {
-      PDFView.page++;
+      PDFViewerApplication.page++;
     });
 
   document.getElementById('zoomIn').addEventListener('click',
     function() {
-      PDFView.zoomIn();
+      PDFViewerApplication.zoomIn();
     });
 
   document.getElementById('zoomOut').addEventListener('click',
     function() {
-      PDFView.zoomOut();
-    });
-
-  document.getElementById('pageNumber').addEventListener('click',
-    function() {
-      this.select();
+      PDFViewerApplication.zoomOut();
     });
 
-  document.getElementById('pageNumber').addEventListener('change',
-    function() {
-      // Handle the user inputting a floating point number.
-      PDFView.page = (this.value | 0);
-
-      if (this.value !== (this.value | 0).toString()) {
-        this.value = PDFView.page;
-      }
-    });
+  document.getElementById('pageNumber').addEventListener('click', function() {
+    this.select();
+  });
+
+  document.getElementById('pageNumber').addEventListener('change', function() {
+    // Handle the user inputting a floating point number.
+    PDFViewerApplication.page = (this.value | 0);
+
+    if (this.value !== (this.value | 0).toString()) {
+      this.value = PDFViewerApplication.page;
+    }
+  });
 
   document.getElementById('scaleSelect').addEventListener('change',
     function() {
-      PDFView.setScale(this.value);
+      PDFViewerApplication.setScale(this.value, false);
     });
 
   document.getElementById('presentationMode').addEventListener('click',
     SecondaryToolbar.presentationModeClick.bind(SecondaryToolbar));
 
   document.getElementById('openFile').addEventListener('click',
     SecondaryToolbar.openFileClick.bind(SecondaryToolbar));
 
   document.getElementById('print').addEventListener('click',
     SecondaryToolbar.printClick.bind(SecondaryToolbar));
 
   document.getElementById('download').addEventListener('click',
     SecondaryToolbar.downloadClick.bind(SecondaryToolbar));
 
-  PDFView.setTitleUsingUrl(file);
-  PDFView.initPassiveLoading();
+  PDFViewerApplication.setTitleUsingUrl(file);
+  PDFViewerApplication.initPassiveLoading();
   return;
 
 
   if (file) {
-    PDFView.open(file, 0);
+    PDFViewerApplication.open(file, 0);
   }
 }
 
 document.addEventListener('DOMContentLoaded', webViewerLoad, true);
 
+document.addEventListener('pagerendered', function (e) {
+  var pageIndex = e.detail.pageNumber - 1;
+  var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
+  var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
+                      getThumbnail(pageIndex);
+  thumbnailView.setImage(pageView.canvas);
+
+  if (pageView.textLayer && pageView.textLayer.textDivs &&
+      pageView.textLayer.textDivs.length > 0 &&
+      !PDFViewerApplication.supportsDocumentColors) {
+    console.error(mozL10n.get('document_colors_disabled', null,
+      'PDF documents are not allowed to use their own colors: ' +
+      '\'Allow pages to choose their own colors\' ' +
+      'is deactivated in the browser.'));
+    PDFViewerApplication.fallback();
+  }
+
+  if (pageView.error) {
+    PDFViewerApplication.error(mozL10n.get('rendering_error', null,
+      'An error occurred while rendering the page.'), pageView.error);
+  }
+
+  FirefoxCom.request('reportTelemetry', JSON.stringify({
+    type: 'pageInfo'
+  }));
+  // It is a good time to report stream and font types
+  PDFViewerApplication.pdfDocument.getStats().then(function (stats) {
+    FirefoxCom.request('reportTelemetry', JSON.stringify({
+      type: 'documentStats',
+      stats: stats
+    }));
+  });
+
+  // If the page is still visible when it has finished rendering,
+  // ensure that the page number input loading indicator is hidden.
+  if ((pageIndex + 1) === PDFViewerApplication.page) {
+    var pageNumberInput = document.getElementById('pageNumber');
+    pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
+  }
+}, true);
+
+window.addEventListener('presentationmodechanged', function (e) {
+  var active = e.detail.active;
+  var switchInProgress = e.detail.switchInProgress;
+  PDFViewerApplication.pdfViewer.presentationModeState =
+    switchInProgress ? PresentationModeState.CHANGING :
+    active ? PresentationModeState.FULLSCREEN : PresentationModeState.NORMAL;
+});
+
 function updateViewarea() {
-
-  if (!PDFView.initialized) {
-    return;
-  }
-  var visible = PDFView.getVisiblePages();
-  var visiblePages = visible.views;
-  if (visiblePages.length === 0) {
+  if (!PDFViewerApplication.initialized) {
     return;
   }
-
-  var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
-      2 * visiblePages.length + 1);
-  cache.resize(suggestedCacheSize);
-
-  PDFView.renderHighestPriority(visible);
-
-  var currentId = PDFView.page;
-  var firstPage = visible.first;
-
-  for (var i = 0, ii = visiblePages.length, stillFullyVisible = false;
-       i < ii; ++i) {
-    var page = visiblePages[i];
-
-    if (page.percent < 100) {
-      break;
-    }
-    if (page.id === PDFView.page) {
-      stillFullyVisible = true;
-      break;
-    }
-  }
-
-  if (!stillFullyVisible) {
-    currentId = visiblePages[0].id;
+  PDFViewerApplication.pdfViewer.update();
+}
+
+window.addEventListener('updateviewarea', function () {
+  if (!PDFViewerApplication.initialized) {
+    return;
   }
 
-  if (!PresentationMode.active) {
-    updateViewarea.inProgress = true; // used in "set page"
-    PDFView.page = currentId;
-    updateViewarea.inProgress = false;
-  }
-
-  var currentScale = PDFView.currentScale;
-  var currentScaleValue = PDFView.currentScaleValue;
-  var normalizedScaleValue = parseFloat(currentScaleValue) === currentScale ?
-    Math.round(currentScale * 10000) / 100 : currentScaleValue;
-
-  var pageNumber = firstPage.id;
-  var pdfOpenParams = '#page=' + pageNumber;
-  pdfOpenParams += '&zoom=' + normalizedScaleValue;
-  var currentPage = PDFView.pages[pageNumber - 1];
-  var container = PDFView.container;
-  var topLeft = currentPage.getPagePoint((container.scrollLeft - firstPage.x),
-                                         (container.scrollTop - firstPage.y));
-  var intLeft = Math.round(topLeft[0]);
-  var intTop = Math.round(topLeft[1]);
-  pdfOpenParams += ',' + intLeft + ',' + intTop;
-
-  if (PresentationMode.active || PresentationMode.switchInProgress) {
-    PDFView.currentPosition = null;
-  } else {
-    PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop };
-  }
-
-  PDFView.store.initializedPromise.then(function() {
-    PDFView.store.setMultiple({
+  var location = PDFViewerApplication.pdfViewer.location;
+
+  PDFViewerApplication.store.initializedPromise.then(function() {
+    PDFViewerApplication.store.setMultiple({
       'exists': true,
-      'page': pageNumber,
-      'zoom': normalizedScaleValue,
-      'scrollLeft': intLeft,
-      'scrollTop': intTop
+      'page': location.pageNumber,
+      'zoom': location.scale,
+      'scrollLeft': location.left,
+      'scrollTop': location.top
     }).catch(function() {
       // unable to write to storage
     });
   });
-  var href = PDFView.getAnchorUrl(pdfOpenParams);
+  var href = PDFViewerApplication.getAnchorUrl(location.pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
   document.getElementById('secondaryViewBookmark').href = href;
 
   // Update the current bookmark in the browsing history.
-  PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber);
-}
+  PDFHistory.updateCurrentBookmark(location.pdfOpenParams, location.pageNumber);
+
+  // Show/hide the loading indicator in the page number input element.
+  var pageNumberInput = document.getElementById('pageNumber');
+  var currentPage =
+    PDFViewerApplication.pdfViewer.getPageView(PDFViewerApplication.page - 1);
+
+  if (currentPage.renderingState === RenderingStates.FINISHED) {
+    pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
+  } else {
+    pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
+  }
+}, true);
 
 window.addEventListener('resize', function webViewerResize(evt) {
-  if (PDFView.initialized &&
+  if (PDFViewerApplication.initialized &&
       (document.getElementById('pageWidthOption').selected ||
        document.getElementById('pageFitOption').selected ||
        document.getElementById('pageAutoOption').selected)) {
-    PDFView.setScale(document.getElementById('scaleSelect').value);
+    var selectedScale = document.getElementById('scaleSelect').value;
+    PDFViewerApplication.setScale(selectedScale, false);
   }
   updateViewarea();
 
   // Set the 'max-height' CSS property of the secondary toolbar.
-  SecondaryToolbar.setMaxHeight(PDFView.container);
+  SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
 });
 
 window.addEventListener('hashchange', function webViewerHashchange(evt) {
   if (PDFHistory.isHashChangeUnlocked) {
-    PDFView.setHash(document.location.hash.substring(1));
+    PDFViewerApplication.setHash(document.location.hash.substring(1));
   }
 });
 
 
 function selectScaleOption(value) {
   var options = document.getElementById('scaleSelect').options;
   var predefinedValueFound = false;
   for (var i = 0; i < options.length; i++) {
@@ -5787,17 +6499,17 @@ function selectScaleOption(value) {
     predefinedValueFound = true;
   }
   return predefinedValueFound;
 }
 
 window.addEventListener('localized', function localized(evt) {
   document.getElementsByTagName('html')[0].dir = mozL10n.getDirection();
 
-  PDFView.animationStartedPromise.then(function() {
+  PDFViewerApplication.animationStartedPromise.then(function() {
     // Adjust the width of the zoom box to fit the content.
     // Note: If the window is narrow enough that the zoom box is not visible,
     //       we temporarily show it to be able to adjust its width.
     var container = document.getElementById('scaleSelectContainer');
     if (container.clientWidth === 0) {
       container.setAttribute('style', 'display: inherit;');
     }
     if (container.clientWidth > 0) {
@@ -5806,95 +6518,96 @@ window.addEventListener('localized', fun
       var width = select.clientWidth + SCALE_SELECT_CONTAINER_PADDING;
       select.setAttribute('style', 'min-width: ' +
                                    (width + SCALE_SELECT_PADDING) + 'px;');
       container.setAttribute('style', 'min-width: ' + width + 'px; ' +
                                       'max-width: ' + width + 'px;');
     }
 
     // Set the 'max-height' CSS property of the secondary toolbar.
-    SecondaryToolbar.setMaxHeight(PDFView.container);
+    SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
   });
 }, true);
 
 window.addEventListener('scalechange', function scalechange(evt) {
   document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
   document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
 
   var customScaleOption = document.getElementById('customScaleOption');
   customScaleOption.selected = false;
 
-  if (!evt.resetAutoSettings &&
+  if (!PDFViewerApplication.updateScaleControls &&
       (document.getElementById('pageWidthOption').selected ||
        document.getElementById('pageFitOption').selected ||
        document.getElementById('pageAutoOption').selected)) {
     updateViewarea();
     return;
   }
 
+  if (evt.presetValue) {
+    selectScaleOption(evt.presetValue);
+    updateViewarea();
+    return;
+  }
+
   var predefinedValueFound = selectScaleOption('' + evt.scale);
   if (!predefinedValueFound) {
     customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
     customScaleOption.selected = true;
   }
   updateViewarea();
 }, true);
 
 window.addEventListener('pagechange', function pagechange(evt) {
   var page = evt.pageNumber;
-  if (PDFView.previousPageNumber !== page) {
+  if (evt.previousPageNumber !== page) {
     document.getElementById('pageNumber').value = page;
-    var selected = document.querySelector('.thumbnail.selected');
-    if (selected) {
-      selected.classList.remove('selected');
-    }
-    var thumbnail = document.getElementById('thumbnailContainer' + page);
-    thumbnail.classList.add('selected');
-    var visibleThumbs = PDFView.getVisibleThumbs();
-    var numVisibleThumbs = visibleThumbs.views.length;
-
-    // If the thumbnail isn't currently visible, scroll it into view.
-    if (numVisibleThumbs > 0) {
-      var first = visibleThumbs.first.id;
-      // Account for only one thumbnail being visible.
-      var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
-      if (page <= first || page >= last) {
-        scrollIntoView(thumbnail, { top: THUMBNAIL_SCROLL_MARGIN });
-      }
-    }
+    PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
   }
-  var numPages = PDFView.pages.length;
+  var numPages = PDFViewerApplication.pagesCount;
 
   document.getElementById('previous').disabled = (page <= 1);
   document.getElementById('next').disabled = (page >= numPages);
 
   document.getElementById('firstPage').disabled = (page <= 1);
   document.getElementById('lastPage').disabled = (page >= numPages);
+
+  // checking if the this.page was called from the updateViewarea function
+  if (evt.updateInProgress) {
+    return;
+  }
+  // Avoid scrolling the first page during loading
+  if (this.loading && page === 1) {
+    return;
+  }
+  PDFViewerApplication.pdfViewer.scrollPageIntoView(page);
 }, true);
 
 function handleMouseWheel(evt) {
   var MOUSE_WHEEL_DELTA_FACTOR = 40;
   var ticks = (evt.type === 'DOMMouseScroll') ? -evt.detail :
               evt.wheelDelta / MOUSE_WHEEL_DELTA_FACTOR;
   var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
 
-  if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
+  if (PresentationMode.active) {
     evt.preventDefault();
-    PDFView[direction](Math.abs(ticks));
-  } else if (PresentationMode.active) {
-    PDFView.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
+    PDFViewerApplication.mouseScroll(ticks * MOUSE_WHEEL_DELTA_FACTOR);
+  } else if (evt.ctrlKey) { // Only zoom the pages, not the entire viewer
+    evt.preventDefault();
+    PDFViewerApplication[direction](Math.abs(ticks));
   }
 }
 
 window.addEventListener('DOMMouseScroll', handleMouseWheel);
 window.addEventListener('mousewheel', handleMouseWheel);
 
 window.addEventListener('click', function click(evt) {
   if (!PresentationMode.active) {
-    if (SecondaryToolbar.opened && PDFView.container.contains(evt.target)) {
+    if (SecondaryToolbar.opened &&
+      PDFViewerApplication.pdfViewer.containsElement(evt.target)) {
       SecondaryToolbar.close();
     }
   } else if (evt.button === 0) {
     // Necessary since preventDefault() in 'mousedown' won't stop
     // the event propagation in all circumstances in presentation mode.
     evt.preventDefault();
   }
 }, false);
@@ -5909,50 +6622,62 @@ window.addEventListener('keydown', funct
             (evt.altKey ? 2 : 0) |
             (evt.shiftKey ? 4 : 0) |
             (evt.metaKey ? 8 : 0);
 
   // 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.
+    var pdfViewer = PDFViewerApplication.pdfViewer;
+    var inPresentationMode =
+      pdfViewer.presentationModeState === PresentationModeState.CHANGING ||
+      pdfViewer.presentationModeState === PresentationModeState.FULLSCREEN;
+
     switch (evt.keyCode) {
       case 70: // f
-        if (!PDFView.supportsIntegratedFind) {
-          PDFView.findBar.open();
+        if (!PDFViewerApplication.supportsIntegratedFind) {
+          PDFViewerApplication.findBar.open();
           handled = true;
         }
         break;
       case 71: // g
-        if (!PDFView.supportsIntegratedFind) {
-          PDFView.findBar.dispatchEvent('again', cmd === 5 || cmd === 12);
+        if (!PDFViewerApplication.supportsIntegratedFind) {
+          PDFViewerApplication.findBar.dispatchEvent('again',
+                                                     cmd === 5 || cmd === 12);
           handled = true;
         }
         break;
       case 61: // FF/Mac '='
       case 107: // FF '+' and '='
       case 187: // Chrome '+'
       case 171: // FF with German keyboard
-        PDFView.zoomIn();
+        if (!inPresentationMode) {
+          PDFViewerApplication.zoomIn();
+        }
         handled = true;
         break;
       case 173: // FF/Mac '-'
       case 109: // FF '-'
       case 189: // Chrome '-'
-        PDFView.zoomOut();
+        if (!inPresentationMode) {
+          PDFViewerApplication.zoomOut();
+        }
         handled = true;
         break;
       case 48: // '0'
       case 96: // '0' on Numpad of Swedish keyboard
-        // keeping it unhandled (to restore page zoom to 100%)
-        setTimeout(function () {
-          // ... and resetting the scale after browser adjusts its scale
-          PDFView.setScale(DEFAULT_SCALE, true);
-        });
-        handled = false;
+        if (!inPresentationMode) {
+          // keeping it unhandled (to restore page zoom to 100%)
+          setTimeout(function () {
+            // ... and resetting the scale after browser adjusts its scale
+            PDFViewerApplication.setScale(DEFAULT_SCALE, true);
+          });
+          handled = false;
+        }
         break;
     }
   }
 
 
   // CTRL+ALT or Option+Command
   if (cmd === 3 || cmd === 10) {
     switch (evt.keyCode) {
@@ -5987,119 +6712,120 @@ window.addEventListener('keydown', funct
   }
 
   if (cmd === 0) { // no control key pressed at all.
     switch (evt.keyCode) {
       case 38: // up arrow
       case 33: // pg up
       case 8: // backspace
         if (!PresentationMode.active &&
-            PDFView.currentScaleValue !== 'page-fit') {
+          PDFViewerApplication.currentScaleValue !== 'page-fit') {
           break;
         }
         /* in presentation mode */
         /* falls through */
       case 37: // left arrow
         // horizontal scrolling using arrow keys
-        if (PDFView.isHorizontalScrollbarEnabled) {
+        if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
           break;
         }
         /* falls through */
       case 75: // 'k'
       case 80: // 'p'
-        PDFView.page--;
+        PDFViewerApplication.page--;
         handled = true;
         break;
       case 27: // esc key
         if (SecondaryToolbar.opened) {
           SecondaryToolbar.close();
           handled = true;
         }
-        if (!PDFView.supportsIntegratedFind && PDFView.findBar.opened) {
-          PDFView.findBar.close();
+        if (!PDFViewerApplication.supportsIntegratedFind &&
+            PDFViewerApplication.findBar.opened) {
+          PDFViewerApplication.findBar.close();
           handled = true;
         }
         break;
       case 40: // down arrow
       case 34: // pg down
       case 32: // spacebar
         if (!PresentationMode.active &&
-            PDFView.currentScaleValue !== 'page-fit') {
+            PDFViewerApplication.currentScaleValue !== 'page-fit') {
           break;
         }
         /* falls through */
       case 39: // right arrow
         // horizontal scrolling using arrow keys
-        if (PDFView.isHorizontalScrollbarEnabled) {
+        if (PDFViewerApplication.pdfViewer.isHorizontalScrollbarEnabled) {
           break;
         }
         /* falls through */
       case 74: // 'j'
       case 78: // 'n'
-        PDFView.page++;
+        PDFViewerApplication.page++;
         handled = true;
         break;
 
       case 36: // home
-        if (PresentationMode.active || PDFView.page > 1) {
-          PDFView.page = 1;
+        if (PresentationMode.active || PDFViewerApplication.page > 1) {
+          PDFViewerApplication.page = 1;
           handled = true;
         }
         break;
       case 35: // end
-        if (PresentationMode.active || (PDFView.pdfDocument &&
-            PDFView.page < PDFView.pdfDocument.numPages)) {
-          PDFView.page = PDFView.pdfDocument.numPages;
+        if (PresentationMode.active || (PDFViewerApplication.pdfDocument &&
+            PDFViewerApplication.page < PDFViewerApplication.pagesCount)) {
+          PDFViewerApplication.page = PDFViewerApplication.pagesCount;
           handled = true;
         }
         break;
 
       case 72: // 'h'
         if (!PresentationMode.active) {
           HandTool.toggle();
         }
         break;
       case 82: // 'r'
-        PDFView.rotatePages(90);
+        PDFViewerApplication.rotatePages(90);
         break;
     }
   }
 
   if (cmd === 4) { // shift-key
     switch (evt.keyCode) {
       case 32: // spacebar
         if (!PresentationMode.active &&
-            PDFView.currentScaleValue !== 'page-fit') {
+            PDFViewerApplication.currentScaleValue !== 'page-fit') {
           break;
         }
-        PDFView.page--;
+        PDFViewerApplication.page--;
         handled = true;
         break;
 
       case 82: // 'r'
-        PDFView.rotatePages(-90);
+        PDFViewerApplication.rotatePages(-90);
         break;
     }
   }
 
   if (!handled && !PresentationMode.active) {
     // 33=Page Up  34=Page Down  35=End    36=Home
     // 37=Left     38=Up         39=Right  40=Down
     if (evt.keyCode >= 33 && evt.keyCode <= 40 &&
-        !PDFView.container.contains(curElement)) {
+        !PDFViewerApplication.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.
-      PDFView.container.focus();
+      PDFViewerApplication.pdfViewer.focus();
     }
     // 32=Spacebar
     if (evt.keyCode === 32 && curElementTagName !== 'BUTTON') {
       // Workaround for issue in Firefox, that prevents scroll keys from
       // working when elements with 'tabindex' are focused. (#3498)
-      PDFView.container.blur();
+      PDFViewerApplication.pdfViewer.blur();
     }
   }
 
   if (cmd === 2) { // alt-key
     switch (evt.keyCode) {
       case 37: // left arrow
         if (PresentationMode.active) {
           PDFHistory.back();
@@ -6112,29 +6838,30 @@ window.addEventListener('keydown', funct
           handled = true;
         }
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
-    PDFView.clearMouseScrollState();
+    PDFViewerApplication.clearMouseScrollState();
   }
 });
 
 window.addEventListener('beforeprint', function beforePrint(evt) {
-  PDFView.beforePrint();
+  PDFViewerApplication.beforePrint();
 });
 
 window.addEventListener('afterprint', function afterPrint(evt) {
-  PDFView.afterPrint();
+  PDFViewerApplication.afterPrint();
 });
 
 (function animationStartedClosure() {
   // The offsetParent is not set until the pdf.js iframe or object is visible.
   // Waiting for first animation.
-  PDFView.animationStartedPromise = new Promise(function (resolve) {
+  PDFViewerApplication.animationStartedPromise = new Promise(
+      function (resolve) {
     window.requestAnimationFrame(resolve);
   });
 })();