Bug 1205051 - Update pdf.js to version 1.1.469. r=bdahl
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 15 Sep 2015 17:03:08 -0400
changeset 295239 4121aaa473b8a26dba48798042c51904db1f82f7
parent 295176 a88abc5e76ed7fc53b826531232df9ae3b939a58
child 295240 e8029818643a8a419b392fba4a4a5a3d4efc7f4e
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbdahl
bugs1205051
milestone43.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 1205051 - Update pdf.js to version 1.1.469. r=bdahl
browser/extensions/pdfjs/README.mozilla
browser/extensions/pdfjs/content/build/pdf.js
browser/extensions/pdfjs/content/build/pdf.worker.js
browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl.png
browser/extensions/pdfjs/content/web/images/treeitem-collapsed-rtl@2x.png
browser/extensions/pdfjs/content/web/images/treeitem-collapsed.png
browser/extensions/pdfjs/content/web/images/treeitem-collapsed@2x.png
browser/extensions/pdfjs/content/web/images/treeitem-expanded.png
browser/extensions/pdfjs/content/web/images/treeitem-expanded@2x.png
browser/extensions/pdfjs/content/web/viewer.css
browser/extensions/pdfjs/content/web/viewer.js
browser/locales/en-US/pdfviewer/viewer.properties
--- 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.403
+Current extension version is: 1.1.469
--- 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.403';
-PDFJS.build = '88e0326';
+PDFJS.version = '1.1.469';
+PDFJS.build = 'f06aa6a';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 
 
 var globalScope = (typeof window === 'undefined') ? this : window;
@@ -3948,16 +3948,19 @@ var CanvasGraphics = (function CanvasGra
       if (this.stateStack.length !== 0) {
         if (this.current.activeSMask !== null) {
           this.endSMaskGroup();
         }
 
         this.current = this.stateStack.pop();
         this.ctx.restore();
 
+        // Ensure that the clipping path is reset (fixes issue6413.pdf).
+        this.pendingClip = null;
+
         this.cachedGetSinglePixelWidth = null;
       }
     },
     transform: function CanvasGraphics_transform(a, b, c, d, e, f) {
       this.ctx.transform(a, b, c, d, e, f);
 
       this.cachedGetSinglePixelWidth = null;
     },
@@ -4335,16 +4338,17 @@ var CanvasGraphics = (function CanvasGra
       var ctx = this.ctx;
       var fontSizeScale = current.fontSizeScale;
       var charSpacing = current.charSpacing;
       var wordSpacing = current.wordSpacing;
       var fontDirection = current.fontDirection;
       var textHScale = current.textHScale * fontDirection;
       var glyphsLength = glyphs.length;
       var vertical = font.vertical;
+      var spacingDir = vertical ? 1 : -1;
       var defaultVMetrics = font.defaultVMetrics;
       var widthAdvanceScale = fontSize * current.fontMatrix[0];
 
       var simpleFillText =
         current.textRenderingMode === TextRenderingMode.FILL &&
         !font.disableFontFace;
 
       ctx.save();
@@ -4381,17 +4385,17 @@ var CanvasGraphics = (function CanvasGra
       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)) {
-          x += -glyph * fontSize * 0.001;
+          x += spacingDir * glyph * fontSize / 1000;
           continue;
         }
 
         var restoreNeeded = false;
         var character = glyph.fontChar;
         var accent = glyph.accent;
         var scaledX, scaledY, scaledAccentX, scaledAccentY;
         var width = glyph.width;
