Bug 1222198 - Update pdf.js to version 1.2.68. r=bdahl, r=Mossop
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 05 Nov 2015 17:22:25 -0500
changeset 271740 27de47e6bbfbbd3a24c49f161098a6a5cea4c804
parent 271739 646659e2477278ef83bf78fa2c45855c61198e7e
child 271741 3a3efe8da7702a755bc00fe169b3c123f2a0465e
push id67746
push usercbook@mozilla.com
push dateMon, 09 Nov 2015 13:58:59 +0000
treeherdermozilla-inbound@e1ef2be156de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbdahl, Mossop
bugs1222198
milestone45.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1222198 - Update pdf.js to version 1.2.68. r=bdahl, r=Mossop
browser/extensions/pdfjs/README.mozilla
browser/extensions/pdfjs/content/PdfStreamConverter.jsm
browser/extensions/pdfjs/content/build/pdf.js
browser/extensions/pdfjs/content/build/pdf.worker.js
browser/extensions/pdfjs/content/web/viewer.css
browser/extensions/pdfjs/content/web/viewer.html
browser/extensions/pdfjs/content/web/viewer.js
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.1.551
+Current extension version is: 1.2.68
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -363,16 +363,22 @@ ChromeActions.prototype = {
   },
   supportsDocumentColors: function() {
     if (getIntPref('browser.display.document_color_use', 0) === 2 ||
         !getBoolPref('browser.display.use_document_colors', true)) {
       return false;
     }
     return true;
   },
