Bug 1007627 - Update pdf.js to version 1.0.162. r=yury, r=Mossop
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 09 May 2014 09:31:32 -0400
changeset 182339 970b90975e1c2bc78fd041bf6bb9530668b30790
parent 182338 2e95c8a3da918ad901239edaa0429bea6dd37f7b
child 182340 d773600f10bdc4e7b37e84ebf7546ac7f11bbc26
push id26758
push userryanvm@gmail.com
push dateFri, 09 May 2014 19:37:26 +0000
treeherdermozilla-central@007380afa681 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyury, Mossop
bugs1007627
milestone32.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1007627 - Update pdf.js to version 1.0.162. r=yury, r=Mossop
browser/extensions/pdfjs/README.mozilla
browser/extensions/pdfjs/content/PdfJs.jsm
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
browser/extensions/pdfjs/content/build/pdf.js
browser/extensions/pdfjs/content/build/pdf.worker.js
browser/extensions/pdfjs/content/web/viewer.css
browser/extensions/pdfjs/content/web/viewer.js
browser/extensions/pdfjs/test/browser_pdfjs_main.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.68
+Current extension version is: 1.0.162
 
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -59,17 +59,17 @@ function getIntPref(aPref, aDefaultValue
   }
 }
 
 function initializeDefaultPreferences() {
 
 var DEFAULT_PREFERENCES = {
   showPreviousViewOnLoad: true,
   defaultZoomValue: '',
-  ifAvailableShowOutlineOnLoad: false,
+  sidebarViewOnLoad: 0,
   enableHandToolOnLoad: false,
   enableWebGL: false
 };
 
 
   var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
   var defaultValue;
   for (var key in DEFAULT_PREFERENCES) {
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -48,22 +48,38 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, 'PdfJsTelemetry',
   'resource://pdf.js/PdfJsTelemetry.jsm');
 
 var Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                    '@mozilla.org/mime;1',
                                    'nsIMIMEService');
 
+function getContainingBrowser(domWindow) {
+  return domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIWebNavigation)
+                  .QueryInterface(Ci.nsIDocShell)
+                  .chromeEventHandler;
+}
+
 function getChromeWindow(domWindow) {
-  var containingBrowser = domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                   .getInterface(Ci.nsIWebNavigation)
-                                   .QueryInterface(Ci.nsIDocShell)
-                                   .chromeEventHandler;
-  return containingBrowser.ownerDocument.defaultView;
+  return getContainingBrowser(domWindow).ownerDocument.defaultView;
+}
+
+function getFindBar(domWindow) {
+  var browser = getContainingBrowser(domWindow);
+  try {
+    var tabbrowser = browser.getTabBrowser();
+    var 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;
+  }
 }
 
 function setBoolPref(pref, value) {
   Services.prefs.setBoolPref(pref, value);
 }
 
 function getBoolPref(pref, def) {
   try {
@@ -269,19 +285,20 @@ ChromeActions.prototype = {
       if ('nsIPrivateBrowsingChannel' in Ci &&
           channel instanceof Ci.nsIPrivateBrowsingChannel) {
         channel.setPrivate(docIsPrivate);
       }
 
       var listener = {
         extListener: null,
         onStartRequest: function(aRequest, aContext) {
-          this.extListener = extHelperAppSvc.doContent((data.isAttachment ? '' :
-                                                        'application/pdf'),
-                                aRequest, frontWindow, false);
+          this.extListener = extHelperAppSvc.doContent(
+            (data.isAttachment ? 'application/octet-stream' :
+                                 'application/pdf'),
+            aRequest, frontWindow, false);
           this.extListener.onStartRequest(aRequest, aContext);
         },
         onStopRequest: function(aRequest, aContext, aStatusCode) {
           if (this.extListener)
             this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
           // Notify the content code we're done downloading.
           if (sendResponse)
             sendResponse(false);
@@ -311,21 +328,23 @@ ChromeActions.prototype = {
       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 and when the
-    // new find events code exists.
-    return this.domWindow.frameElement === null &&
-           getChromeWindow(this.domWindow).gFindBar &&
-           'updateControlState' in getChromeWindow(this.domWindow).gFindBar;
+    // 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);
   },
   supportsDocumentFonts: function() {
     var prefBrowser = getIntPref('browser.display.use_document_fonts', 1);
     var prefGfx = getBoolPref('gfx.downloadable_fonts.enabled', true);
     return (!!prefBrowser && prefGfx);
   },
   supportsDocumentColors: function() {
     return getBoolPref('browser.display.use_document_colors', true);
@@ -432,18 +451,17 @@ ChromeActions.prototype = {
     // Verify what we're sending to the findbar.
     var result = data.result;
     var findPrevious = data.findPrevious;
     var findPreviousType = typeof findPrevious;
     if ((typeof result !== 'number' || result < 0 || result > 3) ||
         (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
       return;
     }
-    getChromeWindow(this.domWindow).gFindBar
-                                   .updateControlState(result, findPrevious);
+    getFindBar(this.domWindow).updateControlState(result, findPrevious);
   },
   setPreferences: function(prefs, sendResponse) {
     var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
     var numberOfPrefs = 0;
     var prefValue, prefName;
     for (var key in prefs) {
       if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
         log('setPreferences - Exceeded the maximum number of preferences ' +
@@ -911,17 +929,18 @@ PdfStreamConverter.prototype = {
               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 findEventManager = new FindEventManager(chromeWindow.gFindBar,
+          var findBar = getFindBar(domWindow);
+          var findEventManager = new FindEventManager(findBar,
                                                       domWindow,
                                                       chromeWindow);
           findEventManager.bind();
         }
         listener.onStopRequest(aRequest, context, statusCode);
       }
     };
 
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -16,18 +16,18 @@
  */
 /*jshint globalstrict: false */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.68';
-PDFJS.build = 'ead4cbf';
+PDFJS.version = '1.0.162';
+PDFJS.build = 'abc924b';
 
 (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
@@ -901,32 +901,39 @@ function isPDFFunction(v) {
     fnDict = v.dict;
   } else {
     return false;
   }
   return fnDict.has('FunctionType');
 }
 
 /**
- * Legacy support for PDFJS Promise implementation.
- * TODO remove eventually
- * @ignore
+ * Promise Capability object.
+ *
+ * @typedef {Object} PromiseCapability
+ * @property {Promise} promise - A promise object.
+ * @property {function} resolve - Fullfills the promise.
+ * @property {function} reject - Rejects the promise.
  */
-var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
-  return function LegacyPromise() {
-    var resolve, reject;
-    var promise = new Promise(function (resolve_, reject_) {
-      resolve = resolve_;
-      reject = reject_;
-    });
-    promise.resolve = resolve;
-    promise.reject = reject;
-    return promise;
-  };
-})();
+
+/**
+ * Creates a promise capability object.
+ * @return {PromiseCapability} A capability object contains:
+ * - a Promise, resolve and reject methods.
+ */
+function createPromiseCapability() {
+  var capability = {};
+  capability.promise = new Promise(function (resolve, reject) {
+    capability.resolve = resolve;
+    capability.reject = reject;
+  });
+  return capability;
+}
+
+PDFJS.createPromiseCapability = createPromiseCapability;
 
 /**
  * Polyfill for Promises:
  * The following promise implementation tries to generally implment the
  * Promise/A+ spec. Some notable differences from other promise libaries are:
  * - There currently isn't a seperate deferred and promise object.
  * - Unhandled rejections eventually show an error if they aren't handled.
  *
@@ -955,18 +962,25 @@ var LegacyPromise = PDFJS.LegacyPromise 
         });
         if (count === 0) {
           resolve(results);
         }
         return promise;
       };
     }
     if (typeof globalScope.Promise.resolve !== 'function') {
-      globalScope.Promise.resolve = function (x) {
-        return new globalScope.Promise(function (resolve) { resolve(x); });
+      globalScope.Promise.resolve = function (value) {
+        return new globalScope.Promise(function (resolve) { resolve(value); });
+      };
+    }
+    if (typeof globalScope.Promise.reject !== 'function') {
+      globalScope.Promise.reject = function (reason) {
+        return new globalScope.Promise(function (resolve, reject) {
+          reject(reason);
+        });
       };
     }
     return;
   }
   throw new Error('DOM Promise is not present');
 })();
 
 var StatTimer = (function StatTimerClosure() {
@@ -1283,26 +1297,27 @@ var ColorSpace = (function ColorSpaceClo
           rgbBuf = new Uint8Array(count * 3);
           this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc,
                             /* alpha01 = */ 0);
         }
       }
 
       if (rgbBuf) {
         if (needsResizing) {
-          rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth,
-                                   originalHeight, width, height);
-        }
-        rgbPos = 0;
-        destPos = 0;
-        for (i = 0, ii = width * actualHeight; i < ii; i++) {
-          dest[destPos++] = rgbBuf[rgbPos++];
-          dest[destPos++] = rgbBuf[rgbPos++];
-          dest[destPos++] = rgbBuf[rgbPos++];
-          destPos += alpha01;
+          PDFImage.resize(rgbBuf, bpc, 3, originalWidth, originalHeight, width,
+                          height, dest, alpha01);
+        } else {
+          rgbPos = 0;
+          destPos = 0;
+          for (i = 0, ii = width * actualHeight; i < ii; i++) {
+            dest[destPos++] = rgbBuf[rgbPos++];
+            dest[destPos++] = rgbBuf[rgbPos++];
+            dest[destPos++] = rgbBuf[rgbPos++];
+            destPos += alpha01;
+          }
         }
       }
     },
     /**
      * True if the colorspace has components in the default range of [0, 1].
      * This should be true for all colorspaces except for lab color spaces
      * which are [0,100], [-128, 127], [-128, 127].
      */
@@ -1884,35 +1899,25 @@ var CalGrayCS = (function CalGrayCSClosu
   }
 
   function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
     // A represents a gray component of a calibrated gray space.
     // A <---> AG in the spec
     var A = src[srcOffset] * scale;
     var AG = Math.pow(A, cs.G);
 
-    // Computes intermediate variables M, L, N as per spec.
+    // Computes L as per spec. ( = cs.YW * AG )
     // Except if other than default BlackPoint values are used.
-    var M = cs.XW * AG;
     var L = cs.YW * AG;
-    var N = cs.ZW * AG;
-
-    // Decode XYZ, as per spec.
-    var X = M;
-    var Y = L;
-    var Z = N;
-
     // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
-    // This yields values in range [0, 100].
-    var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
-
     // Convert values to rgb range [0, 255].
-    dest[destOffset] = Lstar * 255 / 100;
-    dest[destOffset + 1] = Lstar * 255 / 100;
-    dest[destOffset + 2] = Lstar * 255 / 100;
+    var val = Math.max(295.8 * Math.pow(L, 0.333333333333333333) - 40.8, 0) | 0;
+    dest[destOffset] = val;
+    dest[destOffset + 1] = val;
+    dest[destOffset + 2] = val;
   }
 
   CalGrayCS.prototype = {
     getRgb: ColorSpace.prototype.getRgb,
     getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
                                               dest, destOffset) {
       convertToRgb(this, src, srcOffset, dest, destOffset, 1);
     },
@@ -2719,24 +2724,24 @@ var PostScriptEvaluator = (function Post
             break;
           case 'ne':
             b = stack.pop();
             a = stack.pop();
             stack.push(a != b);
             break;
           case 'neg':
             a = stack.pop();
-            stack.push(-b);
+            stack.push(-a);
             break;
           case 'not':
             a = stack.pop();
-            if (isBool(a) && isBool(b)) {
-              stack.push(a && b);
+            if (isBool(a)) {
+              stack.push(!a);
             } else {
-              stack.push(a & b);
+              stack.push(~a);
             }
             break;
           case 'or':
             b = stack.pop();
             a = stack.pop();
             if (isBool(a) && isBool(b)) {
               stack.push(a || b);
             } else {
@@ -2792,16 +2797,17 @@ var PostScriptEvaluator = (function Post
       }
       return stack.stack;
     }
   };
   return PostScriptEvaluator;
 })();
 
 
