Bug 1251620 - Update pdf.js to version 1.4.95. r=bdahl, r=Mossop
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 26 Feb 2016 10:58:42 -0500
changeset 322086 0f3938e0df6405678c951e2541019354c109732b
parent 322085 9e162af63f4c9772c7a63cfd8e1d96000109aaad
child 322087 ec51ade02ea0e58031f3a051c54413e516e865f6
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbdahl, Mossop
bugs1251620
milestone47.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 1251620 - Update pdf.js to version 1.4.95. 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.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.4.83
+Current extension version is: 1.4.95
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -283,17 +283,17 @@ ChromeActions.prototype = {
       // Create a nsIInputStreamChannel so we can set the url on the channel
       // 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) {
+        if (self.contentDispositionFilename && !data.isAttachment) {
           channel.contentDispositionFilename = self.contentDispositionFilename;
         } else {
           channel.contentDispositionFilename = filename;
         }
       } catch (e) {}
       channel.setURI(originalUri);
       channel.contentStream = aInputStream;
       if ('nsIPrivateBrowsingChannel' in Ci &&
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.83';
-var pdfjsBuild = '0629fd0';
+var pdfjsVersion = '1.4.95';
+var pdfjsBuild = '2b813c0';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -402,16 +402,27 @@ var UNSUPPORTED_FEATURES = PDFJS.UNSUPPO
   unknown: 'unknown',
   forms: 'forms',
   javaScript: 'javaScript',
   smask: 'smask',
   shadingPattern: 'shadingPattern',
   font: 'font'
 };
 
+// Gets the file name from a given URL.
+function getFilenameFromUrl(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);
+}
+PDFJS.getFilenameFromUrl = getFilenameFromUrl;
+
 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
 // absolute URL, it will be returned as is.
 function combineUrl(baseUrl, url) {
   if (!url) {
     return baseUrl;
   }
   return new URL(url, baseUrl).href;
 }
@@ -1532,16 +1543,17 @@ exports.UnknownErrorException = UnknownE
 exports.Util = Util;
 exports.XRefParseException = XRefParseException;
 exports.assert = assert;
 exports.bytesToString = bytesToString;
 exports.combineUrl = combineUrl;
 exports.createPromiseCapability = createPromiseCapability;
 exports.deprecated = deprecated;
 exports.error = error;
+exports.getFilenameFromUrl = getFilenameFromUrl;
 exports.getLookupTableFactory = getLookupTableFactory;
 exports.info = info;
 exports.isArray = isArray;
 exports.isArrayBuffer = isArrayBuffer;
 exports.isBool = isBool;
 exports.isEmptyObj = isEmptyObj;
 exports.isExternalLinkTargetSet = isExternalLinkTargetSet;
 exports.isInt = isInt;