@@ -4451,24 +4455,25 @@ var CanvasGraphics = (function CanvasGra
 
     showType3Text: function CanvasGraphics_showType3Text(glyphs) {
       // Type3 fonts - each glyph is a "mini-PDF"
       var ctx = this.ctx;
       var current = this.current;
       var font = current.font;
       var fontSize = current.fontSize;
       var fontDirection = current.fontDirection;
+      var spacingDir = font.vertical ? 1 : -1;
       var charSpacing = current.charSpacing;
       var wordSpacing = current.wordSpacing;
       var textHScale = current.textHScale * fontDirection;
       var fontMatrix = current.fontMatrix || FONT_IDENTITY_MATRIX;
       var glyphsLength = glyphs.length;
       var isTextInvisible =
         current.textRenderingMode === TextRenderingMode.INVISIBLE;
-      var i, glyph, width;
+      var i, glyph, width, spacingLength;
 
       if (isTextInvisible || fontSize === 0) {
         return;
       }
       this.cachedGetSinglePixelWidth = null;
 
       ctx.save();
       ctx.transform.apply(ctx, current.textMatrix);
@@ -4479,17 +4484,17 @@ var CanvasGraphics = (function CanvasGra
       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)) {
-          var spacingLength = -glyph * 0.001 * fontSize;
+          spacingLength = spacingDir * glyph * fontSize / 1000;
           this.ctx.translate(spacingLength, 0);
           current.x += spacingLength * textHScale;
           continue;
         }
 
         var operatorList = font.charProcOperatorList[glyph.operatorListId];
         if (!operatorList) {
           warn('Type3 character \"' + glyph.operatorListId +
--- 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.403';
-PDFJS.build = '88e0326';
+PDFJS.version = '1.1.469';
+PDFJS.build = 'f06aa6a';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 
 
 var globalScope = (typeof window === 'undefined') ? this : window;
@@ -2501,22 +2501,21 @@ var PDFDocument = (function PDFDocumentC
               info('Bad value in document info for "' + key + '"');
             }
           }
         }
       }
       return shadow(this, 'documentInfo', docInfo);
     },
     get fingerprint() {
-      var xref = this.xref, idArray, hash, fileID = '';
-
-      if (xref.trailer.has('ID')) {
-        idArray = xref.trailer.get('ID');
-      }
-      if (idArray && isArray(idArray) && idArray[0] !== EMPTY_FINGERPRINT) {
+      var xref = this.xref, hash, fileID = '';
+      var idArray = xref.trailer.get('ID');
+
+      if (idArray && isArray(idArray) && idArray[0] && isString(idArray[0]) &&
+          idArray[0] !== EMPTY_FINGERPRINT) {
         hash = stringToBytes(idArray[0]);
       } else {
         if (this.stream.ensureRange) {
           this.stream.ensureRange(0,
             Math.min(FINGERPRINT_FIRST_BYTES, this.stream.end));
         }
         hash = calculateMD5(this.stream.bytes.subarray(0,
           FINGERPRINT_FIRST_BYTES), 0, FINGERPRINT_FIRST_BYTES);
@@ -10897,21 +10896,22 @@ var PartialEvaluator = (function Partial
               continue;
             case OPS.showText:
               args[0] = self.handleText(args[0], stateManager.state);
               break;
             case OPS.showSpacedText:
               var arr = args[0];
               var combinedGlyphs = [];
               var arrLength = arr.length;
+              var state = stateManager.state;
               for (i = 0; i < arrLength; ++i) {
                 var arrItem = arr[i];
                 if (isString(arrItem)) {
                   Array.prototype.push.apply(combinedGlyphs,
-                    self.handleText(arrItem, stateManager.state));
+                    self.handleText(arrItem, state));
                 } else if (isNum(arrItem)) {
                   combinedGlyphs.push(arrItem);
                 }
               }
               args[0] = combinedGlyphs;
               fn = OPS.showText;
               break;
             case OPS.nextLineShowText:
@@ -11297,27 +11297,36 @@ var PartialEvaluator = (function Partial
             case OPS.showSpacedText:
               var items = args[0];
               var textChunk = newTextChunk();
               var offset;
               for (var j = 0, jj = items.length; j < jj; j++) {
                 if (typeof items[j] === 'string') {
                   buildTextGeometry(items[j], textChunk);
                 } else {
-                  var val = items[j] / 1000;
-                  if (!textState.font.vertical) {
-                    offset = -val * textState.fontSize * textState.textHScale *
-                      textState.textMatrix[0];
+                  // 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;
+                  if (textState.font.vertical) {
+                    offset = val * textState.textMatrix[3];
+                    textState.translateTextMatrix(0, offset);
+                    // 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);
-                    textChunk.width += offset;
-                  } else {
-                    offset = -val * textState.fontSize *
-                      textState.textMatrix[3];
-                    textState.translateTextMatrix(0, offset);
-                    textChunk.height += offset;
+                    // Value needs to be subtracted from width to paint left.
+                    textChunk.width -= offset;
                   }
                   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(' ');
                       }
@@ -17031,17 +17040,17 @@ var Font = (function FontClosure() {
           rangeShift: ttf.getUint16()
         };
       }
 
       /**
        * Read the appropriate subtable from the cmap according to 9.6.6.4 from
        * PDF spec
        */
-      function readCmapTable(cmap, font, isSymbolicFont) {
+      function readCmapTable(cmap, font, isSymbolicFont, hasEncoding) {
         var segment;
         var start = (font.start ? font.start : 0) + cmap.offset;
         font.pos = start;
 
         var version = font.getUint16();
         var numTables = font.getUint16();
 
         var potentialTable;
@@ -17062,17 +17071,17 @@ var Font = (function FontClosure() {
             useTable = true;
             // Continue the loop since there still may be a higher priority
             // table.
           } else if (platformId === 1 && encodingId === 0) {
             useTable = true;
             // Continue the loop since there still may be a higher priority
             // table.
           } else if (platformId === 3 && encodingId === 1 &&
-                     (!isSymbolicFont || !potentialTable)) {
+                     ((!isSymbolicFont && hasEncoding) || !potentialTable)) {
             useTable = true;
             if (!isSymbolicFont) {
               canBreak = true;
             }
           } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
             useTable = true;
             canBreak = true;
           }
@@ -17199,17 +17208,23 @@ var Font = (function FontClosure() {
             var charCode = firstCode + j;
 
             mappings.push({
               charCode: charCode,
               glyphId: glyphId
             });
           }
         } else {
-          error('cmap table has unsupported format: ' + format);
+          warn('cmap table has unsupported format: ' + format);
+          return {
+            platformId: -1,
+            encodingId: -1,
+            mappings: [],
+            hasShortCmap: false
+          };
         }
 
         // removing duplicate entries
         mappings.sort(function (a, b) {
           return a.charCode - b.charCode;
         });
         for (i = 1; i < mappings.length; i++) {
           if (mappings[i - 1].charCode === mappings[i].charCode) {
@@ -18005,23 +18020,24 @@ var Font = (function FontClosure() {
           }
         });
         if (dupFirstEntry) {
           charCodeToGlyphId[0] = numGlyphs - 1;
         }
       } else {
         // Most of the following logic in this code branch is based on the
         // 9.6.6.4 of the PDF spec.
-        var cmapTable = readCmapTable(tables.cmap, font, this.isSymbolicFont);
+        var hasEncoding =
+          properties.differences.length > 0 || !!properties.baseEncodingName;
+        var cmapTable =
+          readCmapTable(tables.cmap, font, this.isSymbolicFont, hasEncoding);
         var cmapPlatformId = cmapTable.platformId;
         var cmapEncodingId = cmapTable.encodingId;
         var cmapMappings = cmapTable.mappings;
         var cmapMappingsLength = cmapMappings.length;
-        var hasEncoding = properties.differences.length ||
-                          !!properties.baseEncodingName;
 
         // The spec seems to imply that if the font is symbolic the encoding
         // should be ignored, this doesn't appear to work for 'preistabelle.pdf'
         // where the the font is symbolic and it has an encoding.
         if (hasEncoding &&
             (cmapPlatformId === 3 && cmapEncodingId === 1 ||
              cmapPlatformId === 1 && cmapEncodingId === 0) ||
             (cmapPlatformId === -1 && cmapEncodingId === -1 && // Temporary hack
@@ -29889,16 +29905,19 @@ var Parser = (function ParserClosure() {
         this.buf2 = this.lexer.getObj();
       }
     },
     tryShift: function Parser_tryShift() {
       try {
         this.shift();
         return true;
       } catch (e) {
+        if (e instanceof MissingDataException) {
+          throw e;
+        }
         // Upon failure, the caller should reset this.lexer.pos to a known good
         // state and call this.shift() twice to reset the buffers.
         return false;
       }
     },
     getObj: function Parser_getObj(cipherTransform) {
       var buf1 = this.buf1;
       this.shift();
@@ -30356,16 +30375,17 @@ var Parser = (function ParserClosure() {
           // after the first stream the length variable is invalid
           maybeLength = null;
         }
       }
       return stream;
     },
     makeFilter: function Parser_makeFilter(stream, name, maybeLength, params) {
       if (stream.dict.get('Length') === 0 && !maybeLength) {
+        warn('Empty "' + name + '" stream.');
         return new NullStream(stream);
       }
       try {
         if (params) {
           params = this.fetchIfRef(params);
         }
         var xrefStreamStats = this.xref.stats.streamTypes;
         if (name === 'FlateDecode' || name === 'Fl') {
@@ -38857,24 +38877,23 @@ var bidi = PDFJS.bidi = (function bidiCl
      L4. A character that possesses the mirrored property as specified by
      Section 4.7, Mirrored, must be depicted by a mirrored glyph if the resolved
      directionality of that character is R.
      */
 
     // don't mirror as characters are already mirrored in the pdf
 
     // Finally, return string
-    var result = '';
     for (i = 0, ii = chars.length; i < ii; ++i) {
       var ch = chars[i];
-      if (ch !== '<' && ch !== '>') {
-        result += ch;
-      }
-    }
-    return createBidiText(result, isLTR);
+      if (ch === '<' || ch === '>') {
+        chars[i] = '';
+      }
+    }
+    return createBidiText(chars.join(''), isLTR);
   }
 
   return bidi;
 })();
 
 
 var MurmurHash3_64 = (function MurmurHash3_64Closure (seed) {
   // Workaround for missing math precison in JS.
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1c8b9f7010cae8edd6ca3a1146c6f07f68f5931e
GIT binary patch
literal 183
zc%17D@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#SkfJR9T^xl_H+M9WCik>lDyqr
z82-2SpV<%Ov6p!Iy0YKrWaF2XJ+}I0Cs4@O)5S4_<9c#}1C#!gI3K5HU3*qk7JfP^
zDKY)8>!ogI{c{Zp9jCt>`hHZ5LtHJS<$+UcLsOsrwZ)u_tda3Y_T1=KVi6Oo2<%Hy
cUbUNH=6`p$$8L6yfCe*oy85}Sb4q9e0CZwJr2qf`
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..84279368d985d74ba324ea3433b08ea5974bcc62
GIT binary patch
literal 205
zc%17D@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mSkfJR9T^xl_H+M9WCik>lDyqr
z82-2SpV<%Ov6p!Iy0YKrWaF2XJ+}I0Cr~Ka)5S4F<9u?$0p>W5{V$RatZw-0daHY}
z{<#OiNxzd0cN;5o92e?flIcpA_8_oj153M7i1!AKLt9v!1<kw-L~riB;SnGhc+FAt
z)SegWD>$YfeZs0{*8S1d;7*d)WUfO=0=x|MW<S1$ebZG3TF2n&>gTe~DWM4fAaX}J
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..06d4d3769672390920ddd9d423f52d7908fcfb36
GIT binary patch
literal 128
zc%17D@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#+&x_!LpZJ{CnQuDzHAj@)~YCc
zcJ!pUslH1<tHXy2-AlyZtZ-7;@wD|lw_1pzfR2-B)c-?*k&pk08|p_)%+;w^Y{*fb
awVT0yk-J;*^C}~tkqn-$elF{r5}E)McPU5!
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..eec1e58c125c80af090ae0845e81191d86740141
GIT binary patch
literal 149
zc%17D@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mVmw_OLn;`P85+fF^_NI;G>WIk
z$<$8#)-<<4$>7e1Lt^WdBv>MS0tC+_?dkdu(KF##vY``K|4|Ok95LarEgxHDk6!z_
xe}ZGN#WGb9Hnl@SJ`EdME-hi&#=_6Qka7RV*Qd`VTY>g4c)I$ztaD0e0sz~DGV1^U
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c8d557351cd2fc80c3abd4b19e52924cd74780d5
GIT binary patch
literal 125
zc%17D@N?(olHy`uVBq!ia0vp^oFL4>0wld=oSO}#Ts&PILn;`P75MhNs4Zk;-DbE)
zpt_L5GvPZo<AhxXDvIVEVavonFJ!9a_#6^Hhk@%&y<%66r&+)W`Kxi;#6|Tb6muAM
XHQ9^&G`nvKG>^g4)z4*}Q$iB}$ABb+
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3b3b6103b356200d05caf7fa692cadf2c1e89043
GIT binary patch
literal 172
zc%17D@N?(olHy`uVBq!ia0vp^LLkh+0wn(&ce?|mialK%Ln;`P7Z_I<K0GSPRKRqY
zd!_jC51NdpyqeE=9bc`%o1}QV+jqwlhSVDg?=~G?d1Bgz&*}?)1&Uq%=^&{2Wx<I9
z&r(WU-*7xvp3}4P45RBr-iH_7AC`43Qh1(hCAs@^@JDy^<K1WV&5|r6FaMn3$Hu^M
W{3oxYXvGDf^$eb_elF{r5}E*hVME3M
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -1230,29 +1230,33 @@ a:focus > .thumbnail > .thumbnailSelecti
 
 #outlineView {
   padding: 4px 4px 0;
 }
 #attachmentsView {
   padding: 3px 4px 0;
 }
 
+html[dir='ltr'] .outlineWithDeepNesting > .outlineItem,
 html[dir='ltr'] .outlineItem > .outlineItems {
   margin-left: 20px;
 }
 
+html[dir='rtl'] .outlineWithDeepNesting > .outlineItem,
 html[dir='rtl'] .outlineItem > .outlineItems {
   margin-right: 20px;
 }
 
 .outlineItem > a,
 .attachmentsItem > button {
   text-decoration: none;
   display: inline-block;
   min-width: 95%;
+  min-width: calc(100% - 4px); /* Subtract the right padding (left, in RTL mode)
+                                  of the container. */
   height: auto;
   margin-bottom: 1px;
   border-radius: 2px;
   color: hsla(0,0%,100%,.8);
   font-size: 13px;
   line-height: 15px;
   -moz-user-select: none;
   white-space: normal;
@@ -1261,39 +1265,76 @@ html[dir='rtl'] .outlineItem > .outlineI
 .attachmentsItem > button {
   border: 0 none;
   background: none;
   cursor: pointer;
   width: 100%;
 }
 
 html[dir='ltr'] .outlineItem > a {
-  padding: 2px 0 5px 10px;
+  padding: 2px 0 5px 4px;
 }
 html[dir='ltr'] .attachmentsItem > button {
   padding: 2px 0 3px 7px;
   text-align: left;
 }
 
 html[dir='rtl'] .outlineItem > a {
-  padding: 2px 10px 5px 0;
+  padding: 2px 4px 5px 0;
 }
 html[dir='rtl'] .attachmentsItem > button {
   padding: 2px 7px 3px 0;
   text-align: right;
 }
 
+.outlineItemToggler {
+  position: relative;
+  height: 0;
+  width: 0;
+  color: hsla(0,0%,100%,.5);
+}
+.outlineItemToggler::before {
+  content: url(images/treeitem-expanded.png);
+  display: inline-block;
+  position: absolute;
+}
+html[dir='ltr'] .outlineItemToggler.outlineItemsHidden::before {
+  content: url(images/treeitem-collapsed.png);
+}
+html[dir='rtl'] .outlineItemToggler.outlineItemsHidden::before {
+  content: url(images/treeitem-collapsed-rtl.png);
+}
+.outlineItemToggler.outlineItemsHidden ~ .outlineItems {
+  display: none;
+}
+html[dir='ltr'] .outlineItemToggler {
+  float: left;
+}
+html[dir='rtl'] .outlineItemToggler {
+  float: right;
+}
+html[dir='ltr'] .outlineItemToggler::before {
+  right: 4px;
+}
+html[dir='rtl'] .outlineItemToggler::before {
+  left: 4px;
+}
+
+.outlineItemToggler:hover,
+.outlineItemToggler:hover + a,
+.outlineItemToggler:hover ~ .outlineItems,
 .outlineItem > a:hover,
 .attachmentsItem > button:hover {
   background-color: hsla(0,0%,100%,.02);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.2) inset,
               0 0 1px hsla(0,0%,0%,.2);
+  border-radius: 2px;
   color: hsla(0,0%,100%,.9);
 }
 
 .outlineItem.selected {
   background-color: hsla(0,0%,100%,.08);
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
@@ -1699,16 +1740,34 @@ html[dir='rtl'] #documentPropertiesOverl
 
   .secondaryToolbarButton.handTool::before {
     content: url(images/secondaryToolbarButton-handTool@2x.png);
   }
 
   .secondaryToolbarButton.documentProperties::before {
     content: url(images/secondaryToolbarButton-documentProperties@2x.png);
   }
+
+  .outlineItemToggler::before {
+    transform: scale(0.5);
+    top: -1px;
+    content: url(images/treeitem-expanded@2x.png);
+  }
+  html[dir='ltr'] .outlineItemToggler.outlineItemsHidden::before {
+    content: url(images/treeitem-collapsed@2x.png);
+  }
+  html[dir='rtl'] .outlineItemToggler.outlineItemsHidden::before {
+    content: url(images/treeitem-collapsed-rtl@2x.png);
+  }
+  html[dir='ltr'] .outlineItemToggler::before {
+    right: 0;
+  }
+  html[dir='rtl'] .outlineItemToggler::before {
+    left: 0;
+  }
 }
 
 @media print {
   /* General rules for printing. */
   body {
     background: transparent none;
   }
 
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -99,16 +99,22 @@ var CustomStyle = (function CustomStyleC
     if (prop !== 'undefined') {
       element.style[prop] = str;
     }
   };
 
   return CustomStyle;
 })();
 
+var NullCharactersRegExp = /\x00/g;
+
+function removeNullCharacters(str) {
+  return str.replace(NullCharactersRegExp, '');
+}
+
 function getFileName(url) {
   var anchor = url.indexOf('#');
   var query = url.indexOf('?');
   var end = Math.min(
     anchor > 0 ? anchor : url.length,
     query > 0 ? query : url.length);
   return url.substring(url.lastIndexOf('/', end) + 1, end);
 }
@@ -5368,16 +5374,20 @@ var PDFThumbnailView = (function PDFThum
 
       if (this.canvas) {
         // Zeroing the width and height causes Firefox to release graphics
         // resources immediately, which can greatly reduce memory consumption.
         this.canvas.width = 0;
         this.canvas.height = 0;
         delete this.canvas;
       }
+      if (this.image) {
+        this.image.removeAttribute('src');
+        delete this.image;
+      }
     },
 
     update: function PDFThumbnailView_update(rotation) {
       if (typeof rotation !== 'undefined') {
         this.rotation = rotation;
       }
       var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
       this.viewport = this.viewport.clone({
@@ -5388,38 +5398,63 @@ var PDFThumbnailView = (function PDFThum
     },
 
     /**
      * @private
      */
     _getPageDrawContext:
         function PDFThumbnailView_getPageDrawContext(noCtxScale) {
       var canvas = document.createElement('canvas');
-      canvas.id = this.renderingId;
-
-      canvas.className = 'thumbnailImage';
-      canvas.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
-        {page: this.id}, 'Thumbnail of Page {{page}}'));
-
       this.canvas = canvas;
-      this.div.setAttribute('data-loaded', true);
-      this.ring.appendChild(canvas);
 
       var ctx = canvas.getContext('2d');
       var outputScale = getOutputScale(ctx);
+
       canvas.width = (this.canvasWidth * outputScale.sx) | 0;
       canvas.height = (this.canvasHeight * outputScale.sy) | 0;
       canvas.style.width = this.canvasWidth + 'px';
       canvas.style.height = this.canvasHeight + 'px';
+
       if (!noCtxScale && outputScale.scaled) {
         ctx.scale(outputScale.sx, outputScale.sy);
       }
+
+      var image = document.createElement('img');
+      this.image = image;
+
+      image.id = this.renderingId;
+      image.className = 'thumbnailImage';
+      image.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
+        { page: this.id }, 'Thumbnail of Page {{page}}'));
+
+      image.style.width = canvas.style.width;
+      image.style.height = canvas.style.height;
+
       return ctx;
     },
 
+    /**
+     * @private
+     */
+    _convertCanvasToImage: function PDFThumbnailView_convertCanvasToImage() {
+      if (!this.canvas) {
+        return;
+      }
+      this.image.src = this.canvas.toDataURL();
+
+      this.div.setAttribute('data-loaded', true);
+      this.ring.appendChild(this.image);
+
+      // Zeroing the width and height causes Firefox to release graphics
+      // resources immediately, which can greatly reduce memory consumption.
+      this.canvas.width = 0;
+      this.canvas.height = 0;
+      delete this.canvas;
+    },
+
     draw: function PDFThumbnailView_draw() {
       if (this.renderingState !== RenderingStates.INITIAL) {
         console.error('Must be in new state before drawing');
       }
       if (this.hasImage) {
         return Promise.resolve(undefined);
       }
       this.hasImage = true;
@@ -5439,16 +5474,17 @@ var PDFThumbnailView = (function PDFThum
         if (renderTask === self.renderTask) {
           self.renderTask = null;
         }
         if (error === 'cancelled') {
           rejectRenderPromise(error);
           return;
         }
         self.renderingState = RenderingStates.FINISHED;
+        self._convertCanvasToImage();
 
         if (!error) {
           resolveRenderPromise(undefined);
         } else {
           rejectRenderPromise(error);
         }
       }
 
@@ -5496,16 +5532,17 @@ var PDFThumbnailView = (function PDFThum
       this.renderingState = RenderingStates.FINISHED;
 
       var ctx = this._getPageDrawContext(true);
       var canvas = ctx.canvas;
 
       if (img.width <= 2 * canvas.width) {
         ctx.drawImage(img, 0, 0, img.width, img.height,
                       0, 0, canvas.width, canvas.height);
+        this._convertCanvasToImage();
         return;
       }
       // drawImage does an awful job of rescaling the image, doing it gradually.
       var MAX_NUM_SCALING_STEPS = 3;
       var reducedWidth = canvas.width << MAX_NUM_SCALING_STEPS;
       var reducedHeight = canvas.height << MAX_NUM_SCALING_STEPS;
       var reducedImage = getTempCanvas(reducedWidth, reducedHeight);
       var reducedImageCtx = reducedImage.getContext('2d');
@@ -5520,16 +5557,17 @@ var PDFThumbnailView = (function PDFThum
         reducedImageCtx.drawImage(reducedImage,
                                   0, 0, reducedWidth, reducedHeight,
                                   0, 0, reducedWidth >> 1, reducedHeight >> 1);
         reducedWidth >>= 1;
         reducedHeight >>= 1;
       }
       ctx.drawImage(reducedImage, 0, 0, reducedWidth, reducedHeight,
                     0, 0, canvas.width, canvas.height);
+      this._convertCanvasToImage();
     }
   };
 
   return PDFThumbnailView;
 })();
 
 PDFThumbnailView.tempImageCache = null;
 
@@ -5731,24 +5769,26 @@ var PDFOutlineView = (function PDFOutlin
   /**
    * @constructs PDFOutlineView
    * @param {PDFOutlineViewOptions} options
    */
   function PDFOutlineView(options) {
     this.container = options.container;
     this.outline = options.outline;
     this.linkService = options.linkService;
+    this.lastToggleIsShow = true;
   }
 
   PDFOutlineView.prototype = {
     reset: function PDFOutlineView_reset() {
       var container = this.container;
       while (container.firstChild) {
         container.removeChild(container.firstChild);
       }
+      this.lastToggleIsShow = true;
     },
 
     /**
      * @private
      */
     _dispatchEvent: function PDFOutlineView_dispatchEvent(outlineCount) {
       var event = document.createEvent('CustomEvent');
       event.initCustomEvent('outlineloaded', true, true, {
@@ -5764,50 +5804,105 @@ var PDFOutlineView = (function PDFOutlin
       var linkService = this.linkService;
       element.href = linkService.getDestinationHash(item.dest);
       element.onclick = function goToDestination(e) {
         linkService.navigateTo(item.dest);
         return false;
       };
     },
 
+    /**
+     * Prepend a button before an outline item which allows the user to toggle
+     * the visibility of all outline items at that level.
+     *
+     * @private
+     */
+    _addToggleButton: function PDFOutlineView_addToggleButton(div) {
+      var toggler = document.createElement('div');
+      toggler.className = 'outlineItemToggler';
+      toggler.onclick = function(event) {
+        event.stopPropagation();
+        toggler.classList.toggle('outlineItemsHidden');
+
+        if (event.shiftKey) {
+          var shouldShowAll = !toggler.classList.contains('outlineItemsHidden');
+          this._toggleOutlineItem(div, shouldShowAll);
+        }
+      }.bind(this);
+      div.insertBefore(toggler, div.firstChild);
+    },
+
+    /**
+     * Toggle the visibility of the subtree of an outline item.
+     *
+     * @param {Element} root - the root of the outline (sub)tree.
+     * @param {boolean} state - whether to show the outline (sub)tree. If false,
+     *   the outline subtree rooted at |root| will be collapsed.
+     *
+     * @private
+     */
+    _toggleOutlineItem: function PDFOutlineView_toggleOutlineItem(root, show) {
+      this.lastToggleIsShow = show;
+      var togglers = root.querySelectorAll('.outlineItemToggler');
+      for (var i = 0, ii = togglers.length; i < ii; ++i) {
+        togglers[i].classList[show ? 'remove' : 'add']('outlineItemsHidden');
+      }
+    },
+
+    /**
+     * Collapse or expand all subtrees of the outline.
+     */
+    toggleOutlineTree: function PDFOutlineView_toggleOutlineTree() {
+      this._toggleOutlineItem(this.container, !this.lastToggleIsShow);
+    },
+
     render: function PDFOutlineView_render() {
       var outline = this.outline;
       var outlineCount = 0;
 
       this.reset();
 
       if (!outline) {
         this._dispatchEvent(outlineCount);
         return;
       }
 
-      var queue = [{ parent: this.container, items: this.outline }];
+      var fragment = document.createDocumentFragment();
+      var queue = [{ parent: fragment, items: this.outline }];
+      var hasAnyNesting = false;
       while (queue.length > 0) {
         var levelData = queue.shift();
         for (var i = 0, len = levelData.items.length; i < len; i++) {
           var item = levelData.items[i];
           var div = document.createElement('div');
           div.className = 'outlineItem';
           var element = document.createElement('a');
           this._bindLink(element, item);
-          element.textContent = item.title;
+          element.textContent = removeNullCharacters(item.title);
           div.appendChild(element);
 
           if (item.items.length > 0) {
+            hasAnyNesting = true;
+            this._addToggleButton(div);
+
             var itemsDiv = document.createElement('div');
             itemsDiv.className = 'outlineItems';
             div.appendChild(itemsDiv);
             queue.push({ parent: itemsDiv, items: item.items });
           }
 
           levelData.parent.appendChild(div);
           outlineCount++;
         }
       }
+      if (hasAnyNesting) {
+        this.container.classList.add('outlineWithDeepNesting');
+      }
+
+      this.container.appendChild(fragment);
 
       this._dispatchEvent(outlineCount);
     }
   };
 
   return PDFOutlineView;
 })();
 
@@ -5880,17 +5975,17 @@ var PDFAttachmentView = (function PDFAtt
 
       for (var i = 0; i < attachmentsCount; i++) {
         var item = attachments[names[i]];
         var filename = getFileName(item.filename);
         var div = document.createElement('div');
         div.className = 'attachmentsItem';
         var button = document.createElement('button');
         this._bindLink(button, item.content, filename);
-        button.textContent = filename;
+        button.textContent = removeNullCharacters(filename);
         div.appendChild(button);
         this.container.appendChild(div);
       }
 
       this._dispatchEvent(attachmentsCount);
     }
   };
 
@@ -6078,16 +6173,22 @@ var PDFViewerApplication = {
         PDFJS.disableTextLayer = value;
       }),
       Preferences.get('disableRange').then(function resolved(value) {
         if (PDFJS.disableRange === true) {
           return;
         }
         PDFJS.disableRange = value;
       }),
+      Preferences.get('disableStream').then(function resolved(value) {
+        if (PDFJS.disableStream === true) {
+          return;
+        }
+        PDFJS.disableStream = value;
+      }),
       Preferences.get('disableAutoFetch').then(function resolved(value) {
         PDFJS.disableAutoFetch = value;
       }),
       Preferences.get('disableFontFace').then(function resolved(value) {
         if (PDFJS.disableFontFace === true) {
           return;
         }
         PDFJS.disableFontFace = value;
@@ -7067,16 +7168,21 @@ function webViewerInitialized() {
       PDFViewerApplication.switchSidebarView('thumbs');
     });
 
   document.getElementById('viewOutline').addEventListener('click',
     function() {
       PDFViewerApplication.switchSidebarView('outline');
     });
 
+  document.getElementById('viewOutline').addEventListener('dblclick',
+    function() {
+      PDFViewerApplication.outline.toggleOutlineTree();
+    });
+
   document.getElementById('viewAttachments').addEventListener('click',
     function() {
       PDFViewerApplication.switchSidebarView('attachments');
     });
 
   document.getElementById('previous').addEventListener('click',
     function() {
       PDFViewerApplication.page--;
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -62,24 +62,30 @@ hand_tool_enable_label=Enable hand tool
 hand_tool_disable.title=Disable hand tool
 hand_tool_disable_label=Disable hand tool
 
 # Document properties dialog box
 document_properties.title=Document Properties…
 document_properties_label=Document Properties…
 document_properties_file_name=File name:
 document_properties_file_size=File size:
+# LOCALIZATION NOTE (document_properties_kb): "{{size_kb}}" and "{{size_b}}"
+# will be replaced by the PDF file size in kilobytes, respectively in bytes.
 document_properties_kb={{size_kb}} KB ({{size_b}} bytes)
+# LOCALIZATION NOTE (document_properties_mb): "{{size_mb}}" and "{{size_b}}"
+# will be replaced by the PDF file size in megabytes, respectively in bytes.
 document_properties_mb={{size_mb}} MB ({{size_b}} bytes)
 document_properties_title=Title:
 document_properties_author=Author:
 document_properties_subject=Subject:
 document_properties_keywords=Keywords:
 document_properties_creation_date=Creation Date:
 document_properties_modification_date=Modification Date:
+# LOCALIZATION NOTE (document_properties_date_string): "{{date}}" and "{{time}}"
+# will be replaced by the creation/modification date, and time, of the PDF file.
 document_properties_date_string={{date}}, {{time}}
 document_properties_creator=Creator:
 document_properties_producer=PDF Producer:
 document_properties_version=PDF Version:
 document_properties_page_count=Page Count:
 document_properties_close=Close
 
 # Tooltips and alt text for side panel toolbar buttons