+var DEFAULT_ICON_SIZE = 22; // px
 var HIGHLIGHT_OFFSET = 4; // px
 var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
 
 var Annotation = (function AnnotationClosure() {
   // 12.5.5: Algorithm: Appearance streams
   function getTransformMatrix(rect, bbox, matrix) {
     var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
     var minX = bounds[0];
@@ -2965,41 +2971,39 @@ var Annotation = (function AnnotationClo
       var data = this.data;
       return !!(!this.isInvisible() &&
                 data &&
                 data.annotationFlags &&              // Default: not printable
                 data.annotationFlags & 0x4 &&        // Print
                 data.rect);                          // rectangle is nessessary
     },
 
-    loadResources: function(keys) {
-      var promise = new LegacyPromise();
-      this.appearance.dict.getAsync('Resources').then(function(resources) {
-        if (!resources) {
-          promise.resolve();
-          return;
-        }
-        var objectLoader = new ObjectLoader(resources.map,
-                                            keys,
-                                            resources.xref);
-        objectLoader.load().then(function() {
-          promise.resolve(resources);
-        });
+    loadResources: function Annotation_loadResources(keys) {
+      return new Promise(function (resolve, reject) {
+        this.appearance.dict.getAsync('Resources').then(function (resources) {
+          if (!resources) {
+            resolve();
+            return;
+          }
+          var objectLoader = new ObjectLoader(resources.map,
+                                              keys,
+                                              resources.xref);
+          objectLoader.load().then(function() {
+            resolve(resources);
+          }, reject);
+        }, reject);
       }.bind(this));
-
-      return promise;
     },
 
     getOperatorList: function Annotation_getOperatorList(evaluator) {
-
-      var promise = new LegacyPromise();
+      var capability = createPromiseCapability();
 
       if (!this.appearance) {
-        promise.resolve(new OperatorList());
-        return promise;
+        capability.resolve(new OperatorList());
+        return capability.promise;
       }
 
       var data = this.data;
 
       var appearanceDict = this.appearance.dict;
       var resourcesPromise = this.loadResources([
         'ExtGState',
         'ColorSpace',
@@ -3014,22 +3018,22 @@ var Annotation = (function AnnotationClo
       var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
 
       resourcesPromise.then(function(resources) {
         var opList = new OperatorList();
         opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
         evaluator.getOperatorList(this.appearance, resources, opList);
         opList.addOp(OPS.endAnnotation, []);
-        promise.resolve(opList);
+        capability.resolve(opList);
 
         this.appearance.reset();
-      }.bind(this));
-
-      return promise;
+      }.bind(this), capability.reject);
+
+      return capability.promise;
     }
   };
 
   Annotation.getConstructor =
       function Annotation_getConstructor(subtype, fieldType) {
 
     if (!subtype) {
       return;
@@ -3099,40 +3103,40 @@ var Annotation = (function AnnotationClo
       warn('unimplemented annotation type: ' + subtype);
     }
   };
 
   Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
       annotations, opList, pdfManager, partialEvaluator, intent) {
 
     function reject(e) {
-      annotationsReadyPromise.reject(e);
-    }
-
-    var annotationsReadyPromise = new LegacyPromise();
+      annotationsReadyCapability.reject(e);
+    }
+
+    var annotationsReadyCapability = createPromiseCapability();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
       if (intent === 'display' && annotations[i].isViewable() ||
           intent === 'print' && annotations[i].isPrintable()) {
         annotationPromises.push(
           annotations[i].getOperatorList(partialEvaluator));
       }
     }
     Promise.all(annotationPromises).then(function(datas) {
       opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
         var annotOpList = datas[i];
         opList.addOpList(annotOpList);
       }
       opList.addOp(OPS.endAnnotations, []);
-      annotationsReadyPromise.resolve();
+      annotationsReadyCapability.resolve();
     }, reject);
 
-    return annotationsReadyPromise;
+    return annotationsReadyCapability.promise;
   };
 
   return Annotation;
 })();
 PDFJS.Annotation = Annotation;
 
 
 var WidgetAnnotation = (function WidgetAnnotationClosure() {
@@ -3268,27 +3272,25 @@ var TextWidgetAnnotation = (function Tex
       return element;
     },
 
     getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
       if (this.appearance) {
         return Annotation.prototype.getOperatorList.call(this, evaluator);
       }
 
-      var promise = new LegacyPromise();
       var opList = new OperatorList();
       var data = this.data;
 
       // Even if there is an appearance stream, ignore it. This is the
       // behaviour used by Adobe Reader.
 
       var defaultAppearance = data.defaultAppearance;
       if (!defaultAppearance) {
-        promise.resolve(opList);
-        return promise;
+        return Promise.resolve(opList);
       }
 
       // Include any font resources found in the default appearance
 
       var stream = new Stream(stringToBytes(defaultAppearance));
       evaluator.getOperatorList(stream, this.fieldResources, opList);
       var appearanceFnArray = opList.fnArray;
       var appearanceArgsArray = opList.argsArray;
@@ -3313,18 +3315,17 @@ var TextWidgetAnnotation = (function Tex
           }
         } else if (fnId === OPS.setFillRGBColor) {
           data.rgb = args;
         } else if (fnId === OPS.setFillGray) {
           var rgbValue = args[0] * 255;
           data.rgb = [rgbValue, rgbValue, rgbValue];
         }
       }
-      promise.resolve(opList);
-      return promise;
+      return Promise.resolve(opList);
     }
   });
 
   return TextWidgetAnnotation;
 })();
 
 var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
   function InteractiveAnnotation(params) {
@@ -3395,16 +3396,18 @@ var TextAnnotation = (function TextAnnot
     var content = dict.get('Contents');
     var title = dict.get('T');
     data.content = stringToPDFString(content || '');
     data.title = stringToPDFString(title || '');
 
     if (data.hasAppearance) {
       data.name = 'NoIcon';
     } else {
+      data.rect[1] = data.rect[3] - DEFAULT_ICON_SIZE;
+      data.rect[2] = data.rect[0] + DEFAULT_ICON_SIZE;
       data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
     }
 
     if (dict.has('C')) {
       data.hasBgColor = true;
     }
   }
 
@@ -3777,17 +3780,17 @@ PDFJS.verbosity = (PDFJS.verbosity === u
  *
  * @return {Promise} A promise that is resolved with {@link PDFDocumentProxy}
  *   object.
  */
 PDFJS.getDocument = function getDocument(source,
                                          pdfDataRangeTransport,
                                          passwordCallback,
                                          progressCallback) {
-  var workerInitializedPromise, workerReadyPromise, transport;
+  var workerInitializedCapability, workerReadyCapability, transport;
 
   if (typeof source === 'string') {
     source = { url: source };
   } else if (isArrayBuffer(source)) {
     source = { data: source };
   } else if (typeof source !== 'object') {
     error('Invalid parameter in getDocument, need either Uint8Array, ' +
           'string or a parameter object');
@@ -3802,25 +3805,26 @@ PDFJS.getDocument = function getDocument
   for (var key in source) {
     if (key === 'url' && typeof window !== 'undefined') {
       params[key] = combineUrl(window.location.href, source[key]);
       continue;
     }
     params[key] = source[key];
   }
 
-  workerInitializedPromise = new PDFJS.LegacyPromise();
-  workerReadyPromise = new PDFJS.LegacyPromise();
-  transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise,
-                                  pdfDataRangeTransport, progressCallback);
-  workerInitializedPromise.then(function transportInitialized() {
+  workerInitializedCapability = createPromiseCapability();
+  workerReadyCapability = createPromiseCapability();
+  transport = new WorkerTransport(workerInitializedCapability,
+                                  workerReadyCapability, pdfDataRangeTransport,
+                                  progressCallback);
+  workerInitializedCapability.promise.then(function transportInitialized() {
     transport.passwordCallback = passwordCallback;
     transport.fetchDocument(params);
   });
-  return workerReadyPromise;
+  return workerReadyCapability.promise;
 };
 
 /**
  * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
  * properties that can be read synchronously.
  * @class
  */
 var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
@@ -3873,20 +3877,17 @@ var PDFDocumentProxy = (function PDFDocu
     getAttachments: function PDFDocumentProxy_getAttachments() {
       return this.transport.getAttachments();
     },
     /**
      * @return {Promise} A promise that is resolved with an array of all the
      * JavaScript strings in the name tree.
      */
     getJavaScript: function PDFDocumentProxy_getJavaScript() {
-      var promise = new PDFJS.LegacyPromise();
-      var js = this.pdfInfo.javaScript;
-      promise.resolve(js);
-      return promise;
+      return this.transport.getJavaScript();
     },
     /**
      * @return {Promise} A promise that is resolved with an {Array} that is a
      * tree outline (if it has one) of the PDF. The tree is in the format of:
      * [
      *  {
      *   title: string,
      *   bold: boolean,
@@ -3894,53 +3895,43 @@ var PDFDocumentProxy = (function PDFDocu
      *   color: rgb array,
      *   dest: dest obj,
      *   items: array of more items like this
      *  },
      *  ...
      * ].
      */
     getOutline: function PDFDocumentProxy_getOutline() {
-      var promise = new PDFJS.LegacyPromise();
-      var outline = this.pdfInfo.outline;
-      promise.resolve(outline);
-      return promise;
+      return this.transport.getOutline();
     },
     /**
      * @return {Promise} A promise that is resolved with an {Object} that has
      * info and metadata properties.  Info is an {Object} filled with anything
      * available in the information dictionary and similarly metadata is a
      * {Metadata} object with information from the metadata section of the PDF.
      */
     getMetadata: function PDFDocumentProxy_getMetadata() {
-      var promise = new PDFJS.LegacyPromise();
-      var info = this.pdfInfo.info;
-      var metadata = this.pdfInfo.metadata;
-      promise.resolve({
-        info: info,
-        metadata: (metadata ? new PDFJS.Metadata(metadata) : null)
-      });
-      return promise;
+      return this.transport.getMetadata();
     },
     /**
      * @return {Promise} A promise that is resolved with a TypedArray that has
      * the raw data from the PDF.
      */
     getData: function PDFDocumentProxy_getData() {
-      var promise = new PDFJS.LegacyPromise();
-      this.transport.getData(promise);
-      return promise;
+      var capability = createPromiseCapability();
+      this.transport.getData(capability);
+      return capability.promise;
     },
     /**
      * @return {Promise} A promise that is resolved when the document's data
      * is loaded. It is resolved with an {Object} that contains the length
      * property that indicates size of the PDF data in bytes.
      */
     getDownloadInfo: function PDFDocumentProxy_getDownloadInfo() {
-      return this.transport.downloadInfoPromise;
+      return this.transport.downloadInfoCapability.promise;
     },
     /**
      * Cleans up resources allocated by the document, e.g. created @font-face.
      */
     cleanup: function PDFDocumentProxy_cleanup() {
       this.transport.startCleanup();
     },
     /**
@@ -4057,24 +4048,24 @@ var PDFPageProxy = (function PDFPageProx
       }
       return new PDFJS.PageViewport(this.view, scale, rotate, 0, 0);
     },
     /**
      * @return {Promise} A promise that is resolved with an {Array} of the
      * annotation objects.
      */
     getAnnotations: function PDFPageProxy_getAnnotations() {
-      if (this.annotationsPromise) {
-        return this.annotationsPromise;
-      }
-
-      var promise = new PDFJS.LegacyPromise();
-      this.annotationsPromise = promise;
+      if (this.annotationsCapability) {
+        return this.annotationsCapability.promise;
+      }
+
+      var capability = createPromiseCapability();
+      this.annotationsCapability = capability;
       this.transport.getAnnotations(this.pageInfo.pageIndex);
-      return promise;
+      return capability.promise;
     },
     /**
      * Begins the process of rendering a page to the desired context.
      * @param {RenderParameters} params Page render parameters.
      * @return {RenderTask} An object that contains the promise, which
      *                      is resolved when the page finishes rendering.
      */
     render: function PDFPageProxy_render(params) {
@@ -4088,21 +4079,21 @@ var PDFPageProxy = (function PDFPageProx
       var renderingIntent = ('intent' in params ?
         (params.intent == 'print' ? 'print' : 'display') : 'display');
 
       if (!this.intentStates[renderingIntent]) {
         this.intentStates[renderingIntent] = {};
       }
       var intentState = this.intentStates[renderingIntent];
 
-      // If there is no displayReadyPromise yet, then the operatorList was never
-      // requested before. Make the request and create the promise.
-      if (!intentState.displayReadyPromise) {
+      // If there's no displayReadyCapability yet, then the operatorList
+      // was never requested before. Make the request and create the promise.
+      if (!intentState.displayReadyCapability) {
         intentState.receivingOperatorList = true;
-        intentState.displayReadyPromise = new LegacyPromise();
+        intentState.displayReadyCapability = createPromiseCapability();
         intentState.operatorList = {
           fnArray: [],
           argsArray: [],
           lastChunk: false
         };
 
         this.stats.time('Page Request');
         this.transport.messageHandler.send('RenderPageRequest', {
@@ -4118,17 +4109,17 @@ var PDFPageProxy = (function PDFPageProx
                                                       this.pageNumber);
       if (!intentState.renderTasks) {
         intentState.renderTasks = [];
       }
       intentState.renderTasks.push(internalRenderTask);
       var renderTask = new RenderTask(internalRenderTask);
 
       var self = this;
-      intentState.displayReadyPromise.then(
+      intentState.displayReadyCapability.promise.then(
         function pageDisplayReadyPromise(transparency) {
           if (self.pendingDestroy) {
             complete();
             return;
           }
           stats.time('Rendering');
           internalRenderTask.initalizeGraphics(transparency);
           internalRenderTask.operatorListChanged();
@@ -4145,40 +4136,40 @@ var PDFPageProxy = (function PDFPageProx
         }
 
         if (self.cleanupAfterRender) {
           self.pendingDestroy = true;
         }
         self._tryDestroy();
 
         if (error) {
-          renderTask.promise.reject(error);
+          internalRenderTask.capability.reject(error);
         } else {
-          renderTask.promise.resolve();
+          internalRenderTask.capability.resolve();
         }
         stats.timeEnd('Rendering');
         stats.timeEnd('Overall');
       }
 
       return renderTask;
     },
     /**
      * @return {Promise} That is resolved a {@link TextContent}
      * object that represent the page text content.
      */
     getTextContent: function PDFPageProxy_getTextContent() {
-      var promise = new PDFJS.LegacyPromise();
-      this.transport.messageHandler.send('GetTextContent', {
-          pageIndex: this.pageNumber - 1
-        },
-        function textContentCallback(textContent) {
-          promise.resolve(textContent);
-        }
-      );
-      return promise;
+      return new Promise(function (resolve) {
+        this.transport.messageHandler.send('GetTextContent', {
+            pageIndex: this.pageNumber - 1
+          },
+          function textContentCallback(textContent) {
+            resolve(textContent);
+          }
+        );
+      }.bind(this));
     },
     /**
      * Destroys resources allocated by the page.
      */
     destroy: function PDFPageProxy_destroy() {
       this.pendingDestroy = true;
       this._tryDestroy();
     },
@@ -4205,17 +4196,17 @@ var PDFPageProxy = (function PDFPageProx
     },
     /**
      * For internal use only.
      * @ignore
      */
     _startRenderPage: function PDFPageProxy_startRenderPage(transparency,
                                                             intent) {
       var intentState = this.intentStates[intent];
-      intentState.displayReadyPromise.resolve(transparency);
+      intentState.displayReadyCapability.resolve(transparency);
     },
     /**
      * For internal use only.
      * @ignore
      */
     _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk,
                                                             intent) {
       var intentState = this.intentStates[intent];
@@ -4242,27 +4233,27 @@ var PDFPageProxy = (function PDFPageProx
   return PDFPageProxy;
 })();
 
 /**
  * For internal use only.
  * @ignore
  */
 var WorkerTransport = (function WorkerTransportClosure() {
-  function WorkerTransport(workerInitializedPromise, workerReadyPromise,
+  function WorkerTransport(workerInitializedCapability, workerReadyCapability,
                            pdfDataRangeTransport, progressCallback) {
     this.pdfDataRangeTransport = pdfDataRangeTransport;
 
-    this.workerReadyPromise = workerReadyPromise;
+    this.workerReadyCapability = workerReadyCapability;
     this.progressCallback = progressCallback;
     this.commonObjs = new PDFObjects();
 
     this.pageCache = [];
-    this.pagePromises = [];
-    this.downloadInfoPromise = new PDFJS.LegacyPromise();
+    this.pageCapabilities = [];
+    this.downloadInfoCapability = createPromiseCapability();
     this.passwordCallback = null;
 
     // If worker support isn't disabled explicit and the browser has worker
     // support, create a new web worker and test if it/the browser fullfills
     // all requirements to run parts of pdf.js in a web worker.
     // Right now, the requirement is, that an Uint8Array is still an Uint8Array
     // as it arrives on the worker. Chrome added this with version 15.
     if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
@@ -4281,22 +4272,22 @@ var WorkerTransport = (function WorkerTr
         messageHandler.on('test', function transportTest(data) {
           var supportTypedArray = data && data.supportTypedArray;
           if (supportTypedArray) {
             this.worker = worker;
             if (!data.supportTransfers) {
               PDFJS.postMessageTransfers = false;
             }
             this.setupMessageHandler(messageHandler);
-            workerInitializedPromise.resolve();
+            workerInitializedCapability.resolve();
           } else {
             globalScope.PDFJS.disableWorker = true;
             this.loadFakeWorkerFiles().then(function() {
               this.setupFakeWorker();
-              workerInitializedPromise.resolve();
+              workerInitializedCapability.resolve();
             }.bind(this));
           }
         }.bind(this));
 
         var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
         // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
         // typed array. Also, checking if we can use transfers.
         try {
@@ -4311,43 +4302,43 @@ var WorkerTransport = (function WorkerTr
         info('The worker has been disabled.');
       }
     }
     // Either workers are disabled, not supported or have thrown an exception.
     // Thus, we fallback to a faked worker.
     globalScope.PDFJS.disableWorker = true;
     this.loadFakeWorkerFiles().then(function() {
       this.setupFakeWorker();
-      workerInitializedPromise.resolve();
+      workerInitializedCapability.resolve();
     }.bind(this));
   }
   WorkerTransport.prototype = {
     destroy: function WorkerTransport_destroy() {
       this.pageCache = [];
-      this.pagePromises = [];
+      this.pageCapabilities = [];
       var self = this;
       this.messageHandler.send('Terminate', null, function () {
         FontLoader.clear();
         if (self.worker) {
           self.worker.terminate();
         }
       });
     },
 
     loadFakeWorkerFiles: function WorkerTransport_loadFakeWorkerFiles() {
-      if (!PDFJS.fakeWorkerFilesLoadedPromise) {
-        PDFJS.fakeWorkerFilesLoadedPromise = new LegacyPromise();
+      if (!PDFJS.fakeWorkerFilesLoadedCapability) {
+        PDFJS.fakeWorkerFilesLoadedCapability = createPromiseCapability();
         // In the developer build load worker_loader which in turn loads all the
         // other files and resolves the promise. In production only the
         // pdf.worker.js file is needed.
         Util.loadScript(PDFJS.workerSrc, function() {
-          PDFJS.fakeWorkerFilesLoadedPromise.resolve();
+          PDFJS.fakeWorkerFilesLoadedCapability.resolve();
         });
       }
-      return PDFJS.fakeWorkerFilesLoadedPromise;
+      return PDFJS.fakeWorkerFilesLoadedCapability.promise;
     },
 
     setupFakeWorker: function WorkerTransport_setupFakeWorker() {
       warn('Setting up fake worker.');
       // If we don't use a worker, just post/sendMessage to the main thread.
       var fakeWorker = {
         postMessage: function WorkerTransport_postMessage(obj) {
           fakeWorker.onmessage({data: obj});
@@ -4392,62 +4383,66 @@ var WorkerTransport = (function WorkerTr
           }, this);
       }
 
       messageHandler.on('GetDoc', function transportDoc(data) {
         var pdfInfo = data.pdfInfo;
         this.numPages = data.pdfInfo.numPages;
         var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
         this.pdfDocument = pdfDocument;
-        this.workerReadyPromise.resolve(pdfDocument);
+        this.workerReadyCapability.resolve(pdfDocument);
       }, this);
 
       messageHandler.on('NeedPassword', function transportPassword(data) {
         if (this.passwordCallback) {
           return this.passwordCallback(updatePassword,
                                        PasswordResponses.NEED_PASSWORD);
         }
-        this.workerReadyPromise.reject(data.exception.message, data.exception);
+        this.workerReadyCapability.reject(data.exception.message,
+                                          data.exception);
       }, this);
 
       messageHandler.on('IncorrectPassword', function transportBadPass(data) {
         if (this.passwordCallback) {
           return this.passwordCallback(updatePassword,
                                        PasswordResponses.INCORRECT_PASSWORD);
         }
-        this.workerReadyPromise.reject(data.exception.message, data.exception);
+        this.workerReadyCapability.reject(data.exception.message,
+                                          data.exception);
       }, this);
 
       messageHandler.on('InvalidPDF', function transportInvalidPDF(data) {
-        this.workerReadyPromise.reject(data.exception.name, data.exception);
+        this.workerReadyCapability.reject(data.exception.name, data.exception);
       }, this);
 
       messageHandler.on('MissingPDF', function transportMissingPDF(data) {
-        this.workerReadyPromise.reject(data.exception.message, data.exception);
+        this.workerReadyCapability.reject(data.exception.message,
+                                          data.exception);
       }, this);
 
       messageHandler.on('UnknownError', function transportUnknownError(data) {
-        this.workerReadyPromise.reject(data.exception.message, data.exception);
+        this.workerReadyCapability.reject(data.exception.message,
+                                          data.exception);
       }, this);
 
       messageHandler.on('DataLoaded', function transportPage(data) {
-        this.downloadInfoPromise.resolve(data);
+        this.downloadInfoCapability.resolve(data);
       }, this);
 
       messageHandler.on('GetPage', function transportPage(data) {
         var pageInfo = data.pageInfo;
         var page = new PDFPageProxy(pageInfo, this);
         this.pageCache[pageInfo.pageIndex] = page;
-        var promise = this.pagePromises[pageInfo.pageIndex];
+        var promise = this.pageCapabilities[pageInfo.pageIndex];
         promise.resolve(page);
       }, this);
 
       messageHandler.on('GetAnnotations', function transportAnnotations(data) {
         var annotations = data.annotations;
-        var promise = this.pageCache[data.pageIndex].annotationsPromise;
+        var promise = this.pageCache[data.pageIndex].annotationsCapability;
         promise.resolve(annotations);
       }, this);
 
       messageHandler.on('StartRenderPage', function transportRender(data) {
         var page = this.pageCache[data.pageIndex];
 
         page.stats.timeEnd('Page Request');
         page._startRenderPage(data.transparency, data.intent);
@@ -4531,24 +4526,24 @@ var WorkerTransport = (function WorkerTr
           this.progressCallback({
             loaded: data.loaded,
             total: data.total
           });
         }
       }, this);
 
       messageHandler.on('DocError', function transportDocError(data) {
-        this.workerReadyPromise.reject(data);
+        this.workerReadyCapability.reject(data);
       }, this);
 
       messageHandler.on('PageError', function transportError(data, intent) {
         var page = this.pageCache[data.pageNum - 1];
         var intentState = page.intentStates[intent];
-        if (intentState.displayReadyPromise) {
-          intentState.displayReadyPromise.reject(data.error);
+        if (intentState.displayReadyCapability.promise) {
+          intentState.displayReadyCapability.reject(data.error);
         } else {
           error(data.error);
         }
       }, this);
 
       messageHandler.on('JpegDecode', function(data, deferred) {
         var imageUrl = data[0];
         var components = data[1];
@@ -4596,73 +4591,104 @@ var WorkerTransport = (function WorkerTr
         cMapUrl: PDFJS.cMapUrl,
         cMapPacked: PDFJS.cMapPacked,
         disableFontFace: PDFJS.disableFontFace,
         disableCreateObjectURL: PDFJS.disableCreateObjectURL,
         verbosity: PDFJS.verbosity
       });
     },
 
-    getData: function WorkerTransport_getData(promise) {
+    getData: function WorkerTransport_getData(capability) {
       this.messageHandler.send('GetData', null, function(data) {
-        promise.resolve(data);
+        capability.resolve(data);
       });
     },
 
-    getPage: function WorkerTransport_getPage(pageNumber, promise) {
+    getPage: function WorkerTransport_getPage(pageNumber, capability) {
       if (pageNumber <= 0 || pageNumber > this.numPages ||
           (pageNumber|0) !== pageNumber) {
-        var pagePromise = new PDFJS.LegacyPromise();
-        pagePromise.reject(new Error('Invalid page request'));
-        return pagePromise;
+        return new Promise.reject(new Error('Invalid page request'));
       }
 
       var pageIndex = pageNumber - 1;
-      if (pageIndex in this.pagePromises) {
-        return this.pagePromises[pageIndex];
-      }
-      promise = new PDFJS.LegacyPromise();
-      this.pagePromises[pageIndex] = promise;
+      if (pageIndex in this.pageCapabilities) {
+        return this.pageCapabilities[pageIndex].promise;
+      }
+      capability = createPromiseCapability();
+      this.pageCapabilities[pageIndex] = capability;
       this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
-      return promise;
+      return capability.promise;
     },
 
     getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
-      var promise = new PDFJS.LegacyPromise();
-      this.messageHandler.send('GetPageIndex', { ref: ref },
-        function (pageIndex) {
-          promise.resolve(pageIndex);
-        }
-      );
-      return promise;
+      return new Promise(function (resolve) {
+        this.messageHandler.send('GetPageIndex', { ref: ref },
+          function (pageIndex) {
+            resolve(pageIndex);
+          }
+        );
+      }.bind(this));
     },
 
     getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
       this.messageHandler.send('GetAnnotationsRequest',
         { pageIndex: pageIndex });
     },
 
     getDestinations: function WorkerTransport_getDestinations() {
-      var promise = new PDFJS.LegacyPromise();
-      this.messageHandler.send('GetDestinations', null,
-        function transportDestinations(destinations) {
-          promise.resolve(destinations);
-        }
-      );
-      return promise;
+      return new Promise(function (resolve) {
+        this.messageHandler.send('GetDestinations', null,
+          function transportDestinations(destinations) {
+            resolve(destinations);
+          }
+        );
+      }.bind(this));
     },
 
     getAttachments: function WorkerTransport_getAttachments() {
-      var promise = new PDFJS.LegacyPromise();
-      this.messageHandler.send('GetAttachments', null,
-        function transportAttachments(attachments) {
-          promise.resolve(attachments);
-        }
-      );
-      return promise;
+      return new Promise(function (resolve) {
+        this.messageHandler.send('GetAttachments', null,
+          function transportAttachments(attachments) {
+            resolve(attachments);
+          }
+        );
+      }.bind(this));
+    },
+
+    getJavaScript: function WorkerTransport_getJavaScript() {
+      return new Promise(function (resolve) {
+        this.messageHandler.send('GetJavaScript', null,
+          function transportJavaScript(js) {
+            resolve(js);
+          }
+        );
+      }.bind(this));
+    },
+
+    getOutline: function WorkerTransport_getOutline() {
+      return new Promise(function (resolve) {
+        this.messageHandler.send('GetOutline', null,
+          function transportOutline(outline) {
+            resolve(outline);
+          }
+        );
+      }.bind(this));
+    },
+
+    getMetadata: function WorkerTransport_getMetadata() {
+      return new Promise(function (resolve) {
+        this.messageHandler.send('GetMetadata', null,
+          function transportMetadata(results) {
+            resolve({
+              info: results[0],
+              metadata: (results[1] ? new PDFJS.Metadata(results[1]) : null)
+            });
+          }
+        );
+      }.bind(this));
     },
 
     startCleanup: function WorkerTransport_startCleanup() {
       this.messageHandler.send('Cleanup', null,
         function endCleanup() {
           for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
             var page = this.pageCache[i];
             if (page) {
@@ -4697,17 +4723,17 @@ var PDFObjects = (function PDFObjectsClo
      * Ensures there is an object defined for `objId`.
      */
     ensureObj: function PDFObjects_ensureObj(objId) {
       if (this.objs[objId]) {
         return this.objs[objId];
       }
 
       var obj = {
-        promise: new LegacyPromise(),
+        capability: createPromiseCapability(),
         data: null,
         resolved: false
       };
       this.objs[objId] = obj;
 
       return obj;
     },
 
@@ -4719,17 +4745,17 @@ var PDFObjects = (function PDFObjectsClo
      * object once the object is resolved. That means, if you call this
      * function and the object is already resolved, the callback gets called
      * right away.
      */
     get: function PDFObjects_get(objId, callback) {
       // If there is a callback, then the get can be async and the object is
       // not required to be resolved right now
       if (callback) {
-        this.ensureObj(objId).promise.then(callback);
+        this.ensureObj(objId).capability.promise.then(callback);
         return null;
       }
 
       // If there isn't a callback, the user expects to get the resolved data
       // directly.
       var obj = this.objs[objId];
 
       // If there isn't an object yet or the object isn't resolved, then the
@@ -4744,17 +4770,17 @@ var PDFObjects = (function PDFObjectsClo
     /**
      * Resolves the object `objId` with optional `data`.
      */
     resolve: function PDFObjects_resolve(objId, data) {
       var obj = this.ensureObj(objId);
 
       obj.resolved = true;
       obj.data = data;
-      obj.promise.resolve(data);
+      obj.capability.resolve(data);
     },
 
     isResolved: function PDFObjects_isResolved(objId) {
       var objs = this.objs;
 
       if (!objs[objId]) {
         return false;
       } else {
@@ -4791,28 +4817,27 @@ var PDFObjects = (function PDFObjectsClo
  */
 var RenderTask = (function RenderTaskClosure() {
   function RenderTask(internalRenderTask) {
     this.internalRenderTask = internalRenderTask;
     /**
      * Promise for rendering task completion.
      * @type {Promise}
      */
-    this.promise = new PDFJS.LegacyPromise();
+    this.promise = this.internalRenderTask.capability.promise;
   }
 
   RenderTask.prototype = /** @lends RenderTask.prototype */ {
     /**
      * Cancels the rendering task. If the task is currently rendering it will
      * not be cancelled until graphics pauses with a timeout. The promise that
      * this object extends will resolved when cancelled.
      */
     cancel: function RenderTask_cancel() {
       this.internalRenderTask.cancel();
-      this.promise.reject(new Error('Rendering is cancelled'));
     },
 
     /**
      * Registers callback to indicate the rendering task completion.
      *
      * @param {function} onFulfilled The callback for the rendering completion.
      * @param {function} onRejected The callback for the rendering failure.
      * @return {Promise} A promise that is resolved after the onFulfilled or
@@ -4840,16 +4865,17 @@ var InternalRenderTask = (function Inter
     this.commonObjs = commonObjs;
     this.operatorListIdx = null;
     this.operatorList = operatorList;
     this.pageNumber = pageNumber;
     this.running = false;
     this.graphicsReadyCallback = null;
     this.graphicsReady = false;
     this.cancelled = false;
+    this.capability = createPromiseCapability();
   }
 
   InternalRenderTask.prototype = {
 
     initalizeGraphics:
         function InternalRenderTask_initalizeGraphics(transparency) {
 
       if (this.cancelled) {
@@ -5499,34 +5525,43 @@ var CanvasGraphics = (function CanvasGra
           dest32[destPos++] = 0;
         }
 
         ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
       }
     } else if (imgData.kind === ImageKind.RGBA_32BPP) {
       // RGBA, 32-bits per pixel.
 
-      for (i = 0; i < totalChunks; i++) {
-        thisChunkHeight =
-          (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
-        elemsInThisChunk = imgData.width * thisChunkHeight * 4;
-
+      j = 0;
+      elemsInThisChunk = width * fullChunkHeight * 4;
+      for (i = 0; i < fullChunks; i++) {
         dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
         srcPos += elemsInThisChunk;
 
-        ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
-      }
+        ctx.putImageData(chunkImgData, 0, j);
+        j += fullChunkHeight;
+      }
+      if (i < totalChunks) {
+        elemsInThisChunk = width * partialChunkHeight * 4;
+        dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk));
+        ctx.putImageData(chunkImgData, 0, j);
+      }
+
     } else if (imgData.kind === ImageKind.RGB_24BPP) {
       // RGB, 24-bits per pixel.
+      thisChunkHeight = fullChunkHeight;
+      elemsInThisChunk = width * thisChunkHeight;
       for (i = 0; i < totalChunks; i++) {
-        thisChunkHeight =
-          (i < fullChunks) ? fullChunkHeight : partialChunkHeight;
-        elemsInThisChunk = imgData.width * thisChunkHeight * 3;
+        if (i >= fullChunks) {
+          thisChunkHeight =partialChunkHeight;
+          elemsInThisChunk = width * thisChunkHeight;
+        }
+
         destPos = 0;
-        for (j = 0; j < elemsInThisChunk; j += 3) {
+        for (j = elemsInThisChunk; j--;) {
           dest[destPos++] = src[srcPos++];
           dest[destPos++] = src[srcPos++];
           dest[destPos++] = src[srcPos++];
           dest[destPos++] = 255;
         }
         ctx.putImageData(chunkImgData, 0, i * fullChunkHeight);
       }
     } else {
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -16,18 +16,18 @@
  */
 /*jshint globalstrict: false */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.68';
-PDFJS.build = 'ead4cbf';
+PDFJS.version = '1.0.162';
+PDFJS.build = 'abc924b';
 
 (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
@@ -901,32 +901,39 @@ function isPDFFunction(v) {
     fnDict = v.dict;
   } else {
     return false;
   }
   return fnDict.has('FunctionType');
 }
 
 /**
- * Legacy support for PDFJS Promise implementation.
- * TODO remove eventually
- * @ignore
+ * Promise Capability object.
+ *
+ * @typedef {Object} PromiseCapability
+ * @property {Promise} promise - A promise object.
+ * @property {function} resolve - Fullfills the promise.
+ * @property {function} reject - Rejects the promise.
  */
-var LegacyPromise = PDFJS.LegacyPromise = (function LegacyPromiseClosure() {
-  return function LegacyPromise() {
-    var resolve, reject;
-    var promise = new Promise(function (resolve_, reject_) {
-      resolve = resolve_;
-      reject = reject_;
-    });
-    promise.resolve = resolve;
-    promise.reject = reject;
-    return promise;
-  };
-})();
+
+/**
+ * Creates a promise capability object.
+ * @return {PromiseCapability} A capability object contains:
+ * - a Promise, resolve and reject methods.
+ */
+function createPromiseCapability() {
+  var capability = {};
+  capability.promise = new Promise(function (resolve, reject) {
+    capability.resolve = resolve;
+    capability.reject = reject;
+  });
+  return capability;
+}
+
+PDFJS.createPromiseCapability = createPromiseCapability;
 
 /**
  * Polyfill for Promises:
  * The following promise implementation tries to generally implment the
  * Promise/A+ spec. Some notable differences from other promise libaries are:
  * - There currently isn't a seperate deferred and promise object.
  * - Unhandled rejections eventually show an error if they aren't handled.
  *
@@ -955,18 +962,25 @@ var LegacyPromise = PDFJS.LegacyPromise 
         });
         if (count === 0) {
           resolve(results);
         }
         return promise;
       };
     }
     if (typeof globalScope.Promise.resolve !== 'function') {
-      globalScope.Promise.resolve = function (x) {
-        return new globalScope.Promise(function (resolve) { resolve(x); });
+      globalScope.Promise.resolve = function (value) {
+        return new globalScope.Promise(function (resolve) { resolve(value); });
+      };
+    }
+    if (typeof globalScope.Promise.reject !== 'function') {
+      globalScope.Promise.reject = function (reason) {
+        return new globalScope.Promise(function (resolve, reject) {
+          reject(reason);
+        });
       };
     }
     return;
   }
   throw new Error('DOM Promise is not present');
 })();
 
 var StatTimer = (function StatTimerClosure() {
@@ -1283,26 +1297,27 @@ var ColorSpace = (function ColorSpaceClo
           rgbBuf = new Uint8Array(count * 3);
           this.getRgbBuffer(comps, 0, count, rgbBuf, 0, bpc,
                             /* alpha01 = */ 0);
         }
       }
 
       if (rgbBuf) {
         if (needsResizing) {
-          rgbBuf = PDFImage.resize(rgbBuf, bpc, 3, originalWidth,
-                                   originalHeight, width, height);
-        }
-        rgbPos = 0;
-        destPos = 0;
-        for (i = 0, ii = width * actualHeight; i < ii; i++) {
-          dest[destPos++] = rgbBuf[rgbPos++];
-          dest[destPos++] = rgbBuf[rgbPos++];
-          dest[destPos++] = rgbBuf[rgbPos++];
-          destPos += alpha01;
+          PDFImage.resize(rgbBuf, bpc, 3, originalWidth, originalHeight, width,
+                          height, dest, alpha01);
+        } else {
+          rgbPos = 0;
+          destPos = 0;
+          for (i = 0, ii = width * actualHeight; i < ii; i++) {
+            dest[destPos++] = rgbBuf[rgbPos++];
+            dest[destPos++] = rgbBuf[rgbPos++];
+            dest[destPos++] = rgbBuf[rgbPos++];
+            destPos += alpha01;
+          }
         }
       }
     },
     /**
      * True if the colorspace has components in the default range of [0, 1].
      * This should be true for all colorspaces except for lab color spaces
      * which are [0,100], [-128, 127], [-128, 127].
      */
@@ -1884,35 +1899,25 @@ var CalGrayCS = (function CalGrayCSClosu
   }
 
   function convertToRgb(cs, src, srcOffset, dest, destOffset, scale) {
     // A represents a gray component of a calibrated gray space.
     // A <---> AG in the spec
     var A = src[srcOffset] * scale;
     var AG = Math.pow(A, cs.G);
 
-    // Computes intermediate variables M, L, N as per spec.
+    // Computes L as per spec. ( = cs.YW * AG )
     // Except if other than default BlackPoint values are used.
-    var M = cs.XW * AG;
     var L = cs.YW * AG;
-    var N = cs.ZW * AG;
-
-    // Decode XYZ, as per spec.
-    var X = M;
-    var Y = L;
-    var Z = N;
-
     // http://www.poynton.com/notes/colour_and_gamma/ColorFAQ.html, Ch 4.
-    // This yields values in range [0, 100].
-    var Lstar = Math.max(116 * Math.pow(Y, 1 / 3) - 16, 0);
-
     // Convert values to rgb range [0, 255].
-    dest[destOffset] = Lstar * 255 / 100;
-    dest[destOffset + 1] = Lstar * 255 / 100;
-    dest[destOffset + 2] = Lstar * 255 / 100;
+    var val = Math.max(295.8 * Math.pow(L, 0.333333333333333333) - 40.8, 0) | 0;
+    dest[destOffset] = val;
+    dest[destOffset + 1] = val;
+    dest[destOffset + 2] = val;
   }
 
   CalGrayCS.prototype = {
     getRgb: ColorSpace.prototype.getRgb,
     getRgbItem: function CalGrayCS_getRgbItem(src, srcOffset,
                                               dest, destOffset) {
       convertToRgb(this, src, srcOffset, dest, destOffset, 1);
     },
@@ -2719,24 +2724,24 @@ var PostScriptEvaluator = (function Post
             break;
           case 'ne':
             b = stack.pop();
             a = stack.pop();
             stack.push(a != b);
             break;
           case 'neg':
             a = stack.pop();
-            stack.push(-b);
+            stack.push(-a);
             break;
           case 'not':
             a = stack.pop();
-            if (isBool(a) && isBool(b)) {
-              stack.push(a && b);
+            if (isBool(a)) {
+              stack.push(!a);
             } else {
-              stack.push(a & b);
+              stack.push(~a);
             }
             break;
           case 'or':
             b = stack.pop();
             a = stack.pop();
             if (isBool(a) && isBool(b)) {
               stack.push(a || b);
             } else {
@@ -2792,16 +2797,17 @@ var PostScriptEvaluator = (function Post
       }
       return stack.stack;
     }
   };
   return PostScriptEvaluator;
 })();
 
 
+var DEFAULT_ICON_SIZE = 22; // px
 var HIGHLIGHT_OFFSET = 4; // px
 var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
 
 var Annotation = (function AnnotationClosure() {
   // 12.5.5: Algorithm: Appearance streams
   function getTransformMatrix(rect, bbox, matrix) {
     var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
     var minX = bounds[0];
@@ -2965,41 +2971,39 @@ var Annotation = (function AnnotationClo
       var data = this.data;
       return !!(!this.isInvisible() &&
                 data &&
                 data.annotationFlags &&              // Default: not printable
                 data.annotationFlags & 0x4 &&        // Print
                 data.rect);                          // rectangle is nessessary
     },
 
-    loadResources: function(keys) {
-      var promise = new LegacyPromise();
-      this.appearance.dict.getAsync('Resources').then(function(resources) {
-        if (!resources) {
-          promise.resolve();
-          return;
-        }
-        var objectLoader = new ObjectLoader(resources.map,
-                                            keys,
-                                            resources.xref);
-        objectLoader.load().then(function() {
-          promise.resolve(resources);
-        });
+    loadResources: function Annotation_loadResources(keys) {
+      return new Promise(function (resolve, reject) {
+        this.appearance.dict.getAsync('Resources').then(function (resources) {
+          if (!resources) {
+            resolve();
+            return;
+          }
+          var objectLoader = new ObjectLoader(resources.map,
+                                              keys,
+                                              resources.xref);
+          objectLoader.load().then(function() {
+            resolve(resources);
+          }, reject);
+        }, reject);
       }.bind(this));
-
-      return promise;
     },
 
     getOperatorList: function Annotation_getOperatorList(evaluator) {
-
-      var promise = new LegacyPromise();
+      var capability = createPromiseCapability();
 
       if (!this.appearance) {
-        promise.resolve(new OperatorList());
-        return promise;
+        capability.resolve(new OperatorList());
+        return capability.promise;
       }
 
       var data = this.data;
 
       var appearanceDict = this.appearance.dict;
       var resourcesPromise = this.loadResources([
         'ExtGState',
         'ColorSpace',
@@ -3014,22 +3018,22 @@ var Annotation = (function AnnotationClo
       var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
 
       resourcesPromise.then(function(resources) {
         var opList = new OperatorList();
         opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
         evaluator.getOperatorList(this.appearance, resources, opList);
         opList.addOp(OPS.endAnnotation, []);
-        promise.resolve(opList);
+        capability.resolve(opList);
 
         this.appearance.reset();
-      }.bind(this));
-
-      return promise;
+      }.bind(this), capability.reject);
+
+      return capability.promise;
     }
   };
 
   Annotation.getConstructor =
       function Annotation_getConstructor(subtype, fieldType) {
 
     if (!subtype) {
       return;
@@ -3099,40 +3103,40 @@ var Annotation = (function AnnotationClo
       warn('unimplemented annotation type: ' + subtype);
     }
   };
 
   Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
       annotations, opList, pdfManager, partialEvaluator, intent) {
 
     function reject(e) {
-      annotationsReadyPromise.reject(e);
-    }
-
-    var annotationsReadyPromise = new LegacyPromise();
+      annotationsReadyCapability.reject(e);
+    }
+
+    var annotationsReadyCapability = createPromiseCapability();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
       if (intent === 'display' && annotations[i].isViewable() ||
           intent === 'print' && annotations[i].isPrintable()) {
         annotationPromises.push(
           annotations[i].getOperatorList(partialEvaluator));
       }
     }
     Promise.all(annotationPromises).then(function(datas) {
       opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
         var annotOpList = datas[i];
         opList.addOpList(annotOpList);
       }
       opList.addOp(OPS.endAnnotations, []);
-      annotationsReadyPromise.resolve();
+      annotationsReadyCapability.resolve();
     }, reject);
 
-    return annotationsReadyPromise;
+    return annotationsReadyCapability.promise;
   };
 
   return Annotation;
 })();
 PDFJS.Annotation = Annotation;
 
 
 var WidgetAnnotation = (function WidgetAnnotationClosure() {
@@ -3268,27 +3272,25 @@ var TextWidgetAnnotation = (function Tex
       return element;
     },
 
     getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
       if (this.appearance) {
         return Annotation.prototype.getOperatorList.call(this, evaluator);
       }
 
-      var promise = new LegacyPromise();
       var opList = new OperatorList();
       var data = this.data;
 
       // Even if there is an appearance stream, ignore it. This is the
       // behaviour used by Adobe Reader.
 
       var defaultAppearance = data.defaultAppearance;
       if (!defaultAppearance) {
-        promise.resolve(opList);
-        return promise;
+        return Promise.resolve(opList);
       }
 
       // Include any font resources found in the default appearance
 
       var stream = new Stream(stringToBytes(defaultAppearance));
       evaluator.getOperatorList(stream, this.fieldResources, opList);
       var appearanceFnArray = opList.fnArray;
       var appearanceArgsArray = opList.argsArray;
@@ -3313,18 +3315,17 @@ var TextWidgetAnnotation = (function Tex
           }
         } else if (fnId === OPS.setFillRGBColor) {
           data.rgb = args;
         } else if (fnId === OPS.setFillGray) {
           var rgbValue = args[0] * 255;
           data.rgb = [rgbValue, rgbValue, rgbValue];
         }
       }
-      promise.resolve(opList);
-      return promise;
+      return Promise.resolve(opList);
     }
   });
 
   return TextWidgetAnnotation;
 })();
 
 var InteractiveAnnotation = (function InteractiveAnnotationClosure() {
   function InteractiveAnnotation(params) {
@@ -3395,16 +3396,18 @@ var TextAnnotation = (function TextAnnot
     var content = dict.get('Contents');
     var title = dict.get('T');
     data.content = stringToPDFString(content || '');
     data.title = stringToPDFString(title || '');
 
     if (data.hasAppearance) {
       data.name = 'NoIcon';
     } else {
+      data.rect[1] = data.rect[3] - DEFAULT_ICON_SIZE;
+      data.rect[2] = data.rect[0] + DEFAULT_ICON_SIZE;
       data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
     }
 
     if (dict.has('C')) {
       data.hasBgColor = true;
     }
   }
 
@@ -3879,46 +3882,47 @@ var ChunkedStreamManager = (function Chu
     }
 
     this.currRequestId = 0;
 
     this.chunksNeededByRequest = {};
     this.requestsByChunk = {};
     this.callbacksByRequest = {};
 
-    this.loadedStream = new LegacyPromise();
+    this._loadedStreamCapability = createPromiseCapability();
+
     if (args.initialData) {
       this.setInitialData(args.initialData);
     }
   }
 
   ChunkedStreamManager.prototype = {
 
     setInitialData: function ChunkedStreamManager_setInitialData(data) {
       this.stream.onReceiveInitialData(data);
       if (this.stream.allChunksLoaded()) {
-        this.loadedStream.resolve(this.stream);
+        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.loadedStream;
+      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();
       this.requestChunks(missingChunks);
-      return this.loadedStream;
+      return this._loadedStreamCapability.promise;
     },
 
     requestChunks: function ChunkedStreamManager_requestChunks(chunks,
                                                                callback) {
       var requestId = this.currRequestId++;
 
       var chunksNeeded;
       var i, ii;
@@ -4044,17 +4048,17 @@ var ChunkedStreamManager = (function Chu
       var begin = args.begin;
       var end = begin + chunk.byteLength;
 
       var beginChunk = this.getBeginChunk(begin);
       var endChunk = this.getEndChunk(end);
 
       this.stream.onReceiveData(begin, chunk);
       if (this.stream.allChunksLoaded()) {
-        this.loadedStream.resolve(this.stream);
+        this._loadedStreamCapability.resolve(this.stream);
       }
 
       var loadedRequests = [];
       var i, requestId;
       for (chunk = beginChunk; chunk < endChunk; ++chunk) {
         // The server might return more chunks than requested
         var requestIds = this.requestsByChunk[chunk] || [];
         delete this.requestsByChunk[chunk];
@@ -4104,16 +4108,20 @@ var ChunkedStreamManager = (function Chu
       }
 
       this.msgHandler.send('DocProgress', {
         loaded: this.stream.numChunksLoaded * this.chunkSize,
         total: this.length
       });
     },
 
+    onError: function ChunkedStreamManager_onError(err) {
+      this._loadedStreamCapability.reject(err);
+    },
+
     getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
       var chunk = Math.floor(begin / this.chunkSize);
       return chunk;
     },
 
     getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
       if (end % this.chunkSize === 0) {
         return end / this.chunkSize;
@@ -4177,73 +4185,75 @@ var BasePdfManager = (function BasePdfMa
     },
 
     requestLoadedStream: function BasePdfManager_requestLoadedStream() {
       return new NotImplementedException();
     },
 
     updatePassword: function BasePdfManager_updatePassword(password) {
       this.pdfDocument.xref.password = this.password = password;
-      if (this.passwordChangedPromise) {
-        this.passwordChangedPromise.resolve();
-      }
+      if (this._passwordChangedCapability) {
+        this._passwordChangedCapability.resolve();
+      }
+    },
+
+    passwordChanged: function BasePdfManager_passwordChanged() {
+      this._passwordChangedCapability = createPromiseCapability();
+      return this._passwordChangedCapability.promise;
     },
 
     terminate: function BasePdfManager_terminate() {
       return new NotImplementedException();
     }
   };
 
   return BasePdfManager;
 })();
 
 var LocalPdfManager = (function LocalPdfManagerClosure() {
   function LocalPdfManager(data, password) {
     var stream = new Stream(data);
     this.pdfDocument = new PDFDocument(this, stream, password);
-    this.loadedStream = new LegacyPromise();
-    this.loadedStream.resolve(stream);
+    this._loadedStreamCapability = createPromiseCapability();
+    this._loadedStreamCapability.resolve(stream);
   }
 
   LocalPdfManager.prototype = Object.create(BasePdfManager.prototype);
   LocalPdfManager.prototype.constructor = LocalPdfManager;
 
   LocalPdfManager.prototype.ensure =
       function LocalPdfManager_ensure(obj, prop, args) {
-    var promise = new LegacyPromise();
-    try {
-      var value = obj[prop];
-      var result;
-      if (typeof(value) === 'function') {
-        result = value.apply(obj, args);
-      } else {
-        result = value;
-      }
-      promise.resolve(result);
-    } catch (e) {
-      console.log(e.stack);
-      promise.reject(e);
-    }
-    return promise;
+    return new Promise(function (resolve, reject) {
+      try {
+        var value = obj[prop];
+        var result;
+        if (typeof value === 'function') {
+          result = value.apply(obj, args);
+        } else {
+          result = value;
+        }
+        resolve(result);
+      } catch (e) {
+        reject(e);
+      }
+    });
   };
 
   LocalPdfManager.prototype.requestRange =
       function LocalPdfManager_requestRange(begin, end) {
-    var promise = new LegacyPromise();
-    promise.resolve();
-    return promise;
+    return Promise.resolve();
   };
 
   LocalPdfManager.prototype.requestLoadedStream =
       function LocalPdfManager_requestLoadedStream() {
   };
 
   LocalPdfManager.prototype.onLoadedStream =
       function LocalPdfManager_getLoadedStream() {
-    return this.loadedStream;
+    return this._loadedStreamCapability.promise;
   };
 
   LocalPdfManager.prototype.terminate =
       function LocalPdfManager_terminate() {
     return;
   };
 
   return LocalPdfManager;
@@ -4269,52 +4279,49 @@ var NetworkPdfManager = (function Networ
                                     args.password);
   }
 
   NetworkPdfManager.prototype = Object.create(BasePdfManager.prototype);
   NetworkPdfManager.prototype.constructor = NetworkPdfManager;
 
   NetworkPdfManager.prototype.ensure =
       function NetworkPdfManager_ensure(obj, prop, args) {
-    var promise = new LegacyPromise();
-    this.ensureHelper(promise, obj, prop, args);
-    return promise;
-  };
-
-  NetworkPdfManager.prototype.ensureHelper =
-      function NetworkPdfManager_ensureHelper(promise, obj, prop, args) {
-    try {
-      var result;
-      var value = obj[prop];
-      if (typeof(value) === 'function') {
-        result = value.apply(obj, args);
-      } else {
-        result = value;
-      }
-      promise.resolve(result);
-    } catch(e) {
-      if (!(e instanceof MissingDataException)) {
-        console.log(e.stack);
-        promise.reject(e);
-        return;
-      }
-
-      this.streamManager.requestRange(e.begin, e.end, function() {
-        this.ensureHelper(promise, obj, prop, args);
-      }.bind(this));
-    }
+    var pdfManager = this;
+
+    return new Promise(function (resolve, reject) {
+      function ensureHelper() {
+        try {
+          var result;
+          var value = obj[prop];
+          if (typeof value === 'function') {
+            result = value.apply(obj, args);
+          } else {
+            result = value;
+          }
+          resolve(result);
+        } catch(e) {
+          if (!(e instanceof MissingDataException)) {
+            reject(e);
+            return;
+          }
+          pdfManager.streamManager.requestRange(e.begin, e.end, ensureHelper);
+        }
+      }
+
+      ensureHelper();
+    });
   };
 
   NetworkPdfManager.prototype.requestRange =
       function NetworkPdfManager_requestRange(begin, end) {
-    var promise = new LegacyPromise();
-    this.streamManager.requestRange(begin, end, function() {
-      promise.resolve();
-    });
-    return promise;
+    return new Promise(function (resolve) {
+      this.streamManager.requestRange(begin, end, function() {
+        resolve();
+      });
+    }.bind(this));
   };
 
   NetworkPdfManager.prototype.requestLoadedStream =
       function NetworkPdfManager_requestLoadedStream() {
     this.streamManager.requestAllChunks();
   };
 
   NetworkPdfManager.prototype.onLoadedStream =
@@ -4445,42 +4452,38 @@ var Page = (function PageClosure() {
         stream = content;
       } else {
         // replacing non-existent page content with empty one
         stream = new NullStream();
       }
       return stream;
     },
 
-    loadResources: function(keys) {
+    loadResources: function Page_loadResources(keys) {
       if (!this.resourcesPromise) {
         // TODO: add async getInheritedPageProp and remove this.
         this.resourcesPromise = this.pdfManager.ensure(this, 'resources');
       }
-      var promise = new LegacyPromise();
-      this.resourcesPromise.then(function resourceSuccess() {
+      return this.resourcesPromise.then(function resourceSuccess() {
         var objectLoader = new ObjectLoader(this.resources.map,
                                             keys,
                                             this.xref);
-        objectLoader.load().then(function objectLoaderSuccess() {
-          promise.resolve();
-        });
+        return objectLoader.load();
       }.bind(this));
-      return promise;
     },
 
     getOperatorList: function Page_getOperatorList(handler, intent) {
       var self = this;
-      var promise = new LegacyPromise();
+      var capability = createPromiseCapability();
 
       function reject(e) {
-        promise.reject(e);
-      }
-
-      var pageListPromise = new LegacyPromise();
+        capability.reject(e);
+      }
+
+      var pageListCapability = createPromiseCapability();
 
       var pdfManager = this.pdfManager;
       var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
                                                    []);
       var resourcesPromise = this.loadResources([
         'ExtGState',
         'ColorSpace',
         'Pattern',
@@ -4504,39 +4507,40 @@ var Page = (function PageClosure() {
         var opList = new OperatorList(intent, handler, self.pageIndex);
 
         handler.send('StartRenderPage', {
           transparency: partialEvaluator.hasBlendModes(self.resources),
           pageIndex: self.pageIndex,
           intent: intent
         });
         partialEvaluator.getOperatorList(contentStream, self.resources, opList);
-        pageListPromise.resolve(opList);
+        pageListCapability.resolve(opList);
       });
 
       var annotationsPromise = pdfManager.ensure(this, 'annotations');
-      Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
+      Promise.all([pageListCapability.promise, annotationsPromise]).then(
+          function(datas) {
         var pageOpList = datas[0];
         var annotations = datas[1];
 
         if (annotations.length === 0) {
           pageOpList.flush(true);
-          promise.resolve(pageOpList);
+          capability.resolve(pageOpList);
           return;
         }
 
         var annotationsReadyPromise = Annotation.appendToOperatorList(
           annotations, pageOpList, pdfManager, partialEvaluator, intent);
         annotationsReadyPromise.then(function () {
           pageOpList.flush(true);
-          promise.resolve(pageOpList);
+          capability.resolve(pageOpList);
         }, reject);
       }, reject);
 
-      return promise;
+      return capability.promise;
     },
 
     extractTextContent: function Page_extractTextContent() {
       var handler = {
         on: function nullHandlerOn() {},
         send: function nullHandlerSend() {}
       };
 
@@ -4933,43 +4937,36 @@ var Dict = (function DictClosure() {
       }
       value = this.map[key3] || null;
       return xref ? xref.fetchIfRef(value) : value;
     },
 
     // Same as get(), but returns a promise and uses fetchIfRefAsync().
     getAsync: function Dict_getAsync(key1, key2, key3) {
       var value;
-      var promise;
       var xref = this.xref;
       if (typeof (value = this.map[key1]) !== undefined || key1 in this.map ||
           typeof key2 === undefined) {
         if (xref) {
           return xref.fetchIfRefAsync(value);
         }
-        promise = new LegacyPromise();
-        promise.resolve(value);
-        return promise;
+        return Promise.resolve(value);
       }
       if (typeof (value = this.map[key2]) !== undefined || key2 in this.map ||
           typeof key3 === undefined) {
         if (xref) {
           return xref.fetchIfRefAsync(value);
         }
-        promise = new LegacyPromise();
-        promise.resolve(value);
-        return promise;
+        return Promise.resolve(value);
       }
       value = this.map[key3] || null;
       if (xref) {
         return xref.fetchIfRefAsync(value);
       }
-      promise = new LegacyPromise();
-      promise.resolve(value);
-      return promise;
+      return Promise.resolve(value);
     },
 
     // no dereferencing
     getRaw: function Dict_getRaw(key) {
       return this.map[key];
     },
 
     // creates new map and dereferences all Refs
@@ -5356,39 +5353,39 @@ var Catalog = (function CatalogClosure()
                             this.fontCache);
           }.bind(this)
         );
       }
       return this.pagePromises[pageIndex];
     },
 
     getPageDict: function Catalog_getPageDict(pageIndex) {
-      var promise = new LegacyPromise();
+      var capability = createPromiseCapability();
       var nodesToVisit = [this.catDict.getRaw('Pages')];
       var currentPageIndex = 0;
       var xref = this.xref;
 
       function next() {
         while (nodesToVisit.length) {
           var currentNode = nodesToVisit.pop();
 
           if (isRef(currentNode)) {
             xref.fetchAsync(currentNode).then(function (obj) {
               if ((isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids')))) {
                 if (pageIndex === currentPageIndex) {
-                  promise.resolve([obj, currentNode]);
+                  capability.resolve([obj, currentNode]);
                 } else {
                   currentPageIndex++;
                   next();
                 }
                 return;
               }
               nodesToVisit.push(obj);
               next();
-            }.bind(this), promise.reject.bind(promise));
+            }.bind(this), capability.reject.bind(capability));
             return;
           }
 
           // must be a child page dictionary
           assert(
             isDict(currentNode),
             'page dictionary kid reference points to wrong type of object'
           );
@@ -5413,20 +5410,20 @@ var Catalog = (function CatalogClosure()
             currentPageIndex = pageIndex;
             continue;
           } else {
             for (var last = kids.length - 1; last >= 0; last--) {
               nodesToVisit.push(kids[last]);
             }
           }
         }
-        promise.reject('Page index ' + pageIndex + ' not found.');
+        capability.reject('Page index ' + pageIndex + ' not found.');
       }
       next();
-      return promise;
+      return capability.promise;
     },
 
     getPageIndex: function Catalog_getPageIndex(ref) {
       // The page tree nodes have the count of all the leaves below them. To get
       // how many pages are before we just have to walk up the tree and keep
       // adding the count of siblings to the left of the node.
       var xref = this.xref;
       function pagesBeforeRef(kidRef) {
@@ -5558,16 +5555,21 @@ var XRef = (function XRefClosure() {
       //    << /Size 22
       //      /Root 20R
       //      /Info 10R
       //      /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
       //    >>
       // The parser goes through the entire stream << ... >> and provides
       // a getter interface for the key-value table
       var dict = parser.getObj();
+
+      // The pdflib PDF generator can generate a nested trailer dictionary
+      if (!isDict(dict) && dict.dict) {
+        dict = dict.dict;
+      }
       if (!isDict(dict)) {
         error('Invalid XRef table: could not parse trailer dictionary');
       }
       delete this.tableState;
 
       return dict;
     },
 
@@ -6074,39 +6076,37 @@ var XRef = (function XRefClosure() {
       if (xrefEntry === undefined) {
         error('bad XRef entry for compressed object');
       }
       return xrefEntry;
     },
 
     fetchIfRefAsync: function XRef_fetchIfRefAsync(obj) {
       if (!isRef(obj)) {
-        var promise = new LegacyPromise();
-        promise.resolve(obj);
-        return promise;
+        return Promise.resolve(obj);
       }
       return this.fetchAsync(obj);
     },
 
     fetchAsync: function XRef_fetchAsync(ref, suppressEncryption) {
-      var promise = new LegacyPromise();
-      var tryFetch = function (promise) {
-        try {
-          promise.resolve(this.fetch(ref, suppressEncryption));
-        } catch (e) {
-          if (e instanceof MissingDataException) {
-            this.stream.manager.requestRange(e.begin, e.end, tryFetch);
-            return;
-          }
-          promise.reject(e);
-        }
-      }.bind(this, promise);
-      tryFetch();
-      return promise;
-    },
+      return new Promise(function (resolve, reject) {
+          var tryFetch = function () {
+            try {
+              resolve(this.fetch(ref, suppressEncryption));
+            } catch (e) {
+              if (e instanceof MissingDataException) {
+                this.stream.manager.requestRange(e.begin, e.end, tryFetch);
+                return;
+              }
+              reject(e);
+            }
+          }.bind(this);
+          tryFetch();
+        }.bind(this));
+      },
 
     getCatalogObj: function XRef_getCatalogObj() {
       return this.root;
     }
   };
 
   return XRef;
 })();
@@ -6301,33 +6301,33 @@ var ObjectLoader = (function() {
     this.keys = keys;
     this.xref = xref;
     this.refSet = null;
   }
 
   ObjectLoader.prototype = {
     load: function ObjectLoader_load() {
       var keys = this.keys;
-      this.promise = new LegacyPromise();
+      this.capability = createPromiseCapability();
       // Don't walk the graph if all the data is already loaded.
       if (!(this.xref.stream instanceof ChunkedStream) ||
           this.xref.stream.getMissingChunks().length === 0) {
-        this.promise.resolve();
-        return this.promise;
+        this.capability.resolve();
+        return this.capability.promise;
       }
 
       this.refSet = new RefSet();
       // Setup the initial nodes to visit.
       var nodesToVisit = [];
       for (var i = 0; i < keys.length; i++) {
         nodesToVisit.push(this.obj[keys[i]]);
       }
 
       this.walk(nodesToVisit);
-      return this.promise;
+      return this.capability.promise;
     },
 
     walk: function ObjectLoader_walk(nodesToVisit) {
       var nodesToRevisit = [];
       var pendingRequests = [];
       // DFS walk of the object graph.
       while (nodesToVisit.length) {
         var currentNode = nodesToVisit.pop();
@@ -6384,17 +6384,17 @@ var ObjectLoader = (function() {
             }
           }
           this.walk(nodesToVisit);
         }.bind(this));
         return;
       }
       // Everything is loaded.
       this.refSet = null;
-      this.promise.resolve();
+      this.capability.resolve();
     }
   };
 
   return ObjectLoader;
 })();
 
 
 var ISOAdobeCharset = [
@@ -18355,17 +18355,17 @@ var symbolsFonts = {
   'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true
 };
 
 // Glyph map for well-known standard fonts. Sometimes Ghostscript uses CID fonts
 // but does not embed the CID to GID mapping. The mapping is incomplete for all
 // glyphs, but common for some set of the standard fonts.
 var GlyphMapForStandardFonts = {
   '2': 10, '3': 32, '4': 33, '5': 34, '6': 35, '7': 36, '8': 37, '9': 38,
-  '10': 39, '11': 40, '12': 41, '13': 42, '14': 43, '15': 44, '16': 173,
+  '10': 39, '11': 40, '12': 41, '13': 42, '14': 43, '15': 44, '16': 45,
   '17': 46, '18': 47, '19': 48, '20': 49, '21': 50, '22': 51, '23': 52,
   '24': 53, '25': 54, '26': 55, '27': 56, '28': 57, '29': 58, '30': 894,
   '31': 60, '32': 61, '33': 62, '34': 63, '35': 64, '36': 65, '37': 66,
   '38': 67, '39': 68, '40': 69, '41': 70, '42': 71, '43': 72, '44': 73,
   '45': 74, '46': 75, '47': 76, '48': 77, '49': 78, '50': 79, '51': 80,
   '52': 81, '53': 82, '54': 83, '55': 84, '56': 85, '57': 86, '58': 87,
   '59': 88, '60': 89, '61': 90, '62': 91, '63': 92, '64': 93, '65': 94,
   '66': 95, '67': 96, '68': 97, '69': 98, '70': 99, '71': 100, '72': 101,
@@ -18379,17 +18379,19 @@ var GlyphMapForStandardFonts = {
   '119': 239, '120': 241, '121': 243, '122': 242, '123': 244, '124': 246,
   '125': 245, '126': 250, '127': 249, '128': 251, '129': 252, '130': 8224,
   '131': 176, '132': 162, '133': 163, '134': 167, '135': 8226, '136': 182,
   '137': 223, '138': 174, '139': 169, '140': 8482, '141': 180, '142': 168,
   '143': 8800, '144': 198, '145': 216, '146': 8734, '147': 177, '148': 8804,
   '149': 8805, '150': 165, '151': 181, '152': 8706, '153': 8721, '154': 8719,
   '156': 8747, '157': 170, '158': 186, '159': 8486, '160': 230, '161': 248,
   '162': 191, '163': 161, '164': 172, '165': 8730, '166': 402, '167': 8776,
-  '168': 8710, '169': 171, '170': 187, '171': 8230, '210': 218, '305': 963,
+  '168': 8710, '169': 171, '170': 187, '171': 8230, '210': 218, '223': 711,
+  '227': 353, '229': 382, '234': 253, '253': 268, '254': 269, '258': 258,
+  '268': 283, '269': 313, '278': 328, '284': 345, '292': 367, '305': 963,
   '306': 964, '307': 966, '308': 8215, '309': 8252, '310': 8319, '311': 8359,
   '312': 8592, '313': 8593, '337': 9552, '493': 1039, '494': 1040, '705': 1524,
   '706': 8362, '710': 64288, '711': 64298, '759': 1617, '761': 1776,
   '763': 1778, '775': 1652, '777': 1764, '778': 1780, '779': 1781, '780': 1782,
   '782': 771, '783': 64726, '786': 8363, '788': 8532, '790': 768, '791': 769,
   '792': 768, '795': 803, '797': 64336, '798': 64337, '799': 64342,
   '800': 64343, '801': 64344, '802': 64345, '803': 64362, '804': 64363,
   '805': 64364, '2424': 7821, '2425': 7822, '2426': 7823, '2427': 7824,
@@ -20183,17 +20185,22 @@ var Font = (function FontClosure() {
       this.loadedName = fontName.split('-')[0];
       this.loading = false;
       return;
     }
 
     // Some fonts might use wrong font types for Type1C or CIDFontType0C
     var subtype = properties.subtype;
     if (subtype == 'Type1C' && (type != 'Type1' && type != 'MMType1')) {
-      type = 'Type1';
+      // Some TrueType fonts by mistake claim Type1C
+      if (isTrueTypeFile(file)) {
+        subtype = 'TrueType';
+      } else {
+        type = 'Type1';
+      }
     }
     if (subtype == 'CIDFontType0C' && type != 'CIDFontType0') {
       type = 'CIDFontType0';
     }
     // 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';
@@ -20331,16 +20338,21 @@ var Font = (function FontClosure() {
     }
 
     var tableEntry = (tag + string32(checksum) +
                       string32(offset) + string32(length));
     file.file += tableEntry;
     file.virtualOffset += data.length;
   }
 
+  function isTrueTypeFile(file) {
+    var header = file.peekBytes(4);
+    return readUint32(header, 0) === 0x00010000;
+  }
+
   /**
    * Rebuilds the char code to glyph ID map by trying to replace the char codes
    * with their unicode value. It also moves char codes that are in known
    * problematic locations.
    * @return {Object} Two properties:
    * 'toFontChar' - maps original char codes(the value that will be read
    * from commands such as show text) to the char codes that will be used in the
    * font that we build
@@ -20373,16 +20385,18 @@ var Font = (function FontClosure() {
       // font was symbolic and there is only an identity unicode map since the
       // characters probably aren't in the correct position (fixes an issue
       // with firefox and thuluthfont).
       if ((fontCharCode in usedFontCharCodes ||
            fontCharCode <= 0x1f || // Control chars
            fontCharCode === 0x7F || // Control char
            fontCharCode === 0xAD || // Soft hyphen
            (fontCharCode >= 0x80 && fontCharCode <= 0x9F) || // Control chars
+           // Prevent drawing characters in the specials unicode block.
+           (fontCharCode >= 0xFFF0 && fontCharCode <= 0xFFFF) ||
            (isSymbolic && isIdentityUnicode)) &&
           nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
         // Loop to try and find a free spot in the private use area.
         do {
           fontCharCode = nextAvailableFontCharCode++;
 
           if (SKIP_PRIVATE_USE_RANGE_F000_TO_F01F && fontCharCode === 0xF000) {
             fontCharCode = 0xF020;
@@ -29825,25 +29839,27 @@ var PDFImage = (function PDFImageClosure
       });
       return new Promise(function (resolve) {
         resolvePromise = resolve;
       });
     } else {
       return Promise.resolve(image);
     }
   }
+
   /**
    * Decode and clamp a value. The formula is different from the spec because we
    * don't decode to float range [0,1], we decode it in the [0,max] range.
    */
   function decodeAndClamp(value, addend, coefficient, max) {
     value = addend + value * coefficient;
     // Clamp the value to the range
     return (value < 0 ? 0 : (value > max ? max : value));
   }
+
   function PDFImage(xref, res, image, inline, smask, mask, isMask) {
     this.image = image;
     var dict = image.dict;
     if (dict.has('Filter')) {
       var filter = dict.get('Filter').name;
       if (filter === 'JPXDecode') {
         var jpxImage = new JpxImage();
         jpxImage.parseImageProperties(image.stream);
@@ -29979,40 +29995,60 @@ var PDFImage = (function PDFImageClosure
    * supports one and three component images.
    * @param {TypedArray} pixels The original image with one component.
    * @param {Number} bpc Number of bits per component.
    * @param {Number} components Number of color components, 1 or 3 is supported.
    * @param {Number} w1 Original width.
    * @param {Number} h1 Original height.
    * @param {Number} w2 New width.
    * @param {Number} h2 New height.
+   * @param {TypedArray} dest (Optional) The destination buffer.
+   * @param {Number} alpha01 (Optional) Size reserved for the alpha channel.
    * @return {TypedArray} Resized image data.
    */
   PDFImage.resize = function PDFImage_resize(pixels, bpc, components,
-                                             w1, h1, w2, h2) {
+                                             w1, h1, w2, h2, dest, alpha01) {
+
+    if (components !== 1 && components !== 3) {
+      error('Unsupported component count for resizing.');
+    }
+
     var length = w2 * h2 * components;
-    var temp = (bpc <= 8 ? new Uint8Array(length) :
+    var temp = dest ? dest : (bpc <= 8 ? new Uint8Array(length) :
         (bpc <= 16 ? new Uint16Array(length) : new Uint32Array(length)));
     var xRatio = w1 / w2;
     var yRatio = h1 / h2;
-    var px, py, newIndex, oldIndex;
-    for (var i = 0; i < h2; i++) {
-      for (var j = 0; j < w2; j++) {
-        px = Math.floor(j * xRatio);
-        py = Math.floor(i * yRatio);
-        newIndex = (i * w2) + j;
-        oldIndex = ((py * w1) + px);
-        if (components === 1) {
-          temp[newIndex] = pixels[oldIndex];
-        } else if (components === 3) {
-          newIndex *= 3;
-          oldIndex *= 3;
-          temp[newIndex] = pixels[oldIndex];
-          temp[newIndex + 1] = pixels[oldIndex + 1];
-          temp[newIndex + 2] = pixels[oldIndex + 2];
+    var i, j, py, newIndex = 0, oldIndex;
+    var xScaled = new Uint16Array(w2);
+    var w1Scanline = w1 * components;
+    if (alpha01 !== 1) {
+      alpha01 = 0;
+    }
+
+    for (j = 0; j < w2; j++) {
+      xScaled[j] = Math.floor(j * xRatio) * components;
+    }
+
+    if (components === 1) {
+      for (i = 0; i < h2; i++) {
+        py = Math.floor(i * yRatio) * w1Scanline;
+        for (j = 0; j < w2; j++) {
+          oldIndex = py + xScaled[j];
+          temp[newIndex++] = pixels[oldIndex];
+        }
+      }
+    } else if (components === 3) {
+      for (i = 0; i < h2; i++) {
+        py = Math.floor(i * yRatio) * w1Scanline;
+        for (j = 0; j < w2; j++) {
+          oldIndex = py + xScaled[j];
+          temp[newIndex++] = pixels[oldIndex++];
+          temp[newIndex++] = pixels[oldIndex++];
+          temp[newIndex++] = pixels[oldIndex++];
+          newIndex += alpha01;
         }
       }
     }
     return temp;
   };
 
   PDFImage.createMask =
       function PDFImage_createMask(imgArray, width, height, canTransfer,
@@ -30040,21 +30076,23 @@ var PDFImage = (function PDFImageClosure
   };
 
   PDFImage.prototype = {
     get drawWidth() {
       return Math.max(this.width,
                       this.smask && this.smask.width || 0,
                       this.mask && this.mask.width || 0);
     },
+
     get drawHeight() {
       return Math.max(this.height,
                       this.smask && this.smask.height || 0,
                       this.mask && this.mask.height || 0);
     },
+
     decodeBuffer: function PDFImage_decodeBuffer(buffer) {
       var bpc = this.bpc;
       var numComps = this.numComps;
 
       var decodeAddends = this.decodeAddends;
       var decodeCoefficients = this.decodeCoefficients;
       var max = (1 << bpc) - 1;
       var i, ii;
@@ -30070,16 +30108,17 @@ var PDFImage = (function PDFImageClosure
       for (i = 0, ii = this.width * this.height; i < ii; i++) {
         for (var j = 0; j < numComps; j++) {
           buffer[index] = decodeAndClamp(buffer[index], decodeAddends[j],
                                          decodeCoefficients[j], max);
           index++;
         }
       }
     },
+
     getComponents: function PDFImage_getComponents(buffer) {
       var bpc = this.bpc;
 
       // This image doesn't require any extra work.
       if (bpc === 8) {
         return buffer;
       }
 
@@ -30146,16 +30185,17 @@ var PDFImage = (function PDFImageClosure
           var value = buf >> remainingBits;
           output[i] = (value < 0 ? 0 : (value > max ? max : value));
           buf = buf & ((1 << remainingBits) - 1);
           bits = remainingBits;
         }
       }
       return output;
     },
+
     fillOpacity: function PDFImage_fillOpacity(rgbaBuf, width, height,
                                                actualHeight, image) {
       var smask = this.smask;
       var mask = this.mask;
       var alphaBuf, sw, sh, i, ii, j;
 
       if (smask) {
         sw = smask.width;
@@ -30212,60 +30252,64 @@ var PDFImage = (function PDFImageClosure
         }
       } else {
         // No mask.
         for (i = 0, j = 3, ii = width * actualHeight; i < ii; ++i, j += 4) {
           rgbaBuf[j] = 255;
         }
       }
     },
+
     undoPreblend: function PDFImage_undoPreblend(buffer, width, height) {
       var matte = this.smask && this.smask.matte;
       if (!matte) {
         return;
       }
-
-      function clamp(value) {
-        return (value < 0 ? 0 : (value > 255 ? 255 : value)) | 0;
-      }
-
       var matteRgb = this.colorSpace.getRgb(matte, 0);
+      var matteR = matteRgb[0];
+      var matteG = matteRgb[1];
+      var matteB = matteRgb[2];
       var length = width * height * 4;
+      var r, g, b;
       for (var i = 0; i < length; i += 4) {
         var alpha = buffer[i + 3];
         if (alpha === 0) {
           // according formula we have to get Infinity in all components
-          // making it as white (tipical paper color) should be okay
+          // making it white (typical paper color) should be okay
           buffer[i] = 255;
           buffer[i + 1] = 255;
           buffer[i + 2] = 255;
           continue;
         }
         var k = 255 / alpha;
-        buffer[i] = clamp((buffer[i] - matteRgb[0]) * k + matteRgb[0]);
-        buffer[i + 1] = clamp((buffer[i + 1] - matteRgb[1]) * k + matteRgb[1]);
-        buffer[i + 2] = clamp((buffer[i + 2] - matteRgb[2]) * k + matteRgb[2]);
-      }
-    },
+        r = (buffer[i] - matteR) * k + matteR;
+        g = (buffer[i + 1] - matteG) * k + matteG;
+        b = (buffer[i + 2] - matteB) * k + matteB;
+        buffer[i] = r <= 0 ? 0 : r >= 255 ? 255 : r | 0;
+        buffer[i + 1] = g <= 0 ? 0 : g >= 255 ? 255 : g | 0;
+        buffer[i + 2] = b <= 0 ? 0 : b >= 255 ? 255 : b | 0;
+      }
+    },
+
     createImageData: function PDFImage_createImageData(forceRGBA) {
       var drawWidth = this.drawWidth;
       var drawHeight = this.drawHeight;
       var imgData = { // other fields are filled in below
         width: drawWidth,
         height: drawHeight
       };
 
       var numComps = this.numComps;
       var originalWidth = this.width;
       var originalHeight = this.height;
       var bpc = this.bpc;
 
       // Rows start at byte boundary.
       var rowBytes = (originalWidth * numComps * bpc + 7) >> 3;
-      var imgArray = this.getImageBytes(originalHeight * rowBytes);
+      var imgArray;
 
       if (!forceRGBA) {
         // If it is a 1-bit-per-pixel grayscale (i.e. black-and-white) image
         // without any complications, we pass a same-sized copy to the main
         // thread rather than expanding by 32x to RGBA form. This saves *lots*
         // of memory for many scanned documents. It's also much faster.
         //
         // Similarly, if it is a 24-bit-per pixel RGB image without any
@@ -30275,32 +30319,40 @@ var PDFImage = (function PDFImageClosure
           kind = ImageKind.GRAYSCALE_1BPP;
         } else if (this.colorSpace.name === 'DeviceRGB' && bpc === 8) {
           kind = ImageKind.RGB_24BPP;
         }
         if (kind && !this.smask && !this.mask && !this.needsDecode &&
             drawWidth === originalWidth && drawHeight === originalHeight) {
           imgData.kind = kind;
 
+          imgArray = this.getImageBytes(originalHeight * rowBytes);
           // If imgArray came from a DecodeStream, we're safe to transfer it
           // (and thus neuter it) because it will constitute the entire
           // DecodeStream's data.  But if it came from a Stream, we need to
           // copy it because it'll only be a portion of the Stream's data, and
           // the rest will be read later on.
           if (this.image instanceof DecodeStream) {
             imgData.data = imgArray;
           } else {
             var newArray = new Uint8Array(imgArray.length);
             newArray.set(imgArray);
             imgData.data = newArray;
           }
           return imgData;
         }
       }
-
+      if (this.image instanceof JpegStream) {
+        imgData.kind = ImageKind.RGB_24BPP;
+        imgData.data = this.getImageBytes(originalHeight * rowBytes,
+                                          drawWidth, drawHeight);
+        return imgData;
+      }
+
+      imgArray = this.getImageBytes(originalHeight * rowBytes);
       // imgArray can be incomplete (e.g. after CCITT fax encoding).
       var actualHeight = 0 | (imgArray.length / rowBytes *
                          drawHeight / originalHeight);
 
       var comps = this.getComponents(imgArray);
 
       // If opacity data is present, use RGBA_32BPP form. Otherwise, use the
       // more compact RGB_24BPP form if allowable.
@@ -30328,16 +30380,17 @@ var PDFImage = (function PDFImageClosure
                               drawWidth, drawHeight, actualHeight, bpc, comps,
                               alpha01);
       if (maybeUndoPreblend) {
         this.undoPreblend(imgData.data, drawWidth, actualHeight);
       }
 
       return imgData;
     },
+
     fillGrayBuffer: function PDFImage_fillGrayBuffer(buffer) {
       var numComps = this.numComps;
       if (numComps != 1) {
         error('Reading gray scale from a color image: ' + numComps);
       }
 
       var width = this.width;
       var height = this.height;
@@ -30349,17 +30402,17 @@ var PDFImage = (function PDFImageClosure
 
       var comps = this.getComponents(imgArray);
       var i, length;
 
       if (bpc === 1) {
         // inline decoding (= inversion) for 1 bpc images
         length = width * height;
         if (this.needsDecode) {
-          // invert and scale to {0, 255} 
+          // invert and scale to {0, 255}
           for (i = 0; i < length; ++i) {
             buffer[i] = (comps[i] - 1) & 255;
           }
         } else {
           // scale to {0, 255}
           for (i = 0; i < length; ++i) {
             buffer[i] = (-comps[i]) & 255;
           }
@@ -30372,18 +30425,22 @@ var PDFImage = (function PDFImageClosure
       }
       length = width * height;
       // we aren't using a colorspace so we need to scale the value
       var scale = 255 / ((1 << bpc) - 1);
       for (i = 0; i < length; ++i) {
         buffer[i] = (scale * comps[i]) | 0;
       }
     },
-    getImageBytes: function PDFImage_getImageBytes(length) {
+
+    getImageBytes: function PDFImage_getImageBytes(length,
+                                                   drawWidth, drawHeight) {
       this.image.reset();
+      this.image.drawWidth = drawWidth;
+      this.image.drawHeight = drawHeight;
       return this.image.getBytes(length);
     }
   };
   return PDFImage;
 })();
 
 
 // The Metrics object contains glyph widths (in glyph space units).
@@ -35227,26 +35284,31 @@ var JpegStream = (function JpegStreamClo
       return;
     }
     try {
       var jpegImage = new JpegImage();
       if (this.colorTransform != -1) {
         jpegImage.colorTransform = this.colorTransform;
       }
       jpegImage.parse(this.bytes);
-      var width = jpegImage.width;
-      var height = jpegImage.height;
-      var data = jpegImage.getData(width, height);
+      var data = jpegImage.getData(this.drawWidth, this.drawHeight,
+                                   /* forceRGBoutput = */true);
       this.buffer = data;
       this.bufferLength = data.length;
       this.eof = true;
     } catch (e) {
       error('JPEG error: ' + e);
     }
   };
+
+  JpegStream.prototype.getBytes = function JpegStream_getBytes(length) {
+    this.ensureBuffer();
+    return this.buffer;
+  };
+
   JpegStream.prototype.getIR = function JpegStream_getIR() {
     return PDFJS.createObjectURL(this.bytes, 'image/jpeg');
   };
   /**
    * Checks if the image can be decoded and displayed by the browser without any
    * further processing such as color space conversions.
    */
   JpegStream.prototype.isNativelySupported =
@@ -36769,79 +36831,69 @@ var NullStream = (function NullStreamClo
 })();
 
 
 var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
   setup: function wphSetup(handler) {
     var pdfManager;
 
     function loadDocument(recoveryMode) {
-      var loadDocumentPromise = new LegacyPromise();
+      var loadDocumentCapability = createPromiseCapability();
 
       var parseSuccess = function parseSuccess() {
         var numPagesPromise = pdfManager.ensureDoc('numPages');
         var fingerprintPromise = pdfManager.ensureDoc('fingerprint');
-        var outlinePromise = pdfManager.ensureCatalog('documentOutline');
-        var infoPromise = pdfManager.ensureDoc('documentInfo');
-        var metadataPromise = pdfManager.ensureCatalog('metadata');
         var encryptedPromise = pdfManager.ensureXRef('encrypt');
-        var javaScriptPromise = pdfManager.ensureCatalog('javaScript');
-        Promise.all([numPagesPromise, fingerprintPromise, outlinePromise,
-                     infoPromise, metadataPromise, encryptedPromise,
-                     javaScriptPromise]).then(function onDocReady(results) {
-
+        Promise.all([numPagesPromise, fingerprintPromise,
+                     encryptedPromise]).then(function onDocReady(results) {
           var doc = {
             numPages: results[0],
             fingerprint: results[1],
-            outline: results[2],
-            info: results[3],
-            metadata: results[4],
-            encrypted: !!results[5],
-            javaScript: results[6]
+            encrypted: !!results[2],
           };
-          loadDocumentPromise.resolve(doc);
+          loadDocumentCapability.resolve(doc);
         },
         parseFailure);
       };
 
       var parseFailure = function parseFailure(e) {
-        loadDocumentPromise.reject(e);
+        loadDocumentCapability.reject(e);
       };
 
       pdfManager.ensureDoc('checkHeader', []).then(function() {
         pdfManager.ensureDoc('parseStartXRef', []).then(function() {
           pdfManager.ensureDoc('parse', [recoveryMode]).then(
             parseSuccess, parseFailure);
         }, parseFailure);
       }, parseFailure);
 
-      return loadDocumentPromise;
+      return loadDocumentCapability.promise;
     }
 
     function getPdfManager(data) {
-      var pdfManagerPromise = new LegacyPromise();
+      var pdfManagerCapability = createPromiseCapability();
 
       var source = data.source;
       var disableRange = data.disableRange;
       if (source.data) {
         try {
           pdfManager = new LocalPdfManager(source.data, source.password);
-          pdfManagerPromise.resolve();
+          pdfManagerCapability.resolve();
         } catch (ex) {
-          pdfManagerPromise.reject(ex);
-        }
-        return pdfManagerPromise;
+          pdfManagerCapability.reject(ex);
+        }
+        return pdfManagerCapability.promise;
       } else if (source.chunkedViewerLoading) {
         try {
           pdfManager = new NetworkPdfManager(source, handler);
-          pdfManagerPromise.resolve();
+          pdfManagerCapability.resolve();
         } catch (ex) {
-          pdfManagerPromise.reject(ex);
-        }
-        return pdfManagerPromise;
+          pdfManagerCapability.reject(ex);
+        }
+        return pdfManagerCapability.promise;
       }
 
       var networkManager = new NetworkManager(source.url, {
         httpHeaders: source.httpHeaders,
         withCredentials: source.withCredentials
       });
       var fullRequestXhrId = networkManager.requestFull({
         onHeadersReceived: function onHeadersReceived() {
@@ -36876,29 +36928,29 @@ var WorkerMessageHandler = PDFJS.WorkerM
           // 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);
-            pdfManagerPromise.resolve(pdfManager);
+            pdfManagerCapability.resolve(pdfManager);
           } catch (ex) {
-            pdfManagerPromise.reject(ex);
+            pdfManagerCapability.reject(ex);
           }
         },
 
         onDone: function onDone(args) {
           // the data is array, instantiating directly from it
           try {
             pdfManager = new LocalPdfManager(args.chunk, source.password);
-            pdfManagerPromise.resolve();
+            pdfManagerCapability.resolve();
           } catch (ex) {
-            pdfManagerPromise.reject(ex);
+            pdfManagerCapability.reject(ex);
           }
         },
 
         onError: function onError(status) {
           if (status == 404) {
             var exception = new MissingPDFException('Missing PDF "' +
                                                     source.url + '".');
             handler.send('MissingPDF', { exception: exception });
@@ -36912,17 +36964,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
         onProgress: function onProgress(evt) {
           handler.send('DocProgress', {
             loaded: evt.loaded,
             total: evt.lengthComputable ? evt.total : source.length
           });
         }
       });
 
-      return pdfManagerPromise;
+      return pdfManagerCapability.promise;
     }
 
     handler.on('test', function wphSetupTest(data) {
       // check if Uint8Array can be sent to worker
       if (!(data instanceof Uint8Array)) {
         handler.send('test', false);
         return;
       }
@@ -36995,18 +37047,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
         });
       }).then(function pdfManagerReady() {
         loadDocument(false).then(onSuccess, function loadFailure(ex) {
           // Try again with recoveryMode == true
           if (!(ex instanceof XRefParseException)) {
             if (ex instanceof PasswordException) {
               // after password exception prepare to receive a new password
               // to repeat loading
-              pdfManager.passwordChangedPromise = new LegacyPromise();
-              pdfManager.passwordChangedPromise.then(pdfManagerReady);
+              pdfManager.passwordChanged().then(pdfManagerReady);
             }
 
             onFailure(ex);
             return;
           }
 
           pdfManager.requestLoadedStream();
           pdfManager.onLoadedStream().then(function() {
@@ -37056,16 +37107,42 @@ var WorkerMessageHandler = PDFJS.WorkerM
     handler.on('GetAttachments',
       function wphSetupGetAttachments(data, deferred) {
         pdfManager.ensureCatalog('attachments').then(function(attachments) {
           deferred.resolve(attachments);
         }, deferred.reject);
       }
     );
 
+    handler.on('GetJavaScript',
+      function wphSetupGetJavaScript(data, deferred) {
+        pdfManager.ensureCatalog('javaScript').then(function (js) {
+          deferred.resolve(js);
+        }, deferred.reject);
+      }
+    );
+
+    handler.on('GetOutline',
+      function wphSetupGetOutline(data, deferred) {
+        pdfManager.ensureCatalog('documentOutline').then(function (outline) {
+          deferred.resolve(outline);
+        }, deferred.reject);
+      }
+    );
+
+    handler.on('GetMetadata',
+      function wphSetupGetMetadata(data, deferred) {
+        Promise.all([pdfManager.ensureDoc('documentInfo'),
+                     pdfManager.ensureCatalog('metadata')]).then(
+            function (results) {
+          deferred.resolve(results);
+        }, deferred.reject);
+      }
+    );
+
     handler.on('GetData', function wphSetupGetData(data, deferred) {
       pdfManager.requestLoadedStream();
       pdfManager.onLoadedStream().then(function(stream) {
         deferred.resolve(stream.bytes);
       });
     });
 
     handler.on('UpdatePassword', function wphSetupUpdatePassword(data) {
@@ -37373,16 +37450,1000 @@ var ArithmeticDecoder = (function Arithm
       return d;
     }
   };
 
   return ArithmeticDecoder;
 })();
 
 
+var JpegImage = (function jpegImage() {
+  var dctZigZag = new Int32Array([
+     0,
+     1,  8,
+    16,  9,  2,
+     3, 10, 17, 24,
+    32, 25, 18, 11, 4,
+     5, 12, 19, 26, 33, 40,
+    48, 41, 34, 27, 20, 13,  6,
+     7, 14, 21, 28, 35, 42, 49, 56,
+    57, 50, 43, 36, 29, 22, 15,
+    23, 30, 37, 44, 51, 58,
+    59, 52, 45, 38, 31,
+    39, 46, 53, 60,
+    61, 54, 47,
+    55, 62,
+    63
+  ]);
+
+  var dctCos1  =  4017;   // cos(pi/16)
+  var dctSin1  =   799;   // sin(pi/16)
+  var dctCos3  =  3406;   // cos(3*pi/16)
+  var dctSin3  =  2276;   // sin(3*pi/16)
+  var dctCos6  =  1567;   // cos(6*pi/16)
+  var dctSin6  =  3784;   // sin(6*pi/16)
+  var dctSqrt2 =  5793;   // sqrt(2)
+  var dctSqrt1d2 = 2896;  // sqrt(2) / 2
+
+  function constructor() {
+  }
+
+  function buildHuffmanTable(codeLengths, values) {
+    var k = 0, code = [], i, j, length = 16;
+    while (length > 0 && !codeLengths[length - 1]) {
+      length--;
+    }
+    code.push({children: [], index: 0});
+    var p = code[0], q;
+    for (i = 0; i < length; i++) {
+      for (j = 0; j < codeLengths[i]; j++) {
+        p = code.pop();
+        p.children[p.index] = values[k];
+        while (p.index > 0) {
+          p = code.pop();
+        }
+        p.index++;
+        code.push(p);
+        while (code.length <= i) {
+          code.push(q = {children: [], index: 0});
+          p.children[p.index] = q.children;
+          p = q;
+        }
+        k++;
+      }
+      if (i + 1 < length) {
+        // p here points to last code
+        code.push(q = {children: [], index: 0});
+        p.children[p.index] = q.children;
+        p = q;
+      }
+    }
+    return code[0].children;
+  }
+
+  function getBlockBufferOffset(component, row, col) {
+    return 64 * ((component.blocksPerLine + 1) * row + col);
+  }
+
+  function decodeScan(data, offset,
+                      frame, components, resetInterval,
+                      spectralStart, spectralEnd,
+                      successivePrev, successive) {
+    var precision = frame.precision;
+    var samplesPerLine = frame.samplesPerLine;
+    var scanLines = frame.scanLines;
+    var mcusPerLine = frame.mcusPerLine;
+    var progressive = frame.progressive;
+    var maxH = frame.maxH, maxV = frame.maxV;
+
+    var startOffset = offset, bitsData = 0, bitsCount = 0;
+
+    function readBit() {
+      if (bitsCount > 0) {
+        bitsCount--;
+        return (bitsData >> bitsCount) & 1;
+      }
+      bitsData = data[offset++];
+      if (bitsData == 0xFF) {
+        var nextByte = data[offset++];
+        if (nextByte) {
+          throw 'unexpected marker: ' +
+            ((bitsData << 8) | nextByte).toString(16);
+        }
+        // unstuff 0
+      }
+      bitsCount = 7;
+      return bitsData >>> 7;
+    }
+
+    function decodeHuffman(tree) {
+      var node = tree;
+      var bit;
+      while ((bit = readBit()) !== null) {
+        node = node[bit];
+        if (typeof node === 'number') {
+          return node;
+        }
+        if (typeof node !== 'object') {
+          throw 'invalid huffman sequence';
+        }
+      }
+      return null;
+    }
+
+    function receive(length) {
+      var n = 0;
+      while (length > 0) {
+        var bit = readBit();
+        if (bit === null) {
+          return;
+        }
+        n = (n << 1) | bit;
+        length--;
+      }
+      return n;
+    }
+
+    function receiveAndExtend(length) {
+      var n = receive(length);
+      if (n >= 1 << (length - 1)) {
+        return n;
+      }
+      return n + (-1 << length) + 1;
+    }
+
+    function decodeBaseline(component, offset) {
+      var t = decodeHuffman(component.huffmanTableDC);
+      var diff = t === 0 ? 0 : receiveAndExtend(t);
+      component.blockData[offset] = (component.pred += diff);
+      var k = 1;
+      while (k < 64) {
+        var rs = decodeHuffman(component.huffmanTableAC);
+        var s = rs & 15, r = rs >> 4;
+        if (s === 0) {
+          if (r < 15) {
+            break;
+          }
+          k += 16;
+          continue;
+        }
+        k += r;
+        var z = dctZigZag[k];
+        component.blockData[offset + z] = receiveAndExtend(s);
+        k++;
+      }
+    }
+
+    function decodeDCFirst(component, offset) {
+      var t = decodeHuffman(component.huffmanTableDC);
+      var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
+      component.blockData[offset] = (component.pred += diff);
+    }
+
+    function decodeDCSuccessive(component, offset) {
+      component.blockData[offset] |= readBit() << successive;
+    }
+
+    var eobrun = 0;
+    function decodeACFirst(component, offset) {
+      if (eobrun > 0) {
+        eobrun--;
+        return;
+      }
+      var k = spectralStart, e = spectralEnd;
+      while (k <= e) {
+        var rs = decodeHuffman(component.huffmanTableAC);
+        var s = rs & 15, r = rs >> 4;
+        if (s === 0) {
+          if (r < 15) {
+            eobrun = receive(r) + (1 << r) - 1;
+            break;
+          }
+          k += 16;
+          continue;
+        }
+        k += r;
+        var z = dctZigZag[k];
+        component.blockData[offset + z] =
+          receiveAndExtend(s) * (1 << successive);
+        k++;
+      }
+    }
+
+    var successiveACState = 0, successiveACNextValue;
+    function decodeACSuccessive(component, offset) {
+      var k = spectralStart;
+      var e = spectralEnd;
+      var r = 0;
+      var s;
+      var rs;
+      while (k <= e) {
+        var z = dctZigZag[k];
+        switch (successiveACState) {
+        case 0: // initial state
+          rs = decodeHuffman(component.huffmanTableAC);
+          s = rs & 15;
+          r = rs >> 4;
+          if (s === 0) {
+            if (r < 15) {
+              eobrun = receive(r) + (1 << r);
+              successiveACState = 4;
+            } else {
+              r = 16;
+              successiveACState = 1;
+            }
+          } else {
+            if (s !== 1) {
+              throw 'invalid ACn encoding';
+            }
+            successiveACNextValue = receiveAndExtend(s);
+            successiveACState = r ? 2 : 3;
+          }
+          continue;
+        case 1: // skipping r zero items
+        case 2:
+          if (component.blockData[offset + z]) {
+            component.blockData[offset + z] += (readBit() << successive);
+          } else {
+            r--;
+            if (r === 0) {
+              successiveACState = successiveACState == 2 ? 3 : 0;
+            }
+          }
+          break;
+        case 3: // set value for a zero item
+          if (component.blockData[offset + z]) {
+            component.blockData[offset + z] += (readBit() << successive);
+          } else {
+            component.blockData[offset + z] =
+              successiveACNextValue << successive;
+            successiveACState = 0;
+          }
+          break;
+        case 4: // eob
+          if (component.blockData[offset + z]) {
+            component.blockData[offset + z] += (readBit() << successive);
+          }
+          break;
+        }
+        k++;
+      }
+      if (successiveACState === 4) {
+        eobrun--;
+        if (eobrun === 0) {
+          successiveACState = 0;
+        }
+      }
+    }
+
+    function decodeMcu(component, decode, mcu, row, col) {
+      var mcuRow = (mcu / mcusPerLine) | 0;
+      var mcuCol = mcu % mcusPerLine;
+      var blockRow = mcuRow * component.v + row;
+      var blockCol = mcuCol * component.h + col;
+      var offset = getBlockBufferOffset(component, blockRow, blockCol);
+      decode(component, offset);
+    }
+
+    function decodeBlock(component, decode, mcu) {
+      var blockRow = (mcu / component.blocksPerLine) | 0;
+      var blockCol = mcu % component.blocksPerLine;
+      var offset = getBlockBufferOffset(component, blockRow, blockCol);
+      decode(component, offset);
+    }
+
+    var componentsLength = components.length;
+    var component, i, j, k, n;
+    var decodeFn;
+    if (progressive) {
+      if (spectralStart === 0) {
+        decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
+      } else {
+        decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
+      }
+    } else {
+      decodeFn = decodeBaseline;
+    }
+
+    var mcu = 0, marker;
+    var mcuExpected;
+    if (componentsLength == 1) {
+      mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
+    } else {
+      mcuExpected = mcusPerLine * frame.mcusPerColumn;
+    }
+    if (!resetInterval) {
+      resetInterval = mcuExpected;
+    }
+
+    var h, v;
+    while (mcu < mcuExpected) {
+      // reset interval stuff
+      for (i = 0; i < componentsLength; i++) {
+        components[i].pred = 0;
+      }
+      eobrun = 0;
+
+      if (componentsLength == 1) {
+        component = components[0];
+        for (n = 0; n < resetInterval; n++) {
+          decodeBlock(component, decodeFn, mcu);
+          mcu++;
+        }
+      } else {
+        for (n = 0; n < resetInterval; n++) {
+          for (i = 0; i < componentsLength; i++) {
+            component = components[i];
+            h = component.h;
+            v = component.v;
+            for (j = 0; j < v; j++) {
+              for (k = 0; k < h; k++) {
+                decodeMcu(component, decodeFn, mcu, j, k);
+              }
+            }
+          }
+          mcu++;
+        }
+      }
+
+      // find marker
+      bitsCount = 0;
+      marker = (data[offset] << 8) | data[offset + 1];
+      if (marker <= 0xFF00) {
+        throw 'marker was not found';
+      }
+
+      if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
+        offset += 2;
+      } else {
+        break;
+      }
+    }
+
+    return offset - startOffset;
+  }
+
+  // A port of poppler's IDCT method which in turn is taken from:
+  //   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
+  //   'Practical Fast 1-D DCT Algorithms with 11 Multiplications',
+  //   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
+  //   988-991.
+  function quantizeAndInverse(component, blockBufferOffset, p) {
+    var qt = component.quantizationTable;
+    var v0, v1, v2, v3, v4, v5, v6, v7, t;
+    var i;
+
+    // dequant
+    for (i = 0; i < 64; i++) {
+      p[i] = component.blockData[blockBufferOffset + i] * qt[i];
+    }
+
+    // inverse DCT on rows
+    for (i = 0; i < 8; ++i) {
+      var row = 8 * i;
+
+      // check for all-zero AC coefficients
+      if (p[1 + row] === 0 && p[2 + row] === 0 && p[3 + row] === 0 &&
+          p[4 + row] === 0 && p[5 + row] === 0 && p[6 + row] === 0 &&
+          p[7 + row] === 0) {
+        t = (dctSqrt2 * p[0 + row] + 512) >> 10;
+        p[0 + row] = t;
+        p[1 + row] = t;
+        p[2 + row] = t;
+        p[3 + row] = t;
+        p[4 + row] = t;
+        p[5 + row] = t;
+        p[6 + row] = t;
+        p[7 + row] = t;
+        continue;
+      }
+
+      // stage 4
+      v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
+      v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
+      v2 = p[2 + row];
+      v3 = p[6 + row];
+      v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
+      v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
+      v5 = p[3 + row] << 4;
+      v6 = p[5 + row] << 4;
+
+      // stage 3
+      t = (v0 - v1+ 1) >> 1;
+      v0 = (v0 + v1 + 1) >> 1;
+      v1 = t;
+      t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
+      v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
+      v3 = t;
+      t = (v4 - v6 + 1) >> 1;
+      v4 = (v4 + v6 + 1) >> 1;
+      v6 = t;
+      t = (v7 + v5 + 1) >> 1;
+      v5 = (v7 - v5 + 1) >> 1;
+      v7 = t;
+
+      // stage 2
+      t = (v0 - v3 + 1) >> 1;
+      v0 = (v0 + v3 + 1) >> 1;
+      v3 = t;
+      t = (v1 - v2 + 1) >> 1;
+      v1 = (v1 + v2 + 1) >> 1;
+      v2 = t;
+      t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+      v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+      v7 = t;
+      t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+      v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+      v6 = t;
+
+      // stage 1
+      p[0 + row] = v0 + v7;
+      p[7 + row] = v0 - v7;
+      p[1 + row] = v1 + v6;
+      p[6 + row] = v1 - v6;
+      p[2 + row] = v2 + v5;
+      p[5 + row] = v2 - v5;
+      p[3 + row] = v3 + v4;
+      p[4 + row] = v3 - v4;
+    }
+
+    // inverse DCT on columns
+    for (i = 0; i < 8; ++i) {
+      var col = i;
+
+      // check for all-zero AC coefficients
+      if (p[1*8 + col] === 0 && p[2*8 + col] === 0 && p[3*8 + col] === 0 &&
+          p[4*8 + col] === 0 && p[5*8 + col] === 0 && p[6*8 + col] === 0 &&
+          p[7*8 + col] === 0) {
+        t = (dctSqrt2 * p[i+0] + 8192) >> 14;
+        p[0*8 + col] = t;
+        p[1*8 + col] = t;
+        p[2*8 + col] = t;
+        p[3*8 + col] = t;
+        p[4*8 + col] = t;
+        p[5*8 + col] = t;
+        p[6*8 + col] = t;
+        p[7*8 + col] = t;
+        continue;
+      }
+
+      // stage 4
+      v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
+      v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
+      v2 = p[2*8 + col];
+      v3 = p[6*8 + col];
+      v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
+      v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
+      v5 = p[3*8 + col];
+      v6 = p[5*8 + col];
+
+      // stage 3
+      t = (v0 - v1 + 1) >> 1;
+      v0 = (v0 + v1 + 1) >> 1;
+      v1 = t;
+      t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
+      v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
+      v3 = t;
+      t = (v4 - v6 + 1) >> 1;
+      v4 = (v4 + v6 + 1) >> 1;
+      v6 = t;
+      t = (v7 + v5 + 1) >> 1;
+      v5 = (v7 - v5 + 1) >> 1;
+      v7 = t;
+
+      // stage 2
+      t = (v0 - v3 + 1) >> 1;
+      v0 = (v0 + v3 + 1) >> 1;
+      v3 = t;
+      t = (v1 - v2 + 1) >> 1;
+      v1 = (v1 + v2 + 1) >> 1;
+      v2 = t;
+      t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
+      v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
+      v7 = t;
+      t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
+      v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
+      v6 = t;
+
+      // stage 1
+      p[0*8 + col] = v0 + v7;
+      p[7*8 + col] = v0 - v7;
+      p[1*8 + col] = v1 + v6;
+      p[6*8 + col] = v1 - v6;
+      p[2*8 + col] = v2 + v5;
+      p[5*8 + col] = v2 - v5;
+      p[3*8 + col] = v3 + v4;
+      p[4*8 + col] = v3 - v4;
+    }
+
+    // convert to 8-bit integers
+    for (i = 0; i < 64; ++i) {
+      var index = blockBufferOffset + i;
+      var q = p[i];
+      q = (q <= -2056) ? 0 : (q >= 2024) ? 255 : (q + 2056) >> 4;
+      component.blockData[index] = q;
+    }
+  }
+
+  function buildComponentData(frame, component) {
+    var lines = [];
+    var blocksPerLine = component.blocksPerLine;
+    var blocksPerColumn = component.blocksPerColumn;
+    var samplesPerLine = blocksPerLine << 3;
+    var computationBuffer = new Int32Array(64);
+
+    var i, j, ll = 0;
+    for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
+      for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
+        var offset = getBlockBufferOffset(component, blockRow, blockCol);
+        quantizeAndInverse(component, offset, computationBuffer);
+      }
+    }
+    return component.blockData;
+  }
+
+  function clamp0to255(a) {
+    return a <= 0 ? 0 : a >= 255 ? 255 : a;
+  }
+
+  constructor.prototype = {
+    parse: function parse(data) {
+
+      function readUint16() {
+        var value = (data[offset] << 8) | data[offset + 1];
+        offset += 2;
+        return value;
+      }
+
+      function readDataBlock() {
+        var length = readUint16();
+        var array = data.subarray(offset, offset + length - 2);
+        offset += array.length;
+        return array;
+      }
+
+      function prepareComponents(frame) {
+        var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
+        var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
+        for (var i = 0; i < frame.components.length; i++) {
+          component = frame.components[i];
+          var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) *
+                                        component.h / frame.maxH);
+          var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines  / 8) *
+                                          component.v / frame.maxV);
+          var blocksPerLineForMcu = mcusPerLine * component.h;
+          var blocksPerColumnForMcu = mcusPerColumn * component.v;
+
+          var blocksBufferSize = 64 * blocksPerColumnForMcu *
+                                      (blocksPerLineForMcu + 1);
+          component.blockData = new Int16Array(blocksBufferSize);
+          component.blocksPerLine = blocksPerLine;
+          component.blocksPerColumn = blocksPerColumn;
+        }
+        frame.mcusPerLine = mcusPerLine;
+        frame.mcusPerColumn = mcusPerColumn;
+      }
+
+      var offset = 0, length = data.length;
+      var jfif = null;
+      var adobe = null;
+      var pixels = null;
+      var frame, resetInterval;
+      var quantizationTables = [];
+      var huffmanTablesAC = [], huffmanTablesDC = [];
+      var fileMarker = readUint16();
+      if (fileMarker != 0xFFD8) { // SOI (Start of Image)
+        throw 'SOI not found';
+      }
+
+      fileMarker = readUint16();
+      while (fileMarker != 0xFFD9) { // EOI (End of image)
+        var i, j, l;
+        switch(fileMarker) {
+          case 0xFFE0: // APP0 (Application Specific)
+          case 0xFFE1: // APP1
+          case 0xFFE2: // APP2
+          case 0xFFE3: // APP3
+          case 0xFFE4: // APP4
+          case 0xFFE5: // APP5
+          case 0xFFE6: // APP6
+          case 0xFFE7: // APP7
+          case 0xFFE8: // APP8
+          case 0xFFE9: // APP9
+          case 0xFFEA: // APP10
+          case 0xFFEB: // APP11
+          case 0xFFEC: // APP12
+          case 0xFFED: // APP13
+          case 0xFFEE: // APP14
+          case 0xFFEF: // APP15
+          case 0xFFFE: // COM (Comment)
+            var appData = readDataBlock();
+
+            if (fileMarker === 0xFFE0) {
+              if (appData[0] === 0x4A && appData[1] === 0x46 &&
+                  appData[2] === 0x49 && appData[3] === 0x46 &&
+                  appData[4] === 0) { // 'JFIF\x00'
+                jfif = {
+                  version: { major: appData[5], minor: appData[6] },
+                  densityUnits: appData[7],
+                  xDensity: (appData[8] << 8) | appData[9],
+                  yDensity: (appData[10] << 8) | appData[11],
+                  thumbWidth: appData[12],
+                  thumbHeight: appData[13],
+                  thumbData: appData.subarray(14, 14 +
+                                              3 * appData[12] * appData[13])
+                };
+              }
+            }
+            // TODO APP1 - Exif
+            if (fileMarker === 0xFFEE) {
+              if (appData[0] === 0x41 && appData[1] === 0x64 &&
+                  appData[2] === 0x6F && appData[3] === 0x62 &&
+                  appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
+                adobe = {
+                  version: appData[6],
+                  flags0: (appData[7] << 8) | appData[8],
+                  flags1: (appData[9] << 8) | appData[10],
+                  transformCode: appData[11]
+                };
+              }
+            }
+            break;
+
+          case 0xFFDB: // DQT (Define Quantization Tables)
+            var quantizationTablesLength = readUint16();
+            var quantizationTablesEnd = quantizationTablesLength + offset - 2;
+            var z;
+            while (offset < quantizationTablesEnd) {
+              var quantizationTableSpec = data[offset++];
+              var tableData = new Int32Array(64);
+              if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
+                for (j = 0; j < 64; j++) {
+                  z = dctZigZag[j];
+                  tableData[z] = data[offset++];
+                }
+              } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
+                for (j = 0; j < 64; j++) {
+                  z = dctZigZag[j];
+                  tableData[z] = readUint16();
+                }
+              } else {
+                throw 'DQT: invalid table spec';
+              }
+              quantizationTables[quantizationTableSpec & 15] = tableData;
+            }
+            break;
+
+          case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
+          case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
+          case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
+            if (frame) {
+              throw 'Only single frame JPEGs supported';
+            }
+            readUint16(); // skip data length
+            frame = {};
+            frame.extended = (fileMarker === 0xFFC1);
+            frame.progressive = (fileMarker === 0xFFC2);
+            frame.precision = data[offset++];
+            frame.scanLines = readUint16();
+            frame.samplesPerLine = readUint16();
+            frame.components = [];
+            frame.componentIds = {};
+            var componentsCount = data[offset++], componentId;
+            var maxH = 0, maxV = 0;
+            for (i = 0; i < componentsCount; i++) {
+              componentId = data[offset];
+              var h = data[offset + 1] >> 4;
+              var v = data[offset + 1] & 15;
+              if (maxH < h) {
+                maxH = h;
+              }
+              if (maxV < v) {
+                maxV = v;
+              }
+              var qId = data[offset + 2];
+              l = frame.components.push({
+                h: h,
+                v: v,
+                quantizationTable: quantizationTables[qId]
+              });
+              frame.componentIds[componentId] = l - 1;
+              offset += 3;
+            }
+            frame.maxH = maxH;
+            frame.maxV = maxV;
+            prepareComponents(frame);
+            break;
+
+          case 0xFFC4: // DHT (Define Huffman Tables)
+            var huffmanLength = readUint16();
+            for (i = 2; i < huffmanLength;) {
+              var huffmanTableSpec = data[offset++];
+              var codeLengths = new Uint8Array(16);
+              var codeLengthSum = 0;
+              for (j = 0; j < 16; j++, offset++) {
+                codeLengthSum += (codeLengths[j] = data[offset]);
+              }
+              var huffmanValues = new Uint8Array(codeLengthSum);
+              for (j = 0; j < codeLengthSum; j++, offset++) {
+                huffmanValues[j] = data[offset];
+              }
+              i += 17 + codeLengthSum;
+
+              ((huffmanTableSpec >> 4) === 0 ?
+                huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] =
+                buildHuffmanTable(codeLengths, huffmanValues);
+            }
+            break;
+
+          case 0xFFDD: // DRI (Define Restart Interval)
+            readUint16(); // skip data length
+            resetInterval = readUint16();
+            break;
+
+          case 0xFFDA: // SOS (Start of Scan)
+            var scanLength = readUint16();
+            var selectorsCount = data[offset++];
+            var components = [], component;
+            for (i = 0; i < selectorsCount; i++) {
+              var componentIndex = frame.componentIds[data[offset++]];
+              component = frame.components[componentIndex];
+              var tableSpec = data[offset++];
+              component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
+              component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
+              components.push(component);
+            }
+            var spectralStart = data[offset++];
+            var spectralEnd = data[offset++];
+            var successiveApproximation = data[offset++];
+            var processed = decodeScan(data, offset,
+              frame, components, resetInterval,
+              spectralStart, spectralEnd,
+              successiveApproximation >> 4, successiveApproximation & 15);
+            offset += processed;
+            break;
+          default:
+            if (data[offset - 3] == 0xFF &&
+                data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
+              // could be incorrect encoding -- last 0xFF byte of the previous
+              // block was eaten by the encoder
+              offset -= 3;
+              break;
+            }
+            throw 'unknown JPEG marker ' + fileMarker.toString(16);
+        }
+        fileMarker = readUint16();
+      }
+
+      this.width = frame.samplesPerLine;
+      this.height = frame.scanLines;
+      this.jfif = jfif;
+      this.adobe = adobe;
+      this.components = [];
+      for (i = 0; i < frame.components.length; i++) {
+        component = frame.components[i];
+        this.components.push({
+          output: buildComponentData(frame, component),
+          scaleX: component.h / frame.maxH,
+          scaleY: component.v / frame.maxV,
+          blocksPerLine: component.blocksPerLine,
+          blocksPerColumn: component.blocksPerColumn
+        });
+      }
+      this.numComponents = this.components.length;
+    },
+
+    _getLinearizedBlockData: function getLinearizedBlockData(width, height) {
+      var scaleX = this.width / width, scaleY = this.height / height;
+
+      var component, componentScaleX, componentScaleY, blocksPerScanline;
+      var x, y, i, j;
+      var index;
+      var offset = 0;
+      var output;
+      var numComponents = this.components.length;
+      var dataLength = width * height * numComponents;
+      var data = new Uint8Array(dataLength);
+      var xScaleBlockOffset = new Uint32Array(width);
+      var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs
+
+      for (i = 0; i < numComponents; i++) {
+        component = this.components[i];
+        componentScaleX = component.scaleX * scaleX;
+        componentScaleY = component.scaleY * scaleY;
+        offset = i;
+        output = component.output;
+        blocksPerScanline = (component.blocksPerLine + 1) << 3;
+        // precalculate the xScaleBlockOffset
+        for (x = 0; x < width; x++) {
+          j = 0 | (x * componentScaleX);
+          xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7);
+        }
+        // linearize the blocks of the component
+        for (y = 0; y < height; y++) {
+          j = 0 | (y * componentScaleY);
+          index = blocksPerScanline * (j & mask3LSB) | ((j & 7) << 3);
+          for (x = 0; x < width; x++) {
+            data[offset] = output[index + xScaleBlockOffset[x]];
+            offset += numComponents;
+          }
+        }
+      }
+      return data;
+    },
+
+    _isColorConversionNeeded: function isColorConversionNeeded() {
+      if (this.adobe && this.adobe.transformCode) {
+        // The adobe transform marker overrides any previous setting
+        return true;
+      } else if (this.numComponents == 3) {
+        return true;
+      } else if (typeof this.colorTransform !== 'undefined') {
+        return !!this.colorTransform;
+      } else {
+        return false;
+      }
+    },
+
+    _convertYccToRgb: function convertYccToRgb(data) {
+      var Y, Cb, Cr;
+      for (var i = 0; i < data.length; i += this.numComponents) {
+        Y  = data[i    ];
+        Cb = data[i + 1];
+        Cr = data[i + 2];
+        data[i    ] = clamp0to255(Y - 179.456 + 1.402 * Cr);
+        data[i + 1] = clamp0to255(Y + 135.459 - 0.344 * Cb - 0.714 * Cr);
+        data[i + 2] = clamp0to255(Y - 226.816 + 1.772 * Cb);
+      }
+      return data;
+    },
+
+    _convertYcckToRgb: function convertYcckToRgb(data) {
+      var Y, Cb, Cr, k, CbCb, CbCr, CbY, Cbk, CrCr, Crk, CrY, YY, Yk, kk;
+      var offset = 0;
+      for (var i = 0; i < data.length; i += this.numComponents) {
+        Y  = data[i];
+        Cb = data[i + 1];
+        Cr = data[i + 2];
+        k = data[i + 3];
+
+        CbCb = Cb * Cb;
+        CbCr = Cb * Cr;
+        CbY = Cb * Y;
+        Cbk = Cb * k;
+        CrCr = Cr * Cr;
+        Crk = Cr * k;
+        CrY = Cr * Y;
+        YY = Y * Y;
+        Yk = Y * k;
+        kk = k * k;
+
+        var r = - 122.67195406894 -
+          6.60635669420364e-5 * CbCb + 0.000437130475926232 * CbCr -
+          5.4080610064599e-5* CbY + 0.00048449797120281* Cbk -
+          0.154362151871126 * Cb - 0.000957964378445773 * CrCr +
+          0.000817076911346625 * CrY - 0.00477271405408747 * Crk +
+          1.53380253221734 * Cr + 0.000961250184130688 * YY -
+          0.00266257332283933 * Yk + 0.48357088451265 * Y -
+          0.000336197177618394 * kk + 0.484791561490776 * k;
+
+        var g = 107.268039397724 +
+          2.19927104525741e-5 * CbCb - 0.000640992018297945 * CbCr +
+          0.000659397001245577* CbY + 0.000426105652938837* Cbk -
+          0.176491792462875 * Cb - 0.000778269941513683 * CrCr +
+          0.00130872261408275 * CrY + 0.000770482631801132 * Crk -
+          0.151051492775562 * Cr + 0.00126935368114843 * YY -
+          0.00265090189010898 * Yk + 0.25802910206845 * Y -
+          0.000318913117588328 * kk - 0.213742400323665 * k;
+
+        var b = - 20.810012546947 -
+          0.000570115196973677 * CbCb - 2.63409051004589e-5 * CbCr +
+          0.0020741088115012* CbY - 0.00288260236853442* Cbk +
+          0.814272968359295 * Cb - 1.53496057440975e-5 * CrCr -
+          0.000132689043961446 * CrY + 0.000560833691242812 * Crk -
+          0.195152027534049 * Cr + 0.00174418132927582 * YY -
+          0.00255243321439347 * Yk + 0.116935020465145 * Y -
+          0.000343531996510555 * kk + 0.24165260232407 * k;
+
+        data[offset++] = clamp0to255(r);
+        data[offset++] = clamp0to255(g);
+        data[offset++] = clamp0to255(b);
+      }
+      return data;
+    },
+
+    _convertYcckToCmyk: function convertYcckToCmyk(data) {
+      var Y, Cb, Cr;
+      for (var i = 0; i < data.length; i += this.numComponents) {
+        Y  = data[i];
+        Cb = data[i + 1];
+        Cr = data[i + 2];
+        data[i    ] = clamp0to255(434.456 - Y - 1.402 * Cr);
+        data[i + 1] = clamp0to255(119.541 - Y + 0.344 * Cb + 0.714 * Cr);
+        data[i + 2] = clamp0to255(481.816 - Y - 1.772 * Cb);
+        // K in data[i + 3] is unchanged
+      }
+      return data;
+    },
+
+    _convertCmykToRgb: function convertCmykToRgb(data) {
+      var c, m, y, k;
+      var offset = 0;
+      var length = data.length;
+      var min = -255 * 255 * 255;
+      var scale = 1 / 255 / 255;
+      for (var i = 0; i < length;) {
+        c = data[i++];
+        m = data[i++];
+        y = data[i++];
+        k = data[i++];
+
+        var r =
+          c * (-4.387332384609988 * c + 54.48615194189176 * m +
+               18.82290502165302 * y + 212.25662451639585 * k -
+               72734.4411664936) +
+          m * (1.7149763477362134 * m - 5.6096736904047315 * y -
+               17.873870861415444 * k - 1401.7366389350734) +
+          y * (-2.5217340131683033 * y - 21.248923337353073 * k +
+               4465.541406466231) -
+          k * (21.86122147463605 * k + 48317.86113160301);
+        var g =
+          c * (8.841041422036149 * c + 60.118027045597366 * m +
+               6.871425592049007 * y + 31.159100130055922 * k -
+               20220.756542821975) +
+          m * (-15.310361306967817 * m + 17.575251261109482 * y +
+               131.35250912493976 * k - 48691.05921601825) +
+          y * (4.444339102852739 * y + 9.8632861493405 * k -
+               6341.191035517494) -
+          k * (20.737325471181034 * k + 47890.15695978492);
+        var b =
+          c * (0.8842522430003296 * c + 8.078677503112928 * m +
+               30.89978309703729 * y - 0.23883238689178934 * k -
+               3616.812083916688) +
+          m * (10.49593273432072 * m + 63.02378494754052 * y +
+               50.606957656360734 * k - 28620.90484698408) +
+          y * (0.03296041114873217 * y + 115.60384449646641 * k -
+               49363.43385999684) -
+          k * (22.33816807309886 * k + 45932.16563550634);
+
+        data[offset++] = r >= 0 ? 255 : r <= min ? 0 : 255 + r * scale | 0;
+        data[offset++] = g >= 0 ? 255 : g <= min ? 0 : 255 + g * scale | 0;
+        data[offset++] = b >= 0 ? 255 : b <= min ? 0 : 255 + b * scale | 0;
+      }
+      return data;
+    },
+
+    getData: function getData(width, height, forceRGBoutput) {
+      if (this.numComponents > 4) {
+        throw 'Unsupported color mode';
+      }
+      // type of data: Uint8Array(width * height * numComponents)
+      var data = this._getLinearizedBlockData(width, height);
+
+      if (this.numComponents === 3) {
+        return this._convertYccToRgb(data);
+      } else if (this.numComponents === 4) {
+        if (this._isColorConversionNeeded()) {
+          if (forceRGBoutput) {
+            return this._convertYcckToRgb(data);
+          } else {
+            return this._convertYcckToCmyk(data);
+          }
+        } else {
+          return this._convertCmykToRgb(data);
+        }
+      }
+      return data;
+    }
+  };
+
+  return constructor;
+})();
+
+
 var JpxImage = (function JpxImageClosure() {
   // Table E.1
   var SubbandsGainLog2 = {
     'LL': 0,
     'LH': 1,
     'HL': 1,
     'HH': 2
   };
@@ -38430,69 +39491,68 @@ var JpxImage = (function JpxImageClosure
         left: tile0.left,
         top: tile0.top,
         width: tile0.width,
         height: tile0.height,
         items: out
       };
 
       // Section G.2.2 Inverse multi component transform
-      var shift, offset, max, min;
+      var shift, offset, max, min, maxK;
       var pos = 0, j, jj, y0, y1, y2, r, g, b, k, val;
       if (tile.codingStyleDefaultParameters.multipleComponentTransform) {
         var fourComponents = componentsCount === 4;
         var y0items = transformedTiles[0].items;
         var y1items = transformedTiles[1].items;
         var y2items = transformedTiles[2].items;
         var y3items = fourComponents ? transformedTiles[3].items : null;
 
         // HACK: The multiple component transform formulas below assume that
         // all components have the same precision. With this in mind, we
         // compute shift and offset only once.
         shift = components[0].precision - 8;
         offset = (128 << shift) + 0.5;
-        max = (127.5 * (1 << shift));
-        min = -max;
+        max = 255 * (1 << shift);
+        maxK = max * 0.5;
+        min = -maxK;
 
         var component0 = tile.components[0];
+        var alpha01 = componentsCount - 3;
+        jj = y0items.length;
         if (!component0.codingStyleParameters.reversibleTransformation) {
           // inverse irreversible multiple component transform
-          for (j = 0, jj = y0items.length; j < jj; ++j) {
-            y0 = y0items[j];
+          for (j = 0; j < jj; j++, pos += alpha01) {
+            y0 = y0items[j] + offset;
             y1 = y1items[j];
             y2 = y2items[j];
             r = y0 + 1.402 * y2;
             g = y0 - 0.34413 * y1 - 0.71414 * y2;
             b = y0 + 1.772 * y1;
-            out[pos++] = r <= min ? 0 : r >= max ? 255 : (r + offset) >> shift;
-            out[pos++] = g <= min ? 0 : g >= max ? 255 : (g + offset) >> shift;
-            out[pos++] = b <= min ? 0 : b >= max ? 255 : (b + offset) >> shift;
-            if (fourComponents) {
-              k = y3items[j];
-              out[pos++] =
-                k <= min ? 0 : k >= max ? 255 : (k + offset) >> shift;
-            }
+            out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift;
+            out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift;
+            out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift;
           }
         } else {
           // inverse reversible multiple component transform
-          for (j = 0, jj = y0items.length; j < jj; ++j) {
-            y0 = y0items[j];
+          for (j = 0; j < jj; j++, pos += alpha01) {
+            y0 = y0items[j] + offset;
             y1 = y1items[j];
             y2 = y2items[j];
             g = y0 - ((y2 + y1) >> 2);
             r = g + y2;
             b = g + y1;
-            out[pos++] = r <= min ? 0 : r >= max ? 255 : (r + offset) >> shift;
-            out[pos++] = g <= min ? 0 : g >= max ? 255 : (g + offset) >> shift;
-            out[pos++] = b <= min ? 0 : b >= max ? 255 : (b + offset) >> shift;
-            if (fourComponents) {
-              k = y3items[j];
-              out[pos++] =
-                k <= min ? 0 : k >= max ? 255 : (k + offset) >> shift;
-            }
+            out[pos++] = r <= 0 ? 0 : r >= max ? 255 : r >> shift;
+            out[pos++] = g <= 0 ? 0 : g >= max ? 255 : g >> shift;
+            out[pos++] = b <= 0 ? 0 : b >= max ? 255 : b >> shift;
+          }
+        }
+        if (fourComponents) {
+          for (j = 0, pos = 3; j < jj; j++, pos += 4) {
+            k = y3items[j];
+            out[pos] = k <= min ? 0 : k >= maxK ? 255 : (k + offset) >> shift;
           }
         }
       } else { // no multi-component transform
         for (c = 0; c < componentsCount; c++) {
           var items = transformedTiles[c].items;
           shift = components[c].precision - 8;
           offset = (128 << shift) + 0.5;
           max = (127.5 * (1 << shift));
@@ -40884,975 +41944,14 @@ var MurmurHash3_64 = (function MurmurHas
       }
 
       return str;
     }
   };
 
   return MurmurHash3_64;
 })();
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-/*
-   Copyright 2011 notmasteryet
-
-   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.
-*/
-
-// - The JPEG specification can be found in the ITU CCITT Recommendation T.81
-//   (www.w3.org/Graphics/JPEG/itu-t81.pdf)
-// - The JFIF specification can be found in the JPEG File Interchange Format
-//   (www.w3.org/Graphics/JPEG/jfif3.pdf)
-// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
-//   in PostScript Level 2, Technical Note #5116
-//   (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
-
-var JpegImage = (function jpegImage() {
-  "use strict";
-  var dctZigZag = new Int32Array([
-     0,
-     1,  8,
-    16,  9,  2,
-     3, 10, 17, 24,
-    32, 25, 18, 11, 4,
-     5, 12, 19, 26, 33, 40,
-    48, 41, 34, 27, 20, 13,  6,
-     7, 14, 21, 28, 35, 42, 49, 56,
-    57, 50, 43, 36, 29, 22, 15,
-    23, 30, 37, 44, 51, 58,
-    59, 52, 45, 38, 31,
-    39, 46, 53, 60,
-    61, 54, 47,
-    55, 62,
-    63
-  ]);
-
-  var dctCos1  =  4017   // cos(pi/16)
-  var dctSin1  =   799   // sin(pi/16)
-  var dctCos3  =  3406   // cos(3*pi/16)
-  var dctSin3  =  2276   // sin(3*pi/16)
-  var dctCos6  =  1567   // cos(6*pi/16)
-  var dctSin6  =  3784   // sin(6*pi/16)
-  var dctSqrt2 =  5793   // sqrt(2)
-  var dctSqrt1d2 = 2896  // sqrt(2) / 2
-
-  function constructor() {
-  }
-
-  function buildHuffmanTable(codeLengths, values) {
-    var k = 0, code = [], i, j, length = 16;
-    while (length > 0 && !codeLengths[length - 1])
-      length--;
-    code.push({children: [], index: 0});
-    var p = code[0], q;
-    for (i = 0; i < length; i++) {
-      for (j = 0; j < codeLengths[i]; j++) {
-        p = code.pop();
-        p.children[p.index] = values[k];
-        while (p.index > 0) {
-          p = code.pop();
-        }
-        p.index++;
-        code.push(p);
-        while (code.length <= i) {
-          code.push(q = {children: [], index: 0});
-          p.children[p.index] = q.children;
-          p = q;
-        }
-        k++;
-      }
-      if (i + 1 < length) {
-        // p here points to last code
-        code.push(q = {children: [], index: 0});
-        p.children[p.index] = q.children;
-        p = q;
-      }
-    }
-    return code[0].children;
-  }
-
-  function getBlockBufferOffset(component, row, col) {
-    return 64 * ((component.blocksPerLine + 1) * row + col);
-  }
-
-  function decodeScan(data, offset,
-                      frame, components, resetInterval,
-                      spectralStart, spectralEnd,
-                      successivePrev, successive) {
-    var precision = frame.precision;
-    var samplesPerLine = frame.samplesPerLine;
-    var scanLines = frame.scanLines;
-    var mcusPerLine = frame.mcusPerLine;
-    var progressive = frame.progressive;
-    var maxH = frame.maxH, maxV = frame.maxV;
-
-    var startOffset = offset, bitsData = 0, bitsCount = 0;
-
-    function readBit() {
-      if (bitsCount > 0) {
-        bitsCount--;
-        return (bitsData >> bitsCount) & 1;
-      }
-      bitsData = data[offset++];
-      if (bitsData == 0xFF) {
-        var nextByte = data[offset++];
-        if (nextByte) {
-          throw "unexpected marker: " + ((bitsData << 8) | nextByte).toString(16);
-        }
-        // unstuff 0
-      }
-      bitsCount = 7;
-      return bitsData >>> 7;
-    }
-
-    function decodeHuffman(tree) {
-      var node = tree;
-      var bit;
-      while ((bit = readBit()) !== null) {
-        node = node[bit];
-        if (typeof node === 'number')
-          return node;
-        if (typeof node !== 'object')
-          throw "invalid huffman sequence";
-      }
-      return null;
-    }
-
-    function receive(length) {
-      var n = 0;
-      while (length > 0) {
-        var bit = readBit();
-        if (bit === null) return;
-        n = (n << 1) | bit;
-        length--;
-      }
-      return n;
-    }
-
-    function receiveAndExtend(length) {
-      var n = receive(length);
-      if (n >= 1 << (length - 1))
-        return n;
-      return n + (-1 << length) + 1;
-    }
-
-    function decodeBaseline(component, offset) {
-      var t = decodeHuffman(component.huffmanTableDC);
-      var diff = t === 0 ? 0 : receiveAndExtend(t);
-      component.blockData[offset] = (component.pred += diff);
-      var k = 1;
-      while (k < 64) {
-        var rs = decodeHuffman(component.huffmanTableAC);
-        var s = rs & 15, r = rs >> 4;
-        if (s === 0) {
-          if (r < 15)
-            break;
-          k += 16;
-          continue;
-        }
-        k += r;
-        var z = dctZigZag[k];
-        component.blockData[offset + z] = receiveAndExtend(s);
-        k++;
-      }
-    }
-
-    function decodeDCFirst(component, offset) {
-      var t = decodeHuffman(component.huffmanTableDC);
-      var diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
-      component.blockData[offset] = (component.pred += diff);
-    }
-
-    function decodeDCSuccessive(component, offset) {
-      component.blockData[offset] |= readBit() << successive;
-    }
-
-    var eobrun = 0;
-    function decodeACFirst(component, offset) {
-      if (eobrun > 0) {
-        eobrun--;
-        return;
-      }
-      var k = spectralStart, e = spectralEnd;
-      while (k <= e) {
-        var rs = decodeHuffman(component.huffmanTableAC);
-        var s = rs & 15, r = rs >> 4;
-        if (s === 0) {
-          if (r < 15) {
-            eobrun = receive(r) + (1 << r) - 1;
-            break;
-          }
-          k += 16;
-          continue;
-        }
-        k += r;
-        var z = dctZigZag[k];
-        component.blockData[offset + z] = receiveAndExtend(s) * (1 << successive);
-        k++;
-      }
-    }
-
-    var successiveACState = 0, successiveACNextValue;
-    function decodeACSuccessive(component, offset) {
-      var k = spectralStart, e = spectralEnd, r = 0;
-      while (k <= e) {
-        var z = dctZigZag[k];
-        switch (successiveACState) {
-        case 0: // initial state
-          var rs = decodeHuffman(component.huffmanTableAC);
-          var s = rs & 15, r = rs >> 4;
-          if (s === 0) {
-            if (r < 15) {
-              eobrun = receive(r) + (1 << r);
-              successiveACState = 4;
-            } else {
-              r = 16;
-              successiveACState = 1;
-            }
-          } else {
-            if (s !== 1)
-              throw "invalid ACn encoding";
-            successiveACNextValue = receiveAndExtend(s);
-            successiveACState = r ? 2 : 3;
-          }
-          continue;
-        case 1: // skipping r zero items
-        case 2:
-          if (component.blockData[offset + z]) {
-            component.blockData[offset + z] += (readBit() << successive);
-          } else {
-            r--;
-            if (r === 0)
-              successiveACState = successiveACState == 2 ? 3 : 0;
-          }
-          break;
-        case 3: // set value for a zero item
-          if (component.blockData[offset + z]) {
-            component.blockData[offset + z] += (readBit() << successive);
-          } else {
-            component.blockData[offset + z] = successiveACNextValue << successive;
-            successiveACState = 0;
-          }
-          break;
-        case 4: // eob
-          if (component.blockData[offset + z]) {
-            component.blockData[offset + z] += (readBit() << successive);
-          }
-          break;
-        }
-        k++;
-      }
-      if (successiveACState === 4) {
-        eobrun--;
-        if (eobrun === 0)
-          successiveACState = 0;
-      }
-    }
-
-    function decodeMcu(component, decode, mcu, row, col) {
-      var mcuRow = (mcu / mcusPerLine) | 0;
-      var mcuCol = mcu % mcusPerLine;
-      var blockRow = mcuRow * component.v + row;
-      var blockCol = mcuCol * component.h + col;
-      var offset = getBlockBufferOffset(component, blockRow, blockCol);
-      decode(component, offset);
-    }
-
-    function decodeBlock(component, decode, mcu) {
-      var blockRow = (mcu / component.blocksPerLine) | 0;
-      var blockCol = mcu % component.blocksPerLine;
-      var offset = getBlockBufferOffset(component, blockRow, blockCol);
-      decode(component, offset);
-    }
-
-    var componentsLength = components.length;
-    var component, i, j, k, n;
-    var decodeFn;
-    if (progressive) {
-      if (spectralStart === 0)
-        decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive;
-      else
-        decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive;
-    } else {
-      decodeFn = decodeBaseline;
-    }
-
-    var mcu = 0, marker;
-    var mcuExpected;
-    if (componentsLength == 1) {
-      mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn;
-    } else {
-      mcuExpected = mcusPerLine * frame.mcusPerColumn;
-    }
-    if (!resetInterval) {
-      resetInterval = mcuExpected;
-    }
-
-    var h, v;
-    while (mcu < mcuExpected) {
-      // reset interval stuff
-      for (i = 0; i < componentsLength; i++) {
-        components[i].pred = 0;
-      }
-      eobrun = 0;
-
-      if (componentsLength == 1) {
-        component = components[0];
-        for (n = 0; n < resetInterval; n++) {
-          decodeBlock(component, decodeFn, mcu);
-          mcu++;
-        }
-      } else {
-        for (n = 0; n < resetInterval; n++) {
-          for (i = 0; i < componentsLength; i++) {
-            component = components[i];
-            h = component.h;
-            v = component.v;
-            for (j = 0; j < v; j++) {
-              for (k = 0; k < h; k++) {
-                decodeMcu(component, decodeFn, mcu, j, k);
-              }
-            }
-          }
-          mcu++;
-        }
-      }
-
-      // find marker
-      bitsCount = 0;
-      marker = (data[offset] << 8) | data[offset + 1];
-      if (marker <= 0xFF00) {
-        throw "marker was not found";
-      }
-
-      if (marker >= 0xFFD0 && marker <= 0xFFD7) { // RSTx
-        offset += 2;
-      } else {
-        break;
-      }
-    }
-
-    return offset - startOffset;
-  }
-
-  // A port of poppler's IDCT method which in turn is taken from:
-  //   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
-  //   "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
-  //   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
-  //   988-991.
-  function quantizeAndInverse(component, blockBufferOffset, p) {
-    var qt = component.quantizationTable;
-    var v0, v1, v2, v3, v4, v5, v6, v7, t;
-    var i;
-
-    // dequant
-    for (i = 0; i < 64; i++) {
-      p[i] = component.blockData[blockBufferOffset + i] * qt[i];
-    }
-
-    // inverse DCT on rows
-    for (i = 0; i < 8; ++i) {
-      var row = 8 * i;
-
-      // check for all-zero AC coefficients
-      if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
-          p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
-          p[7 + row] == 0) {
-        t = (dctSqrt2 * p[0 + row] + 512) >> 10;
-        p[0 + row] = t;
-        p[1 + row] = t;
-        p[2 + row] = t;
-        p[3 + row] = t;
-        p[4 + row] = t;
-        p[5 + row] = t;
-        p[6 + row] = t;
-        p[7 + row] = t;
-        continue;
-      }
-
-      // stage 4
-      v0 = (dctSqrt2 * p[0 + row] + 128) >> 8;
-      v1 = (dctSqrt2 * p[4 + row] + 128) >> 8;
-      v2 = p[2 + row];
-      v3 = p[6 + row];
-      v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) >> 8;
-      v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) >> 8;
-      v5 = p[3 + row] << 4;
-      v6 = p[5 + row] << 4;
-
-      // stage 3
-      t = (v0 - v1+ 1) >> 1;
-      v0 = (v0 + v1 + 1) >> 1;
-      v1 = t;
-      t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8;
-      v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8;
-      v3 = t;
-      t = (v4 - v6 + 1) >> 1;
-      v4 = (v4 + v6 + 1) >> 1;
-      v6 = t;
-      t = (v7 + v5 + 1) >> 1;
-      v5 = (v7 - v5 + 1) >> 1;
-      v7 = t;
-
-      // stage 2
-      t = (v0 - v3 + 1) >> 1;
-      v0 = (v0 + v3 + 1) >> 1;
-      v3 = t;
-      t = (v1 - v2 + 1) >> 1;
-      v1 = (v1 + v2 + 1) >> 1;
-      v2 = t;
-      t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
-      v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
-      v7 = t;
-      t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
-      v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
-      v6 = t;
-
-      // stage 1
-      p[0 + row] = v0 + v7;
-      p[7 + row] = v0 - v7;
-      p[1 + row] = v1 + v6;
-      p[6 + row] = v1 - v6;
-      p[2 + row] = v2 + v5;
-      p[5 + row] = v2 - v5;
-      p[3 + row] = v3 + v4;
-      p[4 + row] = v3 - v4;
-    }
-
-    // inverse DCT on columns
-    for (i = 0; i < 8; ++i) {
-      var col = i;
-
-      // check for all-zero AC coefficients
-      if (p[1*8 + col] == 0 && p[2*8 + col] == 0 && p[3*8 + col] == 0 &&
-          p[4*8 + col] == 0 && p[5*8 + col] == 0 && p[6*8 + col] == 0 &&
-          p[7*8 + col] == 0) {
-        t = (dctSqrt2 * p[i+0] + 8192) >> 14;
-        p[0*8 + col] = t;
-        p[1*8 + col] = t;
-        p[2*8 + col] = t;
-        p[3*8 + col] = t;
-        p[4*8 + col] = t;
-        p[5*8 + col] = t;
-        p[6*8 + col] = t;
-        p[7*8 + col] = t;
-        continue;
-      }
-
-      // stage 4
-      v0 = (dctSqrt2 * p[0*8 + col] + 2048) >> 12;
-      v1 = (dctSqrt2 * p[4*8 + col] + 2048) >> 12;
-      v2 = p[2*8 + col];
-      v3 = p[6*8 + col];
-      v4 = (dctSqrt1d2 * (p[1*8 + col] - p[7*8 + col]) + 2048) >> 12;
-      v7 = (dctSqrt1d2 * (p[1*8 + col] + p[7*8 + col]) + 2048) >> 12;
-      v5 = p[3*8 + col];
-      v6 = p[5*8 + col];
-
-      // stage 3
-      t = (v0 - v1 + 1) >> 1;
-      v0 = (v0 + v1 + 1) >> 1;
-      v1 = t;
-      t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12;
-      v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12;
-      v3 = t;
-      t = (v4 - v6 + 1) >> 1;
-      v4 = (v4 + v6 + 1) >> 1;
-      v6 = t;
-      t = (v7 + v5 + 1) >> 1;
-      v5 = (v7 - v5 + 1) >> 1;
-      v7 = t;
-
-      // stage 2
-      t = (v0 - v3 + 1) >> 1;
-      v0 = (v0 + v3 + 1) >> 1;
-      v3 = t;
-      t = (v1 - v2 + 1) >> 1;
-      v1 = (v1 + v2 + 1) >> 1;
-      v2 = t;
-      t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
-      v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
-      v7 = t;
-      t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12;
-      v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12;
-      v6 = t;
-
-      // stage 1
-      p[0*8 + col] = v0 + v7;
-      p[7*8 + col] = v0 - v7;
-      p[1*8 + col] = v1 + v6;
-      p[6*8 + col] = v1 - v6;
-      p[2*8 + col] = v2 + v5;
-      p[5*8 + col] = v2 - v5;
-      p[3*8 + col] = v3 + v4;
-      p[4*8 + col] = v3 - v4;
-    }
-
-    // convert to 8-bit integers
-    for (i = 0; i < 64; ++i) {
-      var index = blockBufferOffset + i;
-      component.blockData[index] = clampTo8bitInt((p[i] + 2056) >> 4);
-    }
-  }
-
-  function buildComponentData(frame, component) {
-    var lines = [];
-    var blocksPerLine = component.blocksPerLine;
-    var blocksPerColumn = component.blocksPerColumn;
-    var samplesPerLine = blocksPerLine << 3;
-    var computationBuffer = new Int32Array(64);
-
-    var i, j, ll = 0;
-    for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
-      for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
-        var offset = getBlockBufferOffset(component, blockRow, blockCol)
-        quantizeAndInverse(component, offset, computationBuffer);
-      }
-    }
-    return component.blockData;
-  }
-
-  function clampTo8bitInt(a) {
-    return a <= 0 ? 0 : a >= 255 ? 255 : a | 0;
-  }
-
-  function clamp0to255(a) {
-    return a <= 0 ? 0 : a >= 255 ? 255 : a;
-  }
-
-  constructor.prototype = {
-    load: function load(path) {
-      var xhr = new XMLHttpRequest();
-      xhr.open("GET", path, true);
-      xhr.responseType = "arraybuffer";
-      xhr.onload = (function() {
-        // TODO catch parse error
-        var data = new Uint8Array(xhr.response || xhr.mozResponseArrayBuffer);
-        this.parse(data);
-        if (this.onload)
-          this.onload();
-      }).bind(this);
-      xhr.send(null);
-    },
-
-    parse: function parse(data) {
-
-      function readUint16() {
-        var value = (data[offset] << 8) | data[offset + 1];
-        offset += 2;
-        return value;
-      }
-
-      function readDataBlock() {
-        var length = readUint16();
-        var array = data.subarray(offset, offset + length - 2);
-        offset += array.length;
-        return array;
-      }
-
-      function prepareComponents(frame) {
-        var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
-        var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV);
-        for (var i = 0; i < frame.components.length; i++) {
-          component = frame.components[i];
-          var blocksPerLine = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
-          var blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines  / 8) * component.v / frame.maxV);
-          var blocksPerLineForMcu = mcusPerLine * component.h;
-          var blocksPerColumnForMcu = mcusPerColumn * component.v;
-
-          var blocksBufferSize = 64 * blocksPerColumnForMcu
-                                    * (blocksPerLineForMcu + 1);
-          component.blockData = new Int16Array(blocksBufferSize);
-          component.blocksPerLine = blocksPerLine;
-          component.blocksPerColumn = blocksPerColumn;
-        }
-        frame.mcusPerLine = mcusPerLine;
-        frame.mcusPerColumn = mcusPerColumn;
-      }
-
-      var offset = 0, length = data.length;
-      var jfif = null;
-      var adobe = null;
-      var pixels = null;
-      var frame, resetInterval;
-      var quantizationTables = [];
-      var huffmanTablesAC = [], huffmanTablesDC = [];
-      var fileMarker = readUint16();
-      if (fileMarker != 0xFFD8) { // SOI (Start of Image)
-        throw "SOI not found";
-      }
-
-      fileMarker = readUint16();
-      while (fileMarker != 0xFFD9) { // EOI (End of image)
-        var i, j, l;
-        switch(fileMarker) {
-          case 0xFFE0: // APP0 (Application Specific)
-          case 0xFFE1: // APP1
-          case 0xFFE2: // APP2
-          case 0xFFE3: // APP3
-          case 0xFFE4: // APP4
-          case 0xFFE5: // APP5
-          case 0xFFE6: // APP6
-          case 0xFFE7: // APP7
-          case 0xFFE8: // APP8
-          case 0xFFE9: // APP9
-          case 0xFFEA: // APP10
-          case 0xFFEB: // APP11
-          case 0xFFEC: // APP12
-          case 0xFFED: // APP13
-          case 0xFFEE: // APP14
-          case 0xFFEF: // APP15
-          case 0xFFFE: // COM (Comment)
-            var appData = readDataBlock();
-
-            if (fileMarker === 0xFFE0) {
-              if (appData[0] === 0x4A && appData[1] === 0x46 && appData[2] === 0x49 &&
-                appData[3] === 0x46 && appData[4] === 0) { // 'JFIF\x00'
-                jfif = {
-                  version: { major: appData[5], minor: appData[6] },
-                  densityUnits: appData[7],
-                  xDensity: (appData[8] << 8) | appData[9],
-                  yDensity: (appData[10] << 8) | appData[11],
-                  thumbWidth: appData[12],
-                  thumbHeight: appData[13],
-                  thumbData: appData.subarray(14, 14 + 3 * appData[12] * appData[13])
-                };
-              }
-            }
-            // TODO APP1 - Exif
-            if (fileMarker === 0xFFEE) {
-              if (appData[0] === 0x41 && appData[1] === 0x64 && appData[2] === 0x6F &&
-                appData[3] === 0x62 && appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
-                adobe = {
-                  version: appData[6],
-                  flags0: (appData[7] << 8) | appData[8],
-                  flags1: (appData[9] << 8) | appData[10],
-                  transformCode: appData[11]
-                };
-              }
-            }
-            break;
-
-          case 0xFFDB: // DQT (Define Quantization Tables)
-            var quantizationTablesLength = readUint16();
-            var quantizationTablesEnd = quantizationTablesLength + offset - 2;
-            while (offset < quantizationTablesEnd) {
-              var quantizationTableSpec = data[offset++];
-              var tableData = new Int32Array(64);
-              if ((quantizationTableSpec >> 4) === 0) { // 8 bit values
-                for (j = 0; j < 64; j++) {
-                  var z = dctZigZag[j];
-                  tableData[z] = data[offset++];
-                }
-              } else if ((quantizationTableSpec >> 4) === 1) { //16 bit
-                for (j = 0; j < 64; j++) {
-                  var z = dctZigZag[j];
-                  tableData[z] = readUint16();
-                }
-              } else
-                throw "DQT: invalid table spec";
-              quantizationTables[quantizationTableSpec & 15] = tableData;
-            }
-            break;
-
-          case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
-          case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
-          case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
-            if (frame) {
-              throw "Only single frame JPEGs supported";
-            }
-            readUint16(); // skip data length
-            frame = {};
-            frame.extended = (fileMarker === 0xFFC1);
-            frame.progressive = (fileMarker === 0xFFC2);
-            frame.precision = data[offset++];
-            frame.scanLines = readUint16();
-            frame.samplesPerLine = readUint16();
-            frame.components = [];
-            frame.componentIds = {};
-            var componentsCount = data[offset++], componentId;
-            var maxH = 0, maxV = 0;
-            for (i = 0; i < componentsCount; i++) {
-              componentId = data[offset];
-              var h = data[offset + 1] >> 4;
-              var v = data[offset + 1] & 15;
-              if (maxH < h) maxH = h;
-              if (maxV < v) maxV = v;
-              var qId = data[offset + 2];
-              var l = frame.components.push({
-                h: h,
-                v: v,
-                quantizationTable: quantizationTables[qId]
-              });
-              frame.componentIds[componentId] = l - 1;
-              offset += 3;
-            }
-            frame.maxH = maxH;
-            frame.maxV = maxV;
-            prepareComponents(frame);
-            break;
-
-          case 0xFFC4: // DHT (Define Huffman Tables)
-            var huffmanLength = readUint16();
-            for (i = 2; i < huffmanLength;) {
-              var huffmanTableSpec = data[offset++];
-              var codeLengths = new Uint8Array(16);
-              var codeLengthSum = 0;
-              for (j = 0; j < 16; j++, offset++)
-                codeLengthSum += (codeLengths[j] = data[offset]);
-              var huffmanValues = new Uint8Array(codeLengthSum);
-              for (j = 0; j < codeLengthSum; j++, offset++)
-                huffmanValues[j] = data[offset];
-              i += 17 + codeLengthSum;
-
-              ((huffmanTableSpec >> 4) === 0 ?
-                huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] =
-                buildHuffmanTable(codeLengths, huffmanValues);
-            }
-            break;
-
-          case 0xFFDD: // DRI (Define Restart Interval)
-            readUint16(); // skip data length
-            resetInterval = readUint16();
-            break;
-
-          case 0xFFDA: // SOS (Start of Scan)
-            var scanLength = readUint16();
-            var selectorsCount = data[offset++];
-            var components = [], component;
-            for (i = 0; i < selectorsCount; i++) {
-              var componentIndex = frame.componentIds[data[offset++]];
-              component = frame.components[componentIndex];
-              var tableSpec = data[offset++];
-              component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
-              component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
-              components.push(component);
-            }
-            var spectralStart = data[offset++];
-            var spectralEnd = data[offset++];
-            var successiveApproximation = data[offset++];
-            var processed = decodeScan(data, offset,
-              frame, components, resetInterval,
-              spectralStart, spectralEnd,
-              successiveApproximation >> 4, successiveApproximation & 15);
-            offset += processed;
-            break;
-          default:
-            if (data[offset - 3] == 0xFF &&
-                data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
-              // could be incorrect encoding -- last 0xFF byte of the previous
-              // block was eaten by the encoder
-              offset -= 3;
-              break;
-            }
-            throw "unknown JPEG marker " + fileMarker.toString(16);
-        }
-        fileMarker = readUint16();
-      }
-
-      this.width = frame.samplesPerLine;
-      this.height = frame.scanLines;
-      this.jfif = jfif;
-      this.adobe = adobe;
-      this.components = [];
-      for (var i = 0; i < frame.components.length; i++) {
-        var component = frame.components[i];
-        this.components.push({
-          output: buildComponentData(frame, component),
-          scaleX: component.h / frame.maxH,
-          scaleY: component.v / frame.maxV,
-          blocksPerLine: component.blocksPerLine,
-          blocksPerColumn: component.blocksPerColumn
-        });
-      }
-    },
-
-    getData: function getData(width, height) {
-      var scaleX = this.width / width, scaleY = this.height / height;
-
-      var component, componentScaleX, componentScaleY;
-      var x, y, i;
-      var offset = 0;
-      var Y, Cb, Cr, K, C, M, Ye, R, G, B;
-      var colorTransform;
-      var numComponents = this.components.length;
-      var dataLength = width * height * numComponents;
-      var data = new Uint8Array(dataLength);
-      var componentLine;
-
-      // lineData is reused for all components. Assume first component is
-      // the biggest
-      var lineData = new Uint8Array((this.components[0].blocksPerLine << 3) *
-                                    this.components[0].blocksPerColumn * 8);
-
-      // First construct image data ...
-      for (i = 0; i < numComponents; i++) {
-        component = this.components[i];
-        var blocksPerLine = component.blocksPerLine;
-        var blocksPerColumn = component.blocksPerColumn;
-        var samplesPerLine = blocksPerLine << 3;
-
-        var j, k, ll = 0;
-        var lineOffset = 0;
-        for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) {
-          var scanLine = blockRow << 3;
-          for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) {
-            var bufferOffset = getBlockBufferOffset(component, blockRow, blockCol);
-            var offset = 0, sample = blockCol << 3;
-            for (j = 0; j < 8; j++) {
-              var lineOffset = (scanLine + j) * samplesPerLine;
-              for (k = 0; k < 8; k++) {
-                lineData[lineOffset + sample + k] =
-                  component.output[bufferOffset + offset++];
-              }
-            }
-          }
-        }
-
-        componentScaleX = component.scaleX * scaleX;
-        componentScaleY = component.scaleY * scaleY;
-        offset = i;
-
-        var cx, cy;
-        var index;
-        for (y = 0; y < height; y++) {
-          for (x = 0; x < width; x++) {
-            cy = 0 | (y * componentScaleY);
-            cx = 0 | (x * componentScaleX);
-            index = cy * samplesPerLine + cx;
-            data[offset] = lineData[index];
-            offset += numComponents;
-          }
-        }
-      }
-
-      // ... then transform colors, if necessary
-      switch (numComponents) {
-        case 1: case 2: break;
-        // no color conversion for one or two compoenents
-
-        case 3:
-          // The default transform for three components is true
-          colorTransform = true;
-          // The adobe transform marker overrides any previous setting
-          if (this.adobe && this.adobe.transformCode)
-            colorTransform = true;
-          else if (typeof this.colorTransform !== 'undefined')
-            colorTransform = !!this.colorTransform;
-
-          if (colorTransform) {
-            for (i = 0; i < dataLength; i += numComponents) {
-              Y  = data[i    ];
-              Cb = data[i + 1];
-              Cr = data[i + 2];
-
-              R = clamp0to255(Y + 1.402 * (Cr - 128));
-              G = clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128));
-              B = clamp0to255(Y + 1.772 * (Cb - 128));
-
-              data[i    ] = R;
-              data[i + 1] = G;
-              data[i + 2] = B;
-            }
-          }
-          break;
-        case 4:
-          // The default transform for four components is false
-          colorTransform = false;
-          // The adobe transform marker overrides any previous setting
-          if (this.adobe && this.adobe.transformCode)
-            colorTransform = true;
-          else if (typeof this.colorTransform !== 'undefined')
-            colorTransform = !!this.colorTransform;
-
-          if (colorTransform) {
-            for (i = 0; i < dataLength; i += numComponents) {
-              Y  = data[i];
-              Cb = data[i + 1];
-              Cr = data[i + 2];
-
-              C = 255 - clamp0to255(Y + 1.402 * (Cr - 128));
-              M = 255 - clamp0to255(Y - 0.3441363 * (Cb - 128) - 0.71413636 * (Cr - 128));
-              Ye = 255 - clamp0to255(Y + 1.772 * (Cb - 128));
-
-              data[i    ] = C;
-              data[i + 1] = M;
-              data[i + 2] = Ye;
-              // K is unchanged
-            }
-          }
-          break;
-        default:
-          throw 'Unsupported color mode';
-      }
-      return data;
-    },
-    copyToImageData: function copyToImageData(imageData) {
-      var width = imageData.width, height = imageData.height;
-      var imageDataBytes = width * height * 4;
-      var imageDataArray = imageData.data;
-      var data = this.getData(width, height);
-      var i = 0, j = 0;
-      var Y, K, C, M, R, G, B;
-      switch (this.components.length) {
-        case 1:
-          while (j < imageDataBytes) {
-            Y = data[i++];
-
-            imageDataArray[j++] = Y;
-            imageDataArray[j++] = Y;
-            imageDataArray[j++] = Y;
-            imageDataArray[j++] = 255;
-          }
-          break;
-        case 3:
-          while (j < imageDataBytes) {
-            R = data[i++];
-            G = data[i++];
-            B = data[i++];
-
-            imageDataArray[j++] = R;
-            imageDataArray[j++] = G;
-            imageDataArray[j++] = B;
-            imageDataArray[j++] = 255;
-          }
-          break;
-        case 4:
-          while (j < imageDataBytes) {
-            C = data[i++];
-            M = data[i++];
-            Y = data[i++];
-            K = data[i++];
-
-            R = 255 - clamp0to255(C * (1 - K / 255) + K);
-            G = 255 - clamp0to255(M * (1 - K / 255) + K);
-            B = 255 - clamp0to255(Y * (1 - K / 255) + K);
-
-            imageDataArray[j++] = R;
-            imageDataArray[j++] = G;
-            imageDataArray[j++] = B;
-            imageDataArray[j++] = 255;
-          }
-          break;
-        default:
-          throw 'Unsupported color mode';
-      }
-    }
-  };
-
-  return constructor;
-})();
 
 
 }).call((typeof window === 'undefined') ? this : window);
 
 
 
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -1078,56 +1078,75 @@ a:focus > .thumbnail > .thumbnailSelecti
 }
 
 #outlineView,
 #attachmentsView {
   position: absolute;
   width: 192px;
   top: 0;
   bottom: 0;
-  padding: 4px 4px 0;
   overflow: auto;
   -moz-user-select: none;
 }
 
+#outlineView {
+  padding: 4px 4px 0;
+}
+#attachmentsView {
+  padding: 3px 4px 0;
+}
+
 html[dir='ltr'] .outlineItem > .outlineItems {
   margin-left: 20px;
 }
 
 html[dir='rtl'] .outlineItem > .outlineItems {
   margin-right: 20px;
 }
 
 .outlineItem > a,
-.attachmentsItem > a {
+.attachmentsItem > button {
   text-decoration: none;
   display: inline-block;
   min-width: 95%;
   height: auto;
   margin-bottom: 1px;
   border-radius: 2px;
   color: hsla(0,0%,100%,.8);
   font-size: 13px;
   line-height: 15px;
   -moz-user-select: none;
   white-space: normal;
 }
 
-html[dir='ltr'] .outlineItem > a,
-html[dir='ltr'] .attachmentsItem > a {
+.attachmentsItem > button {
+  border: 0 none;
+  background: none;
+  cursor: pointer;
+  width: 100%;
+}
+
+html[dir='ltr'] .outlineItem > a {
   padding: 2px 0 5px 10px;
 }
+html[dir='ltr'] .attachmentsItem > button {
+  padding: 2px 0 3px 7px;
+  text-align: left;
+}
 
-html[dir='rtl'] .outlineItem > a,
-html[dir='rtl'] .attachmentsItem > a {
+html[dir='rtl'] .outlineItem > a {
   padding: 2px 10px 5px 0;
 }
+html[dir='rtl'] .attachmentsItem > button {
+  padding: 2px 7px 3px 0;
+  text-align: right;
+}
 
 .outlineItem > a:hover,
-.attachmentsItem > a:hover {
+.attachmentsItem > button:hover {
   background-color: hsla(0,0%,100%,.02);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.2) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,.9);
 }
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -12,19 +12,20 @@
  * 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, PDFFindBar, CustomStyle,
            PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
            getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory,
-           Preferences, ViewHistory, PageView, ThumbnailView, URL,
+           Preferences, SidebarView, ViewHistory, PageView, ThumbnailView, URL,
            noContextMenuHandler, SecondaryToolbar, PasswordPrompt,
-           PresentationMode, HandTool, Promise, DocumentProperties */
+           PresentationMode, HandTool, Promise, DocumentProperties,
+           DocumentOutlineView, DocumentAttachmentsView */
 
 '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 CACHE_SIZE = 20;
@@ -304,22 +305,29 @@ var Cache = function cacheCache(size) {
 };
 
 
 
 
 var DEFAULT_PREFERENCES = {
   showPreviousViewOnLoad: true,
   defaultZoomValue: '',
-  ifAvailableShowOutlineOnLoad: false,
+  sidebarViewOnLoad: 0,
   enableHandToolOnLoad: false,
   enableWebGL: false
 };
 
 
+var SidebarView = {
+  NONE: 0,
+  THUMBS: 1,
+  OUTLINE: 2,
+  ATTACHMENTS: 3
+};
+
 /**
  * Preferences - Utility for storing persistent settings.
  *   Used for settings that should be applied to all opened documents,
  *   or every time the viewer is loaded.
  */
 var Preferences = {
   prefs: Object.create(DEFAULT_PREFERENCES),
   isInitializedPromiseResolved: false,
@@ -582,36 +590,22 @@ var currentPageNumber = 1;
  * for 'node make <flag>' the following cases exist:
  *  - FIREFOX or MOZCENTRAL - uses sessionStorage.
  *  - B2G                   - uses asyncStorage.
  *  - GENERIC or CHROME     - uses localStorage, if it is available.
  */
 var ViewHistory = (function ViewHistoryClosure() {
   function ViewHistory(fingerprint) {
     this.fingerprint = fingerprint;
-    var initializedPromiseResolve;
     this.isInitializedPromiseResolved = false;
-    this.initializedPromise = new Promise(function (resolve) {
-      initializedPromiseResolve = resolve;
-    });
-
-    var resolvePromise = (function ViewHistoryResolvePromise(db) {
+    this.initializedPromise =
+        this._readFromStorage().then(function (databaseStr) {
       this.isInitializedPromiseResolved = true;
-      this.initialize(db || '{}');
-      initializedPromiseResolve();
-    }).bind(this);
-
-
-    resolvePromise(sessionStorage.getItem('pdfjsHistory'));
-
-  }
-
-  ViewHistory.prototype = {
-    initialize: function ViewHistory_initialize(database) {
-      database = JSON.parse(database);
+
+      var database = JSON.parse(databaseStr || '{}');
       if (!('files' in database)) {
         database.files = [];
       }
       if (database.files.length >= VIEW_HISTORY_MEMORY) {
         database.files.shift();
       }
       var index;
       for (var i = 0, length = database.files.length; i < length; i++) {
@@ -621,29 +615,55 @@ var ViewHistory = (function ViewHistoryC
           break;
         }
       }
       if (typeof index !== 'number') {
         index = database.files.push({fingerprint: this.fingerprint}) - 1;
       }
       this.file = database.files[index];
       this.database = database;
+    }.bind(this));
+  }
+
+  ViewHistory.prototype = {
+    _writeToStorage: function ViewHistory_writeToStorage() {
+      return new Promise(function (resolve) {
+        var databaseStr = JSON.stringify(this.database);
+
+
+        sessionStorage.setItem('pdfjsHistory', databaseStr);
+        resolve();
+
+      }.bind(this));
+    },
+
+    _readFromStorage: function ViewHistory_readFromStorage() {
+      return new Promise(function (resolve) {
+
+        resolve(sessionStorage.getItem('pdfjsHistory'));
+
+      });
     },
 
     set: function ViewHistory_set(name, val) {
       if (!this.isInitializedPromiseResolved) {
         return;
       }
-      var file = this.file;
-      file[name] = val;
-      var database = JSON.stringify(this.database);
-
-
-      sessionStorage.setItem('pdfjsHistory',database);
-
+      this.file[name] = val;
+      this._writeToStorage();
+    },
+
+    setMultiple: function ViewHistory_setMultiple(properties) {
+      if (!this.isInitializedPromiseResolved) {
+        return;
+      }
+      for (var name in properties) {
+        this.file[name] = properties[name];
+      }
+      this._writeToStorage();
     },
 
     get: function ViewHistory_get(name, defaultValue) {
       if (!this.isInitializedPromiseResolved) {
         return defaultValue;
       }
       return this.file[name] || defaultValue;
     }
@@ -2173,21 +2193,21 @@ var HandTool = {
             mozL10n.get('hand_tool_enable_label', null, 'Enable hand tool');
         }
       }
     });
     if (toggleHandTool) {
       toggleHandTool.addEventListener('click', this.toggle.bind(this), false);
 
       window.addEventListener('localized', function (evt) {
-        Preferences.get('enableHandToolOnLoad').then(function (prefValue) {
-          if (prefValue) {
+        Preferences.get('enableHandToolOnLoad').then(function resolved(value) {
+          if (value) {
             this.handTool.activate();
           }
-        }.bind(this));
+        }.bind(this), function rejected(reason) {});
       }.bind(this));
     }
   },
 
   toggle: function handToolToggle() {
     this.handTool.toggle();
     SecondaryToolbar.close();
   },
@@ -2510,19 +2530,22 @@ var PDFView = {
       pageCountField: document.getElementById('pageCountField')
     });
 
     container.addEventListener('scroll', function() {
       self.lastScroll = Date.now();
     }, false);
 
     var initializedPromise = Promise.all([
-      Preferences.get('enableWebGL').then(function (value) {
+      Preferences.get('enableWebGL').then(function resolved(value) {
         PDFJS.disableWebGL = !value;
-      })
+      }, function rejected(reason) {}),
+      Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
+        self.preferenceSidebarViewOnLoad = value;
+      }, function rejected(reason) {})
       // TODO move more preferences and other async stuff here
     ]);
 
     return initializedPromise.then(function () {
       PDFView.initialized = true;
     });
   },
 
@@ -2531,16 +2554,19 @@ var PDFView = {
   },
 
   // 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;
     viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
+      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
@@ -3104,17 +3130,17 @@ var PDFView = {
   },
 
   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) {
+    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;
@@ -3257,18 +3283,18 @@ var PDFView = {
       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(errorMsg) {
-      console.error(errorMsg);
+    }, function rejected(reason) {
+      console.error(reason);
 
       firstPagePromise.then(function () {
         self.setInitialView(null, scale);
       });
     });
 
     pagesPromise.then(function() {
       if (PDFView.supportsPrinting) {
@@ -3301,34 +3327,38 @@ var PDFView = {
     // 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) {
-          Preferences.get('ifAvailableShowOutlineOnLoad').then(
-            function (prefValue) {
-              if (prefValue) {
-                if (!self.sidebarOpen) {
-                  document.getElementById('sidebarToggle').click();
-                }
-                self.switchSidebarView('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() +
@@ -3557,38 +3587,36 @@ var PDFView = {
       }
       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) {
-        var toggle = document.getElementById('sidebarToggle');
         if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
             params.pagemode === 'attachments') {
-          if (!this.sidebarOpen) {
-            toggle.click();
-          }
-          this.switchSidebarView(params.pagemode === 'bookmarks' ?
-                                   'outline' :
-                                   params.pagemode);
+          this.switchSidebarView((params.pagemode === 'bookmarks' ?
+                                  'outline' : params.pagemode), true);
         } else if (params.pagemode === 'none' && this.sidebarOpen) {
-          toggle.click();
+          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) {
+  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');
 
@@ -5064,17 +5092,16 @@ var DocumentOutlineView = function docum
   function bindItemLink(domObj, item) {
     domObj.href = PDFView.getDestinationHash(item.dest);
     domObj.onclick = function documentOutlineViewOnclick(e) {
       PDFView.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;
     for (i = 0; i < n; i++) {
       var item = levelData.items[i];
       var div = document.createElement('div');
       div.className = 'outlineItem';
@@ -5090,55 +5117,56 @@ var DocumentOutlineView = function docum
         queue.push({parent: itemsDiv, items: item.items});
       }
 
       levelData.parent.appendChild(div);
     }
   }
 };
 
+
 var DocumentAttachmentsView = function documentAttachmentsView(attachments) {
   var attachmentsView = document.getElementById('attachmentsView');
   while (attachmentsView.firstChild) {
     attachmentsView.removeChild(attachmentsView.firstChild);
   }
 
   if (!attachments) {
     if (!attachmentsView.classList.contains('hidden')) {
       PDFView.switchSidebarView('thumbs');
     }
     return;
   }
 
   function bindItemLink(domObj, item) {
-    domObj.href = '#';
     domObj.onclick = function documentAttachmentsViewOnclick(e) {
       var downloadManager = new DownloadManager();
       downloadManager.downloadData(item.content, getFileName(item.filename),
                                    '');
       return false;
     };
   }
 
   var names = Object.keys(attachments).sort(function(a,b) {
     return a.toLowerCase().localeCompare(b.toLowerCase());
   });
   for (var i = 0, ii = names.length; i < ii; i++) {
     var item = attachments[names[i]];
     var div = document.createElement('div');
     div.className = 'attachmentsItem';
-    var a = document.createElement('a');
-    bindItemLink(a, item);
-    a.textContent = getFileName(item.filename);
-    div.appendChild(a);
+    var button = document.createElement('button');
+    bindItemLink(button, item);
+    button.textContent = getFileName(item.filename);
+    div.appendChild(button);
     attachmentsView.appendChild(div);
   }
 };
 
 
+
 function webViewerLoad(evt) {
   PDFView.initialize().then(webViewerInitialized);
 }
 
 function webViewerInitialized() {
   var file = window.location.href.split('#')[0];
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
@@ -5390,23 +5418,24 @@ function updateViewarea() {
   pdfOpenParams += ',' + intLeft + ',' + intTop;
 
   if (PresentationMode.active || PresentationMode.switchInProgress) {
     PDFView.currentPosition = null;
   } else {
     PDFView.currentPosition = { page: pageNumber, left: intLeft, top: intTop };
   }
 
-  var store = PDFView.store;
-  store.initializedPromise.then(function() {
-    store.set('exists', true);
-    store.set('page', pageNumber);
-    store.set('zoom', normalizedScaleValue);
-    store.set('scrollLeft', intLeft);
-    store.set('scrollTop', intTop);
+  PDFView.store.initializedPromise.then(function() {
+    PDFView.store.setMultiple({
+      'exists': true,
+      'page': pageNumber,
+      'zoom': normalizedScaleValue,
+      'scrollLeft': intLeft,
+      'scrollTop': intTop
+    });
   });
   var href = PDFView.getAnchorUrl(pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
   document.getElementById('secondaryViewBookmark').href = href;
 
   // Update the current bookmark in the browsing history.
   PDFHistory.updateCurrentBookmark(pdfOpenParams, pageNumber);
 }
--- a/browser/extensions/pdfjs/test/browser_pdfjs_main.js
+++ b/browser/extensions/pdfjs/test/browser_pdfjs_main.js
@@ -27,31 +27,36 @@ function test() {
   newTabBrowser.addEventListener("load", function eventHandler() {
     newTabBrowser.removeEventListener("load", eventHandler, true);
 
     var document = newTabBrowser.contentDocument,
         window = newTabBrowser.contentWindow;
 
     // Runs tests after all 'load' event handlers have fired off
     window.addEventListener("documentload", function() {
-      runTests(document, window, finish);
+      runTests(document, window, tab, finish);
     }, false, true);
   }, true);
 }
 
 
-function runTests(document, window, callback) {
+function runTests(document, window, tab, callback) {
 
   //
   // Overall sanity tests
   //
   ok(document.querySelector('div#viewer'), "document content has viewer UI");
   ok('PDFJS' in window.wrappedJSObject, "window content has PDFJS object");
 
   //
+  // Browser Find
+  //
+  ok(gBrowser.isFindBarInitialized(tab), "Browser FindBar initialized!");
+
+  //
   // Sidebar: open
   //
   var sidebar = document.querySelector('button#sidebarToggle'),
       outerContainer = document.querySelector('div#outerContainer');
 
   sidebar.click();
   ok(outerContainer.classList.contains('sidebarOpen'), 'sidebar opens on click');