@@ -1572,26 +1584,28 @@ exports.warn = warn;
       root.pdfjsDisplayDOMUtils);
   }
 }(this, function (exports, sharedUtil, displayDOMUtils) {
 
 var AnnotationBorderStyleType = sharedUtil.AnnotationBorderStyleType;
 var AnnotationType = sharedUtil.AnnotationType;
 var Util = sharedUtil.Util;
 var addLinkAttributes = sharedUtil.addLinkAttributes;
+var getFilenameFromUrl = sharedUtil.getFilenameFromUrl;
 var warn = sharedUtil.warn;
 var CustomStyle = displayDOMUtils.CustomStyle;
 
 /**
  * @typedef {Object} AnnotationElementParameters
  * @property {Object} data
  * @property {HTMLDivElement} layer
  * @property {PDFPage} page
  * @property {PageViewport} viewport
  * @property {IPDFLinkService} linkService
+ * @property {DownloadManager} downloadManager
  */
 
 /**
  * @class
  * @alias AnnotationElementFactory
  */
 function AnnotationElementFactory() {}
 AnnotationElementFactory.prototype =
@@ -1623,16 +1637,19 @@ AnnotationElementFactory.prototype =
         return new UnderlineAnnotationElement(parameters);
 
       case AnnotationType.SQUIGGLY:
         return new SquigglyAnnotationElement(parameters);
 
       case AnnotationType.STRIKEOUT:
         return new StrikeOutAnnotationElement(parameters);
 
+      case AnnotationType.FILEATTACHMENT:
+        return new FileAttachmentAnnotationElement(parameters);
+
       default:
         return new AnnotationElement(parameters);
     }
   }
 };
 
 /**
  * @class
@@ -1641,16 +1658,17 @@ AnnotationElementFactory.prototype =
 var AnnotationElement = (function AnnotationElementClosure() {
   function AnnotationElement(parameters, isRenderable) {
     this.isRenderable = isRenderable || false;
     this.data = parameters.data;
     this.layer = parameters.layer;
     this.page = parameters.page;
     this.viewport = parameters.viewport;
     this.linkService = parameters.linkService;
+    this.downloadManager = parameters.downloadManager;
 
     if (isRenderable) {
       this.container = this._createContainer();
     }
   }
 
   AnnotationElement.prototype = /** @lends AnnotationElement.prototype */ {
     /**
@@ -1740,16 +1758,53 @@ var AnnotationElement = (function Annota
 
       container.style.width = width + 'px';
       container.style.height = height + 'px';
 
       return container;
     },
 
     /**
+     * Create a popup for the annotation's HTML element. This is used for
+     * annotations that do not have a Popup entry in the dictionary, but
+     * are of a type that works with popups (such as Highlight annotations).
+     *
+     * @private
+     * @param {HTMLSectionElement} container
+     * @param {HTMLDivElement|HTMLImageElement|null} trigger
+     * @param {Object} data
+     * @memberof AnnotationElement
+     */
+    _createPopup:
+        function AnnotationElement_createPopup(container, trigger, data) {
+      // If no trigger element is specified, create it.
+      if (!trigger) {
+        trigger = document.createElement('div');
+        trigger.style.height = container.style.height;
+        trigger.style.width = container.style.width;
+        container.appendChild(trigger);
+      }
+
+      var popupElement = new PopupElement({
+        container: container,
+        trigger: trigger,
+        color: data.color,
+        title: data.title,
+        contents: data.contents,
+        hideWrapper: true
+      });
+      var popup = popupElement.render();
+
+      // Position the popup next to the annotation's container.
+      popup.style.left = container.style.width;
+
+      container.appendChild(popup);
+    },
+
+    /**
      * Render the annotation's HTML element in the empty container.
      *
      * @public
      * @memberof AnnotationElement
      */
     render: function AnnotationElement_render() {
       throw new Error('Abstract method AnnotationElement.render called');
     }
@@ -1867,30 +1922,17 @@ var TextAnnotationElement = (function Te
       image.style.width = this.container.style.width;
       image.src = PDFJS.imageResourcesPath + 'annotation-' +
         this.data.name.toLowerCase() + '.svg';
       image.alt = '[{{type}} Annotation]';
       image.dataset.l10nId = 'text_annotation_type';
       image.dataset.l10nArgs = JSON.stringify({type: this.data.name});
 
       if (!this.data.hasPopup) {
-        var popupElement = new PopupElement({
-          container: this.container,
-          trigger: image,
-          color: this.data.color,
-          title: this.data.title,
-          contents: this.data.contents,
-          hideWrapper: true
-        });
-        var popup = popupElement.render();
-
-        // Position the popup next to the Text annotation's container.
-        popup.style.left = image.style.width;
-
-        this.container.appendChild(popup);
+        this._createPopup(this.container, image, this.data);
       }
 
       this.container.appendChild(image);
       return this.container;
     }
   });
 
   return TextAnnotationElement;