+  supportedMouseWheelZoomModifierKeys: function() {
+    return {
+      ctrlKey: getIntPref('mousewheel.with_control.action', 3) === 3,
+      metaKey: getIntPref('mousewheel.with_meta.action', 1) === 3,
+    };
+  },
   reportTelemetry: function (data) {
     var probeInfo = JSON.parse(data);
     switch (probeInfo.type) {
       case 'documentInfo':
         if (!this.telemetryState.documentInfo) {
           PdfJsTelemetry.onDocumentVersion(probeInfo.version | 0);
           PdfJsTelemetry.onDocumentGenerator(probeInfo.generator | 0);
           if (probeInfo.formType) {
@@ -767,17 +773,17 @@ RequestListener.prototype.receive = func
   var actions = this.actions;
   if (!(action in actions)) {
     log('Unknown action: ' + action);
     return;
   }
   var response;
   if (sync) {
     response = actions[action].call(this.actions, data);
-    event.detail.response = response;
+    event.detail.response = makeContentReadable(response, doc.defaultView);
   } else {
     if (!event.detail.responseExpected) {
       doc.documentElement.removeChild(message);
       response = null;
     } else {
       response = function sendResponse(response) {
         try {
           var listener = doc.createEvent('CustomEvent');
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.1.551';
-PDFJS.build = '2a5616c';
+PDFJS.version = '1.2.68';
+PDFJS.build = '8079bdd';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 
 
 var globalScope = (typeof window === 'undefined') ? this : window;
@@ -1321,16 +1321,18 @@ function loadJpegStream(id, imageUrl, ob
   img.onerror = (function loadJpegStream_onerrorClosure() {
     objs.resolve(id, null);
     warn('Error during JPEG image loading');
   });
   img.src = imageUrl;
 }
 
 
+var DEFAULT_RANGE_CHUNK_SIZE = 65536; // 2^16 = 65536
+
 /**
  * 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);
 
@@ -1515,16 +1517,19 @@ PDFJS.isEvalSupported = (PDFJS.isEvalSup
  *   or authorization headers. The default is false.
  * @property {string}     password - For decrypting password-protected PDFs.
  * @property {TypedArray} initialData - A typed array with the first portion or
  *   all of the pdf data. Used by the extension since some data is already
  *   loaded before the switch to range requests.
  * @property {number}     length - The PDF file length. It's used for progress
  *   reports and range requests operations.
  * @property {PDFDataRangeTransport} range
+ * @property {number}     rangeChunkSize - Optional parameter to specify
+ *   maximum number of bytes fetched per range request. The default value is
+ *   2^16 = 65536.
  */
 
 /**
  * @typedef {Object} PDFDocumentStats
  * @property {Array} streamTypes - Used stream types in the document (an item
  *   is set to true if specific stream ID was used in the document).
  * @property {Array} fontTypes - Used font type in the document (an item is set
  *   to true if specific font ID was used in the document).
@@ -1624,16 +1629,18 @@ PDFJS.getDocument = function getDocument
         error('Invalid PDF binary data: either typed array, string or ' +
               'array-like object is expected in the data property.');
       }
       continue;
     }
     params[key] = source[key];
   }
 
+  params.rangeChunkSize = source.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
+
   workerInitializedCapability = createPromiseCapability();
   transport = new WorkerTransport(workerInitializedCapability, source.range);
   workerInitializedCapability.promise.then(function transportInitialized() {
     transport.fetchDocument(task, params);
   });
   task._transport = transport;
 
   return task;
@@ -2521,29 +2528,39 @@ var WorkerTransport = (function WorkerTr
 
       messageHandler.on('PDFManagerReady', function transportPage(data) {
         if (this.pdfDataRangeTransport) {
           this.pdfDataRangeTransport.transportReady();
         }
       }, this);
 
       messageHandler.on('StartRenderPage', function transportRender(data) {
+        if (this.destroyed) {
+          return; // Ignore any pending requests if the worker was terminated.
+        }
         var page = this.pageCache[data.pageIndex];
 
         page.stats.timeEnd('Page Request');
         page._startRenderPage(data.transparency, data.intent);
       }, this);
 
       messageHandler.on('RenderPageChunk', function transportRender(data) {
+        if (this.destroyed) {
+          return; // Ignore any pending requests if the worker was terminated.
+        }
         var page = this.pageCache[data.pageIndex];
 
         page._renderPageChunk(data.operatorList, data.intent);
       }, this);
 
       messageHandler.on('commonobj', function transportObj(data) {
+        if (this.destroyed) {
+          return; // Ignore any pending requests if the worker was terminated.
+        }
+
         var id = data[0];
         var type = data[1];
         if (this.commonObjs.hasData(id)) {
           return;
         }
 
         switch (type) {
           case 'Font':
@@ -2570,16 +2587,20 @@ var WorkerTransport = (function WorkerTr
             this.commonObjs.resolve(id, data[2]);
             break;
           default:
             error('Got unknown common object type ' + type);
         }
       }, this);
 
       messageHandler.on('obj', function transportObj(data) {
+        if (this.destroyed) {
+          return; // Ignore any pending requests if the worker was terminated.
+        }
+
         var id = data[0];
         var pageIndex = data[1];
         var type = data[2];
         var pageProxy = this.pageCache[pageIndex];
         var imageData;
         if (pageProxy.objs.hasData(id)) {
           return;
         }
@@ -2601,36 +2622,48 @@ var WorkerTransport = (function WorkerTr
             }
             break;
           default:
             error('Got unknown object type ' + type);
         }
       }, this);
 
       messageHandler.on('DocProgress', function transportDocProgress(data) {
+        if (this.destroyed) {
+          return; // Ignore any pending requests if the worker was terminated.
+        }
+
         var loadingTask = this.loadingTask;
         if (loadingTask.onProgress) {
           loadingTask.onProgress({
             loaded: data.loaded,
             total: data.total
           });
         }
       }, this);
 
       messageHandler.on('PageError', function transportError(data) {
+        if (this.destroyed) {
+          return; // Ignore any pending requests if the worker was terminated.
+        }
+
         var page = this.pageCache[data.pageNum - 1];
         var intentState = page.intentStates[data.intent];
         if (intentState.displayReadyCapability) {
           intentState.displayReadyCapability.reject(data.error);
         } else {
           error(data.error);
         }
       }, this);
 
       messageHandler.on('JpegDecode', function(data) {
+        if (this.destroyed) {
+          return Promise.reject('Worker was terminated');
+        }
+
         var imageUrl = data[0];
         var components = data[1];
         if (components !== 3 && components !== 1) {
           return Promise.reject(
             new Error('Only 3 components or 1 component can be returned'));
         }
 
         return new Promise(function (resolve, reject) {
@@ -2660,17 +2693,17 @@ var WorkerTransport = (function WorkerTr
             }
             resolve({ data: buf, width: width, height: height});
           };
           img.onerror = function () {
             reject(new Error('JpegDecode failed to load image'));
           };
           img.src = imageUrl;
         });
-      });
+      }, this);
     },
 
     fetchDocument: function WorkerTransport_fetchDocument(loadingTask, source) {
       if (this.destroyed) {
         loadingTask._capability.reject(new Error('Loading aborted'));
         this.destroyCapability.resolve();
         return;
       }
@@ -4516,26 +4549,23 @@ var CanvasGraphics = (function CanvasGra
         lineWidth /= fontSizeScale;
       }
 
       ctx.lineWidth = lineWidth;
 
       var x = 0, i;
       for (i = 0; i < glyphsLength; ++i) {
         var glyph = glyphs[i];
-        if (glyph === null) {
-          // word break
-          x += fontDirection * wordSpacing;
-          continue;
-        } else if (isNum(glyph)) {
+        if (isNum(glyph)) {
           x += spacingDir * glyph * fontSize / 1000;
           continue;
         }
 
         var restoreNeeded = false;
+        var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
         var character = glyph.fontChar;
         var accent = glyph.accent;
         var scaledX, scaledY, scaledAccentX, scaledAccentY;
         var width = glyph.width;
         if (vertical) {
           var vmetric, vx, vy;
           vmetric = glyph.vmetric || defaultVMetrics;
           vx = glyph.vmetric ? vmetric[1] : width * 0.5;
@@ -4569,17 +4599,17 @@ var CanvasGraphics = (function CanvasGra
           this.paintChar(character, scaledX, scaledY);
           if (accent) {
             scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
             scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
             this.paintChar(accent.fontChar, scaledAccentX, scaledAccentY);
           }
         }
 
-        var charWidth = width * widthAdvanceScale + charSpacing * fontDirection;
+        var charWidth = width * widthAdvanceScale + spacing * fontDirection;
         x += charWidth;
 
         if (restoreNeeded) {
           ctx.restore();
         }
       }
       if (vertical) {
         current.y -= x * textHScale;
@@ -4614,43 +4644,39 @@ var CanvasGraphics = (function CanvasGra
       ctx.save();
       ctx.transform.apply(ctx, current.textMatrix);
       ctx.translate(current.x, current.y);
 
       ctx.scale(textHScale, fontDirection);
 
       for (i = 0; i < glyphsLength; ++i) {
         glyph = glyphs[i];
-        if (glyph === null) {
-          // word break
-          this.ctx.translate(wordSpacing, 0);
-          current.x += wordSpacing * textHScale;
-          continue;
-        } else if (isNum(glyph)) {
+        if (isNum(glyph)) {
           spacingLength = spacingDir * glyph * fontSize / 1000;
           this.ctx.translate(spacingLength, 0);
           current.x += spacingLength * textHScale;
           continue;
         }
 
+        var spacing = (glyph.isSpace ? wordSpacing : 0) + charSpacing;
         var operatorList = font.charProcOperatorList[glyph.operatorListId];
         if (!operatorList) {
           warn('Type3 character \"' + glyph.operatorListId +
                '\" is not available');
           continue;
         }
         this.processingType3 = glyph;
         this.save();
         ctx.scale(fontSize, fontSize);
         ctx.transform.apply(ctx, fontMatrix);
         this.executeOperatorList(operatorList);
         this.restore();
 
         var transformed = Util.applyTransform([glyph.width, 0], fontMatrix);
-        width = transformed[0] * fontSize + charSpacing;
+        width = transformed[0] * fontSize + spacing;
 
         ctx.translate(width, 0);
         current.x += width * textHScale;
       }
       ctx.restore();
       this.processingType3 = null;
     },
 
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.1.551';
-PDFJS.build = '2a5616c';
+PDFJS.version = '1.2.68';
+PDFJS.build = '8079bdd';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 
 
 var globalScope = (typeof window === 'undefined') ? this : window;
@@ -1851,19 +1851,16 @@ var ChunkedStreamManager = (function Chu
       }
     }
   };
 
   return ChunkedStreamManager;
 })();
 
 
-// The maximum number of bytes fetched per range request
-var RANGE_CHUNK_SIZE = 65536;
-
 // TODO(mack): Make use of PDFJS.Util.inherit() when it becomes available
 var BasePdfManager = (function BasePdfManagerClosure() {
   function BasePdfManager() {
     throw new Error('Cannot initialize BaseManagerManager');
   }
 
   BasePdfManager.prototype = {
     onLoadedStream: function BasePdfManager_onLoadedStream() {
@@ -1985,17 +1982,18 @@ var NetworkPdfManager = (function Networ
     var params = {
       msgHandler: msgHandler,
       httpHeaders: args.httpHeaders,
       withCredentials: args.withCredentials,
       chunkedViewerLoading: args.chunkedViewerLoading,
       disableAutoFetch: args.disableAutoFetch,
       initialData: args.initialData
     };
-    this.streamManager = new ChunkedStreamManager(args.length, RANGE_CHUNK_SIZE,
+    this.streamManager = new ChunkedStreamManager(args.length,
+                                                  args.rangeChunkSize,
                                                   args.url, params);
 
     this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(),
                                     args.password);
   }
 
   NetworkPdfManager.prototype = Object.create(BasePdfManager.prototype);
   NetworkPdfManager.prototype.constructor = NetworkPdfManager;
@@ -10582,19 +10580,16 @@ var PartialEvaluator = (function Partial
               'FontPath',
               path
             ]);
           }
         }.bind(this);
 
         for (var i = 0, ii = glyphs.length; i < ii; i++) {
           var glyph = glyphs[i];
-          if (glyph === null) {
-            continue;
-          }
           buildPath(glyph.fontChar);
 
           // If the glyph has an accent we need to build a path for its
           // fontChar too, otherwise CanvasGraphics_paintChar will fail.
           var accent = glyph.accent;
           if (accent && accent.fontChar) {
             buildPath(accent.fontChar);
           }
@@ -11244,20 +11239,16 @@ var PartialEvaluator = (function Partial
           }
         }
         var width = 0;
         var height = 0;
         var glyphs = font.charsToGlyphs(chars);
         var defaultVMetrics = font.defaultVMetrics;
         for (var i = 0; i < glyphs.length; i++) {
           var glyph = glyphs[i];
-          if (!glyph) { // Previous glyph was a space.
-            width += textState.wordSpacing * textState.textHScale;
-            continue;
-          }
           var vMetricX = null;
           var vMetricY = null;
           var glyphWidth = null;
           if (font.vertical) {
             if (glyph.vmetric) {
               glyphWidth = glyph.vmetric[0];
               vMetricX = glyph.vmetric[1];
               vMetricY = glyph.vmetric[2];
@@ -11283,21 +11274,24 @@ var PartialEvaluator = (function Partial
           //   tsm[5] -= vMetricY * textState.fontSize *
           //             textState.fontMatrix[0];
           // }
           // var trm = Util.transform(textState.textMatrix, tsm);
           // var pt = Util.applyTransform([trm[4], trm[5]], textState.ctm);
           // var x = pt[0];
           // var y = pt[1];
 
-          var charSpacing = 0;
-          if (textChunk.str.length > 0) {
-            // Apply char spacing only when there are chars.
-            // As a result there is only spacing between glyphs.
-            charSpacing = textState.charSpacing;
+          var charSpacing = textState.charSpacing;
+          if (glyph.isSpace) {
+            var wordSpacing = textState.wordSpacing;
+            charSpacing += wordSpacing;
+            if (wordSpacing > 0) {
+              addFakeSpaces(wordSpacing * 1000 / textState.fontSize,
+                            textChunk.str);
+            }
           }
 
           var tx = 0;
           var ty = 0;
           if (!font.vertical) {
             var w0 = glyphWidth * textState.fontMatrix[0];
             tx = (w0 * textState.fontSize + charSpacing) *
                  textState.textHScale;
@@ -11321,16 +11315,32 @@ var PartialEvaluator = (function Partial
         if (!font.vertical) {
           textChunk.width += width * scaleCtmX * scaleLineX;
         } else {
           textChunk.height += Math.abs(height * scaleCtmX * scaleLineX);
         }
         return textChunk;
       }
 
+      function addFakeSpaces(width, strBuf) {
+        var spaceWidth = textState.font.spaceWidth;
+        if (spaceWidth <= 0) {
+          return;
+        }
+        var fakeSpaces = width / spaceWidth;
+        if (fakeSpaces > MULTI_SPACE_FACTOR) {
+          fakeSpaces = Math.round(fakeSpaces);
+          while (fakeSpaces--) {
+            strBuf.push(' ');
+          }
+        } else if (fakeSpaces > SPACE_FACTOR) {
+          strBuf.push(' ');
+        }
+      }
+
       var timeSlotManager = new TimeSlotManager();
 
       return new Promise(function next(resolve, reject) {
         task.ensureNotTerminated();
         timeSlotManager.reset();
         var stop, operation = {}, args = [];
         while (!(stop = timeSlotManager.check())) {
           // The arguments parsed by read() are not used beyond this loop, so
@@ -11399,39 +11409,36 @@ var PartialEvaluator = (function Partial
                   // PDF Specification 5.3.2 states:
                   // The number is expressed in thousandths of a unit of text
                   // space.
                   // This amount is subtracted from the current horizontal or
                   // vertical coordinate, depending on the writing mode.
                   // In the default coordinate system, a positive adjustment
                   // has the effect of moving the next glyph painted either to
                   // the left or down by the given amount.
-                  var val = items[j] * textState.fontSize / 1000;
+                  var advance = items[j];
+                  var val = advance * textState.fontSize / 1000;
                   if (textState.font.vertical) {
-                    offset = val * textState.textMatrix[3];
-                    textState.translateTextMatrix(0, offset);
+                    offset = val *
+                      (textState.textHScale * textState.textMatrix[2] +
+                       textState.textMatrix[3]);
+                    textState.translateTextMatrix(0, val);
                     // Value needs to be added to height to paint down.
                     textChunk.height += offset;
                   } else {
-                    offset = val * textState.textHScale *
-                                   textState.textMatrix[0];
-                    textState.translateTextMatrix(offset, 0);
+                    offset = val * (
+                      textState.textHScale * textState.textMatrix[0] +
+                      textState.textMatrix[1]);
+                    textState.translateTextMatrix(-val, 0);
                     // Value needs to be subtracted from width to paint left.
                     textChunk.width -= offset;
+                    advance = -advance;
                   }
-                  if (items[j] < 0 && textState.font.spaceWidth > 0) {
-                    var fakeSpaces = -items[j] / textState.font.spaceWidth;
-                    if (fakeSpaces > MULTI_SPACE_FACTOR) {
-                      fakeSpaces = Math.round(fakeSpaces);
-                      while (fakeSpaces--) {
-                        textChunk.str.push(' ');
-                      }
-                    } else if (fakeSpaces > SPACE_FACTOR) {
-                      textChunk.str.push(' ');
-                    }
+                  if (advance > 0) {
+                    addFakeSpaces(advance, textChunk.str);
                   }
                 }
               }
               bidiTexts.push(runBidi(textChunk));
               break;
             case OPS.showText:
               bidiTexts.push(runBidi(buildTextGeometry(args[0])));
               break;
@@ -12152,25 +12159,34 @@ var OperatorList = (function OperatorLis
     return transfers;
   }
 
   function OperatorList(intent, messageHandler, pageIndex) {
     this.messageHandler = messageHandler;
     this.fnArray = [];
     this.argsArray = [];
     this.dependencies = {};
+    this._totalLength = 0;
     this.pageIndex = pageIndex;
     this.intent = intent;
   }
 
   OperatorList.prototype = {
     get length() {
       return this.argsArray.length;
     },
 
+    /**
+     * @returns {number} The total length of the entire operator list,
+     *                   since `this.length === 0` after flushing.
+     */
+    get totalLength() {
+      return (this._totalLength + this.length);
+    },
+
     addOp: function(fn, args) {
       this.fnArray.push(fn);
       this.argsArray.push(args);
       if (this.messageHandler) {
         if (this.fnArray.length >= CHUNK_SIZE) {
           this.flush();
         } else if (this.fnArray.length >= CHUNK_SIZE_ABOUT &&
                    (fn === OPS.restore || fn === OPS.endText)) {
@@ -12209,22 +12225,25 @@ var OperatorList = (function OperatorLis
       };
     },
 
     flush: function(lastChunk) {
       if (this.intent !== 'oplist') {
         new QueueOptimizer().optimize(this);
       }
       var transfers = getTransfers(this);
+      var length = this.length;
+      this._totalLength += length;
+
       this.messageHandler.send('RenderPageChunk', {
         operatorList: {
           fnArray: this.fnArray,
           argsArray: this.argsArray,
           lastChunk: lastChunk,
-          length: this.length
+          length: length
         },
         pageIndex: this.pageIndex,
         intent: this.intent
       }, transfers);
       this.dependencies = {};
       this.fnArray.length = 0;
       this.argsArray.length = 0;
     }
@@ -16090,33 +16109,36 @@ function getFontType(type, subtype) {
     case 'Type0':
       return FontType.TYPE0;
     default:
       return FontType.UNKNOWN;
   }
 }
 
 var Glyph = (function GlyphClosure() {
-  function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId) {
+  function Glyph(fontChar, unicode, accent, width, vmetric, operatorListId,
+                 isSpace) {
     this.fontChar = fontChar;
     this.unicode = unicode;
     this.accent = accent;
     this.width = width;
     this.vmetric = vmetric;
     this.operatorListId = operatorListId;
-  }
-
-  Glyph.prototype.matchesForCache =
-      function(fontChar, unicode, accent, width, vmetric, operatorListId) {
+    this.isSpace = isSpace;
+  }
+
+  Glyph.prototype.matchesForCache = function(fontChar, unicode, accent, width,
+                                             vmetric, operatorListId, isSpace) {
     return this.fontChar === fontChar &&
            this.unicode === unicode &&
            this.accent === accent &&
            this.width === width &&
            this.vmetric === vmetric &&
-           this.operatorListId === operatorListId;
+           this.operatorListId === operatorListId &&
+           this.isSpace === isSpace;
   };
 
   return Glyph;
 })();
 
 var ToUnicodeMap = (function ToUnicodeMapClosure() {
   function ToUnicodeMap(cmap) {
     // The elements of this._map can be integers or strings, depending on how
@@ -18621,17 +18643,17 @@ var Font = (function FontClosure() {
       }
       width = width || this.defaultWidth;
       // Do not shadow the property here. See discussion:
       // https://github.com/mozilla/pdf.js/pull/2127#discussion_r1662280
       this._shadowWidth = width;
       return width;
     },
 
-    charToGlyph: function Font_charToGlyph(charcode) {
+    charToGlyph: function Font_charToGlyph(charcode, isSpace) {
       var fontCharCode, width, operatorListId;
 
       var widthCode = charcode;
       if (this.cMap && this.cMap.contains(charcode)) {
         widthCode = this.cMap.lookup(charcode);
       }
       width = this.widths[widthCode];
       width = isNum(width) ? width : this.defaultWidth;
@@ -18664,19 +18686,19 @@ var Font = (function FontClosure() {
         };
       }
 
       var fontChar = String.fromCharCode(fontCharCode);
 
       var glyph = this.glyphCache[charcode];
       if (!glyph ||
           !glyph.matchesForCache(fontChar, unicode, accent, width, vmetric,
-                                 operatorListId)) {
+                                 operatorListId, isSpace)) {
         glyph = new Glyph(fontChar, unicode, accent, width, vmetric,
-                          operatorListId);
+                          operatorListId, isSpace);
         this.glyphCache[charcode] = glyph;
       }
       return glyph;
     },
 
     charsToGlyphs: function Font_charsToGlyphs(chars) {
       var charsCache = this.charsCache;
       var glyphs, glyph, charcode;
@@ -18702,32 +18724,26 @@ var Font = (function FontClosure() {
         // composite fonts have multi-byte strings convert the string from
         // single-byte to multi-byte
         var c = {};
         while (i < chars.length) {
           this.cMap.readCharCode(chars, i, c);
           charcode = c.charcode;
           var length = c.length;
           i += length;
-          glyph = this.charToGlyph(charcode);
+          // Space is char with code 0x20 and length 1 in multiple-byte codes.
+          var isSpace = length === 1 && chars.charCodeAt(i - 1) === 0x20;
+          glyph = this.charToGlyph(charcode, isSpace);
           glyphs.push(glyph);
-          // placing null after each word break charcode (ASCII SPACE)
-          // Ignore occurences of 0x20 in multiple-byte codes.
-          if (length === 1 && chars.charCodeAt(i - 1) === 0x20) {
-            glyphs.push(null);
-          }
         }
       } else {
         for (i = 0, ii = chars.length; i < ii; ++i) {
           charcode = chars.charCodeAt(i);
-          glyph = this.charToGlyph(charcode);
+          glyph = this.charToGlyph(charcode, charcode === 0x20);
           glyphs.push(glyph);
-          if (charcode === 0x20) {
-            glyphs.push(null);
-          }
         }
       }
 
       // Enter the translated string into the cache
       return (charsCache[charsCacheKey] = glyphs);
     }
   };
 
@@ -33803,17 +33819,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
           }
 
           var length = fullRequestXhr.getResponseHeader('Content-Length');
           length = parseInt(length, 10);
           if (!isInt(length)) {
             return;
           }
           source.length = length;
-          if (length <= 2 * RANGE_CHUNK_SIZE) {
+          if (length <= 2 * source.rangeChunkSize) {
             // The file size is smaller than the size of two chunks, so it does
             // not make any sense to abort the request and retry with a range
             // request.
             return;
           }
 
           if (networkManager.isStreamingRequest(fullRequestXhrId)) {
             // We can continue fetching when progressive loading is enabled,
@@ -34110,17 +34126,17 @@ var WorkerMessageHandler = PDFJS.WorkerM
         var pageNum = pageIndex + 1;
         var start = Date.now();
         // Pre compile the pdf page and fetch the fonts/images.
         page.getOperatorList(handler, task, data.intent).then(
             function(operatorList) {
           finishWorkerTask(task);
 
           info('page=' + pageNum + ' - getOperatorList: time=' +
-               (Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
+               (Date.now() - start) + 'ms, len=' + operatorList.totalLength);
         }, function(e) {
           finishWorkerTask(task);
           if (task.terminated) {
             return; // ignoring errors from the terminated thread
           }
 
           var minimumStackMessage =
             'worker.js: while trying to getPage() and getOperatorList()';
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -54,16 +54,32 @@
 
 .textLayer .highlight.selected {
   background-color: rgb(0, 100, 0);
 }
 
 .textLayer ::selection { background: rgb(0,0,255); }
 .textLayer ::-moz-selection { background: rgb(0,0,255); }
 
+.textLayer .endOfContent {
+  display: block;
+  position: absolute;
+  left: 0px;
+  top: 100%;
+  right: 0px;
+  bottom: 0px;
+  z-index: -1;
+  cursor: default;
+  -moz-user-select: none;
+}
+
+.textLayer .endOfContent.active {
+  top: 0px;
+}
+
 
 .annotationLayer .annotLink > a:hover {
   opacity: 0.2;
   background: #ff0;
   box-shadow: 0px 2px 10px #ff0;
 }
 
 .annotationLayer .annotText > img {
@@ -415,38 +431,38 @@ html[dir='rtl'] #toolbarContainer, .find
   width: 0%;
   height: 100%;
   background-color: #ddd;
   overflow: hidden;
   transition: width 200ms;
 }
 
 @keyframes progressIndeterminate {
-  0% { left: 0%; }
-  50% { left: 100%; }
-  100% { left: 100%; }
+  0% { left: -142px; }
+  100% { left: 0; }
 }
 
 #loadingBar .progress.indeterminate {
   background-color: #999;
   transition: none;
 }
 
-#loadingBar .indeterminate .glimmer {
+#loadingBar .progress.indeterminate .glimmer {
   position: absolute;
   top: 0;
   left: 0;
   height: 100%;
-  width: 50px;
+  width: calc(100% + 150px);
 
-  background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
-  background-size: 100% 100%;
-  background-repeat: no-repeat;
+  background: repeating-linear-gradient(135deg,
+                                        #bbb 0, #999 5px,
+                                        #999 45px, #ddd 55px,
+                                        #ddd 95px, #bbb 100px);
 
-  animation: progressIndeterminate 2s linear infinite;
+  animation: progressIndeterminate 950ms linear infinite;
 }
 
 .findbar, .secondaryToolbar {
   top: 32px;
   position: absolute;
   z-index: 10000;
   height: 32px;
 
@@ -546,16 +562,23 @@ html[dir='ltr'] .doorHangerRight:after {
 }
 
 html[dir='rtl'] .doorHanger:before,
 html[dir='ltr'] .doorHangerRight:before {
   right: 13px;
   margin-right: -9px;
 }
 
+#findResultsCount {
+  background-color: hsl(0, 0%, 85%);
+  color: hsl(0, 0%, 32%);
+  text-align: center;
+  padding: 3px 4px;
+}
+
 #findMsg {
   font-style: italic;
   color: #A6B7D0;
 }
 
 #findInput.notFound {
   background-color: rgb(255, 102, 102);
 }
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -83,16 +83,17 @@ See https://github.com/adobe-type-tools/
             <button class="toolbarButton findNext" title="" id="findNext" tabindex="93" data-l10n-id="find_next">
               <span data-l10n-id="find_next_label">Next</span>
             </button>
           </div>
           <input type="checkbox" id="findHighlightAll" class="toolbarField" tabindex="94">
           <label for="findHighlightAll" class="toolbarLabel" data-l10n-id="find_highlight">Highlight all</label>
           <input type="checkbox" id="findMatchCase" class="toolbarField" tabindex="95">
           <label for="findMatchCase" class="toolbarLabel" data-l10n-id="find_match_case_label">Match case</label>
+          <span id="findResultsCount" class="toolbarLabel hidden"></span>
           <span id="findMsg" class="toolbarLabel"></span>
         </div>  <!-- findbar -->
 
         <div id="secondaryToolbar" class="secondaryToolbar hidden doorHangerRight">
           <div id="secondaryToolbarButtonContainer">
             <button id="secondaryPresentationMode" class="secondaryToolbarButton presentationMode visibleLargeView" title="Switch to Presentation Mode" tabindex="51" data-l10n-id="presentation_mode">
               <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
             </button>
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -26,17 +26,16 @@
            IGNORE_CURRENT_POSITION_ON_ZOOM: true */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 var DEFAULT_SCALE_DELTA = 1.1;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 10.0;
-var VIEW_HISTORY_MEMORY = 20;
 var SCALE_SELECT_CONTAINER_PADDING = 8;
 var SCALE_SELECT_PADDING = 22;
 var PAGE_NUMBER_LOADING_INDICATOR = 'visiblePageIsLoading';
 var DISABLE_AUTO_FETCH_LOADING_BAR_TIMEOUT = 5000;
 
 PDFJS.imageResourcesPath = './images/';
   PDFJS.workerSrc = '../build/pdf.worker.js';
   PDFJS.cMapUrl = '../web/cmaps/';
@@ -262,16 +261,65 @@ function binarySearchFirstItem(items, co
     } else {
       minIndex = currentIndex + 1;
     }
   }
   return minIndex; /* === maxIndex */
 }
 
 /**
+ *  Approximates float number as a fraction using Farey sequence (max order
+ *  of 8).
+ *  @param {number} x - Positive float number.
+ *  @returns {Array} Estimated fraction: the first array item is a numerator,
+ *                   the second one is a denominator.
+ */
+function approximateFraction(x) {
+  // Fast paths for int numbers or their inversions.
+  if (Math.floor(x) === x) {
+    return [x, 1];
+  }
+  var xinv = 1 / x;
+  var limit = 8;
+  if (xinv > limit) {
+    return [1, limit];
+  } else  if (Math.floor(xinv) === xinv) {
+    return [1, xinv];
+  }
+
+  var x_ = x > 1 ? xinv : x;
+  // a/b and c/d are neighbours in Farey sequence.
+  var a = 0, b = 1, c = 1, d = 1;
+  // Limiting search to order 8.
+  while (true) {
+    // Generating next term in sequence (order of q).
+    var p = a + c, q = b + d;
+    if (q > limit) {
+      break;
+    }
+    if (x_ <= p / q) {
+      c = p; d = q;
+    } else {
+      a = p; b = q;
+    }
+  }
+  // Select closest of the neighbours to x.
+  if (x_ - a / b < c / d - x_) {
+    return x_ === x ? [a, b] : [b, a];
+  } else {
+    return x_ === x ? [c, d] : [d, c];
+  }
+}
+
+function roundToDivide(x, div) {
+  var r = x % div;
+  return r === 0 ? x : Math.round(x - r + div);
+}
+
+/**
  * Generic helper to find out what elements are visible within a scroll pane.
  */
 function getVisibleElements(scrollEl, views, sortByVisibility) {
   var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
   var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
 
   function isElementBottomBelowViewTop(view) {
     var element = view.div;
@@ -728,38 +776,41 @@ Preferences._readFromStorage = function 
       resolve(readPrefs);
     });
   });
 };
 
 
 
 
+var DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
+
 /**
  * View History - This is a utility for saving various view parameters for
  *                recently opened files.
  *
  * The way that the view parameters are stored depends on how PDF.js is built,
  * for 'node make <flag>' the following cases exist:
  *  - FIREFOX or MOZCENTRAL - uses sessionStorage.
  *  - GENERIC or CHROME     - uses localStorage, if it is available.
  */
 var ViewHistory = (function ViewHistoryClosure() {
-  function ViewHistory(fingerprint) {
+  function ViewHistory(fingerprint, cacheSize) {
     this.fingerprint = fingerprint;
+    this.cacheSize = cacheSize || DEFAULT_VIEW_HISTORY_CACHE_SIZE;
     this.isInitializedPromiseResolved = false;
     this.initializedPromise =
         this._readFromStorage().then(function (databaseStr) {
       this.isInitializedPromiseResolved = true;
 
       var database = JSON.parse(databaseStr || '{}');
       if (!('files' in database)) {
         database.files = [];
       }
-      if (database.files.length >= VIEW_HISTORY_MEMORY) {
+      if (database.files.length >= this.cacheSize) {
         database.files.shift();
       }
       var index;
       for (var i = 0, length = database.files.length; i < length; i++) {
         var branch = database.files[i];
         if (branch.fingerprint === this.fingerprint) {
           index = i;
           break;
@@ -831,16 +882,17 @@ var PDFFindBar = (function PDFFindBarClo
   function PDFFindBar(options) {
     this.opened = false;
     this.bar = options.bar || null;
     this.toggleButton = options.toggleButton || null;
     this.findField = options.findField || null;
     this.highlightAll = options.highlightAllCheckbox || null;
     this.caseSensitive = options.caseSensitiveCheckbox || null;
     this.findMsg = options.findMsg || null;
+    this.findResultsCount = options.findResultsCount || null;
     this.findStatusIcon = options.findStatusIcon || null;
     this.findPreviousButton = options.findPreviousButton || null;
     this.findNextButton = options.findNextButton || null;
     this.findController = options.findController || null;
 
     if (this.findController === null) {
       throw new Error('PDFFindBar cannot be used without a ' +
                       'PDFFindController instance.');
@@ -893,17 +945,18 @@ var PDFFindBar = (function PDFFindBarClo
         query: this.findField.value,
         caseSensitive: this.caseSensitive.checked,
         highlightAll: this.highlightAll.checked,
         findPrevious: findPrev
       });
       return window.dispatchEvent(event);
     },
 
-    updateUIState: function PDFFindBar_updateUIState(state, previous) {
+    updateUIState:
+        function PDFFindBar_updateUIState(state, previous, matchCount) {
       var notFound = false;
       var findMsg = '';
       var status = '';
 
       switch (state) {
         case FindStates.FIND_FOUND:
           break;
 
@@ -930,16 +983,36 @@ var PDFFindBar = (function PDFFindBarClo
       if (notFound) {
         this.findField.classList.add('notFound');
       } else {
         this.findField.classList.remove('notFound');
       }
 
       this.findField.setAttribute('data-status', status);
       this.findMsg.textContent = findMsg;
+
+      this.updateResultsCount(matchCount);
+    },
+
+    updateResultsCount: function(matchCount) {
+      if (!this.findResultsCount) {
+        return; // no UI control is provided
+      }
+
+      // If there are no matches, hide the counter
+      if (!matchCount) {
+        this.findResultsCount.classList.add('hidden');
+        return;
+      }
+
+      // Create the match counter
+      this.findResultsCount.textContent = matchCount.toLocaleString();
+
+      // Show the counter
+      this.findResultsCount.classList.remove('hidden');
     },
 
     open: function PDFFindBar_open() {
       if (!this.opened) {
         this.opened = true;
         this.toggleButton.classList.add('toggled');
         this.bar.classList.remove('hidden');
       }
@@ -986,16 +1059,17 @@ var FIND_SCROLL_OFFSET_LEFT = -400;
 var PDFFindController = (function PDFFindControllerClosure() {
   function PDFFindController(options) {
     this.startedTextExtraction = false;
     this.extractTextPromises = [];
     this.pendingFindMatches = {};
     this.active = false; // If active, find results will be highlighted.
     this.pageContents = []; // Stores the text for each page.
     this.pageMatches = [];
+    this.matchCount = 0;
     this.selected = { // Currently selected match.
       pageIdx: -1,
       matchIdx: -1
     };
     this.offset = { // Where the find algorithm currently is in the document.
       pageIdx: null,
       matchIdx: null
     };
@@ -1063,17 +1137,18 @@ var PDFFindController = (function PDFFin
 
     calcFindMatch: function PDFFindController_calcFindMatch(pageIndex) {
       var pageContent = this.normalize(this.pageContents[pageIndex]);
       var query = this.normalize(this.state.query);
       var caseSensitive = this.state.caseSensitive;
       var queryLen = query.length;
 
       if (queryLen === 0) {
-        return; // Do nothing: the matches should be wiped out already.
+        // Do nothing: the matches should be wiped out already.
+        return;
       }
 
       if (!caseSensitive) {
         pageContent = pageContent.toLowerCase();
         query = query.toLowerCase();
       }
 
       var matches = [];
@@ -1086,16 +1161,22 @@ var PDFFindController = (function PDFFin
         matches.push(matchIdx);
       }
       this.pageMatches[pageIndex] = matches;
       this.updatePage(pageIndex);
       if (this.resumePageIdx === pageIndex) {
         this.resumePageIdx = null;
         this.nextPageMatch();
       }
+
+      // Update the matches count
+      if (matches.length > 0) {
+        this.matchCount += matches.length;
+        this.updateUIResultsCount();
+      }
     },
 
     extractText: function PDFFindController_extractText() {
       if (this.startedTextExtraction) {
         return;
       }
       this.startedTextExtraction = true;
 
@@ -1177,16 +1258,17 @@ var PDFFindController = (function PDFFin
         // Need to recalculate the matches, reset everything.
         this.dirtyMatch = false;
         this.selected.pageIdx = this.selected.matchIdx = -1;
         this.offset.pageIdx = currentPageIndex;
         this.offset.matchIdx = null;
         this.hadMatch = false;
         this.resumePageIdx = null;
         this.pageMatches = [];
+        this.matchCount = 0;
         var self = this;
 
         for (var i = 0; i < numPages; i++) {
           // Wipe out any previous highlighted matches.
           this.updatePage(i);
 
           // As soon as the text is extracted start finding the matches.
           if (!(i in this.pendingFindMatches)) {
@@ -1333,27 +1415,36 @@ var PDFFindController = (function PDFFin
       }
 
       this.updateUIState(state, this.state.findPrevious);
       if (this.selected.pageIdx !== -1) {
         this.updatePage(this.selected.pageIdx);
       }
     },
 
+    updateUIResultsCount:
+        function PDFFindController_updateUIResultsCount() {
+      if (this.findBar === null) {
+        throw new Error('PDFFindController is not initialized with a ' +
+          'PDFFindBar instance.');
+      }
+      this.findBar.updateResultsCount(this.matchCount);
+    },
+
     updateUIState: function PDFFindController_updateUIState(state, previous) {
       if (this.integratedFind) {
         FirefoxCom.request('updateFindControlState',
                            { result: state, findPrevious: previous });
         return;
       }
       if (this.findBar === null) {
         throw new Error('PDFFindController is not initialized with a ' +
                         'PDFFindBar instance.');
       }
-      this.findBar.updateUIState(state, previous);
+      this.findBar.updateUIState(state, previous, this.matchCount);
     }
   };
   return PDFFindController;
 })();
 
 
 /**
  * Performs navigation functions inside PDF, such as opening specified page,
@@ -3700,17 +3791,17 @@ var PDFPageView = (function PDFPageViewC
         div.appendChild(canvasWrapper);
       }
       this.canvas = canvas;
 
       var ctx = canvas.getContext('2d');
       var outputScale = getOutputScale(ctx);
 
       if (PDFJS.useOnlyCssZoom) {
-        var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
+        var actualSizeViewport = viewport.clone({scale: CSS_UNITS});
         // Use a scale that will make the canvas be the original intended size
         // of the page.
         outputScale.sx *= actualSizeViewport.width / viewport.width;
         outputScale.sy *= actualSizeViewport.height / viewport.height;
         outputScale.scaled = true;
       }
 
       if (PDFJS.maxCanvasPixels > 0) {
@@ -3721,20 +3812,22 @@ var PDFPageView = (function PDFPageViewC
           outputScale.sy = maxScale;
           outputScale.scaled = true;
           this.hasRestrictedScaling = true;
         } else {
           this.hasRestrictedScaling = false;
         }
       }
 
-      canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
-      canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
-      canvas.style.width = Math.floor(viewport.width) + 'px';
-      canvas.style.height = Math.floor(viewport.height) + 'px';
+      var sfx = approximateFraction(outputScale.sx);
+      var sfy = approximateFraction(outputScale.sy);
+      canvas.width = roundToDivide(viewport.width * outputScale.sx, sfx[0]);
+      canvas.height = roundToDivide(viewport.height * outputScale.sy, sfy[0]);
+      canvas.style.width = roundToDivide(viewport.width, sfx[1]) + 'px';
+      canvas.style.height = roundToDivide(viewport.height, sfy[1]) + 'px';
       // Add the viewport so it's known what it was originally drawn with.
       canvas._viewport = viewport;
 
       var textLayerDiv = null;
       var textLayer = null;
       if (this.textLayerFactory) {
         textLayerDiv = document.createElement('div');
         textLayerDiv.className = 'textLayer';
@@ -3964,22 +4057,27 @@ var TextLayerBuilder = (function TextLay
     this.renderingDone = false;
     this.divContentDone = false;
     this.pageIdx = options.pageIndex;
     this.pageNumber = this.pageIdx + 1;
     this.matches = [];
     this.viewport = options.viewport;
     this.textDivs = [];
     this.findController = options.findController || null;
+    this._bindMouse();
   }
 
   TextLayerBuilder.prototype = {
     _finishRendering: function TextLayerBuilder_finishRendering() {
       this.renderingDone = true;
 
+      var endOfContent = document.createElement('div');
+      endOfContent.className = 'endOfContent';
+      this.textLayerDiv.appendChild(endOfContent);
+
       var event = document.createEvent('CustomEvent');
       event.initCustomEvent('textlayerrendered', true, true, {
         pageNumber: this.pageNumber
       });
       this.textLayerDiv.dispatchEvent(event);
     },
 
     renderLayer: function TextLayerBuilder_renderLayer() {
@@ -4305,17 +4403,40 @@ var TextLayerBuilder = (function TextLay
         return;
       }
 
       // Convert the matches on the page controller into the match format
       // used for the textLayer.
       this.matches = this.convertMatches(this.findController === null ?
         [] : (this.findController.pageMatches[this.pageIdx] || []));
       this.renderMatches(this.matches);
-    }
+    },
+
+    /**
+     * Fixes text selection: adds additional div where mouse was clicked.
+     * This reduces flickering of the content if mouse slowly dragged down/up.
+     * @private
+     */
+    _bindMouse: function TextLayerBuilder_bindMouse() {
+      var div = this.textLayerDiv;
+      div.addEventListener('mousedown', function (e) {
+        var end = div.querySelector('.endOfContent');
+        if (!end) {
+          return;
+        }
+        end.classList.add('active');
+      });
+      div.addEventListener('mouseup', function (e) {
+        var end = div.querySelector('.endOfContent');
+        if (!end) {
+          return;
+        }
+        end.classList.remove('active');
+      });
+    },
   };
   return TextLayerBuilder;
 })();
 
 /**
  * @constructor
  * @implements IPDFTextLayerFactory
  */
@@ -4925,16 +5046,20 @@ var PDFViewer = (function pdfViewer() {
     /**
      * Scrolls page into view.
      * @param {number} pageNumber
      * @param {Array} dest - (optional) original PDF destination array:
      *   <page-ref> </XYZ|FitXXX> <args..>
      */
     scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
                                                               dest) {
+      if (!this.pdfDocument) {
+        return;
+      }
+      
       var pageView = this._pages[pageNumber - 1];
 
       if (this.isInPresentationMode) {
         if (this._currentPageNumber !== pageView.id) {
           // Avoid breaking getVisiblePages in presentation mode.
           this.currentPageNumber = pageView.id;
           return;
         }
@@ -6106,16 +6231,17 @@ var PDFViewerApplication = {
 
     this.findBar = new PDFFindBar({
       bar: document.getElementById('findbar'),
       toggleButton: document.getElementById('viewFind'),
       findField: document.getElementById('findInput'),
       highlightAllCheckbox: document.getElementById('findHighlightAll'),
       caseSensitiveCheckbox: document.getElementById('findMatchCase'),
       findMsg: document.getElementById('findMsg'),
+      findResultsCount: document.getElementById('findResultsCount'),
       findStatusIcon: document.getElementById('findStatusIcon'),
       findPreviousButton: document.getElementById('findPrevious'),
       findNextButton: document.getElementById('findNext'),
       findController: this.findController
     });
 
     this.findController.setFindBar(this.findBar);
 
@@ -6333,16 +6459,26 @@ var PDFViewerApplication = {
   },
 
   get loadingBar() {
     var bar = new ProgressBar('#loadingBar', {});
 
     return PDFJS.shadow(this, 'loadingBar', bar);
   },
 
+  get supportedMouseWheelZoomModifierKeys() {
+    var support = {
+      ctrlKey: true,
+      metaKey: true,
+    };
+    support = FirefoxCom.requestSync('supportedMouseWheelZoomModifierKeys');
+
+    return PDFJS.shadow(this, 'supportedMouseWheelZoomModifierKeys', support);
+  },
+
   initPassiveLoading: function pdfViewInitPassiveLoading() {
     function FirefoxComDataRangeTransport(length, initialData) {
       PDFJS.PDFDataRangeTransport.call(this, length, initialData);
     }
     FirefoxComDataRangeTransport.prototype =
       Object.create(PDFJS.PDFDataRangeTransport.prototype);
     FirefoxComDataRangeTransport.prototype.requestDataRange =
         function FirefoxComDataRangeTransport_requestDataRange(begin, end) {
@@ -7619,16 +7755,21 @@ function handleMouseWheel(evt) {
   var direction = (ticks < 0) ? 'zoomOut' : 'zoomIn';
 
   var pdfViewer = PDFViewerApplication.pdfViewer;
   if (pdfViewer.isInPresentationMode) {
     evt.preventDefault();
     PDFViewerApplication.scrollPresentationMode(ticks *
                                                 MOUSE_WHEEL_DELTA_FACTOR);
   } else if (evt.ctrlKey || evt.metaKey) {
+    var support = PDFViewerApplication.supportedMouseWheelZoomModifierKeys;
+    if ((evt.ctrlKey && !support.ctrlKey) ||
+        (evt.metaKey && !support.metaKey)) {
+      return;
+    }
     // Only zoom the pages, not the entire viewer.
     evt.preventDefault();
 
     var previousScale = pdfViewer.currentScale;
 
     PDFViewerApplication[direction](Math.abs(ticks));
 
     var currentScale = pdfViewer.currentScale;