Bug 897554 - Update pdf.js to version 0.8.377. r=bdahl
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 24 Jul 2013 17:48:29 -0400
changeset 139875 ff8930432f20864827c27c399b1678e8313529cd
parent 139874 91356879fbfd87ec111b2ecd317a85f6bc081b85
child 139876 bf3c83a7aed05e9ca91802e16a0045a848535372
push idunknown
push userunknown
push dateunknown
reviewersbdahl
bugs897554
milestone25.0a1
Bug 897554 - Update pdf.js to version 0.8.377. r=bdahl
browser/extensions/pdfjs/README.mozilla
browser/extensions/pdfjs/components/PdfStreamConverter.js
browser/extensions/pdfjs/content/build/pdf.js
browser/extensions/pdfjs/content/web/viewer.css
browser/extensions/pdfjs/content/web/viewer.html
browser/extensions/pdfjs/content/web/viewer.js
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.291
+Current extension version is: 0.8.377
 
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -201,16 +201,20 @@ ChromeActions.prototype = {
     return PrivateBrowsingUtils.isWindowPrivate(this.domWindow);
   },
   download: function(data, sendResponse) {
     var self = this;
     var originalUrl = data.originalUrl;
     // The data may not be downloaded so we need just retry getting the pdf with
     // the original url.
     var originalUri = NetUtil.newURI(data.originalUrl);
+    var filename = data.filename;
+    if (typeof filename !== 'string' || !/\.pdf$/i.test(filename)) {
+      filename = 'document.pdf';
+    }
     var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
     var extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
              getService(Ci.nsIExternalHelperAppService);
     var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                          getService(Ci.nsIWindowWatcher).activeWindow;
 
     var docIsPrivate = this.isInPrivateBrowsing();
@@ -229,17 +233,19 @@ ChromeActions.prototype = {
       // so the filename will be correct.
       var channel = Cc['@mozilla.org/network/input-stream-channel;1'].
                        createInstance(Ci.nsIInputStreamChannel);
       channel.QueryInterface(Ci.nsIChannel);
       try {
         // contentDisposition/contentDispositionFilename is readonly before FF18
         channel.contentDisposition = Ci.nsIChannel.DISPOSITION_ATTACHMENT;
         if (self.contentDispositionFilename) {
-           channel.contentDispositionFilename = self.contentDispositionFilename;
+          channel.contentDispositionFilename = self.contentDispositionFilename;
+        } else {
+          channel.contentDispositionFilename = filename;
         }
       } catch (e) {}
       channel.setURI(originalUri);
       channel.contentStream = aInputStream;
       if ('nsIPrivateBrowsingChannel' in Ci &&
           channel instanceof Ci.nsIPrivateBrowsingChannel) {
         channel.setPrivate(docIsPrivate);
       }
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.291';
-PDFJS.build = '4e83123';
+PDFJS.version = '0.8.377';
+PDFJS.build = 'c682c25';
 
 (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
@@ -70,16 +70,20 @@ var ChunkedStream = (function ChunkedStr
       for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
         if (!(chunk in this.loadedChunks)) {
           chunks.push(chunk);
         }
       }
       return chunks;
     },
 
+    getBaseStreams: function ChunkedStream_getBaseStreams() {
+      return [this];
+    },
+
     allChunksLoaded: function ChunkedStream_allChunksLoaded() {
       return this.numChunksLoaded === this.numChunks;
     },
 
     onReceiveData: function(begin, chunk) {
       var end = begin + chunk.byteLength;
 
       assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
@@ -138,17 +142,17 @@ var ChunkedStream = (function ChunkedStr
 
     get length() {
       return this.end - this.start;
     },
 
     getByte: function ChunkedStream_getByte() {
       var pos = this.pos;
       if (pos >= this.end) {
-        return null;
+        return -1;
       }
       this.ensureRange(pos, pos + 1);
       return this.bytes[this.pos++];
     },
 
     // returns subarray of original buffer
     // should only be read
     getBytes: function ChunkedStream_getBytes(length) {
@@ -176,32 +180,16 @@ var ChunkedStream = (function ChunkedStr
       return bytes;
     },
 
     getByteRange: function ChunkedStream_getBytes(begin, end) {
       this.ensureRange(begin, end);
       return this.bytes.subarray(begin, end);
     },
 
-    lookChar: function ChunkedStream_lookChar() {
-      var pos = this.pos;
-      if (pos >= this.end)
-        return null;
-      this.ensureRange(pos, pos + 1);
-      return String.fromCharCode(this.bytes[pos]);
-    },
-
-    getChar: function ChunkedStream_getChar() {
-      var pos = this.pos;
-      if (pos >= this.end)
-        return null;
-      this.ensureRange(pos, pos + 1);
-      return String.fromCharCode(this.bytes[this.pos++]);
-    },
-
     skip: function ChunkedStream_skip(n) {
       if (!n)
         n = 1;
       this.pos += n;
     },
 
     reset: function ChunkedStream_reset() {
       this.pos = this.start;
@@ -977,17 +965,17 @@ var PDFDocument = (function PDFDocumentC
 
   function find(stream, needle, limit, backwards) {
     var pos = stream.pos;
     var end = stream.end;
     var str = '';
     if (pos + limit > end)
       limit = end - pos;
     for (var n = 0; n < limit; ++n)
-      str += stream.getChar();
+      str += String.fromCharCode(stream.getByte());
     stream.pos = pos;
     var index = backwards ? str.lastIndexOf(needle) : str.indexOf(needle);
     if (index == -1)
       return false; /* not found */
     stream.pos += index;
     return true; /* found */
   }
 
@@ -1056,22 +1044,22 @@ var PDFDocument = (function PDFDocumentC
             pos = 0;
           stream.pos = pos;
           found = find(stream, 'startxref', step, true);
         }
         if (found) {
           stream.skip(9);
           var ch;
           do {
-            ch = stream.getChar();
+            ch = stream.getByte();
           } while (Lexer.isSpace(ch));
           var str = '';
-          while ((ch - '0') <= 9) {
-            str += ch;
-            ch = stream.getChar();
+          while (ch >= 0x20 && ch <= 0x39) { // < '9'
+            str += String.fromCharCode(ch);
+            ch = stream.getByte();
           }
           startXRef = parseInt(str, 10);
           if (isNaN(startXRef))
             startXRef = 0;
         }
       }
       // shadow the prototype getter with a data property
       return shadow(this, 'startXRef', startXRef);
@@ -1090,21 +1078,21 @@ var PDFDocument = (function PDFDocumentC
       var stream = this.stream;
       stream.reset();
       if (find(stream, '%PDF-', 1024)) {
         // Found the header, trim off any garbage before it.
         stream.moveStart();
         // Reading file format version
         var MAX_VERSION_LENGTH = 12;
         var version = '', ch;
-        while ((ch = stream.getChar()) > ' ') {
+        while ((ch = stream.getByte()) > 0x20) { // SPACE
           if (version.length >= MAX_VERSION_LENGTH) {
             break;
           }
-          version += ch;
+          version += String.fromCharCode(ch);
         }
         // removing "%PDF-"-prefix
         this.pdfFormatVersion = version.substring(5);
         return;
       }
       // May not be a PDF file, continue anyway.
     },
     parseStartXRef: function PDFDocument_parseStartXRef() {
@@ -1266,16 +1254,38 @@ function combineUrl(baseUrl, url) {
     pathLength = i >= 0 ? i : pathLength;
     i = baseUrl.lastIndexOf('?', pathLength);
     pathLength = i >= 0 ? i : pathLength;
     var prefixLength = baseUrl.lastIndexOf('/', pathLength);
     return baseUrl.substring(0, prefixLength + 1) + url;
   }
 }
 
+// Validates if URL is safe and allowed, e.g. to avoid XSS.
+function isValidUrl(url, allowRelative) {
+  if (!url) {
+    return false;
+  }
+  var colon = url.indexOf(':');
+  if (colon < 0) {
+    return allowRelative;
+  }
+  var protocol = url.substr(0, colon);
+  switch (protocol) {
+    case 'http':
+    case 'https':
+    case 'ftp':
+    case 'mailto':
+      return true;
+    default:
+      return false;
+  }
+}
+PDFJS.isValidUrl = isValidUrl;
+
 // In a well-formed PDF, |cond| holds.  If it doesn't, subsequent
 // behavior is undefined.
 function assertWellFormed(cond, msg) {
   if (!cond)
     error(msg);
 }
 
 var LogManager = PDFJS.LogManager = (function LogManagerClosure() {
@@ -1781,17 +1791,17 @@ function isDict(v, type) {
 }
 
 function isArray(v) {
   return v instanceof Array;
 }
 
 function isStream(v) {
   return typeof v == 'object' && v !== null && v !== undefined &&
-         ('getChar' in v);
+         ('getBytes' in v);
 }
 
 function isArrayBuffer(v) {
   return typeof v == 'object' && v !== null && v !== undefined &&
          ('byteLength' in v);
 }
 
 function isRef(v) {
@@ -2087,16 +2097,23 @@ PDFJS.createBlob = function createBlob(d
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
 
 /**
+ * The maximum allowed image size in total pixels e.g. width * height. Images
+ * above this value will not be drawn. Use -1 for no limit.
+ * @var {Number}
+ */
+PDFJS.maxImageSize = PDFJS.maxImageSize === undefined ? -1 : PDFJS.maxImageSize;
+
+/**
  * This is the main entry point for loading a PDF and interacting with it.
  * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
  * is used, which means it must follow the same origin rules that any XHR does
  * e.g. No cross domain requests without CORS.
  *
  * @param {string|TypedAray|object} source Can be an url to where a PDF is
  * located, a typed array (Uint8Array) already populated with data or
  * and parameter object with the following possible fields:
@@ -2825,17 +2842,18 @@ var WorkerTransport = (function WorkerTr
       });
     },
 
     fetchDocument: function WorkerTransport_fetchDocument(source) {
       source.disableAutoFetch = PDFJS.disableAutoFetch;
       source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
       this.messageHandler.send('GetDocRequest', {
         source: source,
-        disableRange: PDFJS.disableRange
+        disableRange: PDFJS.disableRange,
+        maxImageSize: PDFJS.maxImageSize
       });
     },
 
     getData: function WorkerTransport_getData(promise) {
       this.messageHandler.send('GetData', null, function(data) {
         promise.resolve(data);
       });
     },
@@ -3049,17 +3067,17 @@ var CachedCanvases = (function CachedCan
 })();
 
 function compileType3Glyph(imgData) {
   var POINT_TO_PROCESS_LIMIT = 1000;
 
   var width = imgData.width, height = imgData.height;
   var i, j, j0, width1 = width + 1;
   var points = new Uint8Array(width1 * (height + 1));
-  var POINT_TYPES = 
+  var POINT_TYPES =
       new Uint8Array([0, 2, 4, 0, 1, 0, 5, 4, 8, 10, 0, 8, 0, 2, 1, 0]);
   // finding iteresting points: every point is located between mask pixels,
   // so there will be points of the (width + 1)x(height + 1) grid. Every point
   // will have flags assigned based on neighboring mask pixels:
   //   4 | 8
   //   --P--
   //   2 | 1
   // We are interested only in points with the flags:
@@ -3090,17 +3108,17 @@ function compileType3Glyph(imgData) {
       ++count;
     }
     // 'sum' is the position of the current pixel configuration in the 'TYPES'
     // array (in order 8-1-2-4, so we can use '>>2' to shift the column).
     var sum = (data[pos] ? 4 : 0) + (data[pos - lineSize] ? 8 : 0);
     for (j = 1; j < width; j++) {
       sum = (sum >> 2) + (data[pos + 4] ? 4 : 0) +
             (data[pos - lineSize + 4] ? 8 : 0);
-      if (POINT_TYPES[sum]) { 
+      if (POINT_TYPES[sum]) {
         points[j0 + j] = POINT_TYPES[sum];
         ++count;
       }
       pos += 4;
     }
     if (data[pos - lineSize] !== data[pos]) {
       points[j0 + j] = data[pos] ? 2 : 4;
       ++count;
@@ -3146,26 +3164,26 @@ function compileType3Glyph(imgData) {
       continue;
     }
     var coords = [p % width1, i];
 
     var type = points[p], p0 = p, pp;
     do {
       var step = steps[type];
       do { p += step; } while (!points[p]);
-      
+
       pp = points[p];
       if (pp !== 5 && pp !== 10) {
         // set new direction
-        type = pp; 
+        type = pp;
         // delete mark
-        points[p] = 0; 
+        points[p] = 0;
       } else { // type is 5 or 10, ie, a crossing
         // set new direction
-        type = pp & ((0x33 * type) >> 4); 
+        type = pp & ((0x33 * type) >> 4);
         // set new type for "future hit"
         points[p] &= (type >> 2 | type << 2);
       }
 
       coords.push(p % width1);
       coords.push((p / width1) | 0);
       --count;
     } while (p0 !== p);
@@ -3185,17 +3203,17 @@ function compileType3Glyph(imgData) {
       for (var j = 2, jj = o.length; j < jj; j += 2) {
         c.lineTo(o[j], o[j+1]);
       }
     }
     c.fill();
     c.beginPath();
     c.restore();
   };
-  
+
   return drawOutline;
 }
 
 var CanvasExtraState = (function CanvasExtraStateClosure() {
   function CanvasExtraState(old) {
     // Are soft masks and alpha values shapes or opacities?
     this.alphaIsShape = false;
     this.fontSize = 0;
@@ -3821,24 +3839,30 @@ var CanvasGraphics = (function CanvasGra
         ctx.scale(-current.textHScale, 1);
       }
     },
     createTextGeometry: function CanvasGraphics_createTextGeometry() {
       var geometry = {};
       var ctx = this.ctx;
       var font = this.current.font;
       var ctxMatrix = ctx.mozCurrentTransform;
-      if (ctxMatrix) {
-        var bl = Util.applyTransform([0, 0], ctxMatrix);
-        var tr = Util.applyTransform([1, 1], ctxMatrix);
-        geometry.x = bl[0];
-        geometry.y = bl[1];
-        geometry.hScale = tr[0] - bl[0];
-        geometry.vScale = tr[1] - bl[1];
-      }
+      var a = ctxMatrix[0], b = ctxMatrix[1], c = ctxMatrix[2];
+      var d = ctxMatrix[3], e = ctxMatrix[4], f = ctxMatrix[5];
+      var sx = (a >= 0) ?
+          Math.sqrt((a * a) + (b * b)) : -Math.sqrt((a * a) + (b * b));
+      var sy = (d >= 0) ?
+          Math.sqrt((c * c) + (d * d)) : -Math.sqrt((c * c) + (d * d));
+      var angle = Math.atan2(b, a);
+      var x = e;
+      var y = f;
+      geometry.x = x;
+      geometry.y = y;
+      geometry.hScale = sx;
+      geometry.vScale = sy;
+      geometry.angle = angle;
       geometry.spaceWidth = font.spaceWidth;
       geometry.fontName = font.loadedName;
       geometry.fontFamily = font.fallbackName;
       geometry.fontSize = this.current.fontSize;
       return geometry;
     },
 
     paintChar: function (character, x, y) {
@@ -4046,21 +4070,18 @@ var CanvasGraphics = (function CanvasGra
           current.x += x * textHScale;
         }
         ctx.restore();
       }
 
       if (textSelection) {
         geom.canvasWidth = canvasWidth;
         if (vertical) {
-          var vmetric = font.defaultVMetrics;
-          geom.x += vmetric[1] * fontSize * current.fontMatrix[0] /
-                    fontSizeScale * geom.hScale;
-          geom.y += vmetric[2] * fontSize * current.fontMatrix[0] /
-                    fontSizeScale * geom.vScale;
+          var VERTICAL_TEXT_ROTATION = Math.PI / 2;
+          geom.angle += VERTICAL_TEXT_ROTATION;
         }
         this.textLayer.appendText(geom);
       }
 
       return canvasWidth;
     },
     showSpacedText: function CanvasGraphics_showSpacedText(arr) {
       var ctx = this.ctx;
@@ -4106,22 +4127,18 @@ var CanvasGraphics = (function CanvasGra
         } else {
           error('TJ array element ' + e + ' is not string or num');
         }
       }
 
       if (textSelection) {
         geom.canvasWidth = canvasWidth;
         if (vertical) {
-          var fontSizeScale = current.fontSizeScale;
-          var vmetric = font.defaultVMetrics;
-          geom.x += vmetric[1] * fontSize * current.fontMatrix[0] /
-                    fontSizeScale * geom.hScale;
-          geom.y += vmetric[2] * fontSize * current.fontMatrix[0] /
-                    fontSizeScale * geom.vScale;
+          var VERTICAL_TEXT_ROTATION = Math.PI / 2;
+          geom.angle += VERTICAL_TEXT_ROTATION;
         }
         this.textLayer.appendText(geom);
       }
     },
     nextLineShowText: function CanvasGraphics_nextLineShowText(text) {
       this.nextLine();
       this.showText(text);
     },
@@ -5661,24 +5678,17 @@ var XRef = (function XRefClosure() {
             // almost all streams must be encrypted, but sometimes
             // they are not probably due to some broken generators
             // re-trying without encryption
             return this.fetch(ref, true);
           }
         } else {
           e = parser.getObj();
         }
-        if (!isStream(e) || e instanceof JpegStream) {
-          this.cache[num] = e;
-        } else if (e instanceof Stream) {
-          e = e.makeSubStream(e.start, e.length, e.dict);
-          this.cache[num] = e;
-        } else if ('readBlock' in e) {
-          e.getBytes();
-          e = e.makeSubStream(0, e.bufferLength, e.dict);
+        if (!isStream(e)) {
           this.cache[num] = e;
         }
         return e;
       }
 
       // compressed entry
       var tableOffset = e.offset;
       stream = this.fetch(new Ref(tableOffset, 0));
@@ -5997,23 +6007,32 @@ var ObjectLoader = (function() {
           } catch (e) {
             if (!(e instanceof MissingDataException)) {
               throw e;
             }
             nodesToRevisit.push(currentNode);
             pendingRequests.push({ begin: e.begin, end: e.end });
           }
         }
-        if (currentNode instanceof ChunkedStream &&
-            currentNode.getMissingChunks().length) {
-          nodesToRevisit.push(currentNode);
-          pendingRequests.push({
-            begin: currentNode.start,
-            end: currentNode.end
-          });
+        if (currentNode && currentNode.getBaseStreams) {
+          var baseStreams = currentNode.getBaseStreams();
+          var foundMissingData = false;
+          for (var i = 0; i < baseStreams.length; i++) {
+            var stream = baseStreams[i];
+            if (stream.getMissingChunks && stream.getMissingChunks().length) {
+              foundMissingData = true;
+              pendingRequests.push({
+                begin: stream.start,
+                end: stream.end
+              });
+            }
+          }
+          if (foundMissingData) {
+            nodesToRevisit.push(currentNode);
+          }
         }
 
         addChildren(currentNode, nodesToVisit);
       }
 
       if (pendingRequests.length) {
         this.xref.stream.manager.requestRanges(pendingRequests,
             function pendingRequestCallback() {
@@ -6570,17 +6589,16 @@ var TextAnnotation = (function TextAnnot
     var content = dict.get('Contents');
     var title = dict.get('T');
     data.content = stringToPDFString(content || '');
     data.title = stringToPDFString(title || '');
     data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name;
   }
 
   var ANNOT_MIN_SIZE = 10;
-  var IMAGE_DIR = './images/';
 
   Util.inherit(TextAnnotation, Annotation, {
 
     getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
       var promise = new Promise();
       promise.resolve({
         queue: {
           fnArray: [],
@@ -6611,17 +6629,17 @@ var TextAnnotation = (function TextAnnot
 
       var container = this.getEmptyContainer('section', rect);
       container.className = 'annotText';
 
       var image = document.createElement('img');
       image.style.width = container.style.width;
       image.style.height = container.style.height;
       var iconName = item.name;
-      image.src = IMAGE_DIR + 'annotation-' +
+      image.src = PDFJS.imageResourcesPath + 'annotation-' +
         iconName.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
       image.dataset.l10nArgs = JSON.stringify({type: iconName});
       var content = document.createElement('div');
       content.setAttribute('hidden', true);
       var title = document.createElement('h1');
       var text = document.createElement('p');
@@ -6660,34 +6678,16 @@ var TextAnnotation = (function TextAnnot
       return container;
     }
   });
 
   return TextAnnotation;
 })();
 
 var LinkAnnotation = (function LinkAnnotationClosure() {
-  function isValidUrl(url) {
-    if (!url)
-      return false;
-    var colon = url.indexOf(':');
-    if (colon < 0)
-      return false;
-    var protocol = url.substr(0, colon);
-    switch (protocol) {
-      case 'http':
-      case 'https':
-      case 'ftp':
-      case 'mailto':
-        return true;
-      default:
-        return false;
-    }
-  }
-
   function LinkAnnotation(params) {
     Annotation.call(this, params);
 
     if (params.data) {
       return;
     }
 
     var dict = params.dict;
@@ -6695,33 +6695,33 @@ var LinkAnnotation = (function LinkAnnot
 
     var action = dict.get('A');
     if (action) {
       var linkType = action.get('S').name;
       if (linkType === 'URI') {
         var url = action.get('URI');
         // TODO: pdf spec mentions urls can be relative to a Base
         // entry in the dictionary.
-        if (!isValidUrl(url)) {
+        if (!isValidUrl(url, false)) {
           url = '';
         }
         data.url = url;
       } else if (linkType === 'GoTo') {
         data.dest = action.get('D');
       } else if (linkType === 'GoToR') {
         var urlDict = action.get('F');
         if (isDict(urlDict)) {
           // We assume that the 'url' is a Filspec dictionary
           // and fetch the url without checking any further
           url = urlDict.get('F') || '';
         }
 
         // TODO: pdf reference says that GoToR
         // can also have 'NewWindow' attribute
-        if (!isValidUrl(url)) {
+        if (!isValidUrl(url, false)) {
           url = '';
         }
         data.url = url;
         data.dest = action.get('D');
       } else {
         TODO('unrecognized link type: ' + linkType);
       }
     } else if (dict.has('Dest')) {
@@ -7561,81 +7561,82 @@ var PostScriptToken = (function PostScri
   PostScriptToken.IFELSE = new PostScriptToken(PostScriptTokenTypes.IFELSE,
                                                 'IFELSE');
   return PostScriptToken;
 })();
 
 var PostScriptLexer = (function PostScriptLexerClosure() {
   function PostScriptLexer(stream) {
     this.stream = stream;
+    this.nextChar();
   }
   PostScriptLexer.prototype = {
+    nextChar: function PostScriptLexer_nextChar() {
+      return (this.currentChar = this.stream.getByte());
+    },
     getToken: function PostScriptLexer_getToken() {
       var s = '';
-      var ch;
       var comment = false;
-      var stream = this.stream;
+      var ch = this.currentChar;
 
       // skip comments
       while (true) {
-        if (!(ch = stream.getChar()))
+        if (ch < 0) {
           return EOF;
+        }
 
         if (comment) {
-          if (ch == '\x0a' || ch == '\x0d')
+          if (ch === 0x0A || ch === 0x0D) {
             comment = false;
-        } else if (ch == '%') {
+          }
+        } else if (ch == 0x25) { // '%'
           comment = true;
         } else if (!Lexer.isSpace(ch)) {
           break;
         }
-      }
-      switch (ch) {
-        case '0': case '1': case '2': case '3': case '4':
-        case '5': case '6': case '7': case '8': case '9':
-        case '+': case '-': case '.':
+        ch = this.nextChar();
+      }
+      switch (ch | 0) {
+        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
+        case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
+        case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
           return new PostScriptToken(PostScriptTokenTypes.NUMBER,
-                                      this.getNumber(ch));
-        case '{':
+                                      this.getNumber());
+        case 0x7B: // '{'
+          this.nextChar();
           return PostScriptToken.LBRACE;
-        case '}':
+        case 0x7D: // '}'
+          this.nextChar();
           return PostScriptToken.RBRACE;
       }
       // operator
-      var str = ch.toLowerCase();
-      while (true) {
-        ch = stream.lookChar();
-        if (ch === null)
-          break;
-        ch = ch.toLowerCase();
-        if (ch >= 'a' && ch <= 'z')
-          str += ch;
-        else
-          break;
-        stream.skip();
-      }
-      switch (str) {
+      var str = String.fromCharCode(ch);
+      while ((ch = this.nextChar()) >= 0 && // and 'A'-'Z', 'a'-'z'
+             ((ch >= 0x41 && ch <= 0x5A) || (ch >= 0x61 && ch <= 0x7A))) {
+        str += String.fromCharCode(ch);
+      }
+      switch (str.toLowerCase()) {
         case 'if':
           return PostScriptToken.IF;
         case 'ifelse':
           return PostScriptToken.IFELSE;
         default:
           return PostScriptToken.getOperator(str);
       }
     },
-    getNumber: function PostScriptLexer_getNumber(ch) {
-      var str = ch;
-      var stream = this.stream;
-      while (true) {
-        ch = stream.lookChar();
-        if ((ch >= '0' && ch <= '9') || ch == '-' || ch == '.')
-          str += ch;
-        else
-          break;
-        stream.skip();
+    getNumber: function PostScriptLexer_getNumber() {
+      var ch = this.currentChar;
+      var str = String.fromCharCode(ch);
+      while ((ch = this.nextChar()) >= 0) {
+        if ((ch >= 0x30 && ch <= 0x39) || // '0'-'9'
+             ch === 0x2D || ch === 0x2E) { // '-', '.'
+          str += String.fromCharCode(ch);
+        } else {
+          break;
+        }
       }
       var value = parseFloat(str);
       if (isNaN(value))
         error('Invalid floating point number: ' + value);
       return value;
     }
   };
   return PostScriptLexer;
@@ -15214,17 +15215,17 @@ var DeviceCmykCS = (function DeviceCmykC
     var g =
       c * (8.841041422036149 * c + 60.118027045597366 * m +
            6.871425592049007 * y + 31.159100130055922 * k +
            -79.2970844816548) +
       m * (-15.310361306967817 * m + 17.575251261109482 * y +
            131.35250912493976 * k - 190.9453302588951) +
       y * (4.444339102852739 * y + 9.8632861493405 * k - 24.86741582555878) +
       k * (-20.737325471181034 * k - 187.80453709719578) + 255;
-    var b = 
+    var b =
       c * (0.8842522430003296 * c + 8.078677503112928 * m +
            30.89978309703729 * y - 0.23883238689178934 * k +
            -14.183576799673286) +
       m * (10.49593273432072 * m + 63.02378494754052 * y +
            50.606957656360734 * k - 112.23884253719248) +
       y * (0.03296041114873217 * y + 115.60384449646641 * k +
            -193.58209356861505) +
       k * (-22.33816807309886 * k - 180.12613974708367) + 255;
@@ -16274,16 +16275,21 @@ var PartialEvaluator = (function Partial
 
     buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(
                                 resources, image, inline) {
       var self = this;
       var dict = image.dict;
       var w = dict.get('Width', 'W');
       var h = dict.get('Height', 'H');
 
+      if (PDFJS.maxImageSize !== -1 && w * h > PDFJS.maxImageSize) {
+        warn('Image exceeded maximum allowed size and was removed.');
+        return null;
+      }
+
       var dependencies = {};
       var retData = {
         dependencies: dependencies
       };
 
       var imageMask = dict.get('ImageMask', 'IM') || false;
       if (imageMask) {
         // This depends on a tmpCanvas beeing filled with the
@@ -16746,30 +16752,38 @@ var PartialEvaluator = (function Partial
               );
 
               if ('Form' == type.name) {
                 fn = 'promise';
                 args = [self.buildFormXObject(resources, xobj)];
               } else if ('Image' == type.name) {
                 var data = self.buildPaintImageXObject(
                     resources, xobj, false);
+                if (!data) {
+                  args = [];
+                  continue;
+                }
                 Util.extendObj(dependencies, data.dependencies);
                 self.insertDependencies(queue, data.dependencies);
                 fn = data.fn;
                 args = data.args;
               } else {
                 error('Unhandled XObject subtype ' + type.name);
               }
             }
           } else if (cmd == 'Tf') { // eagerly collect all fonts
             fn = 'promise';
             args = [self.handleSetFont(resources, args)];
           } else if (cmd == 'EI') {
             var data = self.buildPaintImageXObject(
                 resources, args[0], true);
+            if (!data) {
+              args = [];
+              continue;
+            }
             Util.extendObj(dependencies, data.dependencies);
             self.insertDependencies(queue, data.dependencies);
             fn = data.fn;
             args = data.args;
           }
 
           switch (fn) {
             // Parse the ColorSpace data to a raw format.
@@ -18207,16 +18221,28 @@ function mapPrivateUseChars(code) {
     case 0xF6D9: // copyrightserif
       return 0x00A9; // copyright
     default:
       return code;
   }
 }
 
 var FontLoader = {
+  insertRule: function fontLoaderInsertRule(rule) {
+    var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
+    if (!styleElement) {
+        styleElement = document.createElement('style');
+        styleElement.id = 'PDFJS_FONT_STYLE_TAG';
+        document.documentElement.getElementsByTagName('head')[0].appendChild(
+          styleElement);
+    }
+
+    var styleSheet = styleElement.sheet;
+    styleSheet.insertRule(rule, styleSheet.cssRules.length);
+  },
   bind: function fontLoaderBind(fonts, callback) {
     assert(!isWorker, 'bind() shall be called from main thread');
   
     for (var i = 0, ii = fonts.length; i < ii; i++) {
       var font = fonts[i];
       if (font.attached)
         continue;
   
@@ -19953,31 +19979,28 @@ var Font = (function FontClosure() {
           new CFFFont(file, properties) : new Type1Font(name, file, properties);
 
         adjustWidths(properties);
 
         // Wrap the CFF data inside an OTF font file
         data = this.convert(name, cff, properties);
         break;
 
+      case 'OpenType':
       case 'TrueType':
       case 'CIDFontType2':
         this.mimetype = 'font/opentype';
 
         // Repair the TrueType file. It is can be damaged in the point of
         // view of the sanitizer
         data = this.checkAndRepair(name, file, properties);
-        if (!data) {
-          // TrueType data is not found, e.g. when the font is an OpenType font
-          warn('Font is not a TrueType font');
-        }
         break;
 
       default:
-        warn('Font ' + type + ' is not supported');
+        error('Font ' + type + ' is not supported');
         break;
     }
 
     this.data = data;
 
     // Transfer some properties again that could change during font conversion
     this.fontMatrix = properties.fontMatrix;
     this.widths = properties.widths;
@@ -20146,17 +20169,17 @@ var Font = (function FontClosure() {
     for (var i = ranges.length - 1; i >= 0; --i) {
       if (ranges[i][0] <= 0xFFFF) { break; }
     }
     var bmpLength = i + 1;
 
     if (ranges[i][0] < 0xFFFF && ranges[i][1] === 0xFFFF) {
       ranges[i][1] = 0xFFFE;
     }
-    var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0; 
+    var trailingRangesCount = ranges[i][1] < 0xFFFF ? 1 : 0;
     var segCount = bmpLength + trailingRangesCount;
     var segCount2 = segCount * 2;
     var searchRange = getMaxPower2(segCount) * 2;
     var searchEntry = Math.log(segCount) / Math.log(2);
     var rangeShift = 2 * segCount - searchRange;
 
     // Fill up the 4 parallel arrays describing the segments.
     var startCount = '';
@@ -20998,40 +21021,16 @@ var Font = (function FontClosure() {
             itemEncode(locaData, j, simpleGlyph.length);
           glyf.data = simpleGlyph;
           return;
         }
 
         glyf.data = newGlyfData.subarray(0, writeOffset);
       }
 
-      function findEmptyGlyphs(locaTable, isGlyphLocationsLong, emptyGlyphIds) {
-        var itemSize, itemDecode;
-        if (isGlyphLocationsLong) {
-          itemSize = 4;
-          itemDecode = function fontItemDecodeLong(data, offset) {
-            return (data[offset] << 24) | (data[offset + 1] << 16) |
-                   (data[offset + 2] << 8) | data[offset + 3];
-          };
-        } else {
-          itemSize = 2;
-          itemDecode = function fontItemDecode(data, offset) {
-            return (data[offset] << 9) | (data[offset + 1] << 1);
-          };
-        }
-        var data = locaTable.data, length = data.length;
-        var lastOffset = itemDecode(data, 0);
-        for (var i = itemSize, j = 0; i < length; i += itemSize, j++) {
-          var offset = itemDecode(data, i);
-          if (offset == lastOffset)
-            emptyGlyphIds[j] = true;
-          lastOffset = offset;
-        }
-      }
-
       function readPostScriptTable(post, properties, maxpNumGlyphs) {
         var start = (font.start ? font.start : 0) + post.offset;
         font.pos = start;
 
         var length = post.length, end = start + length;
         var version = int32(font.getBytes(4));
         // skip rest to the tables
         font.getBytes(28);
@@ -21059,18 +21058,19 @@ var Font = (function FontClosure() {
             }
             if (!valid) {
               break;
             }
             var customNames = [];
             while (font.pos < end) {
               var stringLength = font.getByte();
               var string = '';
-              for (var i = 0; i < stringLength; ++i)
-                string += font.getChar();
+              for (var i = 0; i < stringLength; ++i) {
+                string += String.fromCharCode(font.getByte());
+              }
               customNames.push(string);
             }
             glyphNames = [];
             for (var i = 0; i < numGlyphs; ++i) {
               var j = glyphNameIndexes[i];
               if (j < 258) {
                 glyphNames.push(MacStandardGlyphOrdering[j]);
                 continue;
@@ -21356,146 +21356,144 @@ var Font = (function FontClosure() {
           checkInvalidFunctions(ttContext, maxFunctionDefs);
         }
         return ttContext.hintsValid;
       }
 
       // The following steps modify the original font data, making copy
       font = new Stream(new Uint8Array(font.getBytes()));
 
-      // Check that required tables are present
-      var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
-                             'hmtx', 'maxp', 'name', 'post'];
+      var VALID_TABLES = ['OS/2', 'cmap', 'head', 'hhea', 'hmtx', 'maxp',
+        'name', 'post', 'loca', 'glyf', 'fpgm', 'prep', 'cvt ', 'CFF '];
 
       var header = readOpenTypeHeader(font);
       var numTables = header.numTables;
 
-      var cmap, post, maxp, hhea, hmtx, head, os2;
-      var glyf, fpgm, loca, prep, cvt;
-      var tables = [];
+      var tables = { 'OS/2': null, cmap: null, head: null, hhea: null,
+                     hmtx: null, maxp: null, name: null, post: null};
       for (var i = 0; i < numTables; i++) {
         var table = readTableEntry(font);
-        var index = requiredTables.indexOf(table.tag);
-        if (index != -1) {
-          if (table.tag == 'cmap')
-            cmap = table;
-          else if (table.tag == 'post')
-            post = table;
-          else if (table.tag == 'maxp')
-            maxp = table;
-          else if (table.tag == 'hhea')
-            hhea = table;
-          else if (table.tag == 'hmtx')
-            hmtx = table;
-          else if (table.tag == 'head')
-            head = table;
-          else if (table.tag == 'OS/2')
-            os2 = table;
-
-          requiredTables.splice(index, 1);
-        } else {
-          if (table.tag == 'loca')
-            loca = table;
-          else if (table.tag == 'glyf')
-            glyf = table;
-          else if (table.tag == 'fpgm')
-            fpgm = table;
-          else if (table.tag == 'prep')
-            prep = table;
-          else if (table.tag == 'cvt ')
-            cvt = table;
-          else if (table.tag == 'CFF ')
-            return null; // XXX: OpenType font is found, stopping
-          else // skipping table if it's not a required or optional table
-            continue;
-        }
-        tables.push(table);
-      }
-
-      // Ensure the hmtx table contains the advance width and
-      // sidebearings information for numGlyphs in the maxp table
-      font.pos = (font.start || 0) + maxp.offset;
+        if (VALID_TABLES.indexOf(table.tag) < 0) {
+          continue; // skipping table if it's not a required or optional table
+        }
+        tables[table.tag] = table;
+      }
+
+      var isTrueType = !tables['CFF '];
+      if (!isTrueType) {
+        // OpenType font
+        if (!tables.head || !tables.hhea || !tables.maxp || !tables.post) {
+          // no major tables: throwing everything at CFFFont
+          var cffFile = new Stream(tables['CFF '].data);
+          var cff = new CFFFont(cffFile, properties);
+
+          return this.convert(name, cff, properties);
+        }
+
+        delete tables.glyf;
+        delete tables.loca;
+        delete tables.fpgm;
+        delete tables.prep;
+        delete tables['cvt '];
+      } else {
+        if (!tables.glyf || !tables.loca) {
+          error('Required "glyf" or "loca" tables are not found');
+        }
+      }
+
+      if (!tables.maxp) {
+        error('Required "maxp" table is not found');
+      }
+
+      font.pos = (font.start || 0) + tables.maxp.offset;
       var version = int32(font.getBytes(4));
       var numGlyphs = int16(font.getBytes(2));
       var maxFunctionDefs = 0;
-      if (version >= 0x00010000 && maxp.length >= 22) {
+      if (version >= 0x00010000 && tables.maxp.length >= 22) {
         font.pos += 14;
-        var maxFunctionDefs = int16(font.getBytes(2));
-      }
-
-      var hintsValid = sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
+        maxFunctionDefs = int16(font.getBytes(2));
+      }
+
+      var hintsValid = sanitizeTTPrograms(tables.fpgm, tables.prep,
+                                          maxFunctionDefs);
       if (!hintsValid) {
-        tables.splice(tables.indexOf(fpgm), 1);
-        fpgm = null;
-        tables.splice(tables.indexOf(prep), 1);
-        prep = null;
-      }
-
-      var numTables = tables.length + requiredTables.length;
+        delete tables.fpgm;
+        delete tables.prep;
+      }
+
+      // Tables needs to be written by ascendant alphabetic order
+      var tablesNames = Object.keys(tables);
+      tablesNames.sort();
+
+      numTables = tablesNames.length;
 
       // header and new offsets. Table entry information is appended to the
       // end of file. The virtualOffset represents where to put the actual
       // data of a particular table;
       var ttf = {
         file: '',
         virtualOffset: numTables * (4 * 4)
       };
 
       // The new numbers of tables will be the last one plus the num
       // of missing tables
       createOpenTypeHeader(header.version, ttf, numTables);
 
-      sanitizeMetrics(font, hhea, hmtx, numGlyphs);
-
-      if (head) {
-        sanitizeHead(head, numGlyphs, loca.length);
-      }
-
-      var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
-      if (head && loca && glyf) {
-        sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong,
-                               hintsValid);
-      }
-
-      var emptyGlyphIds = [];
-      if (glyf)
-        findEmptyGlyphs(loca, isGlyphLocationsLong, emptyGlyphIds);
+      // Ensure the hmtx table contains the advance width and
+      // sidebearings information for numGlyphs in the maxp table
+      sanitizeMetrics(font, tables.hhea, tables.hmtx, numGlyphs);
+
+      if (!tables.head) {
+        error('Required "head" table is not found');
+      }
+
+      sanitizeHead(tables.head, numGlyphs, isTrueType ? tables.loca.length : 0);
+
+      if (isTrueType) {
+        var isGlyphLocationsLong = int16([tables.head.data[50],
+                                          tables.head.data[51]]);
+
+        sanitizeGlyphLocations(tables.loca, tables.glyf, numGlyphs,
+                               isGlyphLocationsLong, hintsValid);
+      }
+
+      if (!tables.hhea) {
+        error('Required "hhea" table is not found');
+      }
 
       // Sanitizer reduces the glyph advanceWidth to the maxAdvanceWidth
       // Sometimes it's 0. That needs to be fixed
-      if (hhea.data[10] === 0 && hhea.data[11] === 0) {
-        hhea.data[10] = 0xFF;
-        hhea.data[11] = 0xFF;
+      if (tables.hhea.data[10] === 0 && tables.hhea.data[11] === 0) {
+        tables.hhea.data[10] = 0xFF;
+        tables.hhea.data[11] = 0xFF;
       }
 
       // The 'post' table has glyphs names.
-      if (post) {
-        var valid = readPostScriptTable(post, properties, numGlyphs);
+      if (tables.post) {
+        var valid = readPostScriptTable(tables.post, properties, numGlyphs);
         if (!valid) {
-          tables.splice(tables.indexOf(post), 1);
-          post = null;
+          tables.post = null;
         }
       }
 
       var glyphs, ids;
       if (properties.type == 'CIDFontType2') {
         // Replace the old CMAP table with a shiny new one
         // Type2 composite fonts map characters directly to glyphs so the cmap
         // table must be replaced.
         // canvas fillText will reencode some characters even if the font has a
         // glyph at that position - e.g. newline is converted to a space and
         // U+00AD (soft hyphen) is not drawn.
         // So, offset all the glyphs by 0xFF to avoid these cases and use
         // the encoding to map incoming characters to the new glyph positions
-        if (!cmap) {
-          cmap = {
+        if (!tables.cmap) {
+          tables.cmap = {
             tag: 'cmap',
             data: null
           };
-          tables.push(cmap);
         }
 
         var cidToGidMap = properties.cidToGidMap || [];
         var gidToCidMap = [0];
         if (cidToGidMap.length > 0) {
           for (var j = cidToGidMap.length - 1; j >= 0; j--) {
             var gid = cidToGidMap[j];
             if (gid)
@@ -21553,17 +21551,17 @@ var Font = (function FontClosure() {
         // 9.6.6.4 of the PDF spec.
 
         // TODO(mack):
         // We are using this.hasEncoding to mean that the encoding is either
         // MacRomanEncoding or WinAnsiEncoding (following spec in 9.6.6.4),
         // but this.hasEncoding is currently true for any encodings on the
         // Encodings object (e.g. MacExpertEncoding). So should consider using
         // better check for this.
-        var cmapTable = readCmapTable(cmap, font, this.hasEncoding,
+        var cmapTable = readCmapTable(tables.cmap, font, this.hasEncoding,
             this.isSymbolicFont);
 
         // TODO(mack): If the (3, 0) cmap table used, then the font is
         // symbolic. The range of charcodes in the cmap table should be
         // one of the following:
         //   -> 0x0000 - 0x00FF
         //   -> 0xF000 - 0xF0FF
         //   -> 0xF100 - 0xF1FF
@@ -21680,90 +21678,87 @@ var Font = (function FontClosure() {
 
       if (glyphs.length === 0) {
         // defines at least one glyph
         glyphs.push({ unicode: 0xF000, code: 0xF000, glyph: '.notdef' });
         ids.push(0);
       }
 
       // Converting glyphs and ids into font's cmap table
-      cmap.data = createCmapTable(glyphs, ids);
+      tables.cmap.data = createCmapTable(glyphs, ids);
       var unicodeIsEnabled = [];
       for (var i = 0, ii = glyphs.length; i < ii; i++) {
         unicodeIsEnabled[glyphs[i].unicode] = true;
       }
       this.unicodeIsEnabled = unicodeIsEnabled;
 
-      if (os2 && !validateOS2Table(os2)) {
-        tables.splice(tables.indexOf(os2), 1);
-        os2 = null;
-      }
-
-      if (!os2) {
+      if (!tables['OS/2'] || !validateOS2Table(tables['OS/2'])) {
         // extract some more font properties from the OpenType head and
         // hhea tables; yMin and descent value are always negative
         var override = {
-          unitsPerEm: int16([head.data[18], head.data[19]]),
-          yMax: int16([head.data[42], head.data[43]]),
-          yMin: int16([head.data[38], head.data[39]]) - 0x10000,
-          ascent: int16([hhea.data[4], hhea.data[5]]),
-          descent: int16([hhea.data[6], hhea.data[7]]) - 0x10000
+          unitsPerEm: int16([tables.head.data[18], tables.head.data[19]]),
+          yMax: int16([tables.head.data[42], tables.head.data[43]]),
+          yMin: int16([tables.head.data[38], tables.head.data[39]]) - 0x10000,
+          ascent: int16([tables.hhea.data[4], tables.hhea.data[5]]),
+          descent: int16([tables.hhea.data[6], tables.hhea.data[7]]) - 0x10000
         };
 
-        tables.push({
+        tables['OS/2'] = {
           tag: 'OS/2',
           data: stringToArray(createOS2Table(properties, glyphs, override))
-        });
+        };
       }
 
       // Rewrite the 'post' table if needed
-      if (!post) {
-        tables.push({
+      if (!tables.post) {
+        tables.post = {
           tag: 'post',
           data: stringToArray(createPostTable(properties))
-        });
+        };
+      }
+
+      if (!isTrueType) {
+        try {
+          // Trying to repair CFF file
+          var cffFile = new Stream(tables['CFF '].data);
+          var parser = new CFFParser(cffFile, properties);
+          var cff = parser.parse();
+          var compiler = new CFFCompiler(cff);
+          tables['CFF '].data = compiler.compile();
+        } catch (e) {
+          warn('Failed to compile font ' + properties.loadedName);
+        }
       }
 
       // Re-creating 'name' table
-      if (requiredTables.indexOf('name') != -1) {
-        tables.push({
+      if (!tables.name) {
+        tables.name = {
           tag: 'name',
           data: stringToArray(createNameTable(this.name))
-        });
+        };
       } else {
         // ... using existing 'name' table as prototype
-        for (var i = 0, ii = tables.length; i < ii; i++) {
-          var table = tables[i];
-          if (table.tag === 'name') {
-            var namePrototype = readNameTable(table);
-            table.data = stringToArray(createNameTable(name, namePrototype));
-            break;
-          }
-        }
-      }
-
-      // Tables needs to be written by ascendant alphabetic order
-      tables.sort(function tables_sort(a, b) {
-        return (a.tag > b.tag) - (a.tag < b.tag);
-      });
+        var namePrototype = readNameTable(tables.name);
+        tables.name.data = stringToArray(createNameTable(name, namePrototype));
+      }
 
       // rewrite the tables but tweak offsets
-      for (var i = 0, ii = tables.length; i < ii; i++) {
-        var table = tables[i];
+      for (var i = 0; i < numTables; i++) {
+        var table = tables[tablesNames[i]];
         var data = [];
 
         var tableData = table.data;
         for (var j = 0, jj = tableData.length; j < jj; j++)
           data.push(tableData[j]);
         createTableEntry(ttf, table.tag, data);
       }
 
       // Add the table datas
-      for (var i = 0, ii = tables.length; i < ii; i++) {
-        var table = tables[i];
+      for (var i = 0; i < numTables; i++) {
+        var table = tables[tablesNames[i]];
         var tableData = table.data;
         ttf.file += arrayToString(tableData);
 
         // 4-byte aligned data
         while (ttf.file.length & 3)
           ttf.file += String.fromCharCode(0);
       }
 
@@ -22071,26 +22066,17 @@ var Font = (function FontClosure() {
       var data = bytesToString(this.data);
       var fontName = this.loadedName;
 
       // Add the font-face rule to the document
       var url = ('url(data:' + this.mimetype + ';base64,' +
                  window.btoa(data) + ');');
       var rule = '@font-face { font-family:"' + fontName + '";src:' + url + '}';
 
-      var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
-      if (!styleElement) {
-          styleElement = document.createElement('style');
-          styleElement.id = 'PDFJS_FONT_STYLE_TAG';
-          document.documentElement.getElementsByTagName('head')[0].appendChild(
-            styleElement);
-      }
-
-      var styleSheet = styleElement.sheet;
-      styleSheet.insertRule(rule, styleSheet.cssRules.length);
+      FontLoader.insertRule(rule);
 
       if (PDFJS.pdfBug && 'FontInspector' in globalScope &&
           globalScope['FontInspector'].enabled)
         globalScope['FontInspector'].fontAdded(this, url);
 
       return rule;
     },
 
@@ -22672,27 +22658,28 @@ var Type1Parser = (function Type1ParserC
       value = stream[i];
       decryptedString[i] = value ^ (r >> 8);
       r = ((value + r) * c1 + c2) & ((1 << 16) - 1);
     }
     return decryptedString.slice(discardNumber);
   }
 
   function isSpecial(c) {
-    return c === '/' ||
-           c === '[' || c === ']' ||
-           c === '{' || c === '}' ||
-           c === '(' || c === ')';
+    return c === 0x2F || // '/'
+           c === 0x5B || c === 0x5D || // '[', ']'
+           c === 0x7B || c === 0x7D || // '{', '}'
+           c === 0x28 || c === 0x29; // '(', ')'
   }
 
   function Type1Parser(stream, encrypted) {
     if (encrypted) {
       stream = new Stream(decrypt(stream.getBytes(), EEXEC_ENCRYPT_KEY, 4));
     }
     this.stream = stream;
+    this.nextChar();
   }
 
   Type1Parser.prototype = {
     readNumberArray: function Type1Parser_readNumberArray() {
       this.getToken(); // read '[' or '{' (arrays can start with either)
       var array = [];
       while (true) {
         var token = this.getToken();
@@ -22718,46 +22705,49 @@ var Type1Parser = (function Type1ParserC
 
     readBoolean: function Type1Parser_readBoolean() {
       var token = this.getToken();
 
       // Use 1 and 0 since that's what type2 charstrings use.
       return token === 'true' ? 1 : 0;
     },
 
+    nextChar : function Type1_nextChar() {
+      return (this.currentChar = this.stream.getByte());
+    },
+
     getToken: function Type1Parser_getToken() {
       // Eat whitespace and comments.
       var comment = false;
-      var ch;
-      var stream = this.stream;
+      var ch = this.currentChar;
       while (true) {
-        if ((ch = stream.lookChar()) === null)
+        if (ch === -1) {
           return null;
+        }
 
         if (comment) {
-          if (ch === '\x0a' || ch === '\x0d') {
+          if (ch === 0x0A || ch === 0x0D) {
             comment = false;
           }
-        } else if (ch === '%') {
+        } else if (ch === 0x25) { // '%'
           comment = true;
         } else if (!Lexer.isSpace(ch)) {
           break;
         }
-        stream.skip();
+        ch = this.nextChar();
       }
       if (isSpecial(ch)) {
-        stream.skip();
-        return ch;
+        this.nextChar();
+        return String.fromCharCode(ch);
       }
       var token = '';
       do {
-        token += ch;
-        stream.skip();
-        ch = stream.lookChar();
-      } while (ch !== null && !Lexer.isSpace(ch) && !isSpecial(ch));
+        token += String.fromCharCode(ch);
+        ch = this.nextChar();
+      } while (ch >= 0 && !Lexer.isSpace(ch) && !isSpecial(ch));
       return token;
     },
 
     /*
      * Returns an object containing a Subrs array and a CharStrings
      * array extracted from and eexec encrypted block of data
      */
     extractFontProgram: function Type1Parser_extractFontProgram() {
@@ -22794,22 +22784,23 @@ var Type1Parser = (function Type1ParserC
               }
 
               if (token !== '/') {
                 continue;
               }
               var glyph = this.getToken();
               var length = this.readInt();
               this.getToken(); // read in 'RD' or '-|'
-              var data = stream.makeSubStream(stream.pos + 1, length);
+              var data = stream.makeSubStream(stream.pos, length);
               var lenIV = program.properties.privateData['lenIV'];
               var encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY,
                                     lenIV);
               // Skip past the required space and binary data.
-              stream.skip(1 + length);
+              stream.skip(length);
+              this.nextChar();
               token = this.getToken(); // read in 'ND' or '|-'
               if (token === 'noaccess') {
                 this.getToken(); // read in 'def'
               }
               charstrings.push({
                 glyph: glyph,
                 encoded: encoded
               });
@@ -22817,22 +22808,23 @@ var Type1Parser = (function Type1ParserC
             break;
           case 'Subrs':
             var num = this.readInt();
             this.getToken(); // read in 'array'
             while ((token = this.getToken()) === 'dup') {
               var index = this.readInt();
               var length = this.readInt();
               this.getToken(); // read in 'RD' or '-|'
-              var data = stream.makeSubStream(stream.pos + 1, length);
+              var data = stream.makeSubStream(stream.pos, length);
               var lenIV = program.properties.privateData['lenIV'];
               var encoded = decrypt(data.getBytes(), CHAR_STRS_ENCRYPT_KEY,
                                     lenIV);
               // Skip past the required space and binary data.
-              stream.skip(1 + length);
+              stream.skip(length);
+              this.nextChar();
               token = this.getToken(); // read in 'NP' or '|'
               if (token === 'noaccess') {
                 this.getToken(); // read in 'put'
               }
               subrs[index] = encoded;
             }
             break;
           case 'BlueValues':
@@ -29650,16 +29642,17 @@ var PDFImage = (function PDFImageClosure
     this.height = dict.get('Height', 'H');
 
     if (this.width < 1 || this.height < 1)
       error('Invalid image width: ' + this.width + ' or height: ' +
             this.height);
 
     this.interpolate = dict.get('Interpolate', 'I') || false;
     this.imageMask = dict.get('ImageMask', 'IM') || false;
+    this.matte = dict.get('Matte') || false;
 
     var bitsPerComponent = image.bitsPerComponent;
     if (!bitsPerComponent) {
       bitsPerComponent = dict.get('BitsPerComponent', 'BPC');
       if (!bitsPerComponent) {
         if (this.imageMask)
           bitsPerComponent = 1;
         else
@@ -29966,16 +29959,44 @@ var PDFImage = (function PDFImageClosure
         }
       } else {
         buf = new Uint8Array(width * height);
         for (var i = 0, ii = width * height; i < ii; ++i)
           buf[i] = 255;
       }
       return buf;
     },
+    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 length = width * height * 4;
+      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
+          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]);
+      }
+    },
     fillRgbaBuffer: function PDFImage_fillRgbaBuffer(buffer, width, height) {
       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;
@@ -30002,16 +30023,18 @@ var PDFImage = (function PDFImageClosure
       var length = width * actualHeight * 4;
 
       for (var i = 0; i < length; i += 4) {
         buffer[i] = rgbBuf[compsPos++];
         buffer[i + 1] = rgbBuf[compsPos++];
         buffer[i + 2] = rgbBuf[compsPos++];
         buffer[i + 3] = opacity[opacityPos++];
       }
+
+      this.undoPreblend(buffer, width, actualHeight);
     },
     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;
@@ -33038,18 +33061,16 @@ var Parser = (function ParserClosure() {
     refill: function Parser_refill() {
       this.buf1 = this.lexer.getObj();
       this.buf2 = this.lexer.getObj();
     },
     shift: function Parser_shift() {
       if (isCmd(this.buf2, 'ID')) {
         this.buf1 = this.buf2;
         this.buf2 = null;
-        // skip byte after ID
-        this.lexer.skip();
       } else {
         this.buf1 = this.buf2;
         this.buf2 = this.lexer.getObj();
       }
     },
     getObj: function Parser_getObj(cipherTransform) {
       if (isCmd(this.buf1, 'BI')) { // inline image
         this.shift();
@@ -33134,27 +33155,27 @@ var Parser = (function ParserClosure() {
         dict.set(key, this.getObj(cipherTransform));
       }
 
       // parse image stream
       var startPos = stream.pos;
 
       // searching for the /EI\s/
       var state = 0, ch, i, ii;
-      while (state != 4 &&
-             (ch = stream.getByte()) !== null && ch !== undefined) {
-        switch (ch) {
+      while (state != 4 && (ch = stream.getByte()) !== -1) {
+        switch (ch | 0) {
           case 0x20:
           case 0x0D:
           case 0x0A:
             // let's check next five bytes to be ASCII... just be sure
             var followingBytes = stream.peekBytes(5);
             for (i = 0, ii = followingBytes.length; i < ii; i++) {
               ch = followingBytes[i];
-              if (ch !== 0x0A && ch != 0x0D && (ch < 0x20 || ch > 0x7F)) {
+              if (ch !== 0x0A && ch !== 0x0D && (ch < 0x20 || ch > 0x7F)) {
+                // not a LF, CR, SPACE or any visible ASCII character
                 state = 0;
                 break; // some binary stuff found, resetting the state
               }
             }
             state = state === 3 ? 4 : 0;
             break;
           case 0x45:
             state = 2;
@@ -33185,25 +33206,27 @@ var Parser = (function ParserClosure() {
       return isRef(obj) ? this.xref.fetch(obj) : obj;
     },
     makeStream: function Parser_makeStream(dict, cipherTransform) {
       var lexer = this.lexer;
       var stream = lexer.stream;
 
       // get stream start position
       lexer.skipToNextLine();
-      var pos = stream.pos;
+      var pos = stream.pos - 1;
 
       // get length
       var length = this.fetchIfRef(dict.get('Length'));
       if (!isInt(length))
         error('Bad ' + length + ' attribute in stream');
 
       // skip over the stream data
       stream.pos = pos + length;
+      lexer.nextChar();
+
       this.shift(); // '>>'
       this.shift(); // 'stream'
       if (!isCmd(this.buf1, 'endstream')) {
         // bad stream length, scanning for endstream
         stream.pos = pos;
         var SCAN_BLOCK_SIZE = 2048;
         var ENDSTREAM_SIGNATURE_LENGTH = 9;
         var ENDSTREAM_SIGNATURE = [0x65, 0x6E, 0x64, 0x73, 0x74, 0x72, 0x65,
@@ -33233,16 +33256,18 @@ var Parser = (function ParserClosure() {
           }
           skipped += scanLength;
           stream.pos += scanLength;
         }
         if (!found) {
           error('Missing endstream');
         }
         length = skipped;
+
+        lexer.nextChar();
         this.shift();
         this.shift();
       }
       this.shift(); // 'endstream'
 
       stream = stream.makeSubStream(pos, length, dict);
       if (cipherTransform)
         stream = cipherTransform.createStream(stream);
@@ -33323,28 +33348,31 @@ var Parser = (function ParserClosure() {
   };
 
   return Parser;
 })();
 
 var Lexer = (function LexerClosure() {
   function Lexer(stream, knownCommands) {
     this.stream = stream;
+    this.nextChar();
+
     // The PDFs might have "glued" commands with other commands, operands or
     // literals, e.g. "q1". The knownCommands is a dictionary of the valid
     // commands and their prefixes. The prefixes are built the following way:
     // if there a command that is a prefix of the other valid command or
     // literal (e.g. 'f' and 'false') the following prefixes must be included,
     // 'fa', 'fal', 'fals'. The prefixes are not needed, if the command has no
     // other commands or literals as a prefix. The knowCommands is optional.
     this.knownCommands = knownCommands;
   }
 
   Lexer.isSpace = function Lexer_isSpace(ch) {
-    return ch == ' ' || ch == '\t' || ch == '\x0d' || ch == '\x0a';
+    // space is one of the following characters: SPACE, TAB, CR, or LF
+    return ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A;
   };
 
   // A '1' in this array means the character is white space.  A '1' or
   // '2' means the character ends a name or command.
   var specialChars = [
     1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0,   // 0x
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // 1x
     1, 0, 0, 0, 0, 2, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2,   // 2x
@@ -33359,297 +33387,312 @@ var Lexer = (function LexerClosure() {
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // bx
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // cx
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // dx
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   // ex
     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0    // fx
   ];
 
   function toHexDigit(ch) {
-    if (ch >= '0' && ch <= '9')
-      return ch.charCodeAt(0) - 48;
-    ch = ch.toUpperCase();
-    if (ch >= 'A' && ch <= 'F')
-      return ch.charCodeAt(0) - 55;
+    if (ch >= 0x30 && ch <= 0x39) { // '0'-'9'
+      return ch & 0x0F;
+    }
+    if ((ch >= 0x41 && ch <= 0x46) || (ch >= 0x61 && ch <= 0x66)) {
+      // 'A'-'F', 'a'-'f'
+      return (ch & 0x0F) + 9;
+    }
     return -1;
   }
 
   Lexer.prototype = {
-    getNumber: function Lexer_getNumber(ch) {
+    nextChar: function Lexer_nextChar() {
+      return (this.currentChar = this.stream.getByte());
+    },
+    getNumber: function Lexer_getNumber() {
       var floating = false;
-      var str = ch;
-      var stream = this.stream;
-      while ((ch = stream.lookChar())) {
-        if (ch == '.' && !floating) {
-          str += ch;
+      var ch = this.currentChar;
+      var str = String.fromCharCode(ch);
+      while ((ch = this.nextChar()) >= 0) {
+        if (ch === 0x2E && !floating) { // '.'
+          str += '.';
           floating = true;
-        } else if (ch == '-') {
+        } else if (ch === 0x2D) { // '-'
           // ignore minus signs in the middle of numbers to match
           // Adobe's behavior
           warn('Badly formated number');
-        } else if (ch >= '0' && ch <= '9') {
-          str += ch;
-        } else if (ch == 'e' || ch == 'E') {
+        } else if (ch >= 0x30 && ch <= 0x39) { // '0'-'9'
+          str += String.fromCharCode(ch);
+        } else if (ch === 0x45 || ch === 0x65) { // 'E', 'e'
           floating = true;
         } else {
           // the last character doesn't belong to us
           break;
         }
-        stream.skip();
       }
       var value = parseFloat(str);
       if (isNaN(value))
         error('Invalid floating point number: ' + value);
       return value;
     },
     getString: function Lexer_getString() {
       var numParen = 1;
       var done = false;
       var str = '';
-      var stream = this.stream;
-      var ch;
-      do {
-        ch = stream.getChar();
-        switch (ch) {
-          case null:
-          case undefined:
+
+      var ch = this.nextChar();
+      while (true) {
+        var charBuffered = false;
+        switch (ch | 0) {
+          case -1:
             warn('Unterminated string');
             done = true;
             break;
-          case '(':
+          case 0x28: // '('
             ++numParen;
-            str += ch;
-            break;
-          case ')':
+            str += '(';
+            break;
+          case 0x29: // ')'
             if (--numParen === 0) {
+              this.nextChar(); // consume strings ')'
               done = true;
             } else {
-              str += ch;
-            }
-            break;
-          case '\\':
-            ch = stream.getChar();
+              str += ')';
+            }
+            break;
+          case 0x5C: // '\\'
+            ch = this.nextChar();
             switch (ch) {
-              case null:
-              case undefined:
+              case -1:
                 warn('Unterminated string');
                 done = true;
                 break;
-              case 'n':
+              case 0x6E: // 'n'
                 str += '\n';
                 break;
-              case 'r':
+              case 0x72: // 'r'
                 str += '\r';
                 break;
-              case 't':
+              case 0x74: // 't'
                 str += '\t';
                 break;
-              case 'b':
+              case 0x62: // 'b'
                 str += '\b';
                 break;
-              case 'f':
+              case 0x66: // 'f'
                 str += '\f';
                 break;
-              case '\\':
-              case '(':
-              case ')':
-                str += ch;
-                break;
-              case '0': case '1': case '2': case '3':
-              case '4': case '5': case '6': case '7':
-                var x = ch - '0';
-                ch = stream.lookChar();
-                if (ch >= '0' && ch <= '7') {
-                  stream.skip();
-                  x = (x << 3) + (ch - '0');
-                  ch = stream.lookChar();
-                  if (ch >= '0' && ch <= '7') {
-                    stream.skip();
-                    x = (x << 3) + (ch - '0');
+              case 0x5C: // '\'
+              case 0x28: // '('
+              case 0x29: // ')'
+                str += String.fromCharCode(ch);
+                break;
+              case 0x30: case 0x31: case 0x32: case 0x33: // '0'-'3'
+              case 0x34: case 0x35: case 0x36: case 0x37: // '4'-'7'
+                var x = ch & 0x0F;
+                ch = this.nextChar();
+                charBuffered = true;
+                if (ch >= 0x30 && ch <= 0x37) { // '0'-'7'
+                  x = (x << 3) + (ch & 0x0F);
+                  ch = this.nextChar();
+                  if (ch >= 0x30 && ch <= 0x37) {  // '0'-'7'
+                    charBuffered = false;
+                    x = (x << 3) + (ch & 0x0F);
                   }
                 }
 
                 str += String.fromCharCode(x);
                 break;
-              case '\r':
-                ch = stream.lookChar();
-                if (ch == '\n')
-                  stream.skip();
-                break;
-              case '\n':
+              case 0x0A: case 0x0D: // LF, CR
                 break;
               default:
-                str += ch;
+                str += String.fromCharCode(ch);
                 break;
             }
             break;
           default:
-            str += ch;
-            break;
-        }
-      } while (!done);
+            str += String.fromCharCode(ch);
+            break;
+        }
+        if (done) {
+          break;
+        }
+        if (!charBuffered) {
+          ch = this.nextChar();
+        }
+      }
       return str;
     },
-    getName: function Lexer_getName(ch) {
-      var str = '';
-      var stream = this.stream;
-      while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
-        stream.skip();
-        if (ch == '#') {
-          ch = stream.lookChar();
+    getName: function Lexer_getName() {
+      var str = '', ch;
+      while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
+        if (ch === 0x23) { // '#'
+          ch = this.nextChar();
           var x = toHexDigit(ch);
           if (x != -1) {
-            stream.skip();
-            var x2 = toHexDigit(stream.getChar());
+            var x2 = toHexDigit(this.nextChar());
             if (x2 == -1)
               error('Illegal digit in hex char in name: ' + x2);
             str += String.fromCharCode((x << 4) | x2);
           } else {
             str += '#';
-            str += ch;
-          }
-        } else {
-          str += ch;
-        }
-      }
-      if (str.length > 128)
+            str += String.fromCharCode(ch);
+          }
+        } else {
+          str += String.fromCharCode(ch);
+        }
+      }
+      if (str.length > 128) {
         error('Warning: name token is longer than allowed by the spec: ' +
               str.length);
+      }
       return new Name(str);
     },
-    getHexString: function Lexer_getHexString(ch) {
+    getHexString: function Lexer_getHexString() {
       var str = '';
-      var stream = this.stream;
+      var ch = this.currentChar;
       var isFirstHex = true;
       var firstDigit;
       var secondDigit;
       while (true) {
-        ch = stream.getChar();
-        if (!ch) {
+        if (ch < 0) {
           warn('Unterminated hex string');
           break;
-        } else if (ch === '>') {
-          break;
-        } else if (specialChars[ch.charCodeAt(0)] === 1) {
+        } else if (ch === 0x3E) { // '>'
+          this.nextChar();
+          break;
+        } else if (specialChars[ch] === 1) {
+          ch = this.nextChar();
           continue;
         } else {
           if (isFirstHex) {
             firstDigit = toHexDigit(ch);
             if (firstDigit === -1) {
               warn('Ignoring invalid character "' + ch + '" in hex string');
+              ch = this.nextChar();
               continue;
             }
           } else {
             secondDigit = toHexDigit(ch);
             if (secondDigit === -1) {
               warn('Ignoring invalid character "' + ch + '" in hex string');
+              ch = this.nextChar();
               continue;
             }
             str += String.fromCharCode((firstDigit << 4) | secondDigit);
           }
           isFirstHex = !isFirstHex;
+          ch = this.nextChar();
         }
       }
       return str;
     },
     getObj: function Lexer_getObj() {
       // skip whitespace and comments
       var comment = false;
-      var stream = this.stream;
-      var ch;
+      var ch = this.currentChar;
       while (true) {
-        if (!(ch = stream.getChar()))
+        if (ch < 0) {
           return EOF;
+        }
         if (comment) {
-          if (ch == '\r' || ch == '\n')
+          if (ch === 0x0A || ch == 0x0D) // LF, CR
             comment = false;
-        } else if (ch == '%') {
+        } else if (ch === 0x25) { // '%'
           comment = true;
-        } else if (specialChars[ch.charCodeAt(0)] != 1) {
-          break;
-        }
+        } else if (specialChars[ch] !== 1) {
+          break;
+        }
+        ch = this.nextChar();
       }
 
       // start reading token
-      switch (ch) {
-        case '0': case '1': case '2': case '3': case '4':
-        case '5': case '6': case '7': case '8': case '9':
-        case '+': case '-': case '.':
-          return this.getNumber(ch);
-        case '(':
+      switch (ch | 0) {
+        case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: // '0'-'4'
+        case 0x35: case 0x36: case 0x37: case 0x38: case 0x39: // '5'-'9'
+        case 0x2B: case 0x2D: case 0x2E: // '+', '-', '.'
+          return this.getNumber();
+        case 0x28: // '('
           return this.getString();
-        case '/':
-          return this.getName(ch);
+        case 0x2F: // '/'
+          return this.getName();
         // array punctuation
-        case '[':
-        case ']':
-          return Cmd.get(ch);
+        case 0x5B: // '['
+          this.nextChar();
+          return Cmd.get('[');
+        case 0x5D: // ']'
+          this.nextChar();
+          return Cmd.get(']');
         // hex string or dict punctuation
-        case '<':
-          ch = stream.lookChar();
-          if (ch == '<') {
+        case 0x3C: // '<'
+          ch = this.nextChar();
+          if (ch === 0x3C) {
             // dict punctuation
-            stream.skip();
+            this.nextChar();
             return Cmd.get('<<');
           }
-          return this.getHexString(ch);
+          return this.getHexString();
         // dict punctuation
-        case '>':
-          ch = stream.lookChar();
-          if (ch == '>') {
-            stream.skip();
+        case 0x3E: // '>'
+          ch = this.nextChar();
+          if (ch === 0x3E) {
+            this.nextChar();
             return Cmd.get('>>');
           }
-          return Cmd.get(ch);
-        case '{':
-        case '}':
-          return Cmd.get(ch);
-        // fall through
-        case ')':
+          return Cmd.get('>');
+        case 0x7B: // '{'
+          this.nextChar();
+          return Cmd.get('{');
+        case 0x7D: // '}'
+          this.nextChar();
+          return Cmd.get('}');
+        case 0x29: // ')'
           error('Illegal character: ' + ch);
+          break;
       }
 
       // command
-      var str = ch;
+      var str = String.fromCharCode(ch);
       var knownCommands = this.knownCommands;
       var knownCommandFound = knownCommands && (str in knownCommands);
-      while (!!(ch = stream.lookChar()) && !specialChars[ch.charCodeAt(0)]) {
+      while ((ch = this.nextChar()) >= 0 && !specialChars[ch]) {
         // stop if known command is found and next character does not make
         // the str a command
-        if (knownCommandFound && !((str + ch) in knownCommands))
-          break;
-        stream.skip();
+        var possibleCommand = str + String.fromCharCode(ch);
+        if (knownCommandFound && !(possibleCommand in knownCommands)) {
+          break;
+        }
         if (str.length == 128)
           error('Command token too long: ' + str.length);
-        str += ch;
+        str = possibleCommand;
         knownCommandFound = knownCommands && (str in knownCommands);
       }
       if (str == 'true')
         return true;
       if (str == 'false')
         return false;
       if (str == 'null')
         return null;
       return Cmd.get(str);
     },
     skipToNextLine: function Lexer_skipToNextLine() {
       var stream = this.stream;
-      while (true) {
-        var ch = stream.getChar();
-        if (!ch || ch == '\n')
-          return;
-        if (ch == '\r') {
-          if ((ch = stream.lookChar()) == '\n')
-            stream.skip();
-          return;
-        }
-      }
-    },
-    skip: function Lexer_skip() {
-      this.stream.skip();
+      var ch = this.currentChar;
+      while (ch >= 0) {
+        if (ch === 0x0D) { // CR
+          ch = this.nextChar();
+          if (ch === 0x0A) { // LF
+            this.nextChar();
+          }
+          break;
+        } else if (ch === 0x0A) { // LF
+          this.nextChar();
+          break;
+        }
+        ch = this.nextChar();
+      }
     }
   };
 
   return Lexer;
 })();
 
 var Linearization = (function LinearizationClosure() {
   function Linearization(stream) {
@@ -34146,17 +34189,17 @@ var Stream = (function StreamClosure() {
   // required methods for a stream. if a particular stream does not
   // implement these, an error should be thrown
   Stream.prototype = {
     get length() {
       return this.end - this.start;
     },
     getByte: function Stream_getByte() {
       if (this.pos >= this.end)
-        return null;
+        return -1;
       return this.bytes[this.pos++];
     },
     // returns subarray of original buffer
     // should only be read
     getBytes: function Stream_getBytes(length) {
       var bytes = this.bytes;
       var pos = this.pos;
       var strEnd = this.end;
@@ -34171,26 +34214,16 @@ var Stream = (function StreamClosure() {
       this.pos = end;
       return bytes.subarray(pos, end);
     },
     peekBytes: function Stream_peekBytes(length) {
       var bytes = this.getBytes(length);
       this.pos -= bytes.length;
       return bytes;
     },
-    lookChar: function Stream_lookChar() {
-      if (this.pos >= this.end)
-        return null;
-      return String.fromCharCode(this.bytes[this.pos]);
-    },
-    getChar: function Stream_getChar() {
-      if (this.pos >= this.end)
-        return null;
-      return String.fromCharCode(this.bytes[this.pos++]);
-    },
     skip: function Stream_skip(n) {
       if (!n)
         n = 1;
       this.pos += n;
     },
     reset: function Stream_reset() {
       this.pos = this.start;
     },
@@ -34242,17 +34275,17 @@ var DecodeStream = (function DecodeStrea
       for (var i = 0; i < current; ++i)
         buffer2[i] = buffer[i];
       return (this.buffer = buffer2);
     },
     getByte: function DecodeStream_getByte() {
       var pos = this.pos;
       while (this.bufferLength <= pos) {
         if (this.eof)
-          return null;
+          return -1;
         this.readBlock();
       }
       return this.buffer[this.pos++];
     },
     getBytes: function DecodeStream_getBytes(length) {
       var end, pos = this.pos;
 
       if (length) {
@@ -34280,47 +34313,35 @@ var DecodeStream = (function DecodeStrea
       this.pos = end;
       return this.buffer.subarray(pos, end);
     },
     peekBytes: function DecodeStream_peekBytes(length) {
       var bytes = this.getBytes(length);
       this.pos -= bytes.length;
       return bytes;
     },
-    lookChar: function DecodeStream_lookChar() {
-      var pos = this.pos;
-      while (this.bufferLength <= pos) {
-        if (this.eof)
-          return null;
-        this.readBlock();
-      }
-      return String.fromCharCode(this.buffer[this.pos]);
-    },
-    getChar: function DecodeStream_getChar() {
-      var pos = this.pos;
-      while (this.bufferLength <= pos) {
-        if (this.eof)
-          return null;
-        this.readBlock();
-      }
-      return String.fromCharCode(this.buffer[this.pos++]);
-    },
     makeSubStream: function DecodeStream_makeSubStream(start, length, dict) {
       var end = start + length;
       while (this.bufferLength <= end && !this.eof)
         this.readBlock();
       return new Stream(this.buffer, start, length, dict);
     },
-    skip: function DecodeStream_skip(n) {
+    skip: function Stream_skip(n) {
       if (!n)
         n = 1;
       this.pos += n;
     },
     reset: function DecodeStream_reset() {
       this.pos = 0;
+    },
+    getBaseStreams: function DecodeStream_getBaseStreams() {
+      if (this.str && this.str.getBaseStreams) {
+        return this.str.getBaseStreams();
+      }
+      return [];
     }
   };
 
   return DecodeStream;
 })();
 
 var FakeStream = (function FakeStreamClosure() {
   function FakeStream(stream) {
@@ -34381,16 +34402,29 @@ var StreamsSequenceStream = (function St
     var chunk = stream.getBytes();
     var bufferLength = this.bufferLength;
     var newLength = bufferLength + chunk.length;
     var buffer = this.ensureBuffer(newLength);
     buffer.set(chunk, bufferLength);
     this.bufferLength = newLength;
   };
 
+  StreamsSequenceStream.prototype.getBaseStreams =
+    function StreamsSequenceStream_getBaseStreams() {
+
+    var baseStreams = [];
+    for (var i = 0, ii = this.streams.length; i < ii; i++) {
+      var stream = this.streams[i];
+      if (stream.getBaseStreams) {
+        Util.concatenateToArray(baseStreams, stream.getBaseStreams());
+      }
+    }
+    return baseStreams;
+  };
+
   return StreamsSequenceStream;
 })();
 
 var FlateStream = (function FlateStreamClosure() {
   var codeLenCodeMap = new Uint32Array([
     16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15
   ]);
 
@@ -34727,31 +34761,31 @@ var FlateStream = (function FlateStreamC
         buffer[pos] = buffer[pos - dist];
     }
   };
 
   return FlateStream;
 })();
 
 var PredictorStream = (function PredictorStreamClosure() {
-  function PredictorStream(stream, params) {
+  function PredictorStream(str, params) {
     var predictor = this.predictor = params.get('Predictor') || 1;
 
     if (predictor <= 1)
-      return stream; // no prediction
+      return str; // no prediction
     if (predictor !== 2 && (predictor < 10 || predictor > 15))
       error('Unsupported predictor: ' + predictor);
 
     if (predictor === 2)
       this.readBlock = this.readBlockTiff;
     else
       this.readBlock = this.readBlockPng;
 
-    this.stream = stream;
-    this.dict = stream.dict;
+    this.str = str;
+    this.dict = str.dict;
 
     var colors = this.colors = params.get('Colors') || 1;
     var bits = this.bits = params.get('BitsPerComponent') || 8;
     var columns = this.columns = params.get('Columns') || 1;
 
     this.pixBytes = (colors * bits + 7) >> 3;
     this.rowBytes = (columns * colors * bits + 7) >> 3;
 
@@ -34766,17 +34800,17 @@ var PredictorStream = (function Predicto
     var rowBytes = this.rowBytes;
 
     var bufferLength = this.bufferLength;
     var buffer = this.ensureBuffer(bufferLength + rowBytes);
 
     var bits = this.bits;
     var colors = this.colors;
 
-    var rawBytes = this.stream.getBytes(rowBytes);
+    var rawBytes = this.str.getBytes(rowBytes);
     this.eof = !rawBytes.length;
     if (this.eof) {
       return;
     }
 
     var inbuf = 0, outbuf = 0;
     var inbits = 0, outbits = 0;
     var pos = bufferLength;
@@ -34829,18 +34863,18 @@ var PredictorStream = (function Predicto
   };
 
   PredictorStream.prototype.readBlockPng =
     function predictorStreamReadBlockPng() {
 
     var rowBytes = this.rowBytes;
     var pixBytes = this.pixBytes;
 
-    var predictor = this.stream.getByte();
-    var rawBytes = this.stream.getBytes(rowBytes);
+    var predictor = this.str.getByte();
+    var rawBytes = this.str.getBytes(rowBytes);
     this.eof = !rawBytes.length;
     if (this.eof) {
       return;
     }
 
     var bufferLength = this.bufferLength;
     var buffer = this.ensureBuffer(bufferLength + rowBytes);
 
@@ -34920,59 +34954,20 @@ var PredictorStream = (function Predicto
 /**
  * Depending on the type of JPEG a JpegStream is handled in different ways. For
  * JPEG's that are supported natively such as DeviceGray and DeviceRGB the image
  * data is stored and then loaded by the browser.  For unsupported JPEG's we use
  * a library to decode these images and the stream behaves like all the other
  * DecodeStreams.
  */
 var JpegStream = (function JpegStreamClosure() {
-  function isAdobeImage(bytes) {
-    var maxBytesScanned = Math.max(bytes.length - 16, 1024);
-    // Looking for APP14, 'Adobe'
-    for (var i = 0; i < maxBytesScanned; ++i) {
-      if (bytes[i] == 0xFF && bytes[i + 1] == 0xEE &&
-          bytes[i + 2] === 0x00 && bytes[i + 3] == 0x0E &&
-          bytes[i + 4] == 0x41 && bytes[i + 5] == 0x64 &&
-          bytes[i + 6] == 0x6F && bytes[i + 7] == 0x62 &&
-          bytes[i + 8] == 0x65 && bytes[i + 9] === 0x00)
-          return true;
-      // scanning until frame tag
-      if (bytes[i] == 0xFF && bytes[i + 1] == 0xC0)
-        break;
-    }
-    return false;
-  }
-
-  function fixAdobeImage(bytes) {
-    // Inserting 'EMBED' marker after JPEG signature
-    var embedMarker = new Uint8Array([0xFF, 0xEC, 0, 8, 0x45, 0x4D, 0x42, 0x45,
-                                      0x44, 0]);
-    var newBytes = new Uint8Array(bytes.length + embedMarker.length);
-    newBytes.set(bytes, embedMarker.length);
-    // copy JPEG header
-    newBytes[0] = bytes[0];
-    newBytes[1] = bytes[1];
-    newBytes.set(embedMarker, 2);
-    return newBytes;
-  }
-
   function JpegStream(bytes, dict, xref) {
     // TODO: per poppler, some images may have 'junk' before that
     // need to be removed
     this.dict = dict;
-
-    this.isAdobeImage = false;
-    this.colorTransform = dict.get('ColorTransform') || -1;
-
-    if (isAdobeImage(bytes)) {
-      this.isAdobeImage = true;
-      bytes = fixAdobeImage(bytes);
-    }
-
     this.bytes = bytes;
 
     DecodeStream.call(this);
   }
 
   JpegStream.prototype = Object.create(DecodeStream.prototype);
 
   JpegStream.prototype.ensureBuffer = function JpegStream_ensureBuffer(req) {
@@ -34991,46 +34986,33 @@ var JpegStream = (function JpegStreamClo
       this.eof = true;
     } catch (e) {
       error('JPEG error: ' + e);
     }
   };
   JpegStream.prototype.getIR = function JpegStream_getIR() {
     return bytesToString(this.bytes);
   };
-  JpegStream.prototype.getChar = function JpegStream_getChar() {
-    error('internal error: getChar is not valid on JpegStream');
-  };
   /**
    * Checks if the image can be decoded and displayed by the browser without any
    * further processing such as color space conversions.
    */
   JpegStream.prototype.isNativelySupported =
     function JpegStream_isNativelySupported(xref, res) {
     var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res);
-    // when bug 674619 lands, let's check if browser can do
-    // normal cmyk and then we won't need to decode in JS
-    if (cs.name === 'DeviceGray' || cs.name === 'DeviceRGB')
-      return true;
-    if (cs.name === 'DeviceCMYK' && !this.isAdobeImage &&
-        this.colorTransform < 1)
-      return true;
-    return false;
+    return cs.name === 'DeviceGray' || cs.name === 'DeviceRGB';
   };
   /**
    * Checks if the image can be decoded by the browser.
    */
   JpegStream.prototype.isNativelyDecodable =
     function JpegStream_isNativelyDecodable(xref, res) {
     var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res);
     var numComps = cs.numComps;
-    if (numComps == 1 || numComps == 3)
-      return true;
-
-    return false;
+    return numComps == 1 || numComps == 3;
   };
 
   return JpegStream;
 })();
 
 /**
  * For JPEG 2000's we use a library to decode these images and
  * the stream behaves like all the other DecodeStreams.
@@ -35121,19 +35103,16 @@ var JpxStream = (function JpxStreamClosu
           break;
       }
     }
 
     this.buffer = data;
     this.bufferLength = data.length;
     this.eof = true;
   };
-  JpxStream.prototype.getChar = function JpxStream_getChar() {
-    error('internal error: getChar is not valid on JpxStream');
-  };
 
   return JpxStream;
 })();
 
 /**
  * For JBIG2's we use a library to decode these images and
  * the stream behaves like all the other DecodeStreams.
  */
@@ -35166,19 +35145,16 @@ var Jbig2Stream = (function Jbig2StreamC
     // JBIG2 had black as 1 and white as 0, inverting the colors
     for (var i = 0; i < dataLength; i++)
       data[i] ^= 0xFF;
 
     this.buffer = data;
     this.bufferLength = dataLength;
     this.eof = true;
   };
-  Jbig2Stream.prototype.getChar = function Jbig2Stream_getChar() {
-    error('internal error: getChar is not valid on Jbig2Stream');
-  };
 
   return Jbig2Stream;
 })();
 
 var DecryptStream = (function DecryptStreamClosure() {
   function DecryptStream(str, decrypt) {
     this.str = str;
     this.dict = str.dict;
@@ -35229,48 +35205,52 @@ var Ascii85Stream = (function Ascii85Str
     this.input = new Uint8Array(5);
 
     DecodeStream.call(this);
   }
 
   Ascii85Stream.prototype = Object.create(DecodeStream.prototype);
 
   Ascii85Stream.prototype.readBlock = function Ascii85Stream_readBlock() {
-    var tildaCode = '~'.charCodeAt(0);
-    var zCode = 'z'.charCodeAt(0);
+    var TILDA_CHAR = 0x7E; // '~'
+    var Z_LOWER_CHAR = 0x7A; // 'z'
+    var EOF = -1;
+
     var str = this.str;
 
     var c = str.getByte();
-    while (Lexer.isSpace(String.fromCharCode(c)))
+    while (Lexer.isSpace(c)) {
       c = str.getByte();
-
-    if (!c || c === tildaCode) {
+    }
+
+    if (c === EOF || c === TILDA_CHAR) {
       this.eof = true;
       return;
     }
 
     var bufferLength = this.bufferLength, buffer;
 
     // special code for z
-    if (c == zCode) {
+    if (c == Z_LOWER_CHAR) {
       buffer = this.ensureBuffer(bufferLength + 4);
       for (var i = 0; i < 4; ++i)
         buffer[bufferLength + i] = 0;
       this.bufferLength += 4;
     } else {
       var input = this.input;
       input[0] = c;
       for (var i = 1; i < 5; ++i) {
         c = str.getByte();
-        while (Lexer.isSpace(String.fromCharCode(c)))
+        while (Lexer.isSpace(c)) {
           c = str.getByte();
+        }
 
         input[i] = c;
 
-        if (!c || c == tildaCode)
+        if (c === EOF || c == TILDA_CHAR)
           break;
       }
       buffer = this.ensureBuffer(bufferLength + i - 1);
       this.bufferLength += i - 1;
 
       // partial ending;
       if (i < 5) {
         for (; i < 5; ++i)
@@ -35291,76 +35271,63 @@ var Ascii85Stream = (function Ascii85Str
   return Ascii85Stream;
 })();
 
 var AsciiHexStream = (function AsciiHexStreamClosure() {
   function AsciiHexStream(str) {
     this.str = str;
     this.dict = str.dict;
 
+    this.firstDigit = -1;
+
     DecodeStream.call(this);
   }
 
-  var hexvalueMap = {
-      9: -1, // \t
-      32: -1, // space
-      48: 0,
-      49: 1,
-      50: 2,
-      51: 3,
-      52: 4,
-      53: 5,
-      54: 6,
-      55: 7,
-      56: 8,
-      57: 9,
-      65: 10,
-      66: 11,
-      67: 12,
-      68: 13,
-      69: 14,
-      70: 15,
-      97: 10,
-      98: 11,
-      99: 12,
-      100: 13,
-      101: 14,
-      102: 15
-  };
-
   AsciiHexStream.prototype = Object.create(DecodeStream.prototype);
 
   AsciiHexStream.prototype.readBlock = function AsciiHexStream_readBlock() {
-    var gtCode = '>'.charCodeAt(0), bytes = this.str.getBytes(), c, n,
-        decodeLength, buffer, bufferLength, i, length;
-
-    decodeLength = (bytes.length + 1) >> 1;
-    buffer = this.ensureBuffer(this.bufferLength + decodeLength);
-    bufferLength = this.bufferLength;
-
-    for (i = 0, length = bytes.length; i < length; i++) {
-      c = hexvalueMap[bytes[i]];
-      while (c == -1 && (i + 1) < length) {
-        c = hexvalueMap[bytes[++i]];
-      }
-
-      if ((i + 1) < length && (bytes[i + 1] !== gtCode)) {
-        n = hexvalueMap[bytes[++i]];
-        buffer[bufferLength++] = c * 16 + n;
-      } else {
-        // EOD marker at an odd number, behave as if a 0 followed the last
-        // digit.
-        if (bytes[i] !== gtCode) {
-          buffer[bufferLength++] = c * 16;
-        }
-      }
-    }
-
+    var UPSTREAM_BLOCK_SIZE = 8000;
+    var bytes = this.str.getBytes(UPSTREAM_BLOCK_SIZE);
+    if (!bytes.length) {
+      this.eof = true;
+      return;
+    }
+
+    var maxDecodeLength = (bytes.length + 1) >> 1;
+    var buffer = this.ensureBuffer(this.bufferLength + maxDecodeLength);
+    var bufferLength = this.bufferLength;
+
+    var firstDigit = this.firstDigit;
+    for (var i = 0, ii = bytes.length; i < ii; i++) {
+      var ch = bytes[i], digit;
+      if (ch >= 0x30 && ch <= 0x39) { // '0'-'9'
+        digit = ch & 0x0F;
+      } else if ((ch >= 0x41 && ch <= 0x46) || (ch >= 0x61 && ch <= 0x66)) {
+        // 'A'-'Z', 'a'-'z'
+        digit = (ch & 0x0F) + 9;
+      } else if (ch === 0x3E) { // '>'
+        this.eof = true;
+        break;
+      } else { // probably whitespace
+        continue; // ignoring
+      }
+      if (firstDigit < 0) {
+        firstDigit = digit;
+      } else {
+        buffer[bufferLength++] = (firstDigit << 4) | digit;
+        firstDigit = -1;
+      }
+    }
+    if (firstDigit >= 0 && this.eof) {
+      // incomplete byte
+      buffer[bufferLength++] = (firstDigit << 4);
+      firstDigit = -1;
+    }
+    this.firstDigit = firstDigit;
     this.bufferLength = bufferLength;
-    this.eof = true;
   };
 
   return AsciiHexStream;
 })();
 
 var RunLengthStream = (function RunLengthStreamClosure() {
   function RunLengthStream(str) {
     this.str = str;
@@ -36348,17 +36315,17 @@ var CCITTFaxStream = (function CCITTFaxS
     info('bad black code');
     this.eatBits(1);
     return 1;
   };
 
   CCITTFaxStream.prototype.lookBits = function CCITTFaxStream_lookBits(n) {
     var c;
     while (this.inputBits < n) {
-      if ((c = this.str.getByte()) === null || c === undefined) {
+      if ((c = this.str.getByte()) === -1) {
         if (this.inputBits === 0)
           return EOF;
         return ((this.inputBuf << (n - this.inputBits)) &
                 (0xFFFF >> (16 - n)));
       }
       this.inputBuf = (this.inputBuf << 8) + c;
       this.inputBits += 8;
     }
@@ -36402,17 +36369,17 @@ var LZWStream = (function LZWStreamClosu
 
   LZWStream.prototype = Object.create(DecodeStream.prototype);
 
   LZWStream.prototype.readBits = function LZWStream_readBits(n) {
     var bitsCached = this.bitsCached;
     var cachedData = this.cachedData;
     while (bitsCached < n) {
       var c = this.str.getByte();
-      if (c === null || c === undefined) {
+      if (c === -1) {
         this.eof = true;
         return null;
       }
       cachedData = (cachedData << 8) | c;
       bitsCached += 8;
     }
     this.bitsCached = (bitsCached -= n);
     this.cachedData = cachedData;
@@ -36793,16 +36760,19 @@ var WorkerMessageHandler = {
           });
         } else {
           handler.send('UnknownError', {
             exception: new UnknownErrorException(e.message, e.toString())
           });
         }
       };
 
+      PDFJS.maxImageSize = data.maxImageSize === undefined ?
+                           -1 : data.maxImageSize;
+
       getPdfManager(data).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 Promise();
@@ -41340,18 +41310,16 @@ var JpegImage = (function jpegImage() {
 
               data[offset++] = R;
               data[offset++] = G;
               data[offset++] = B;
             }
           }
           break;
         case 4:
-          if (!this.adobe)
-            throw 'Unsupported color mode (4 components)';
           // 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;
 
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -37,27 +37,16 @@ select {
 
 .hidden {
   display: none;
 }
 [hidden] {
   display: none !important;
 }
 
-#viewerContainer:-webkit-full-screen {
-  top: 0px;
-  border-top: 2px solid transparent;
-  background-color: #404040;
-  background-image: url(images/texture.png);
-  width: 100%;
-  height: 100%;
-  overflow: hidden;
-  cursor: none;
-}
-
 #viewerContainer:-moz-full-screen {
   top: 0px;
   border-top: 2px solid transparent;
   background-color: #404040;
   background-image: url(images/texture.png);
   width: 100%;
   height: 100%;
   overflow: hidden;
@@ -71,32 +60,24 @@ select {
   background-image: url(images/texture.png);
   width: 100%;
   height: 100%;
   overflow: hidden;
   cursor: none;
 }
 
 
-:-webkit-full-screen .page {
-  margin-bottom: 100%;
-}
-
 :-moz-full-screen .page {
   margin-bottom: 100%;
 }
 
 :fullscreen .page {
   margin-bottom: 100%;
 }
 
-:-webkit-full-screen a:not(.internalLink) {
-  display: none;
-}
-
 :-moz-full-screen a:not(.internalLink) {
   display: none;
 }
 
 :fullscreen a:not(.internalLink) {
   display: none;
 }
 
@@ -132,40 +113,25 @@ html[dir='rtl'] .innerCenter {
 }
 
 #sidebarContainer {
   position: absolute;
   top: 0;
   bottom: 0;
   width: 200px;
   visibility: hidden;
-  -webkit-transition-duration: 200ms;
-  -webkit-transition-timing-function: ease;
-  -moz-transition-duration: 200ms;
-  -moz-transition-timing-function: ease;
-  -ms-transition-duration: 200ms;
-  -ms-transition-timing-function: ease;
-  -o-transition-duration: 200ms;
-  -o-transition-timing-function: ease;
   transition-duration: 200ms;
   transition-timing-function: ease;
 
 }
 html[dir='ltr'] #sidebarContainer {
-  -webkit-transition-property: left;
-  -moz-transition-property: left;
-  -ms-transition-property: left;
-  -o-transition-property: left;
   transition-property: left;
   left: -200px;
 }
 html[dir='rtl'] #sidebarContainer {
-  -webkit-transition-property: right;
-  -ms-transition-property: right;
-  -o-transition-property: right;
   transition-property: right;
   right: -200px;
 }
 
 #outerContainer.sidebarMoving > #sidebarContainer,
 #outerContainer.sidebarOpen > #sidebarContainer {
   visibility: visible;
 }
@@ -178,40 +144,24 @@ html[dir='rtl'] #outerContainer.sidebarO
 
 #mainContainer {
   position: absolute;
   top: 0;
   right: 0;
   bottom: 0;
   left: 0;
   min-width: 320px;
-  -webkit-transition-duration: 200ms;
-  -webkit-transition-timing-function: ease;
-  -moz-transition-duration: 200ms;
-  -moz-transition-timing-function: ease;
-  -ms-transition-duration: 200ms;
-  -ms-transition-timing-function: ease;
-  -o-transition-duration: 200ms;
-  -o-transition-timing-function: ease;
   transition-duration: 200ms;
   transition-timing-function: ease;
 }
 html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
-  -webkit-transition-property: left;
-  -moz-transition-property: left;
-  -ms-transition-property: left;
-  -o-transition-property: left;
   transition-property: left;
   left: 200px;
 }
 html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
-  -webkit-transition-property: right;
-  -moz-transition-property: right;
-  -ms-transition-property: right;
-  -o-transition-property: right;
   transition-property: right;
   right: 200px;
 }
 
 #sidebarContent {
   top: 32px;
   bottom: 0;
   overflow: auto;
@@ -231,19 +181,17 @@ html[dir='rtl'] #sidebarContent {
 #viewerContainer {
   overflow: auto;
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
   position: absolute;
   top: 32px;
   right: 0;
   bottom: 0;
   left: 0;
-}
-.loadingInProgress #viewerContainer {
-  top: 39px;
+  outline: none;
 }
 
 .toolbar {
   position: relative;
   left: 0;
   right: 0;
   z-index: 9999;
   cursor: default;
@@ -294,67 +242,41 @@ html[dir='rtl'] #sidebarContent {
 #loadingBar .progress {
   position: absolute;
   top: 0;
   left: 0;
   width: 0%;
   height: 100%;
   background-color: #ddd;
   overflow: hidden;
-  -moz-transition: width 200ms;
-  -ms-transition: width 200ms;
-  -webkit-transition: width 200ms;
   transition: width 200ms;
 }
 
-@-moz-keyframes progressIndeterminate {
-  0% { left: 0%; }
-  50% { left: 100%; }
-  100% { left: 100%; }
-}
-
-@-ms-keyframes progressIndeterminate {
-  0% { left: 0%; }
-  50% { left: 100%; }
-  100% { left: 100%; }
-}
-
-@-webkit-keyframes progressIndeterminate {
-  0% { left: 0%; }
-  50% { left: 100%; }
-  100% { left: 100%; }
-}
-
 @keyframes progressIndeterminate {
   0% { left: 0%; }
   50% { left: 100%; }
   100% { left: 100%; }
 }
 
 #loadingBar .progress.indeterminate {
   background-color: #999;
-  -moz-transition: none;
-  -ms-transition: none;
-  -webkit-transition: none;
   transition: none;
 }
 
 #loadingBar .indeterminate .glimmer {
   position: absolute;
   top: 0;
   left: 0;
   height: 100%;
   width: 50px;
 
   background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
-  background-size: 100% 100% no-repeat;
+  background-size: 100% 100%;
+  background-repeat: no-repeat;
 
-  -moz-animation: progressIndeterminate 2s linear infinite;
-  -ms-animation: progressIndeterminate 2s linear infinite;
-  -webkit-animation: progressIndeterminate 2s linear infinite;
   animation: progressIndeterminate 2s linear infinite;
 }
 
 .findbar {
   top: 32px;
   position: absolute;
   z-index: 10000;
   height: 32px;
@@ -373,17 +295,16 @@ html[dir='ltr'] .findbar {
   left: 68px;
 }
 
 html[dir='rtl'] .findbar {
   right: 68px;
 }
 
 .findbar label {
-  -webkit-user-select: none;
   -moz-user-select: none;
 }
 
 #findInput[data-status="pending"] {
   background-image: url(images/loading-small.png);
   background-repeat: no-repeat;
   background-position: right;
 }
@@ -525,28 +446,16 @@ html[dir='rtl'] .splitToolbarButton > .t
   background-color: hsla(0,0%,0%,.12);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   border: 1px solid hsla(0,0%,0%,.35);
   border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.15) inset,
               0 1px 0 hsla(0,0%,100%,.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
-  -moz-transition-property: background-color, border-color, box-shadow;
-  -moz-transition-duration: 150ms;
-  -moz-transition-timing-function: ease;
-  -ms-transition-property: background-color, border-color, box-shadow;
-  -ms-transition-duration: 150ms;
-  -ms-transition-timing-function: ease;
-  -o-transition-property: background-color, border-color, box-shadow;
-  -o-transition-duration: 150ms;
-  -o-transition-timing-function: ease;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
   transition-timing-function: ease;
 
 }
 .splitToolbarButton > .toolbarButton:hover,
 .splitToolbarButton > .toolbarButton:focus,
 .dropdownToolbarButton:hover,
@@ -591,59 +500,33 @@ html[dir='ltr'] .splitToolbarButtonSepar
 html[dir='rtl'] .splitToolbarButtonSeparator {
   float: right;
 }
 .splitToolbarButton:hover > .splitToolbarButtonSeparator,
 .splitToolbarButton.toggled > .splitToolbarButtonSeparator {
   padding: 12px 0;
   margin: 1px 0;
   box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
-  -webkit-transition-property: padding;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: ease;
-  -moz-transition-property: padding;
-  -moz-transition-duration: 10ms;
-  -moz-transition-timing-function: ease;
-  -ms-transition-property: padding;
-  -ms-transition-duration: 10ms;
-  -ms-transition-timing-function: ease;
-  -o-transition-property: padding;
-  -o-transition-duration: 10ms;
-  -o-transition-timing-function: ease;
   transition-property: padding;
   transition-duration: 10ms;
   transition-timing-function: ease;
 }
 
 .toolbarButton,
 .dropdownToolbarButton {
   min-width: 16px;
   padding: 2px 6px 0;
   border: 1px solid transparent;
   border-radius: 2px;
   color: hsl(0,0%,95%);
   font-size: 12px;
   line-height: 14px;
-  -webkit-user-select: none;
   -moz-user-select: none;
-  -ms-user-select: none;
   /* Opera does not support user-select, use <... unselectable="on"> instead */
   cursor: default;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
-  -moz-transition-property: background-color, border-color, box-shadow;
-  -moz-transition-duration: 150ms;
-  -moz-transition-timing-function: ease;
-  -ms-transition-property: background-color, border-color, box-shadow;
-  -ms-transition-duration: 150ms;
-  -ms-transition-timing-function: ease;
-  -o-transition-property: background-color, border-color, box-shadow;
-  -o-transition-duration: 150ms;
-  -o-transition-timing-function: ease;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
   transition-timing-function: ease;
 }
 
 html[dir='ltr'] .toolbarButton,
 html[dir='ltr'] .dropdownToolbarButton {
   margin: 3px 2px 4px 0;
@@ -669,53 +552,29 @@ html[dir='rtl'] .dropdownToolbarButton {
 .toolbarButton:hover:active,
 .dropdownToolbarButton:hover:active {
   background-color: hsla(0,0%,0%,.2);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2) inset,
               0 1px 0 hsla(0,0%,100%,.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
-  -moz-transition-property: background-color, border-color, box-shadow;
-  -moz-transition-duration: 10ms;
-  -moz-transition-timing-function: linear;
-  -ms-transition-property: background-color, border-color, box-shadow;
-  -ms-transition-duration: 10ms;
-  -ms-transition-timing-function: linear;
-  -o-transition-property: background-color, border-color, box-shadow;
-  -o-transition-duration: 10ms;
-  -o-transition-timing-function: linear;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 10ms;
   transition-timing-function: linear;
 }
 
 .toolbarButton.toggled,
 .splitToolbarButton.toggled > .toolbarButton.toggled {
   background-color: hsla(0,0%,0%,.3);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2) inset,
               0 1px 0 hsla(0,0%,100%,.05);
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
-  -moz-transition-property: background-color, border-color, box-shadow;
-  -moz-transition-duration: 10ms;
-  -moz-transition-timing-function: linear;
-  -ms-transition-property: background-color, border-color, box-shadow;
-  -ms-transition-duration: 10ms;
-  -ms-transition-timing-function: linear;
-  -o-transition-property: background-color, border-color, box-shadow;
-  -o-transition-duration: 10ms;
-  -o-transition-timing-function: linear;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 10ms;
   transition-timing-function: linear;
 }
 
 .toolbarButton.toggled:hover:active,
 .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active {
   background-color: hsla(0,0%,0%,.4);
@@ -735,17 +594,16 @@ html[dir='rtl'] .dropdownToolbarButton {
 html[dir='ltr'] .dropdownToolbarButton {
   background-position: 95%;
 }
 html[dir='rtl'] .dropdownToolbarButton {
   background-position: 5%;
 }
 
 .dropdownToolbarButton > select {
-  -webkit-appearance: none;
   -moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */
   min-width: 140px;
   font-size: 12px;
   color: hsl(0,0%,95%);
   margin: 0;
   padding: 0;
   border: none;
   background: rgba(0,0,0,0); /* Opera does not support 'transparent' <select> background */
@@ -778,17 +636,16 @@ html[dir='rtl'] .toolbarButton:first-chi
 
 .toolbarButtonSpacer {
   width: 30px;
   display: inline-block;
   height: 1px;
 }
 
 .toolbarButtonFlexibleSpacer {
-  -webkit-box-flex: 1;
   -moz-box-flex: 1;
   min-width: 30px;
 }
 
 .toolbarButton#sidebarToggle::before {
   display: inline-block;
   content: url(images/toolbarButton-sidebarToggle.png);
 }
@@ -882,17 +739,16 @@ html[dir='rtl'] .toolbarButton.pageDown:
 }
 
 .toolbarButton.download::before {
   display: inline-block;
   content: url(images/toolbarButton-download.png);
 }
 
 .toolbarButton.bookmark {
-  -webkit-box-sizing: border-box;
   -moz-box-sizing: border-box;
   box-sizing: border-box;
   margin-top: 3px;
   padding-top: 4px;
 }
 
 #viewBookmark[href='#'] {
   opacity: .5;
@@ -930,38 +786,32 @@ html[dir='rtl'] .toolbarButton.pageDown:
   border: 1px solid hsla(0,0%,0%,.35);
   border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
   box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   color: hsl(0,0%,95%);
   font-size: 12px;
   line-height: 14px;
   outline-style: none;
-  -moz-transition-property: background-color, border-color, box-shadow;
-  -moz-transition-duration: 150ms;
-  -moz-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
 }
 
 .toolbarField[type=checkbox] {
   display: inline-block;
   margin: 8px 0px;
 }
 
 .toolbarField.pageNumber {
   min-width: 16px;
   text-align: right;
   width: 40px;
 }
 
-.toolbarField.pageNumber::-webkit-inner-spin-button,
-.toolbarField.pageNumber::-webkit-outer-spin-button {
-    -webkit-appearance: none;
-    margin: 0;
-}
-
 .toolbarField:hover {
   background-color: hsla(0,0%,100%,.11);
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.43) hsla(0,0%,0%,.45);
 }
 
 .toolbarField:focus {
   background-color: hsla(0,0%,100%,.15);
   border-color: hsla(204,100%,65%,.8) hsla(204,100%,65%,.85) hsla(204,100%,65%,.9);
@@ -972,17 +822,16 @@ html[dir='rtl'] .toolbarButton.pageDown:
   padding: 3px 6px 3px 2px;
   margin: 4px 2px 4px 0;
   border: 1px solid transparent;
   border-radius: 2px;
   color: hsl(0,0%,85%);
   font-size: 12px;
   line-height: 14px;
   text-align: left;
-  -webkit-user-select: none;
   -moz-user-select: none;
   cursor: default;
 }
 
 #thumbnailView {
   position: absolute;
   width: 120px;
   top: 0;
@@ -996,27 +845,27 @@ html[dir='rtl'] .toolbarButton.pageDown:
 }
 
 .thumbnail:not([data-loaded]) {
   border: 1px dashed rgba(255, 255, 255, 0.5);
   margin-bottom: 10px;
 }
 
 .thumbnailImage {
-  -moz-transition-duration: 150ms;
+  transition-duration: 150ms;
   border: 1px solid transparent;
   box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3);
   opacity: 0.8;
   z-index: 99;
 }
 
 .thumbnailSelectionRing {
   border-radius: 2px;
   padding: 7px;
-  -moz-transition-duration: 150ms;
+  transition-duration: 150ms;
 }
 
 a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
 .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
   opacity: .9;
 }
 
 a:focus > .thumbnail > .thumbnailSelectionRing,
@@ -1047,17 +896,16 @@ a:focus > .thumbnail > .thumbnailSelecti
 
 #outlineView {
   position: absolute;
   width: 192px;
   top: 0;
   bottom: 0;
   padding: 4px 4px 0;
   overflow: auto;
-  -webkit-user-select: none;
   -moz-user-select: none;
 }
 
 html[dir='ltr'] .outlineItem > .outlineItems {
   margin-left: 20px;
 }
 
 html[dir='rtl'] .outlineItem > .outlineItems {
@@ -1356,26 +1204,30 @@ canvas {
   body {
     background: transparent none;
   }
 
   /* Rules for browsers that don't support mozPrintCallback. */
   #sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer {
     display: none;
   }
+  #viewerContainer {
+    overflow: visible;
+  }
 
   #mainContainer, #viewerContainer, .page, .page canvas {
     position: static;
     padding: 0;
     margin: 0;
   }
 
   .page {
     float: left;
     display: none;
+    border: none;
     box-shadow: none;
   }
 
   .page[data-loaded] {
     display: block;
   }
 
   /* Rules for browsers that support mozPrintCallback */
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -169,32 +169,32 @@ limitations under the License.
           <menuitem id="lastPage" label="Last Page"
                     data-l10n-id="last_page" ></menuitem>
           <menuitem id="pageRotateCcw" label="Rotate Counter-Clockwise"
                     data-l10n-id="page_rotate_ccw" ></menuitem>
           <menuitem id="pageRotateCw" label="Rotate Clockwise"
                     data-l10n-id="page_rotate_cw" ></menuitem>
         </menu>
 
-        <div id="viewerContainer">
+      <div id="viewerContainer">   
           <div id="viewer" contextmenu="viewerContextMenu"></div>
         </div>
 
         <div id="errorWrapper" hidden='true'>
           <div id="errorMessageLeft">
             <span id="errorMessage"></span>
-            <button id="errorShowMore" onclick="" oncontextmenu="return false;" data-l10n-id="error_more_info">
+            <button id="errorShowMore" data-l10n-id="error_more_info">
               More Information
             </button>
-            <button id="errorShowLess" onclick="" oncontextmenu="return false;" data-l10n-id="error_less_info" hidden='true'>
+            <button id="errorShowLess" data-l10n-id="error_less_info" hidden='true'>
               Less Information
             </button>
           </div>
           <div id="errorMessageRight">
-            <button id="errorClose" oncontextmenu="return false;" data-l10n-id="error_close">
+            <button id="errorClose" data-l10n-id="error_close">
               Close
             </button>
           </div>
           <div class="clearBoth"></div>
           <textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
         </div>
       </div> <!-- mainContainer -->
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -9,19 +9,20 @@
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar */
-/* globals PDFFindController, ProgressBar, getFileName, CustomStyle */
-/* globals getOutputScale, TextLayerBuilder */
+/* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
+           PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
+           getFileName, getOutputScale, scrollIntoView, getPDFFileNameFromURL,
+           PDFHistory */
 
 '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;
@@ -41,16 +42,17 @@ var RenderingStates = {
 };
 var FindStates = {
   FIND_FOUND: 0,
   FIND_NOTFOUND: 1,
   FIND_WRAPPED: 2,
   FIND_PENDING: 3
 };
 
+PDFJS.imageResourcesPath = './images/';
   PDFJS.workerSrc = '../build/pdf.js';
 
 var mozL10n = document.mozL10n || document.webL10n;
 
 
 // optimised CSS custom property getter/setter
 var CustomStyle = (function CustomStyleClosure() {
 
@@ -121,34 +123,92 @@ function getOutputScale() {
   var pixelRatio = 'devicePixelRatio' in window ? window.devicePixelRatio : 1;
   return {
     sx: pixelRatio,
     sy: pixelRatio,
     scaled: pixelRatio != 1
   };
 }
 
+/**
+ * Scrolls specified element into view of its parent.
+ * element {Object} The element to be visible.
+ * spot {Object} The object with the top property -- offset from the top edge.
+ */
+function scrollIntoView(element, spot) {
+  // Assuming offsetParent is available (it's not available when viewer is in
+  // hidden iframe or object). We have to scroll: if the offsetParent is not set
+  // producing the error. See also animationStartedClosure.
+  var parent = element.offsetParent;
+  var offsetY = element.offsetTop + element.clientTop;
+  if (!parent) {
+    console.error('offsetParent is not set -- cannot scroll');
+    return;
+  }
+  while (parent.clientHeight == parent.scrollHeight) {
+    offsetY += parent.offsetTop;
+    parent = parent.offsetParent;
+    if (!parent)
+      return; // no need to scroll
+  }
+  if (spot)
+    offsetY += spot.top;
+  parent.scrollTop = offsetY;
+}
+
+/**
+ * Returns the filename or guessed filename from the url (see issue 3455).
+ * url {String} The original PDF location.
+ * @return {String} Guessed PDF file name.
+ */
+function getPDFFileNameFromURL(url) {
+  var reURI = /^(?:([^:]+:)?\/\/[^\/]+)?([^?#]*)(\?[^#]*)?(#.*)?$/;
+  //            SCHEME      HOST         1.PATH  2.QUERY   3.REF
+  // Pattern to get last matching NAME.pdf
+  var reFilename = /[^\/?#=]+\.pdf\b(?!.*\.pdf\b)/i;
+  var splitURI = reURI.exec(url);
+  var suggestedFilename = reFilename.exec(splitURI[1]) ||
+                           reFilename.exec(splitURI[2]) ||
+                           reFilename.exec(splitURI[3]);
+  if (suggestedFilename) {
+    suggestedFilename = suggestedFilename[0];
+    if (suggestedFilename.indexOf('%') != -1) {
+      // URL-encoded %2Fpath%2Fto%2Ffile.pdf should be file.pdf
+      try {
+        suggestedFilename =
+          reFilename.exec(decodeURIComponent(suggestedFilename))[0];
+      } catch(e) { // Possible (extremely rare) errors:
+        // URIError "Malformed URI", e.g. for "%AA.pdf"
+        // TypeError "null has no properties", e.g. for "%2F.pdf"
+      }
+    }
+  }
+  return suggestedFilename || 'document.pdf';
+}
 
 var ProgressBar = (function ProgressBarClosure() {
 
   function clamp(v, min, max) {
     return Math.min(Math.max(v, min), max);
   }
 
   function ProgressBar(id, opts) {
 
-    // Fetch the sub-elements for later
+    // Fetch the sub-elements for later.
     this.div = document.querySelector(id + ' .progress');
 
-    // Get options, with sensible defaults
+    // Get the loading bar element, so it can be resized to fit the viewer.
+    this.bar = this.div.parentNode;
+
+    // Get options, with sensible defaults.
     this.height = opts.height || 100;
     this.width = opts.width || 100;
     this.units = opts.units || '%';
 
-    // Initialize heights
+    // Initialize heights.
     this.div.style.height = this.height + this.units;
     this.percent = 0;
   }
 
   ProgressBar.prototype = {
 
     updateBar: function ProgressBar_updateBar() {
       if (this._indeterminate) {
@@ -165,16 +225,32 @@ var ProgressBar = (function ProgressBarC
     get percent() {
       return this._percent;
     },
 
     set percent(val) {
       this._indeterminate = isNaN(val);
       this._percent = clamp(val, 0, 100);
       this.updateBar();
+    },
+
+    setWidth: function ProgressBar_setWidth(viewer) {
+      if (viewer) {
+        var container = viewer.parentNode;
+        var scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
+        if (scrollbarWidth > 0) {
+          this.bar.setAttribute('style', 'width: calc(100% - ' +
+                                         scrollbarWidth + 'px);');
+        }
+      }
+    },
+
+    hide: function ProgressBar_hide() {
+      this.bar.classList.add('hidden');
+      this.bar.removeAttribute('style');
     }
   };
 
   return ProgressBar;
 })();
 
 var Cache = function cacheCache(size) {
   var data = [];
@@ -185,56 +261,18 @@ var Cache = function cacheCache(size) {
     data.push(view);
     if (data.length > size)
       data.shift().destroy();
   };
 };
 
 
 
-function scrollIntoView(element, spot) {
-  // Assuming offsetParent is available (it's not available when viewer is in
-  // hidden iframe or object). We have to scroll: if the offsetParent is not set
-  // producing the error. See also animationStartedClosure.
-  var parent = element.offsetParent;
-  var offsetY = element.offsetTop + element.clientTop;
-  if (!parent) {
-    console.error('offsetParent is not set -- cannot scroll');
-    return;
-  }
-  while (parent.clientHeight == parent.scrollHeight) {
-    offsetY += parent.offsetTop;
-    parent = parent.offsetParent;
-    if (!parent)
-      return; // no need to scroll
-  }
-  if (spot)
-    offsetY += spot.top;
-  parent.scrollTop = offsetY;
-}
-
-
-/* Copyright 2012 Mozilla Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
 
 var FirefoxCom = (function FirefoxComClosure() {
-  'use strict';
-
   return {
     /**
      * Creates an event that the extension is listening for and will
      * synchronously respond to.
      * NOTE: It is reccomended to use request() instead since one day we may not
      * be able to synchronously reply.
      * @param {String} action The action to trigger.
      * @param {String} data Optional data to send.
@@ -279,16 +317,48 @@ var FirefoxCom = (function FirefoxComClo
       sender.initCustomEvent('pdf.js.message', true, false,
                              {action: action, data: data, sync: false,
                               callback: callback});
       return request.dispatchEvent(sender);
     }
   };
 })();
 
+var DownloadManager = (function DownloadManagerClosure() {
+  function DownloadManager() {}
+
+  DownloadManager.prototype = {
+    downloadUrl: function DownloadManager_downloadUrl(url, filename) {
+      FirefoxCom.request('download', {
+        originalUrl: url,
+        filename: filename
+      });
+    },
+
+    download: function DownloadManager_download(blob, url, filename) {
+      var blobUrl = window.URL.createObjectURL(blob);
+
+      FirefoxCom.request('download', {
+        blobUrl: blobUrl,
+        originalUrl: url,
+        filename: filename
+      },
+        function response(err) {
+          if (err && this.onerror) {
+            this.onerror(err);
+          }
+          window.URL.revokeObjectURL(blobUrl);
+        }.bind(this)
+      );
+    }
+  };
+
+  return DownloadManager;
+})();
+
 
 // Settings Manager - This is a utility for saving settings
 // First we see if localStorage is available
 // If not, we use FUEL in FF
 // Use asyncStorage for B2G
 var Settings = (function SettingsClosure() {
 
   function Settings(fingerprint) {
@@ -869,28 +939,28 @@ var PDFHistory = {
     this.nextHashParam = '';
 
     this.fingerprint = fingerprint;
     this.currentUid = this.uid = 0;
     this.current = {};
 
     var state = window.history.state;
     if (this._isStateObjectDefined(state)) {
-      // This case corresponds to navigating back to the document
+      // This corresponds to navigating back to the document
       // from another page in the browser history.
       if (state.target.dest) {
         this.initialDestination = state.target.dest;
       } else {
         PDFView.initialBookmark = state.target.hash;
       }
       this.currentUid = state.uid;
       this.uid = state.uid + 1;
       this.current = state.target;
     } else {
-      // This case corresponds to the loading of a new document.
+      // This corresponds to the loading of a new document.
       if (state && state.fingerprint &&
           this.fingerprint !== state.fingerprint) {
         // Reinitialize the browsing history when a new document
         // is opened in the web viewer.
         this.reInitialized = true;
       }
       window.history.replaceState({ fingerprint: this.fingerprint }, '', '');
     }
@@ -904,45 +974,55 @@ var PDFHistory = {
         return;
       }
       if (evt.state) {
         // Move back/forward in the history.
         self._goTo(evt.state);
       } else {
         // Handle the user modifying the hash of a loaded document.
         self.previousHash = window.location.hash.substring(1);
+
+        // If the history is empty when the hash changes,
+        // update the previous entry in the browser history.
         if (self.uid === 0) {
           var previousParams = (self.previousHash && self.currentBookmark &&
                                 self.previousHash !== self.currentBookmark) ?
-            { hash: self.currentBookmark } : { page: 1 };
+            { hash: self.currentBookmark, page: self.currentPage } :
+            { page: 1 };
           self.historyUnlocked = false;
           self.allowHashChange = false;
           window.history.back();
           self._pushToHistory(previousParams, false, true);
           window.history.forward();
           self.historyUnlocked = true;
         }
         self._pushToHistory({ hash: self.previousHash }, false, true);
-        if (self.currentBookmark) {
-          self.previousBookmark = self.currentBookmark;
-        }
+        self._updatePreviousBookmark();
       }
     }, false);
 
-    window.addEventListener('beforeunload',
-                            function pdfHistoryBeforeunload(evt) {
+    function pdfHistoryBeforeUnload() {
       var previousParams = self._getPreviousParams(null, true);
       if (previousParams) {
-        self._pushToHistory(previousParams, false);
+        var replacePrevious = (!self.current.dest &&
+                               self.current.hash !== self.previousHash);
+        self._pushToHistory(previousParams, false, replacePrevious);
+        self._updatePreviousBookmark();
       }
-      if (PDFView.isPresentationMode) {
-        // Prevent the user from accidentally navigating away from
-        // the document when presentation mode is active.
-        evt.preventDefault();
-      }
+      // Remove the event listener when navigating away from the document,
+      // since 'beforeunload' prevents Firefox from caching the document.
+      window.removeEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+    }
+    window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
+
+    window.addEventListener('pageshow', function pdfHistoryPageShow(evt) {
+      // If the entire viewer (including the PDF file) is cached in the browser,
+      // we need to reattach the 'beforeunload' event listener since
+      // the 'DOMContentLoaded' event is not fired on 'pageshow'.
+      window.addEventListener('beforeunload', pdfHistoryBeforeUnload, false);
     }, false);
   },
 
   _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
     return (state && state.uid >= 0 &&
             state.fingerprint && this.fingerprint === state.fingerprint &&
             state.target && state.target.hash) ? true : false;
   },
@@ -961,26 +1041,31 @@ var PDFHistory = {
     // first 'hashchange' event and immediately reset allowHashChange.
     // If it is not reset, the user would not be able to change the hash.
 
     var temp = this.allowHashChange;
     this.allowHashChange = true;
     return temp;
   },
 
+  _updatePreviousBookmark: function pdfHistory_updatePreviousBookmark() {
+    if (this.updatePreviousBookmark &&
+        this.currentBookmark && this.currentPage) {
+      this.previousBookmark = this.currentBookmark;
+      this.previousPage = this.currentPage;
+      this.updatePreviousBookmark = false;
+    }
+  },
+
   updateCurrentBookmark: function pdfHistoryUpdateCurrentBookmark(bookmark,
                                                                   pageNum) {
     if (this.initialized) {
       this.currentBookmark = bookmark.substring(1);
       this.currentPage = pageNum | 0;
-      if (this.updatePreviousBookmark) {
-        this.previousBookmark = this.currentBookmark;
-        this.previousPage = this.currentPage;
-        this.updatePreviousBookmark = false;
-      }
+      this._updatePreviousBookmark();
     }
   },
 
   updateNextHashParam: function pdfHistoryUpdateNextHashParam(param) {
     if (this.initialized) {
       this.nextHashParam = param;
     }
   },
@@ -993,34 +1078,47 @@ var PDFHistory = {
       params.hash = (this.current.hash && this.current.dest &&
                      this.current.dest === params.dest) ?
         this.current.hash :
         PDFView.getDestinationHash(params.dest).split('#')[1];
     }
     if (params.page) {
       params.page |= 0;
     }
-    if (isInitialBookmark && this.uid === 0) {
-      this._pushToHistory(params, false);
-      this.previousHash = window.location.hash.substring(1);
+    if (isInitialBookmark) {
+      var target = window.history.state.target;
+      if (!target) {
+        // Invoked when the user specifies an initial bookmark,
+        // thus setting PDFView.initialBookmark, when the document is loaded.
+        this._pushToHistory(params, false);
+        this.previousHash = window.location.hash.substring(1);
+      }
       this.updatePreviousBookmark = this.nextHashParam ? false : true;
+      if (target) {
+        // If the current document is reloaded,
+        // avoid creating duplicate entries in the history.
+        this._updatePreviousBookmark();
+      }
       return;
     }
     if (this.nextHashParam && this.nextHashParam === params.hash) {
       this.nextHashParam = null;
       this.updatePreviousBookmark = true;
       return;
     }
 
     if (params.hash) {
       if (this.current.hash) {
         if (this.current.hash !== params.hash) {
           this._pushToHistory(params, true);
-        } else if (!this.current.page && params.page) {
-          this._pushToHistory(params, false, true);
+        } else {
+          if (!this.current.page && params.page) {
+            this._pushToHistory(params, false, true);
+          }
+          this.updatePreviousBookmark = true;
         }
       } else {
         this._pushToHistory(params, true);
       }
     } else if (this.current.page && params.page &&
                this.current.page !== params.page) {
       this._pushToHistory(params, true);
     }
@@ -1059,17 +1157,18 @@ var PDFHistory = {
       return;
     }
     if (!params.hash && params.page) {
       params.hash = ('page=' + params.page);
     }
     if (addPrevious && !overwrite) {
       var previousParams = this._getPreviousParams();
       if (previousParams) {
-        this._pushToHistory(previousParams, false);
+        var replacePrevious = (this.current.hash !== this.previousHash);
+        this._pushToHistory(previousParams, false, replacePrevious);
       }
     }
     if (overwrite || this.uid === 0) {
       window.history.replaceState(this._stateObj(params), '', '');
     } else {
       window.history.pushState(this._stateObj(params), '', '');
     }
     this.currentUid = this.uid++;
@@ -1130,16 +1229,17 @@ var PDFHistory = {
         window.history.back();
       } else if (direction === 1 && state && state.uid < (this.uid - 1)) {
         window.history.forward();
       }
     }
   }
 };
 
+
 var PDFView = {
   pages: [],
   thumbnails: [],
   currentScale: UNKNOWN_SCALE,
   currentScaleValue: null,
   initialBookmark: document.location.hash.substring(1),
   startedTextExtraction: false,
   pageText: [],
@@ -1382,26 +1482,31 @@ var PDFView = {
     support = FirefoxCom.requestSync('supportsDocumentColors');
     Object.defineProperty(this, 'supportsDocumentColors', { value: support,
                                                             enumerable: true,
                                                             configurable: true,
                                                             writable: false });
     return support;
   },
 
+  get loadingBar() {
+    var bar = new ProgressBar('#loadingBar', {});
+    Object.defineProperty(this, 'loadingBar', { value: bar,
+                                                enumerable: true,
+                                                configurable: true,
+                                                writable: false });
+    return bar;
+  },
+
   get isHorizontalScrollbarEnabled() {
     var div = document.getElementById('viewerContainer');
     return div.scrollWidth > div.clientWidth;
   },
 
   initPassiveLoading: function pdfViewInitPassiveLoading() {
-    if (!PDFView.loadingBar) {
-      PDFView.loadingBar = new ProgressBar('#loadingBar', {});
-    }
-
     var pdfDataRangeTransport = {
       rangeListeners: [],
       progressListeners: [],
 
       addRangeListener: function PdfDataRangeTransport_addRangeListener(
                                    listener) {
         this.rangeListeners.push(listener);
       },
@@ -1490,20 +1595,16 @@ var PDFView = {
       parameters.data = url;
     }
     if (args) {
       for (var prop in args) {
         parameters[prop] = args[prop];
       }
     }
 
-    if (!PDFView.loadingBar) {
-      PDFView.loadingBar = new ProgressBar('#loadingBar', {});
-    }
-
     this.pdfDocument = null;
     var self = this;
     self.loading = true;
     var passwordNeeded = function passwordNeeded(updatePassword, reason) {
       var promptString = mozL10n.get('request_password', null,
                                 'PDF is protected by a password:');
 
       if (reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) {
@@ -1550,42 +1651,40 @@ var PDFView = {
         self.error(loadingErrorMessage, moreInfo);
         self.loading = false;
       }
     );
   },
 
   download: function pdfViewDownload() {
     function noData() {
-      FirefoxCom.request('download', { originalUrl: url });
+      downloadManager.downloadUrl(url, filename);
     }
+
     var url = this.url.split('#')[0];
-    // Document isn't ready just try to download with the url.
-    if (!this.pdfDocument) {
+    var filename = getPDFFileNameFromURL(url);
+    var downloadManager = new DownloadManager();
+    downloadManager.onerror = function (err) {
+      // This error won't really be helpful because it's likely the
+      // fallback won't work either (or is already open).
+      PDFView.error('PDF failed to download.');
+    };
+
+    if (!this.pdfDocument) { // the PDF is not ready yet
       noData();
       return;
     }
+
     this.pdfDocument.getData().then(
       function getDataSuccess(data) {
         var blob = PDFJS.createBlob(data.buffer, 'application/pdf');
-        var blobUrl = window.URL.createObjectURL(blob);
-  
-        FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url },
-          function response(err) {
-            if (err) {
-              // This error won't really be helpful because it's likely the
-              // fallback won't work either (or is already open).
-              PDFView.error('PDF failed to download.');
-            }
-            window.URL.revokeObjectURL(blobUrl);
-          }
-        );
+        downloadManager.download(blob, url, filename);
       },
       noData // Error occurred try downloading with just the url.
-    );
+    ).then(null, noData);
   },
 
   fallback: function pdfViewFallback() {
     // Only trigger the fallback once so we don't spam the user with messages
     // for one PDF.
     if (this.fellback)
       return;
     this.fellback = true;
@@ -1593,42 +1692,52 @@ var PDFView = {
     FirefoxCom.request('fallback', url, function response(download) {
       if (!download)
         return;
       PDFView.download();
     });
   },
 
   navigateTo: function pdfViewNavigateTo(dest) {
+    var destString = '';
     var self = this;
-    PDFJS.Promise.all([this.pagesPromise,
-                       this.destinationsPromise]).then(function() {
-      var destString = '';
-      if (typeof dest === 'string') {
-        destString = dest;
-        dest = self.destinations[dest];
-      }
-      if (!(dest instanceof Array)) {
-        return; // invalid destination
-      }
+
+    var goToDestination = function(destRef) {
+      self.pendingRefStr = null;
       // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
-      var destRef = dest[0];
       var pageNumber = destRef instanceof Object ?
         self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
         (destRef + 1);
       if (pageNumber) {
         if (pageNumber > self.pages.length) {
           pageNumber = self.pages.length;
         }
         var currentPage = self.pages[pageNumber - 1];
         currentPage.scrollIntoView(dest);
 
         // Update the browsing history.
         PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
+      } else {
+        self.pendingRefStrLoaded = new PDFJS.Promise();
+        self.pendingRefStr = destRef.num + ' ' + destRef.gen + ' R';
+        self.pendingRefStrLoaded.then(function() {
+          goToDestination(destRef);
+        });
       }
+    };
+
+    this.destinationsPromise.then(function() {
+      if (typeof dest === 'string') {
+        destString = dest;
+        dest = self.destinations[dest];
+      }
+      if (!(dest instanceof Array)) {
+        return; // invalid destination
+      }
+      goToDestination(dest[0]);
     });
   },
 
   getDestinationHash: function pdfViewGetDestinationHash(dest) {
     if (typeof dest === 'string')
       return PDFView.getAnchorUrl('#' + escape(dest));
     if (dest instanceof Array) {
       var destRef = dest[0]; // see navigateTo method for dest format
@@ -1723,18 +1832,17 @@ var PDFView = {
     }
 
     this.pdfDocument = pdfDocument;
 
     var errorWrapper = document.getElementById('errorWrapper');
     errorWrapper.setAttribute('hidden', 'true');
 
     pdfDocument.dataLoaded().then(function() {
-      var loadingBar = document.getElementById('loadingBar');
-      loadingBar.classList.add('hidden');
+      PDFView.loadingBar.hide();
       var outerContainer = document.getElementById('outerContainer');
       outerContainer.classList.remove('loadingInProgress');
     });
 
     var thumbsView = document.getElementById('thumbnailView');
     thumbsView.parentNode.scrollTop = 0;
 
     while (thumbsView.hasChildNodes())
@@ -1786,16 +1894,18 @@ var PDFView = {
         pages.push(pageView);
         thumbnails.push(thumbnailView);
       }
 
       var event = document.createEvent('CustomEvent');
       event.initCustomEvent('documentload', true, true, {});
       window.dispatchEvent(event);
 
+      PDFView.loadingBar.setWidth(container);
+
       for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
         var pagePromise = pdfDocument.getPage(pageNum);
         pagePromise.then(function(pdfPage) {
           var pageNum = pdfPage.pageNumber;
           var pageView = pages[pageNum - 1];
           if (!pageView.pdfPage) {
             // The pdfPage might already be set if we've already entered
             // pageView.draw()
@@ -1804,16 +1914,20 @@ var PDFView = {
           var thumbnailView = thumbnails[pageNum - 1];
           if (!thumbnailView.pdfPage) {
             thumbnailView.setPdfPage(pdfPage);
           }
 
           var pageRef = pdfPage.ref;
           var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
           pagesRefMap[refStr] = pdfPage.pageNumber;
+
+          if (self.pendingRefStr && self.pendingRefStr === refStr) {
+            self.pendingRefStrLoaded.resolve();
+          }
         });
         pagePromises.push(pagePromise);
       }
 
       PDFJS.Promise.all(pagePromises).then(function(pages) {
         pagesPromise.resolve(pages);
       });
     });
@@ -3092,21 +3206,19 @@ var TextLayerBuilder = function textLaye
       }
       textLayerFrag.appendChild(textDiv);
 
       ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
       var width = ctx.measureText(textDiv.textContent).width;
 
       if (width > 0) {
         var textScale = textDiv.dataset.canvasWidth / width;
-
+        var rotation = textDiv.dataset.angle;
         var transform = 'scale(' + textScale + ', 1)';
-        if (bidiTexts[i].dir === 'ttb') {
-          transform = 'rotate(90deg) ' + transform;
-        }
+        transform = 'rotate(' + rotation + 'deg) ' + transform;
         CustomStyle.setProp('transform' , textDiv, transform);
         CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
 
         textLayerDiv.appendChild(textDiv);
       }
     }
 
     this.renderingDone = true;
@@ -3136,23 +3248,24 @@ var TextLayerBuilder = function textLaye
     }
   };
 
   this.appendText = function textLayerBuilderAppendText(geom) {
     var textDiv = document.createElement('div');
 
     // vScale and hScale already contain the scaling to pixel units
     var fontHeight = geom.fontSize * Math.abs(geom.vScale);
-    textDiv.dataset.canvasWidth = geom.canvasWidth * geom.hScale;
+    textDiv.dataset.canvasWidth = geom.canvasWidth * Math.abs(geom.hScale);
     textDiv.dataset.fontName = geom.fontName;
+    textDiv.dataset.angle = geom.angle * (180 / Math.PI);
 
     textDiv.style.fontSize = fontHeight + 'px';
     textDiv.style.fontFamily = geom.fontFamily;
-    textDiv.style.left = geom.x + 'px';
-    textDiv.style.top = (geom.y - fontHeight) + 'px';
+    textDiv.style.left = (geom.x + (fontHeight * Math.sin(geom.angle))) + 'px';
+    textDiv.style.top = (geom.y - (fontHeight * Math.cos(geom.angle))) + 'px';
 
     // The content of the div is set in the `setTextContent` function.
 
     this.textDivs.push(textDiv);
   };
 
   this.insertDivContent = function textLayerUpdateTextContent() {
     // Only set the content of the divs once layout has finished, the content
@@ -3170,17 +3283,17 @@ var TextLayerBuilder = function textLaye
       var textDiv = textDivs[i];
       if (!/\S/.test(bidiText.str)) {
         textDiv.dataset.isWhitespace = true;
         continue;
       }
 
       textDiv.textContent = bidiText.str;
       // bidiText.dir may be 'ttb' for vertical texts.
-      textDiv.dir = bidiText.dir === 'rtl' ? 'rtl' : 'ltr';
+      textDiv.dir = bidiText.dir;
     }
 
     this.setupRenderLayoutTimer();
   };
 
   this.setTextContent = function textLayerBuilderSetTextContent(textContent) {
     this.textContent = textContent;
     this.insertDivContent();
@@ -3872,22 +3985,28 @@ window.addEventListener('keydown', funct
             (evt.shiftKey ? 4 : 0) |
             (evt.metaKey ? 8 : 0);
 
   // First, handle the key bindings that are independent whether an input
   // control is selected or not.
   if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
     // either CTRL or META key with optional SHIFT.
     switch (evt.keyCode) {
-      case 70:
+      case 70: // f
         if (!PDFView.supportsIntegratedFind) {
           PDFFindBar.toggle();
           handled = true;
         }
         break;
+      case 71: // g
+        if (!PDFView.supportsIntegratedFind) {
+          PDFFindBar.dispatchEvent('again', cmd === 5 || cmd === 12);
+          handled = true;
+        }
+        break;
       case 61: // FF/Mac '='
       case 107: // FF '+' and '='
       case 187: // Chrome '+'
       case 171: // FF with German keyboard
         PDFView.zoomIn();
         handled = true;
         break;
       case 173: // FF/Mac '-'
@@ -3899,24 +4018,22 @@ window.addEventListener('keydown', funct
       case 48: // '0'
       case 96: // '0' on Numpad of Swedish keyboard
         PDFView.parseScale(DEFAULT_SCALE, true);
         handled = false; // keeping it unhandled (to restore page zoom to 100%)
         break;
     }
   }
 
-  // CTRL or META with or without SHIFT.
-  if (cmd == 1 || cmd == 8 || cmd == 5 || cmd == 12) {
+  // CTRL+ALT or Option+Command
+  if (cmd === 3 || cmd === 10) {
     switch (evt.keyCode) {
-      case 71: // g
-        if (!PDFView.supportsIntegratedFind) {
-          PDFFindBar.dispatchEvent('again', cmd == 5 || cmd == 12);
-          handled = true;
-        }
+      case 80: // p
+        PDFView.presentationMode();
+        handled = true;
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
     return;
   }