@@ -2157,117 +2199,201 @@ var PopupElement = (function PopupElemen
 
 /**
  * @class
  * @alias HighlightAnnotationElement
  */
 var HighlightAnnotationElement = (
     function HighlightAnnotationElementClosure() {
   function HighlightAnnotationElement(parameters) {
-    AnnotationElement.call(this, parameters, true);
+    var isRenderable = !!(parameters.data.hasPopup ||
+                          parameters.data.title || parameters.data.contents);
+    AnnotationElement.call(this, parameters, isRenderable);
   }
 
   Util.inherit(HighlightAnnotationElement, AnnotationElement, {
     /**
      * Render the highlight annotation's HTML element in the empty container.
      *
      * @public
      * @memberof HighlightAnnotationElement
      * @returns {HTMLSectionElement}
      */
     render: function HighlightAnnotationElement_render() {
       this.container.className = 'highlightAnnotation';
+
+      if (!this.data.hasPopup) {
+        this._createPopup(this.container, null, this.data);
+      }
+
       return this.container;
     }
   });
 
   return HighlightAnnotationElement;
 })();
 
 /**
  * @class
  * @alias UnderlineAnnotationElement
  */
 var UnderlineAnnotationElement = (
     function UnderlineAnnotationElementClosure() {
   function UnderlineAnnotationElement(parameters) {
-    AnnotationElement.call(this, parameters, true);
+    var isRenderable = !!(parameters.data.hasPopup ||
+                          parameters.data.title || parameters.data.contents);
+    AnnotationElement.call(this, parameters, isRenderable);
   }
 
   Util.inherit(UnderlineAnnotationElement, AnnotationElement, {
     /**
      * Render the underline annotation's HTML element in the empty container.
      *
      * @public
      * @memberof UnderlineAnnotationElement
      * @returns {HTMLSectionElement}
      */
     render: function UnderlineAnnotationElement_render() {
       this.container.className = 'underlineAnnotation';
+
+      if (!this.data.hasPopup) {
+        this._createPopup(this.container, null, this.data);
+      }
+
       return this.container;
     }
   });
 
   return UnderlineAnnotationElement;
 })();
 
 /**
  * @class
  * @alias SquigglyAnnotationElement
  */
 var SquigglyAnnotationElement = (function SquigglyAnnotationElementClosure() {
   function SquigglyAnnotationElement(parameters) {
-    AnnotationElement.call(this, parameters, true);
+    var isRenderable = !!(parameters.data.hasPopup ||
+                          parameters.data.title || parameters.data.contents);
+    AnnotationElement.call(this, parameters, isRenderable);
   }
 
   Util.inherit(SquigglyAnnotationElement, AnnotationElement, {
     /**
      * Render the squiggly annotation's HTML element in the empty container.
      *
      * @public
      * @memberof SquigglyAnnotationElement
      * @returns {HTMLSectionElement}
      */
     render: function SquigglyAnnotationElement_render() {
       this.container.className = 'squigglyAnnotation';
+
+      if (!this.data.hasPopup) {
+        this._createPopup(this.container, null, this.data);
+      }
+
       return this.container;
     }
   });
 
   return SquigglyAnnotationElement;
 })();
 
 /**
  * @class
  * @alias StrikeOutAnnotationElement
  */
 var StrikeOutAnnotationElement = (
     function StrikeOutAnnotationElementClosure() {
   function StrikeOutAnnotationElement(parameters) {
-    AnnotationElement.call(this, parameters, true);
+    var isRenderable = !!(parameters.data.hasPopup ||
+                          parameters.data.title || parameters.data.contents);
+    AnnotationElement.call(this, parameters, isRenderable);
   }
 
   Util.inherit(StrikeOutAnnotationElement, AnnotationElement, {
     /**
      * Render the strikeout annotation's HTML element in the empty container.
      *
      * @public
      * @memberof StrikeOutAnnotationElement
      * @returns {HTMLSectionElement}
      */
     render: function StrikeOutAnnotationElement_render() {
       this.container.className = 'strikeoutAnnotation';
+
+      if (!this.data.hasPopup) {
+        this._createPopup(this.container, null, this.data);
+      }
+
       return this.container;
     }
   });
 
   return StrikeOutAnnotationElement;
 })();
 
 /**
+ * @class
+ * @alias FileAttachmentAnnotationElement
+ */
+var FileAttachmentAnnotationElement = (
+    function FileAttachmentAnnotationElementClosure() {
+  function FileAttachmentAnnotationElement(parameters) {
+    AnnotationElement.call(this, parameters, true);
+
+    this.filename = getFilenameFromUrl(parameters.data.file.filename);
+    this.content = parameters.data.file.content;
+  }
+
+  Util.inherit(FileAttachmentAnnotationElement, AnnotationElement, {
+    /**
+     * Render the file attachment annotation's HTML element in the empty
+     * container.
+     *
+     * @public
+     * @memberof FileAttachmentAnnotationElement
+     * @returns {HTMLSectionElement}
+     */
+    render: function FileAttachmentAnnotationElement_render() {
+      this.container.className = 'fileAttachmentAnnotation';
+
+      var trigger = document.createElement('div');
+      trigger.style.height = this.container.style.height;
+      trigger.style.width = this.container.style.width;
+      trigger.addEventListener('dblclick', this._download.bind(this));
+
+      if (!this.data.hasPopup && (this.data.title || this.data.contents)) {
+        this._createPopup(this.container, trigger, this.data);
+      }
+
+      this.container.appendChild(trigger);
+      return this.container;
+    },
+
+    /**
+     * Download the file attachment associated with this annotation.
+     *
+     * @private
+     * @memberof FileAttachmentAnnotationElement
+     */
+    _download: function FileAttachmentAnnotationElement_download() {
+      if (!this.downloadManager) {
+        warn('Download cannot be started due to unavailable download manager');
+        return;
+      }
+      this.downloadManager.downloadData(this.content, this.filename, '');
+    }
+  });
+
+  return FileAttachmentAnnotationElement;
+})();
+
+/**
  * @typedef {Object} AnnotationLayerParameters
  * @property {PageViewport} viewport
  * @property {HTMLDivElement} div
  * @property {Array} annotations
  * @property {PDFPage} page
  * @property {IPDFLinkService} linkService
  */
 
@@ -2293,17 +2419,18 @@ var AnnotationLayer = (function Annotati
           continue;
         }
 
         var properties = {
           data: data,
           layer: parameters.div,
           page: parameters.page,
           viewport: parameters.viewport,
-          linkService: parameters.linkService
+          linkService: parameters.linkService,
+          downloadManager: parameters.downloadManager
         };
         var element = annotationElementFactory.create(properties);
         if (element.isRenderable) {
           parameters.div.appendChild(element.render());
         }
       }
     },
 
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.83';
-var pdfjsBuild = '0629fd0';
+var pdfjsVersion = '1.4.95';
+var pdfjsBuild = '2b813c0';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -2361,16 +2361,27 @@ var UNSUPPORTED_FEATURES = PDFJS.UNSUPPO
   unknown: 'unknown',
   forms: 'forms',
   javaScript: 'javaScript',
   smask: 'smask',
   shadingPattern: 'shadingPattern',
   font: 'font'
 };
 
+// Gets the file name from a given URL.
+function getFilenameFromUrl(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);
+}
+PDFJS.getFilenameFromUrl = getFilenameFromUrl;
+
 // Combines two URLs. The baseUrl shall be absolute URL. If the url is an
 // absolute URL, it will be returned as is.
 function combineUrl(baseUrl, url) {
   if (!url) {
     return baseUrl;
   }
   return new URL(url, baseUrl).href;
 }
@@ -3491,16 +3502,17 @@ exports.UnknownErrorException = UnknownE
 exports.Util = Util;
 exports.XRefParseException = XRefParseException;
 exports.assert = assert;
 exports.bytesToString = bytesToString;
 exports.combineUrl = combineUrl;
 exports.createPromiseCapability = createPromiseCapability;
 exports.deprecated = deprecated;
 exports.error = error;
+exports.getFilenameFromUrl = getFilenameFromUrl;
 exports.getLookupTableFactory = getLookupTableFactory;
 exports.info = info;
 exports.isArray = isArray;
 exports.isArrayBuffer = isArrayBuffer;
 exports.isBool = isBool;
 exports.isEmptyObj = isEmptyObj;
 exports.isExternalLinkTargetSet = isExternalLinkTargetSet;
 exports.isInt = isInt;
@@ -34977,16 +34989,17 @@ var ObjectLoader = (function() {
   };
 
   return ObjectLoader;
 })();
 
 exports.Catalog = Catalog;
 exports.ObjectLoader = ObjectLoader;
 exports.XRef = XRef;
+exports.FileSpec = FileSpec;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsCorePattern = {}), root.pdfjsSharedUtil,
       root.pdfjsCorePrimitives, root.pdfjsCoreFunction,
       root.pdfjsCoreColorSpace);
@@ -38787,16 +38800,17 @@ var stringToPDFString = sharedUtil.strin
 var stringToUTF8String = sharedUtil.stringToUTF8String;
 var warn = sharedUtil.warn;
 var Dict = corePrimitives.Dict;
 var isDict = corePrimitives.isDict;
 var isName = corePrimitives.isName;
 var Stream = coreStream.Stream;
 var ColorSpace = coreColorSpace.ColorSpace;
 var ObjectLoader = coreObj.ObjectLoader;
+var FileSpec = coreObj.FileSpec;
 var OperatorList = coreEvaluator.OperatorList;
 
 /**
  * @class
  * @alias AnnotationFactory
  */
 function AnnotationFactory() {}
 AnnotationFactory.prototype = /** @lends AnnotationFactory.prototype */ {
@@ -38812,16 +38826,17 @@ AnnotationFactory.prototype = /** @lends
     }
 
     // Determine the annotation's subtype.
     var subtype = dict.get('Subtype');
     subtype = isName(subtype) ? subtype.name : '';
 
     // Return the right annotation object based on the subtype and field type.
     var parameters = {
+      xref: xref,
       dict: dict,
       ref: ref
     };
 
     switch (subtype) {
       case 'Link':
         return new LinkAnnotation(parameters);
 
@@ -38845,16 +38860,19 @@ AnnotationFactory.prototype = /** @lends
         return new UnderlineAnnotation(parameters);
 
       case 'Squiggly':
         return new SquigglyAnnotation(parameters);
 
       case 'StrikeOut':
         return new StrikeOutAnnotation(parameters);
 
+      case 'FileAttachment':
+        return new FileAttachmentAnnotation(parameters);
+
       default:
         warn('Unimplemented annotation type "' + subtype + '", ' +
              'falling back to base annotation');
         return new Annotation(parameters);
     }
   }
 };
 
@@ -39078,16 +39096,34 @@ var Annotation = (function AnnotationClo
         // specification, we should draw a solid border of width 1 in that
         // case, but Adobe Reader did not implement that part of the
         // specification and instead draws no border at all, so we do the same.
         // See also https://github.com/mozilla/pdf.js/issues/6179.
         this.borderStyle.setWidth(0);
       }
     },
 
+    /**
+     * Prepare the annotation for working with a popup in the display layer.
+     *
+     * @private
+     * @memberof Annotation
+     * @param {Dict} dict - The annotation's data dictionary
+     */
+    _preparePopup: function Annotation_preparePopup(dict) {
+      if (!dict.has('C')) {
+        // Fall back to the default background color.
+        this.data.color = null;
+      }
+
+      this.data.hasPopup = dict.has('Popup');
+      this.data.title = stringToPDFString(dict.get('T') || '');
+      this.data.contents = stringToPDFString(dict.get('Contents') || '');
+    },
+
     loadResources: function Annotation_loadResources(keys) {
       return new Promise(function (resolve, reject) {
         this.appearance.dict.getAsync('Resources').then(function (resources) {
           if (!resources) {
             resolve();
             return;
           }
           var objectLoader = new ObjectLoader(resources.map,
@@ -39395,37 +39431,25 @@ var TextWidgetAnnotation = (function Tex
 var TextAnnotation = (function TextAnnotationClosure() {
   var DEFAULT_ICON_SIZE = 22; // px
 
   function TextAnnotation(parameters) {
     Annotation.call(this, parameters);
 
     this.data.annotationType = AnnotationType.TEXT;
 
-    var dict = parameters.dict;
     if (this.data.hasAppearance) {
       this.data.name = 'NoIcon';
     } else {
       this.data.rect[1] = this.data.rect[3] - DEFAULT_ICON_SIZE;
       this.data.rect[2] = this.data.rect[0] + DEFAULT_ICON_SIZE;
-      this.data.name = dict.has('Name') ? dict.get('Name').name : 'Note';
-    }
-
-    if (!dict.has('C')) {
-      // Fall back to the default background color.
-      this.data.color = null;
-    }
-
-    this.data.hasPopup = dict.has('Popup');
-    if (!this.data.hasPopup) {
-      // There is no associated Popup annotation, so the Text annotation
-      // must create its own popup.
-      this.data.title = stringToPDFString(dict.get('T') || '');
-      this.data.contents = stringToPDFString(dict.get('Contents') || '');
-    }
+      this.data.name = parameters.dict.has('Name') ?
+                       parameters.dict.get('Name').name : 'Note';
+    }
+    this._preparePopup(parameters.dict);
   }
 
   Util.inherit(TextAnnotation, Annotation, {});
 
   return TextAnnotation;
 })();
 
 var LinkAnnotation = (function LinkAnnotationClosure() {
@@ -39534,71 +39558,91 @@ var PopupAnnotation = (function PopupAnn
   return PopupAnnotation;
 })();
 
 var HighlightAnnotation = (function HighlightAnnotationClosure() {
   function HighlightAnnotation(parameters) {
     Annotation.call(this, parameters);
 
     this.data.annotationType = AnnotationType.HIGHLIGHT;
+    this._preparePopup(parameters.dict);
 
     // PDF viewers completely ignore any border styles.
     this.data.borderStyle.setWidth(0);
   }
 
   Util.inherit(HighlightAnnotation, Annotation, {});
 
   return HighlightAnnotation;
 })();
 
 var UnderlineAnnotation = (function UnderlineAnnotationClosure() {
   function UnderlineAnnotation(parameters) {
     Annotation.call(this, parameters);
 
     this.data.annotationType = AnnotationType.UNDERLINE;
+    this._preparePopup(parameters.dict);
 
     // PDF viewers completely ignore any border styles.
     this.data.borderStyle.setWidth(0);
   }
 
   Util.inherit(UnderlineAnnotation, Annotation, {});
 
   return UnderlineAnnotation;
 })();
 
 var SquigglyAnnotation = (function SquigglyAnnotationClosure() {
   function SquigglyAnnotation(parameters) {
     Annotation.call(this, parameters);
 
     this.data.annotationType = AnnotationType.SQUIGGLY;
+    this._preparePopup(parameters.dict);
 
     // PDF viewers completely ignore any border styles.
     this.data.borderStyle.setWidth(0);
   }
 
   Util.inherit(SquigglyAnnotation, Annotation, {});
 
   return SquigglyAnnotation;
 })();
 
 var StrikeOutAnnotation = (function StrikeOutAnnotationClosure() {
   function StrikeOutAnnotation(parameters) {
     Annotation.call(this, parameters);
 
     this.data.annotationType = AnnotationType.STRIKEOUT;
+    this._preparePopup(parameters.dict);
 
     // PDF viewers completely ignore any border styles.
     this.data.borderStyle.setWidth(0);
   }
 
   Util.inherit(StrikeOutAnnotation, Annotation, {});
 
   return StrikeOutAnnotation;
 })();
 
+var FileAttachmentAnnotation = (function FileAttachmentAnnotationClosure() {
+  function FileAttachmentAnnotation(parameters) {
+    Annotation.call(this, parameters);
+
+    var file = new FileSpec(parameters.dict.get('FS'), parameters.xref);
+
+    this.data.annotationType = AnnotationType.FILEATTACHMENT;
+    this.data.file = file.serializable;
+    this._preparePopup(parameters.dict);
+  }
+
+  Util.inherit(FileAttachmentAnnotation, Annotation, {});
+
+  return FileAttachmentAnnotation;
+})();
+
 exports.Annotation = Annotation;
 exports.AnnotationBorderStyle = AnnotationBorderStyle;
 exports.AnnotationFactory = AnnotationFactory;
 }));
 
 
 (function (root, factory) {
   {
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -127,17 +127,18 @@
 
 .annotationLayer .popup p {
   padding-top: 0.2em;
 }
 
 .annotationLayer .highlightAnnotation,
 .annotationLayer .underlineAnnotation,
 .annotationLayer .squigglyAnnotation,
-.annotationLayer .strikeoutAnnotation {
+.annotationLayer .strikeoutAnnotation,
+.annotationLayer .fileAttachmentAnnotation {
   cursor: pointer;
 }
 
 .pdfViewer .canvasWrapper {
   overflow: hidden;
 }
 
 .pdfViewer .page {
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -7,25 +7,25 @@
  *     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, ProgressBar,
-           DownloadManager, getFileName, getPDFFileNameFromURL,
-           PDFHistory, Preferences, SidebarView, ViewHistory, Stats,
-           PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
-           PasswordPrompt, PDFPresentationMode, PDFDocumentProperties, HandTool,
-           Promise, PDFLinkService, PDFOutlineView, PDFAttachmentView,
-           OverlayManager, PDFFindController, PDFFindBar, PDFViewer,
-           PDFRenderingQueue, PresentationModeState, parseQueryString,
-           RenderingStates, UNKNOWN_SCALE, DEFAULT_SCALE_VALUE,
+/* globals PDFJS, PDFBug, FirefoxCom, Stats, ProgressBar, DownloadManager,
+           getPDFFileNameFromURL, PDFHistory, Preferences, SidebarView,
+           ViewHistory, Stats, PDFThumbnailViewer, URL, noContextMenuHandler,
+           SecondaryToolbar, PasswordPrompt, PDFPresentationMode,
+           PDFDocumentProperties, HandTool, Promise, PDFLinkService,
+           PDFOutlineView, PDFAttachmentView, OverlayManager,
+           PDFFindController, PDFFindBar, PDFViewer, PDFRenderingQueue,
+           PresentationModeState, parseQueryString, RenderingStates,
+           UNKNOWN_SCALE, DEFAULT_SCALE_VALUE,
            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;
@@ -47,25 +47,16 @@ var mozL10n = document.mozL10n || docume
 var CSS_UNITS = 96.0 / 72.0;
 var DEFAULT_SCALE_VALUE = 'auto';
 var DEFAULT_SCALE = 1.0;
 var UNKNOWN_SCALE = 0;
 var MAX_AUTO_SCALE = 1.25;
 var SCROLLBAR_PADDING = 40;
 var VERTICAL_PADDING = 5;
 
-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);
-}
-
 /**
  * Returns scale factor for the canvas. It makes sense for the HiDPI displays.
  * @return {Object} The object with horizontal (sx) and vertical (sy)
                     scales. The scaled property is set to false if scaling is
                     not required, true otherwise.
  */
 function getOutputScale(ctx) {
   var devicePixelRatio = window.devicePixelRatio || 1;
@@ -4294,30 +4285,32 @@ DefaultTextLayerFactory.prototype = {
 };
 
 
 /**
  * @typedef {Object} AnnotationLayerBuilderOptions
  * @property {HTMLDivElement} pageDiv
  * @property {PDFPage} pdfPage
  * @property {IPDFLinkService} linkService
+ * @property {DownloadManager} downloadManager
  */
 
 /**
  * @class
  */
 var AnnotationLayerBuilder = (function AnnotationLayerBuilderClosure() {
   /**
    * @param {AnnotationLayerBuilderOptions} options
    * @constructs AnnotationLayerBuilder
    */
   function AnnotationLayerBuilder(options) {
     this.pageDiv = options.pageDiv;
     this.pdfPage = options.pdfPage;
     this.linkService = options.linkService;
+    this.downloadManager = options.downloadManager;
 
     this.div = null;
   }
 
   AnnotationLayerBuilder.prototype =
       /** @lends AnnotationLayerBuilder.prototype */ {
 
     /**
@@ -4332,17 +4325,18 @@ var AnnotationLayerBuilder = (function A
 
       this.pdfPage.getAnnotations(parameters).then(function (annotations) {
         viewport = viewport.clone({ dontFlip: true });
         parameters = {
           viewport: viewport,
           div: self.div,
           annotations: annotations,
           page: self.pdfPage,
-          linkService: self.linkService
+          linkService: self.linkService,
+          downloadManager: self.downloadManager
         };
 
         if (self.div) {
           // If an annotationLayer already exists, refresh its children's
           // transformation matrices.
           PDFJS.AnnotationLayer.update(parameters);
         } else {
           // Create an annotation layer div and render the annotations
@@ -4396,16 +4390,18 @@ DefaultAnnotationLayerFactory.prototype 
 };
 
 
 /**
  * @typedef {Object} PDFViewerOptions
  * @property {HTMLDivElement} container - The container for the viewer element.
  * @property {HTMLDivElement} viewer - (optional) The viewer element.
  * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {DownloadManager} downloadManager - (optional) The download
+ *   manager component.
  * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
  *   queue object.
  * @property {boolean} removePageBorders - (optional) Removes the border shadow
  *   around the pages. The default is false.
  */
 
 /**
  * Simple viewer control to display PDF content/pages.
@@ -4448,16 +4444,17 @@ var PDFViewer = (function pdfViewer() {
   /**
    * @constructs PDFViewer
    * @param {PDFViewerOptions} options
    */
   function PDFViewer(options) {
     this.container = options.container;
     this.viewer = options.viewer || options.container.firstElementChild;
     this.linkService = options.linkService || new SimpleLinkService();
+    this.downloadManager = options.downloadManager || null;
     this.removePageBorders = options.removePageBorders || false;
 
     this.defaultRenderingQueue = !options.renderingQueue;
     if (this.defaultRenderingQueue) {
       // Custom rendering queue is not specified, using default one
       this.renderingQueue = new PDFRenderingQueue();
       this.renderingQueue.setViewer(this);
     } else {
@@ -5113,17 +5110,18 @@ var PDFViewer = (function pdfViewer() {
      * @param {HTMLDivElement} pageDiv
      * @param {PDFPage} pdfPage
      * @returns {AnnotationLayerBuilder}
      */
     createAnnotationLayerBuilder: function (pageDiv, pdfPage) {
       return new AnnotationLayerBuilder({
         pageDiv: pageDiv,
         pdfPage: pdfPage,
-        linkService: this.linkService
+        linkService: this.linkService,
+        downloadManager: this.downloadManager
       });
     },
 
     setFindController: function (findController) {
       this.findController = findController;
     },
   };
 
@@ -5237,17 +5235,16 @@ var PDFThumbnailView = (function PDFThum
     this.pdfPage = null;
     this.rotation = 0;
     this.viewport = defaultViewport;
     this.pdfPageRotate = defaultViewport.rotation;
 
     this.linkService = linkService;
     this.renderingQueue = renderingQueue;
 
-    this.hasImage = false;
     this.resume = null;
     this.renderingState = RenderingStates.INITIAL;
 
     this.pageWidth = this.viewport.width;
     this.pageHeight = this.viewport.height;
     this.pageRatio = this.pageWidth / this.pageHeight;
 
     this.canvasWidth = THUMBNAIL_WIDTH;
@@ -5293,17 +5290,16 @@ var PDFThumbnailView = (function PDFThum
       this.viewport = pdfPage.getViewport(1, totalRotation);
       this.reset();
     },
 
     reset: function PDFThumbnailView_reset() {
       if (this.renderTask) {
         this.renderTask.cancel();
       }
-      this.hasImage = false;
       this.resume = null;
       this.renderingState = RenderingStates.INITIAL;
 
       this.pageWidth = this.viewport.width;
       this.pageHeight = this.viewport.height;
       this.pageRatio = this.pageWidth / this.pageHeight;
 
       this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0;
@@ -5396,21 +5392,19 @@ var PDFThumbnailView = (function PDFThum
       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;
+
       this.renderingState = RenderingStates.RUNNING;
 
       var resolveRenderPromise, rejectRenderPromise;
       var promise = new Promise(function (resolve, reject) {
         resolveRenderPromise = resolve;
         rejectRenderPromise = reject;
       });
 
@@ -5421,16 +5415,17 @@ var PDFThumbnailView = (function PDFThum
         // triggering this callback.
         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);
         }
@@ -5464,24 +5459,27 @@ var PDFThumbnailView = (function PDFThum
         function pdfPageRenderError(error) {
           thumbnailDrawCallback(error);
         }
       );
       return promise;
     },
 
     setImage: function PDFThumbnailView_setImage(pageView) {
+      if (this.renderingState !== RenderingStates.INITIAL) {
+        return;
+      }
       var img = pageView.canvas;
-      if (this.hasImage || !img) {
+      if (!img) {
         return;
       }
       if (!this.pdfPage) {
         this.setPdfPage(pageView.pdfPage);
       }
-      this.hasImage = true;
+
       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);
@@ -5922,17 +5920,17 @@ var PDFAttachmentView = (function PDFAtt
 
       var names = Object.keys(attachments).sort(function(a, b) {
         return a.toLowerCase().localeCompare(b.toLowerCase());
       });
       attachmentsCount = names.length;
 
       for (var i = 0; i < attachmentsCount; i++) {
         var item = attachments[names[i]];
-        var filename = getFileName(item.filename);
+        var filename = PDFJS.getFilenameFromUrl(item.filename);
         var div = document.createElement('div');
         div.className = 'attachmentsItem';
         var button = document.createElement('button');
         this._bindLink(button, item.content, filename);
         button.textContent = PDFJS.removeNullCharacters(filename);
         div.appendChild(button);
         this.container.appendChild(div);
       }
@@ -5988,17 +5986,18 @@ var PDFViewerApplication = {
     this.pdfLinkService = pdfLinkService;
 
     var container = document.getElementById('viewerContainer');
     var viewer = document.getElementById('viewer');
     this.pdfViewer = new PDFViewer({
       container: container,
       viewer: viewer,
       renderingQueue: pdfRenderingQueue,
-      linkService: pdfLinkService
+      linkService: pdfLinkService,
+      downloadManager: new DownloadManager()
     });
     pdfRenderingQueue.setViewer(this.pdfViewer);
     pdfLinkService.setViewer(this.pdfViewer);
 
     var thumbnailContainer = document.getElementById('thumbnailView');
     this.pdfThumbnailViewer = new PDFThumbnailViewer({
       container: thumbnailContainer,
       renderingQueue: pdfRenderingQueue,
@@ -6329,17 +6328,17 @@ var PDFViewerApplication = {
       }
     });
     FirefoxCom.requestSync('initPassiveLoading', null);
   },
 
   setTitleUsingUrl: function pdfViewSetTitleUsingUrl(url) {
     this.url = url;
     try {
-      this.setTitle(decodeURIComponent(getFileName(url)) || url);
+      this.setTitle(decodeURIComponent(PDFJS.getFilenameFromUrl(url)) || url);
     } catch (e) {
       // decodeURIComponent may throw URIError,
       // fall back to using the unprocessed url in that case
       this.setTitle(url);
     }
   },
 
   setTitle: function pdfViewSetTitle(title) {