From 863d583ae1e5234382c0d13c2c9f8cb88f864d06 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Sat, 27 Sep 2014 11:58:42 -0500
Subject: [PATCH 01/10] Renames page_view.js file.

---
 web/{page_view.js => pdf_page_view.js} | 0
 web/pdf_viewer.js                      | 2 +-
 web/viewer.html                        | 2 +-
 3 files changed, 2 insertions(+), 2 deletions(-)
 rename web/{page_view.js => pdf_page_view.js} (100%)

diff --git a/web/page_view.js b/web/pdf_page_view.js
similarity index 100%
rename from web/page_view.js
rename to web/pdf_page_view.js
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 47e109fab..190c37ed4 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -31,7 +31,7 @@ var PresentationModeState = {
 var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
 
 //#include pdf_rendering_queue.js
-//#include page_view.js
+//#include pdf_page_view.js
 //#include text_layer_builder.js
 
 /**
diff --git a/web/viewer.html b/web/viewer.html
index f562dbd66..c2d3dff69 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -69,7 +69,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/
     <script src="download_manager.js"></script>
     <script src="view_history.js"></script>
     <script src="pdf_rendering_queue.js"></script>
-    <script src="page_view.js"></script>
+    <script src="pdf_page_view.js"></script>
     <script src="text_layer_builder.js"></script>
     <script src="pdf_viewer.js"></script>
     <script src="thumbnail_view.js"></script>

From f68678086dbbc0214eed883905a830f6fc14c1ff Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Sat, 27 Sep 2014 13:03:28 -0500
Subject: [PATCH 02/10] Simple restructuring PageView into PDFPageView

---
 web/pdf_find_controller.js |    3 +-
 web/pdf_page_view.js       | 1053 ++++++++++++++++++------------------
 web/pdf_viewer.js          |   24 +-
 web/viewer.js              |    3 +-
 4 files changed, 556 insertions(+), 527 deletions(-)

diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js
index 1aa912109..c54fc8ade 100644
--- a/web/pdf_find_controller.js
+++ b/web/pdf_find_controller.js
@@ -197,8 +197,6 @@ var PDFFindController = (function PDFFindControllerClosure() {
     },
 
     updatePage: function PDFFindController_updatePage(index) {
-      var page = this.pdfViewer.getPageView(index);
-
       if (this.selected.pageIdx === index) {
         // If the page is selected, scroll the page into view, which triggers
         // rendering the page, which adds the textLayer. Once the textLayer is
@@ -206,6 +204,7 @@ var PDFFindController = (function PDFFindControllerClosure() {
         this.pdfViewer.scrollPageIntoView(index + 1);
       }
 
+      var page = this.pdfViewer.getPageView(index);
       if (page.textLayer) {
         page.textLayer.updateMatches();
       }
diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 92ed39b7d..7e6aa17b3 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -20,591 +20,616 @@
 'use strict';
 
 /**
- * @constructor
- * @param {HTMLDivElement} container - The viewer element.
- * @param {number} id - The page unique ID (normally its number).
- * @param {number} scale - The page scale display.
- * @param {PageViewport} defaultViewport - The page viewport.
- * @param {IPDFLinkService} linkService - The navigation/linking service.
- * @param {PDFRenderingQueue} renderingQueue - The rendering queue object.
- * @param {Cache} cache - The page cache.
- * @param {PDFPageSource} pageSource
- * @param {PDFViewer} viewer
- *
+ * @typedef {Object} PDFPageViewOptions
+ * @property {HTMLDivElement} container - The viewer element.
+ * @property {number} id - The page unique ID (normally its number).
+ * @property {number} scale - The page scale display.
+ * @property {PageViewport} defaultViewport - The page viewport.
+ * @property {IPDFLinkService} linkService - The navigation/linking service.
+ * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @property {Cache} cache - The page cache.
+ * @property {PDFPageSource} pageSource
+ * @property {PDFViewer} viewer
+ */
+
+/**
+ * @class
  * @implements {IRenderableView}
  */
-var PageView = function pageView(container, id, scale, defaultViewport,
-                                 linkService, renderingQueue, cache,
-                                 pageSource, viewer) {
-  this.id = id;
-  this.renderingId = 'page' + id;
-
-  this.rotation = 0;
-  this.scale = scale || 1.0;
-  this.viewport = defaultViewport;
-  this.pdfPageRotate = defaultViewport.rotation;
-  this.hasRestrictedScaling = false;
-
-  this.linkService = linkService;
-  this.renderingQueue = renderingQueue;
-  this.cache = cache;
-  this.pageSource = pageSource;
-  this.viewer = viewer;
-
-  this.renderingState = RenderingStates.INITIAL;
-  this.resume = null;
-
-  this.textLayer = null;
-
-  this.zoomLayer = null;
-
-  this.annotationLayer = null;
-
-  var anchor = document.createElement('a');
-  anchor.name = '' + this.id;
-
-  var div = this.el = document.createElement('div');
-  div.id = 'pageContainer' + this.id;
-  div.className = 'page';
-  div.style.width = Math.floor(this.viewport.width) + 'px';
-  div.style.height = Math.floor(this.viewport.height) + 'px';
-
-  container.appendChild(anchor);
-  container.appendChild(div);
-
-  this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
-    this.pdfPage = pdfPage;
-    this.pdfPageRotate = pdfPage.rotate;
-    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-    this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
-    this.stats = pdfPage.stats;
-    this.reset();
-  };
+var PDFPageView = (function PDFPageViewClosure() {
+  /**
+   * @constructs PDFPageView
+   * @param {PDFPageViewOptions} options
+   */
+  function PDFPageView(options) {
+    var container = options.container;
+    var id = options.id;
+    var scale = options.scale;
+    var defaultViewport = options.defaultViewport;
+    var linkService = options.linkService;
+    var renderingQueue = options.renderingQueue;
+    var cache = options.cache;
+    var pageSource = options.pageSource;
+    var viewer = options.viewer;
+
+    this.id = id;
+    this.renderingId = 'page' + id;
+
+    this.rotation = 0;
+    this.scale = scale || 1.0;
+    this.viewport = defaultViewport;
+    this.pdfPageRotate = defaultViewport.rotation;
+    this.hasRestrictedScaling = false;
+
+    this.linkService = linkService;
+    this.renderingQueue = renderingQueue;
+    this.cache = cache;
+    this.pageSource = pageSource;
+    this.viewer = viewer;
+
+    this.renderingState = RenderingStates.INITIAL;
+    this.resume = null;
+
+    this.textLayer = null;
 
-  this.destroy = function pageViewDestroy() {
     this.zoomLayer = null;
-    this.reset();
-    if (this.pdfPage) {
-      this.pdfPage.destroy();
-    }
-  };
 
-  this.reset = function pageViewReset(keepAnnotations) {
-    if (this.renderTask) {
-      this.renderTask.cancel();
-    }
-    this.resume = null;
-    this.renderingState = RenderingStates.INITIAL;
+    this.annotationLayer = null;
 
+    var anchor = document.createElement('a');
+    anchor.name = '' + this.id;
+
+    var div = document.createElement('div');
+    div.id = 'pageContainer' + this.id;
+    div.className = 'page';
     div.style.width = Math.floor(this.viewport.width) + 'px';
     div.style.height = Math.floor(this.viewport.height) + 'px';
+    this.el = div; // TODO replace 'el' property usage
+    this.div = div;
+
+    container.appendChild(anchor);
+    container.appendChild(div);
+  }
+
+  PDFPageView.prototype = {
+    setPdfPage: function PDFPageView_setPdfPage(pdfPage) {
+      this.pdfPage = pdfPage;
+      this.pdfPageRotate = pdfPage.rotate;
+      var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+      this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS,
+                                          totalRotation);
+      this.stats = pdfPage.stats;
+      this.reset();
+    },
 
-    var childNodes = div.childNodes;
-    for (var i = div.childNodes.length - 1; i >= 0; i--) {
-      var node = childNodes[i];
-      if ((this.zoomLayer && this.zoomLayer === node) ||
-          (keepAnnotations && this.annotationLayer === node)) {
-        continue;
+    destroy: function PDFPageView_destroy() {
+      this.zoomLayer = null;
+      this.reset();
+      if (this.pdfPage) {
+        this.pdfPage.destroy();
       }
-      div.removeChild(node);
-    }
-    div.removeAttribute('data-loaded');
+    },
 
-    if (keepAnnotations) {
-      if (this.annotationLayer) {
-        // Hide annotationLayer until all elements are resized
-        // so they are not displayed on the already-resized page
-        this.annotationLayer.setAttribute('hidden', 'true');
+    reset: function PDFPageView_reset(keepAnnotations) {
+      if (this.renderTask) {
+        this.renderTask.cancel();
+      }
+      this.resume = null;
+      this.renderingState = RenderingStates.INITIAL;
+
+      var div = this.div;
+      div.style.width = Math.floor(this.viewport.width) + 'px';
+      div.style.height = Math.floor(this.viewport.height) + 'px';
+
+      var childNodes = div.childNodes;
+      for (var i = div.childNodes.length - 1; i >= 0; i--) {
+        var node = childNodes[i];
+        if ((this.zoomLayer && this.zoomLayer === node) ||
+            (keepAnnotations && this.annotationLayer === node)) {
+          continue;
+        }
+        div.removeChild(node);
+      }
+      div.removeAttribute('data-loaded');
+
+      if (keepAnnotations) {
+        if (this.annotationLayer) {
+          // Hide annotationLayer until all elements are resized
+          // so they are not displayed on the already-resized page
+          this.annotationLayer.setAttribute('hidden', 'true');
+        }
+      } else {
+        this.annotationLayer = null;
       }
-    } else {
-      this.annotationLayer = null;
-    }
-
-    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;
-    }
-
-    this.loadingIconDiv = document.createElement('div');
-    this.loadingIconDiv.className = 'loadingIcon';
-    div.appendChild(this.loadingIconDiv);
-  };
 
-  this.update = function pageViewUpdate(scale, rotation) {
-    this.scale = scale || this.scale;
+      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 (typeof rotation !== 'undefined') {
-      this.rotation = rotation;
-    }
+      this.loadingIconDiv = document.createElement('div');
+      this.loadingIconDiv.className = 'loadingIcon';
+      div.appendChild(this.loadingIconDiv);
+    },
 
-    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
-    this.viewport = this.viewport.clone({
-      scale: this.scale * CSS_UNITS,
-      rotation: totalRotation
-    });
+    update: function PDFPageView_update(scale, rotation) {
+      this.scale = scale || this.scale;
 
-    var isScalingRestricted = false;
-    if (this.canvas && PDFJS.maxCanvasPixels > 0) {
-      var ctx = this.canvas.getContext('2d');
-      var outputScale = getOutputScale(ctx);
-      var pixelsInViewport = this.viewport.width * this.viewport.height;
-      var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
-      if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
-          ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
-          PDFJS.maxCanvasPixels) {
-        isScalingRestricted = true;
+      if (typeof rotation !== 'undefined') {
+        this.rotation = rotation;
       }
-    }
-
-    if (this.canvas &&
-        (PDFJS.useOnlyCssZoom ||
-          (this.hasRestrictedScaling && isScalingRestricted))) {
-      this.cssTransform(this.canvas, true);
-      return;
-    } else if (this.canvas && !this.zoomLayer) {
-      this.zoomLayer = this.canvas.parentNode;
-      this.zoomLayer.style.position = 'absolute';
-    }
-    if (this.zoomLayer) {
-      this.cssTransform(this.zoomLayer.firstChild);
-    }
-    this.reset(true);
-  };
 
-  this.cssTransform = function pageCssTransform(canvas, redrawAnnotations) {
-    // Scale canvas, canvas wrapper, and page container.
-    var width = this.viewport.width;
-    var height = this.viewport.height;
-    canvas.style.width = canvas.parentNode.style.width = div.style.width =
+      var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+      this.viewport = this.viewport.clone({
+        scale: this.scale * CSS_UNITS,
+        rotation: totalRotation
+      });
+
+      var isScalingRestricted = false;
+      if (this.canvas && PDFJS.maxCanvasPixels > 0) {
+        var ctx = this.canvas.getContext('2d');
+        var outputScale = getOutputScale(ctx);
+        var pixelsInViewport = this.viewport.width * this.viewport.height;
+        var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
+        if (((Math.floor(this.viewport.width) * outputScale.sx) | 0) *
+            ((Math.floor(this.viewport.height) * outputScale.sy) | 0) >
+            PDFJS.maxCanvasPixels) {
+          isScalingRestricted = true;
+        }
+      }
+
+      if (this.canvas &&
+          (PDFJS.useOnlyCssZoom ||
+            (this.hasRestrictedScaling && isScalingRestricted))) {
+        this.cssTransform(this.canvas, true);
+        return;
+      } else if (this.canvas && !this.zoomLayer) {
+        this.zoomLayer = this.canvas.parentNode;
+        this.zoomLayer.style.position = 'absolute';
+      }
+      if (this.zoomLayer) {
+        this.cssTransform(this.zoomLayer.firstChild);
+      }
+      this.reset(true);
+    },
+
+    cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) {
+      // Scale canvas, canvas wrapper, and page container.
+      var width = this.viewport.width;
+      var height = this.viewport.height;
+      var div = this.div;
+      canvas.style.width = canvas.parentNode.style.width = div.style.width =
         Math.floor(width) + 'px';
-    canvas.style.height = canvas.parentNode.style.height = div.style.height =
+      canvas.style.height = canvas.parentNode.style.height = div.style.height =
         Math.floor(height) + 'px';
-    // The canvas may have been originally rotated, so rotate relative to that.
-    var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
-    var absRotation = Math.abs(relativeRotation);
-    var scaleX = 1, scaleY = 1;
-    if (absRotation === 90 || absRotation === 270) {
-      // Scale x and y because of the rotation.
-      scaleX = height / width;
-      scaleY = width / height;
-    }
-    var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
-                       'scale(' + scaleX + ',' + scaleY + ')';
-    CustomStyle.setProp('transform', canvas, cssTransform);
-
-    if (this.textLayer) {
-      // Rotating the text layer is more complicated since the divs inside the
-      // the text layer are rotated.
-      // TODO: This could probably be simplified by drawing the text layer in
-      // one orientation then rotating overall.
-      var textLayerViewport = this.textLayer.viewport;
-      var textRelativeRotation = this.viewport.rotation -
-                                 textLayerViewport.rotation;
-      var textAbsRotation = Math.abs(textRelativeRotation);
-      var scale = width / textLayerViewport.width;
-      if (textAbsRotation === 90 || textAbsRotation === 270) {
-        scale = width / textLayerViewport.height;
+      // The canvas may have been originally rotated, rotate relative to that.
+      var relativeRotation = this.viewport.rotation - canvas._viewport.rotation;
+      var absRotation = Math.abs(relativeRotation);
+      var scaleX = 1, scaleY = 1;
+      if (absRotation === 90 || absRotation === 270) {
+        // Scale x and y because of the rotation.
+        scaleX = height / width;
+        scaleY = width / height;
       }
-      var textLayerDiv = this.textLayer.textLayerDiv;
-      var transX, transY;
-      switch (textAbsRotation) {
-        case 0:
-          transX = transY = 0;
-          break;
-        case 90:
-          transX = 0;
-          transY = '-' + textLayerDiv.style.height;
-          break;
-        case 180:
-          transX = '-' + textLayerDiv.style.width;
-          transY = '-' + textLayerDiv.style.height;
-          break;
-        case 270:
-          transX = '-' + textLayerDiv.style.width;
-          transY = 0;
-          break;
-        default:
-          console.error('Bad rotation value.');
-          break;
+      var cssTransform = 'rotate(' + relativeRotation + 'deg) ' +
+        'scale(' + scaleX + ',' + scaleY + ')';
+      CustomStyle.setProp('transform', canvas, cssTransform);
+
+      if (this.textLayer) {
+        // Rotating the text layer is more complicated since the divs inside the
+        // the text layer are rotated.
+        // TODO: This could probably be simplified by drawing the text layer in
+        // one orientation then rotating overall.
+        var textLayerViewport = this.textLayer.viewport;
+        var textRelativeRotation = this.viewport.rotation -
+          textLayerViewport.rotation;
+        var textAbsRotation = Math.abs(textRelativeRotation);
+        var scale = width / textLayerViewport.width;
+        if (textAbsRotation === 90 || textAbsRotation === 270) {
+          scale = width / textLayerViewport.height;
+        }
+        var textLayerDiv = this.textLayer.textLayerDiv;
+        var transX, transY;
+        switch (textAbsRotation) {
+          case 0:
+            transX = transY = 0;
+            break;
+          case 90:
+            transX = 0;
+            transY = '-' + textLayerDiv.style.height;
+            break;
+          case 180:
+            transX = '-' + textLayerDiv.style.width;
+            transY = '-' + textLayerDiv.style.height;
+            break;
+          case 270:
+            transX = '-' + textLayerDiv.style.width;
+            transY = 0;
+            break;
+          default:
+            console.error('Bad rotation value.');
+            break;
+        }
+        CustomStyle.setProp('transform', textLayerDiv,
+            'rotate(' + textAbsRotation + 'deg) ' +
+            'scale(' + scale + ', ' + scale + ') ' +
+            'translate(' + transX + ', ' + transY + ')');
+        CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
       }
-      CustomStyle.setProp('transform', textLayerDiv,
-                          'rotate(' + textAbsRotation + 'deg) ' +
-                            'scale(' + scale + ', ' + scale + ') ' +
-                            'translate(' + transX + ', ' + transY + ')');
-      CustomStyle.setProp('transformOrigin', textLayerDiv, '0% 0%');
-    }
-
-    if (redrawAnnotations && this.annotationLayer) {
-      setupAnnotations(div, this.pdfPage, this.viewport);
-    }
-  };
 
-  Object.defineProperty(this, 'width', {
-    get: function PageView_getWidth() {
+      if (redrawAnnotations && this.annotationLayer) {
+        this.setupAnnotations();
+      }
+    },
+
+    get width() {
       return this.viewport.width;
     },
-    enumerable: true
-  });
 
-  Object.defineProperty(this, 'height', {
-    get: function PageView_getHeight() {
+    get height() {
       return this.viewport.height;
     },
-    enumerable: true
-  });
-
-  var self = this;
-
-  function setupAnnotations(pageDiv, pdfPage, viewport) {
 
-    function bindLink(link, dest) {
-      link.href = linkService.getDestinationHash(dest);
-      link.onclick = function pageViewSetupLinksOnclick() {
+    setupAnnotations: function PDFPageView_setupAnnotations() {
+      function bindLink(link, dest) {
+        link.href = linkService.getDestinationHash(dest);
+        link.onclick = function pageViewSetupLinksOnclick() {
+          if (dest) {
+            linkService.navigateTo(dest);
+          }
+          return false;
+        };
         if (dest) {
-          linkService.navigateTo(dest);
+          link.className = 'internalLink';
         }
-        return false;
-      };
-      if (dest) {
+      }
+
+      function bindNamedAction(link, action) {
+        link.href = linkService.getAnchorUrl('');
+        link.onclick = function pageViewSetupNamedActionOnClick() {
+          linkService.executeNamedAction(action);
+          return false;
+        };
         link.className = 'internalLink';
       }
-    }
 
-    function bindNamedAction(link, action) {
-      link.href = linkService.getAnchorUrl('');
-      link.onclick = function pageViewSetupNamedActionOnClick() {
-        linkService.executeNamedAction(action);
-        return false;
-      };
-      link.className = 'internalLink';
-    }
-
-    pdfPage.getAnnotations().then(function(annotationsData) {
-      viewport = viewport.clone({ dontFlip: true });
-      var transform = viewport.transform;
-      var transformStr = 'matrix(' + transform.join(',') + ')';
-      var data, element, i, ii;
-
-      if (self.annotationLayer) {
-        // If an annotationLayer already exists, refresh its children's
-        // transformation matrices
-        for (i = 0, ii = annotationsData.length; i < ii; i++) {
-          data = annotationsData[i];
-          element = self.annotationLayer.querySelector(
-            '[data-annotation-id="' + data.id + '"]');
-          if (element) {
-            CustomStyle.setProp('transform', element, transformStr);
-          }
-        }
-        // See this.reset()
-        self.annotationLayer.removeAttribute('hidden');
-      } else {
-        for (i = 0, ii = annotationsData.length; i < ii; i++) {
-          data = annotationsData[i];
-          if (!data || !data.hasHtml) {
-            continue;
+      var linkService = this.linkService;
+      var pageDiv = this.div;
+      var pdfPage = this.pdfPage;
+      var viewport = this.viewport;
+      var self = this;
+
+      pdfPage.getAnnotations().then(function(annotationsData) {
+        viewport = viewport.clone({ dontFlip: true });
+        var transform = viewport.transform;
+        var transformStr = 'matrix(' + transform.join(',') + ')';
+        var data, element, i, ii;
+
+        if (self.annotationLayer) {
+          // If an annotationLayer already exists, refresh its children's
+          // transformation matrices
+          for (i = 0, ii = annotationsData.length; i < ii; i++) {
+            data = annotationsData[i];
+            element = self.annotationLayer.querySelector(
+                '[data-annotation-id="' + data.id + '"]');
+            if (element) {
+              CustomStyle.setProp('transform', element, transformStr);
+            }
           }
+          // See this.reset()
+          self.annotationLayer.removeAttribute('hidden');
+        } else {
+          for (i = 0, ii = annotationsData.length; i < ii; i++) {
+            data = annotationsData[i];
+            if (!data || !data.hasHtml) {
+              continue;
+            }
 
-          element = PDFJS.AnnotationUtils.getHtmlElement(data,
-                                                         pdfPage.commonObjs);
-          element.setAttribute('data-annotation-id', data.id);
-          mozL10n.translate(element);
-
-          var rect = data.rect;
-          var view = pdfPage.view;
-          rect = PDFJS.Util.normalizeRect([
-            rect[0],
-            view[3] - rect[1] + view[1],
-            rect[2],
-            view[3] - rect[3] + view[1]
-          ]);
-          element.style.left = rect[0] + 'px';
-          element.style.top = rect[1] + 'px';
-          element.style.position = 'absolute';
-
-          CustomStyle.setProp('transform', element, transformStr);
-          var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
-          CustomStyle.setProp('transformOrigin', element, transformOriginStr);
-
-          if (data.subtype === 'Link' && !data.url) {
-            var link = element.getElementsByTagName('a')[0];
-            if (link) {
-              if (data.action) {
-                bindNamedAction(link, data.action);
-              } else {
-                bindLink(link, ('dest' in data) ? data.dest : null);
+            element = PDFJS.AnnotationUtils.getHtmlElement(data,
+              pdfPage.commonObjs);
+            element.setAttribute('data-annotation-id', data.id);
+            mozL10n.translate(element);
+
+            var rect = data.rect;
+            var view = pdfPage.view;
+            rect = PDFJS.Util.normalizeRect([
+              rect[0],
+                view[3] - rect[1] + view[1],
+              rect[2],
+                view[3] - rect[3] + view[1]
+            ]);
+            element.style.left = rect[0] + 'px';
+            element.style.top = rect[1] + 'px';
+            element.style.position = 'absolute';
+
+            CustomStyle.setProp('transform', element, transformStr);
+            var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
+            CustomStyle.setProp('transformOrigin', element, transformOriginStr);
+
+            if (data.subtype === 'Link' && !data.url) {
+              var link = element.getElementsByTagName('a')[0];
+              if (link) {
+                if (data.action) {
+                  bindNamedAction(link, data.action);
+                } else {
+                  bindLink(link, ('dest' in data) ? data.dest : null);
+                }
               }
             }
-          }
 
-          if (!self.annotationLayer) {
-            var annotationLayerDiv = document.createElement('div');
-            annotationLayerDiv.className = 'annotationLayer';
-            pageDiv.appendChild(annotationLayerDiv);
-            self.annotationLayer = annotationLayerDiv;
-          }
+            if (!self.annotationLayer) {
+              var annotationLayerDiv = document.createElement('div');
+              annotationLayerDiv.className = 'annotationLayer';
+              pageDiv.appendChild(annotationLayerDiv);
+              self.annotationLayer = annotationLayerDiv;
+            }
 
-          self.annotationLayer.appendChild(element);
+            self.annotationLayer.appendChild(element);
+          }
         }
-      }
-    });
-  }
+      });
+    },
 
-  this.getPagePoint = function pageViewGetPagePoint(x, y) {
-    return this.viewport.convertToPdfPoint(x, y);
-  };
+    getPagePoint: function PDFPageView_getPagePoint(x, y) {
+      return this.viewport.convertToPdfPoint(x, y);
+    },
 
-  this.draw = function pageviewDraw(callback) {
-    var pdfPage = this.pdfPage;
-
-    if (this.pagePdfPromise) {
-      return;
-    }
-    if (!pdfPage) {
-      var promise = this.pageSource.getPage();
-      promise.then(function(pdfPage) {
-        delete this.pagePdfPromise;
-        this.setPdfPage(pdfPage);
-        this.draw(callback);
-      }.bind(this));
-      this.pagePdfPromise = promise;
-      return;
-    }
-
-    if (this.renderingState !== RenderingStates.INITIAL) {
-      console.error('Must be in new state before drawing');
-    }
-
-    this.renderingState = RenderingStates.RUNNING;
-
-    var viewport = this.viewport;
-    // Wrap the canvas so if it has a css transform for highdpi the overflow
-    // will be hidden in FF.
-    var canvasWrapper = document.createElement('div');
-    canvasWrapper.style.width = div.style.width;
-    canvasWrapper.style.height = div.style.height;
-    canvasWrapper.classList.add('canvasWrapper');
-
-    var canvas = document.createElement('canvas');
-    canvas.id = 'page' + this.id;
-    canvasWrapper.appendChild(canvas);
-    if (this.annotationLayer) {
-      // annotationLayer needs to stay on top
-      div.insertBefore(canvasWrapper, this.annotationLayer);
-    } else {
-      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 });
-      // 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) {
-      var pixelsInViewport = viewport.width * viewport.height;
-      var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
-      if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
-        outputScale.sx = maxScale;
-        outputScale.sy = maxScale;
-        outputScale.scaled = true;
-        this.hasRestrictedScaling = true;
-      } else {
-        this.hasRestrictedScaling = false;
+    draw: function PDFPageView_draw(callback) {
+      var pdfPage = this.pdfPage;
+
+      if (this.pagePdfPromise) {
+        return;
+      }
+      if (!pdfPage) {
+        var promise = this.pageSource.getPage();
+        promise.then(function(pdfPage) {
+          delete this.pagePdfPromise;
+          this.setPdfPage(pdfPage);
+          this.draw(callback);
+        }.bind(this));
+        this.pagePdfPromise = promise;
+        return;
       }
-    }
-
-    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';
-    // Add the viewport so it's known what it was originally drawn with.
-    canvas._viewport = viewport;
-
-    var textLayerDiv = null;
-    var textLayer = null;
-    if (!PDFJS.disableTextLayer) {
-      textLayerDiv = document.createElement('div');
-      textLayerDiv.className = 'textLayer';
-      textLayerDiv.style.width = canvas.style.width;
-      textLayerDiv.style.height = canvas.style.height;
+
+      if (this.renderingState !== RenderingStates.INITIAL) {
+        console.error('Must be in new state before drawing');
+      }
+
+      this.renderingState = RenderingStates.RUNNING;
+
+      var viewport = this.viewport;
+      var div = this.div;
+      // Wrap the canvas so if it has a css transform for highdpi the overflow
+      // will be hidden in FF.
+      var canvasWrapper = document.createElement('div');
+      canvasWrapper.style.width = div.style.width;
+      canvasWrapper.style.height = div.style.height;
+      canvasWrapper.classList.add('canvasWrapper');
+
+      var canvas = document.createElement('canvas');
+      canvas.id = 'page' + this.id;
+      canvasWrapper.appendChild(canvas);
       if (this.annotationLayer) {
         // annotationLayer needs to stay on top
-        div.insertBefore(textLayerDiv, this.annotationLayer);
+        div.insertBefore(canvasWrapper, this.annotationLayer);
       } else {
-        div.appendChild(textLayerDiv);
+        div.appendChild(canvasWrapper);
       }
+      this.canvas = canvas;
 
-      textLayer = this.viewer.createTextLayerBuilder(textLayerDiv, this.id - 1,
-                                                     this.viewport);
-    }
-    this.textLayer = textLayer;
-
-    // TODO(mack): use data attributes to store these
-    ctx._scaleX = outputScale.sx;
-    ctx._scaleY = outputScale.sy;
-    if (outputScale.scaled) {
-      ctx.scale(outputScale.sx, outputScale.sy);
-    }
-
-    // Rendering area
-
-    var self = this;
-    function pageViewDrawCallback(error) {
-      // The renderTask may have been replaced by a new one, so only remove the
-      // reference to the renderTask if it matches the one that is triggering
-      // this callback.
-      if (renderTask === self.renderTask) {
-        self.renderTask = null;
+      var ctx = canvas.getContext('2d');
+      var outputScale = getOutputScale(ctx);
+
+      if (PDFJS.useOnlyCssZoom) {
+        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 (error === 'cancelled') {
-        return;
+      if (PDFJS.maxCanvasPixels > 0) {
+        var pixelsInViewport = viewport.width * viewport.height;
+        var maxScale = Math.sqrt(PDFJS.maxCanvasPixels / pixelsInViewport);
+        if (outputScale.sx > maxScale || outputScale.sy > maxScale) {
+          outputScale.sx = maxScale;
+          outputScale.sy = maxScale;
+          outputScale.scaled = true;
+          this.hasRestrictedScaling = true;
+        } else {
+          this.hasRestrictedScaling = false;
+        }
       }
 
-      self.renderingState = RenderingStates.FINISHED;
+      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';
+      // Add the viewport so it's known what it was originally drawn with.
+      canvas._viewport = viewport;
+
+      var textLayerDiv = null;
+      var textLayer = null;
+      if (!PDFJS.disableTextLayer) {
+        textLayerDiv = document.createElement('div');
+        textLayerDiv.className = 'textLayer';
+        textLayerDiv.style.width = canvas.style.width;
+        textLayerDiv.style.height = canvas.style.height;
+        if (this.annotationLayer) {
+          // annotationLayer needs to stay on top
+          div.insertBefore(textLayerDiv, this.annotationLayer);
+        } else {
+          div.appendChild(textLayerDiv);
+        }
 
-      if (self.loadingIconDiv) {
-        div.removeChild(self.loadingIconDiv);
-        delete self.loadingIconDiv;
+        textLayer = this.viewer.createTextLayerBuilder(textLayerDiv,
+                                                       this.id - 1,
+                                                       this.viewport);
       }
+      this.textLayer = textLayer;
 
-      if (self.zoomLayer) {
-        div.removeChild(self.zoomLayer);
-        self.zoomLayer = null;
+      // TODO(mack): use data attributes to store these
+      ctx._scaleX = outputScale.sx;
+      ctx._scaleY = outputScale.sy;
+      if (outputScale.scaled) {
+        ctx.scale(outputScale.sx, outputScale.sy);
       }
 
-      self.error = error;
-      self.stats = pdfPage.stats;
-      self.updateStats();
-      if (self.onAfterDraw) {
-        self.onAfterDraw();
-      }
+      // Rendering area
 
-      var event = document.createEvent('CustomEvent');
-      event.initCustomEvent('pagerender', true, true, {
-        pageNumber: pdfPage.pageNumber
-      });
-      div.dispatchEvent(event);
-
-      callback();
-    }
-
-    var renderContext = {
-      canvasContext: ctx,
-      viewport: this.viewport,
-      // intent: 'default', // === 'display'
-      continueCallback: function pdfViewcContinueCallback(cont) {
-        if (!self.renderingQueue.isHighestPriority(self)) {
-          self.renderingState = RenderingStates.PAUSED;
-          self.resume = function resumeCallback() {
-            self.renderingState = RenderingStates.RUNNING;
-            cont();
-          };
+      var self = this;
+      function pageViewDrawCallback(error) {
+        // The renderTask may have been replaced by a new one, so only remove
+        // the reference to the renderTask if it matches the one that is
+        // triggering this callback.
+        if (renderTask === self.renderTask) {
+          self.renderTask = null;
+        }
+
+        if (error === 'cancelled') {
           return;
         }
-        cont();
-      }
-    };
-    var renderTask = this.renderTask = this.pdfPage.render(renderContext);
-
-    this.renderTask.promise.then(
-      function pdfPageRenderCallback() {
-        pageViewDrawCallback(null);
-        if (textLayer) {
-          self.pdfPage.getTextContent().then(
-            function textContentResolved(textContent) {
-              textLayer.setTextContent(textContent);
-            }
-          );
+
+        self.renderingState = RenderingStates.FINISHED;
+
+        if (self.loadingIconDiv) {
+          div.removeChild(self.loadingIconDiv);
+          delete self.loadingIconDiv;
         }
-      },
-      function pdfPageRenderError(error) {
-        pageViewDrawCallback(error);
-      }
-    );
 
-    setupAnnotations(div, pdfPage, this.viewport);
-    div.setAttribute('data-loaded', true);
+        if (self.zoomLayer) {
+          div.removeChild(self.zoomLayer);
+          self.zoomLayer = null;
+        }
 
-    // Add the page to the cache at the start of drawing. That way it can be
-    // evicted from the cache and destroyed even if we pause its rendering.
-    cache.push(this);
-  };
+        self.error = error;
+        self.stats = pdfPage.stats;
+        self.updateStats();
+        if (self.onAfterDraw) {
+          self.onAfterDraw();
+        }
+
+        var event = document.createEvent('CustomEvent');
+        event.initCustomEvent('pagerender', true, true, {
+          pageNumber: pdfPage.pageNumber
+        });
+        div.dispatchEvent(event);
 
-  this.beforePrint = function pageViewBeforePrint() {
-    var pdfPage = this.pdfPage;
-
-    var viewport = pdfPage.getViewport(1);
-    // Use the same hack we use for high dpi displays for printing to get better
-    // output until bug 811002 is fixed in FF.
-    var PRINT_OUTPUT_SCALE = 2;
-    var canvas = document.createElement('canvas');
-    canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
-    canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
-    canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
-    canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
-    var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
-                              (1 / PRINT_OUTPUT_SCALE) + ')';
-    CustomStyle.setProp('transform' , canvas, cssScale);
-    CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
-
-    var printContainer = document.getElementById('printContainer');
-    var canvasWrapper = document.createElement('div');
-    canvasWrapper.style.width = viewport.width + 'pt';
-    canvasWrapper.style.height = viewport.height + 'pt';
-    canvasWrapper.appendChild(canvas);
-    printContainer.appendChild(canvasWrapper);
-
-    canvas.mozPrintCallback = function(obj) {
-      var ctx = obj.context;
-
-      ctx.save();
-      ctx.fillStyle = 'rgb(255, 255, 255)';
-      ctx.fillRect(0, 0, canvas.width, canvas.height);
-      ctx.restore();
-      ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
+        callback();
+      }
 
       var renderContext = {
         canvasContext: ctx,
-        viewport: viewport,
-        intent: 'print'
+        viewport: this.viewport,
+        // intent: 'default', // === 'display'
+        continueCallback: function pdfViewcContinueCallback(cont) {
+          if (!self.renderingQueue.isHighestPriority(self)) {
+            self.renderingState = RenderingStates.PAUSED;
+            self.resume = function resumeCallback() {
+              self.renderingState = RenderingStates.RUNNING;
+              cont();
+            };
+            return;
+          }
+          cont();
+        }
       };
+      var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+
+      this.renderTask.promise.then(
+        function pdfPageRenderCallback() {
+          pageViewDrawCallback(null);
+          if (textLayer) {
+            self.pdfPage.getTextContent().then(
+              function textContentResolved(textContent) {
+                textLayer.setTextContent(textContent);
+              }
+            );
+          }
+        },
+        function pdfPageRenderError(error) {
+          pageViewDrawCallback(error);
+        }
+      );
 
-      pdfPage.render(renderContext).promise.then(function() {
-        // Tell the printEngine that rendering this canvas/page has finished.
-        obj.done();
-      }, function(error) {
-        console.error(error);
-        // Tell the printEngine that rendering this canvas/page has failed.
-        // This will make the print proces stop.
-        if ('abort' in obj) {
-          obj.abort();
-        } else {
+      this.setupAnnotations();
+      div.setAttribute('data-loaded', true);
+
+      // Add the page to the cache at the start of drawing. That way it can be
+      // evicted from the cache and destroyed even if we pause its rendering.
+      this.cache.push(this);
+    },
+
+    beforePrint: function PDFPageView_beforePrint() {
+      var pdfPage = this.pdfPage;
+
+      var viewport = pdfPage.getViewport(1);
+      // Use the same hack we use for high dpi displays for printing to get
+      // better output until bug 811002 is fixed in FF.
+      var PRINT_OUTPUT_SCALE = 2;
+      var canvas = document.createElement('canvas');
+      canvas.width = Math.floor(viewport.width) * PRINT_OUTPUT_SCALE;
+      canvas.height = Math.floor(viewport.height) * PRINT_OUTPUT_SCALE;
+      canvas.style.width = (PRINT_OUTPUT_SCALE * viewport.width) + 'pt';
+      canvas.style.height = (PRINT_OUTPUT_SCALE * viewport.height) + 'pt';
+      var cssScale = 'scale(' + (1 / PRINT_OUTPUT_SCALE) + ', ' +
+                                (1 / PRINT_OUTPUT_SCALE) + ')';
+      CustomStyle.setProp('transform' , canvas, cssScale);
+      CustomStyle.setProp('transformOrigin' , canvas, '0% 0%');
+
+      var printContainer = document.getElementById('printContainer');
+      var canvasWrapper = document.createElement('div');
+      canvasWrapper.style.width = viewport.width + 'pt';
+      canvasWrapper.style.height = viewport.height + 'pt';
+      canvasWrapper.appendChild(canvas);
+      printContainer.appendChild(canvasWrapper);
+
+      canvas.mozPrintCallback = function(obj) {
+        var ctx = obj.context;
+
+        ctx.save();
+        ctx.fillStyle = 'rgb(255, 255, 255)';
+        ctx.fillRect(0, 0, canvas.width, canvas.height);
+        ctx.restore();
+        ctx.scale(PRINT_OUTPUT_SCALE, PRINT_OUTPUT_SCALE);
+
+        var renderContext = {
+          canvasContext: ctx,
+          viewport: viewport,
+          intent: 'print'
+        };
+
+        pdfPage.render(renderContext).promise.then(function() {
+          // Tell the printEngine that rendering this canvas/page has finished.
           obj.done();
-        }
-      });
-    };
-  };
+        }, function(error) {
+          console.error(error);
+          // Tell the printEngine that rendering this canvas/page has failed.
+          // This will make the print proces stop.
+          if ('abort' in obj) {
+            obj.abort();
+          } else {
+            obj.done();
+          }
+        });
+      };
+    },
 
-  this.updateStats = function pageViewUpdateStats() {
-    if (!this.stats) {
-      return;
-    }
+    updateStats: function PDFPageView_updateStats() {
+      if (!this.stats) {
+        return;
+      }
 
-    if (PDFJS.pdfBug && Stats.enabled) {
-      var stats = this.stats;
-      Stats.add(this.id, stats);
-    }
+      if (PDFJS.pdfBug && Stats.enabled) {
+        var stats = this.stats;
+        Stats.add(this.id, stats);
+      }
+    },
   };
-};
+
+  return PDFPageView;
+})();
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 190c37ed4..bd87a941a 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PageView, UNKNOWN_SCALE,
+ /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PDFPageView, UNKNOWN_SCALE,
            SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS,
            DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates,
            PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue */
@@ -236,10 +236,17 @@ var PDFViewer = (function pdfViewer() {
         var viewport = pdfPage.getViewport(scale * CSS_UNITS);
         for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
           var pageSource = new PDFPageSource(pdfDocument, pageNum);
-          var pageView = new PageView(this.viewer, pageNum, scale,
-                                      viewport.clone(), this.linkService,
-                                      this.renderingQueue, this.cache,
-                                      pageSource, this);
+          var pageView = new PDFPageView({
+            container: this.viewer,
+            id: pageNum,
+            scale: scale,
+            defaultViewport: viewport.clone(),
+            linkService: this.linkService,
+            renderingQueue: this.renderingQueue,
+            cache: this.cache,
+            pageSource: pageSource,
+            viewer: this
+          });
           bindOnAfterDraw(pageView);
           this.pages.push(pageView);
         }
@@ -398,7 +405,6 @@ var PDFViewer = (function pdfViewer() {
     scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
                                                               dest) {
       var pageView = this.pages[pageNumber - 1];
-      var pageViewDiv = pageView.el;
 
       if (this.presentationModeState ===
         PresentationModeState.FULLSCREEN) {
@@ -412,7 +418,7 @@ var PDFViewer = (function pdfViewer() {
         this._setScale(this.currentScaleValue, true);
       }
       if (!dest) {
-        scrollIntoView(pageViewDiv);
+        scrollIntoView(pageView.div);
         return;
       }
 
@@ -475,7 +481,7 @@ var PDFViewer = (function pdfViewer() {
       }
 
       if (scale === 'page-fit' && !dest[4]) {
-        scrollIntoView(pageViewDiv);
+        scrollIntoView(pageView.div);
         return;
       }
 
@@ -486,7 +492,7 @@ var PDFViewer = (function pdfViewer() {
       var left = Math.min(boundingRect[0][0], boundingRect[1][0]);
       var top = Math.min(boundingRect[0][1], boundingRect[1][1]);
 
-      scrollIntoView(pageViewDiv, { left: left, top: top });
+      scrollIntoView(pageView.div, { left: left, top: top });
     },
 
     _updateLocation: function (firstPage) {
diff --git a/web/viewer.js b/web/viewer.js
index 439fb0106..5528dd36c 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -16,7 +16,7 @@
  */
 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
            DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
-           PDFHistory, Preferences, SidebarView, ViewHistory, PageView,
+           PDFHistory, Preferences, SidebarView, ViewHistory,
            PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
            PasswordPrompt, PresentationMode, HandTool, Promise,
            DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
@@ -1353,7 +1353,6 @@ var PDFViewerApplication = {
 
   rotatePages: function pdfViewRotatePages(delta) {
     var pageNumber = this.page;
-
     this.pageRotation = (this.pageRotation + 360 + delta) % 360;
     this.pdfViewer.pagesRotation = this.pageRotation;
     this.pdfThumbnailViewer.pagesRotation = this.pageRotation;

From 7663942ee5ac344326b4b32a1e6e333209ab1e34 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Sun, 28 Sep 2014 09:35:33 -0500
Subject: [PATCH 03/10] Creates IPDFTextLayerFactory interface

---
 web/interfaces.js    | 14 ++++++++++++++
 web/pdf_page_view.js | 14 +++++++-------
 web/pdf_viewer.js    | 12 ++++++++----
 3 files changed, 29 insertions(+), 11 deletions(-)

diff --git a/web/interfaces.js b/web/interfaces.js
index 5f0ad04c6..91ac65d9b 100644
--- a/web/interfaces.js
+++ b/web/interfaces.js
@@ -83,3 +83,17 @@ ILastScrollSource.prototype = {
    */
   get lastScroll() {},
 };
+
+/**
+ * @interface
+ */
+function IPDFTextLayerFactory() {}
+IPDFTextLayerFactory.prototype = {
+  /**
+   * @param {HTMLDivElement} textLayerDiv
+   * @param {number} pageIndex
+   * @param {PageViewport} viewport
+   * @returns {TextLayerBuilder}
+   */
+  createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {}
+};
diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 7e6aa17b3..d0a081827 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -29,7 +29,7 @@
  * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
  * @property {Cache} cache - The page cache.
  * @property {PDFPageSource} pageSource
- * @property {PDFViewer} viewer
+ * @property {IPDFTextLayerFactory} textLayerFactory
  */
 
 /**
@@ -50,7 +50,7 @@ var PDFPageView = (function PDFPageViewClosure() {
     var renderingQueue = options.renderingQueue;
     var cache = options.cache;
     var pageSource = options.pageSource;
-    var viewer = options.viewer;
+    var textLayerFactory = options.textLayerFactory;
 
     this.id = id;
     this.renderingId = 'page' + id;
@@ -65,7 +65,7 @@ var PDFPageView = (function PDFPageViewClosure() {
     this.renderingQueue = renderingQueue;
     this.cache = cache;
     this.pageSource = pageSource;
-    this.viewer = viewer;
+    this.textLayerFactory = textLayerFactory;
 
     this.renderingState = RenderingStates.INITIAL;
     this.resume = null;
@@ -454,7 +454,7 @@ var PDFPageView = (function PDFPageViewClosure() {
 
       var textLayerDiv = null;
       var textLayer = null;
-      if (!PDFJS.disableTextLayer) {
+      if (this.textLayerFactory) {
         textLayerDiv = document.createElement('div');
         textLayerDiv.className = 'textLayer';
         textLayerDiv.style.width = canvas.style.width;
@@ -466,9 +466,9 @@ var PDFPageView = (function PDFPageViewClosure() {
           div.appendChild(textLayerDiv);
         }
 
-        textLayer = this.viewer.createTextLayerBuilder(textLayerDiv,
-                                                       this.id - 1,
-                                                       this.viewport);
+        textLayer = this.textLayerFactory.createTextLayerBuilder(textLayerDiv,
+                                                                 this.id - 1,
+                                                                 this.viewport);
       }
       this.textLayer = textLayer;
 
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index bd87a941a..292d765cd 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -236,6 +236,10 @@ var PDFViewer = (function pdfViewer() {
         var viewport = pdfPage.getViewport(scale * CSS_UNITS);
         for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
           var pageSource = new PDFPageSource(pdfDocument, pageNum);
+          var textLayerFactory = null;
+          if (!PDFJS.disableTextLayer) {
+            textLayerFactory = this;
+          }
           var pageView = new PDFPageView({
             container: this.viewer,
             id: pageNum,
@@ -245,7 +249,7 @@ var PDFViewer = (function pdfViewer() {
             renderingQueue: this.renderingQueue,
             cache: this.cache,
             pageSource: pageSource,
-            viewer: this
+            textLayerFactory: textLayerFactory
           });
           bindOnAfterDraw(pageView);
           this.pages.push(pageView);
@@ -629,9 +633,9 @@ var PDFViewer = (function pdfViewer() {
     },
 
     /**
-     * @param textLayerDiv {HTMLDivElement}
-     * @param pageIndex {number}
-     * @param viewport {PageViewport}
+     * @param {HTMLDivElement} textLayerDiv
+     * @param {number} pageIndex
+     * @param {PageViewport} viewport
      * @returns {TextLayerBuilder}
      */
     createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {

From fe4ac8678198937aca49c02fb7d2e83de183cd0e Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Mon, 29 Sep 2014 08:11:46 -0500
Subject: [PATCH 04/10] Removes PDFPageSource

---
 web/pdf_page_view.js  | 20 +-------------
 web/pdf_viewer.js     | 58 +++++++++++++++++++---------------------
 web/thumbnail_view.js | 61 +++++++++++++++++++++++++------------------
 web/viewer.js         |  2 +-
 4 files changed, 64 insertions(+), 77 deletions(-)

diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index d0a081827..38556efc6 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -28,7 +28,6 @@
  * @property {IPDFLinkService} linkService - The navigation/linking service.
  * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
  * @property {Cache} cache - The page cache.
- * @property {PDFPageSource} pageSource
  * @property {IPDFTextLayerFactory} textLayerFactory
  */
 
@@ -49,7 +48,6 @@ var PDFPageView = (function PDFPageViewClosure() {
     var linkService = options.linkService;
     var renderingQueue = options.renderingQueue;
     var cache = options.cache;
-    var pageSource = options.pageSource;
     var textLayerFactory = options.textLayerFactory;
 
     this.id = id;
@@ -64,7 +62,6 @@ var PDFPageView = (function PDFPageViewClosure() {
     this.linkService = linkService;
     this.renderingQueue = renderingQueue;
     this.cache = cache;
-    this.pageSource = pageSource;
     this.textLayerFactory = textLayerFactory;
 
     this.renderingState = RenderingStates.INITIAL;
@@ -378,28 +375,13 @@ var PDFPageView = (function PDFPageViewClosure() {
     },
 
     draw: function PDFPageView_draw(callback) {
-      var pdfPage = this.pdfPage;
-
-      if (this.pagePdfPromise) {
-        return;
-      }
-      if (!pdfPage) {
-        var promise = this.pageSource.getPage();
-        promise.then(function(pdfPage) {
-          delete this.pagePdfPromise;
-          this.setPdfPage(pdfPage);
-          this.draw(callback);
-        }.bind(this));
-        this.pagePdfPromise = promise;
-        return;
-      }
-
       if (this.renderingState !== RenderingStates.INITIAL) {
         console.error('Must be in new state before drawing');
       }
 
       this.renderingState = RenderingStates.RUNNING;
 
+      var pdfPage = this.pdfPage;
       var viewport = this.viewport;
       var div = this.div;
       // Wrap the canvas so if it has a css transform for highdpi the overflow
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 292d765cd..6441eae9e 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -235,7 +235,6 @@ var PDFViewer = (function pdfViewer() {
         var scale = this._currentScale || 1.0;
         var viewport = pdfPage.getViewport(scale * CSS_UNITS);
         for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-          var pageSource = new PDFPageSource(pdfDocument, pageNum);
           var textLayerFactory = null;
           if (!PDFJS.disableTextLayer) {
             textLayerFactory = this;
@@ -248,7 +247,6 @@ var PDFViewer = (function pdfViewer() {
             linkService: this.linkService,
             renderingQueue: this.renderingQueue,
             cache: this.cache,
-            pageSource: pageSource,
             textLayerFactory: textLayerFactory
           });
           bindOnAfterDraw(pageView);
@@ -299,6 +297,7 @@ var PDFViewer = (function pdfViewer() {
       this._currentScaleValue = null;
       this.location = null;
       this._pagesRotation = 0;
+      this._pagesRequests = [];
 
       var container = this.viewer;
       while (container.hasChildNodes()) {
@@ -614,13 +613,38 @@ var PDFViewer = (function pdfViewer() {
       }
     },
 
+    /**
+     * @param {PDFPageView} pageView
+     * @returns {PDFPage}
+     * @private
+     */
+    _ensurePdfPageLoaded: function (pageView) {
+      if (pageView.pdfPage) {
+        return Promise.resolve(pageView.pdfPage);
+      }
+      var pageNumber = pageView.id;
+      if (this._pagesRequests[pageNumber]) {
+        return this._pagesRequests[pageNumber];
+      }
+      var promise = this.pdfDocument.getPage(pageNumber).then(
+          function (pdfPage) {
+        pageView.setPdfPage(pdfPage);
+        this._pagesRequests[pageNumber] = null;
+        return pdfPage;
+      }.bind(this));
+      this._pagesRequests[pageNumber] = promise;
+      return promise;
+    },
+
     forceRendering: function (currentlyVisiblePages) {
       var visiblePages = currentlyVisiblePages || this._getVisiblePages();
       var pageView = this.renderingQueue.getHighestPriority(visiblePages,
                                                             this.pages,
                                                             this.scroll.down);
       if (pageView) {
-        this.renderingQueue.renderView(pageView);
+        this._ensurePdfPageLoaded(pageView).then(function () {
+          this.renderingQueue.renderView(pageView);
+        }.bind(this));
         return true;
       }
       return false;
@@ -705,31 +729,3 @@ var SimpleLinkService = (function SimpleLinkServiceClosure() {
   };
   return SimpleLinkService;
 })();
-
-/**
- * PDFPage object source.
- * @class
- */
-var PDFPageSource = (function PDFPageSourceClosure() {
-  /**
-   * @constructs
-   * @param {PDFDocument} pdfDocument
-   * @param {number} pageNumber
-   * @constructor
-   */
-  function PDFPageSource(pdfDocument, pageNumber) {
-    this.pdfDocument = pdfDocument;
-    this.pageNumber = pageNumber;
-  }
-
-  PDFPageSource.prototype = /** @lends PDFPageSource.prototype */ {
-    /**
-     * @returns {Promise<PDFPage>}
-     */
-    getPage: function () {
-      return this.pdfDocument.getPage(this.pageNumber);
-    }
-  };
-
-  return PDFPageSource;
-})();
diff --git a/web/thumbnail_view.js b/web/thumbnail_view.js
index 1e16a7de4..f8c207ee9 100644
--- a/web/thumbnail_view.js
+++ b/web/thumbnail_view.js
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals mozL10n, RenderingStates, Promise, scrollIntoView, PDFPageSource,
+/* globals mozL10n, RenderingStates, Promise, scrollIntoView,
            watchScroll, getVisibleElements */
 
 'use strict';
@@ -28,13 +28,11 @@ var THUMBNAIL_SCROLL_MARGIN = -19;
  * @param defaultViewport
  * @param linkService
  * @param renderingQueue
- * @param pageSource
  *
  * @implements {IRenderableView}
  */
 var ThumbnailView = function thumbnailView(container, id, defaultViewport,
-                                           linkService, renderingQueue,
-                                           pageSource) {
+                                           linkService, renderingQueue) {
   var anchor = document.createElement('a');
   anchor.href = linkService.getAnchorUrl('#page=' + id);
   anchor.title = mozL10n.get('thumb_page_title', {page: id}, 'Page {{page}}');
@@ -80,7 +78,6 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport,
   this.hasImage = false;
   this.renderingState = RenderingStates.INITIAL;
   this.renderingQueue = renderingQueue;
-  this.pageSource = pageSource;
 
   this.setPdfPage = function thumbnailViewSetPdfPage(pdfPage) {
     this.pdfPage = pdfPage;
@@ -143,15 +140,6 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport,
   };
 
   this.draw = function thumbnailViewDraw(callback) {
-    if (!this.pdfPage) {
-      var promise = this.pageSource.getPage(this.id);
-      promise.then(function(pdfPage) {
-        this.setPdfPage(pdfPage);
-        this.draw(callback);
-      }.bind(this));
-      return;
-    }
-
     if (this.renderingState !== RenderingStates.INITIAL) {
       console.error('Must be in new state before drawing');
     }
@@ -204,18 +192,14 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport,
     return tempCanvas;
   }
 
-  this.setImage = function thumbnailViewSetImage(img) {
-    if (!this.pdfPage) {
-      var promise = this.pageSource.getPage();
-      promise.then(function(pdfPage) {
-        this.setPdfPage(pdfPage);
-        this.setImage(img);
-      }.bind(this));
-      return;
-    }
+  this.setImage = function thumbnailViewSetImage(pageView) {
+    var img = pageView.canvas;
     if (this.hasImage || !img) {
       return;
     }
+    if (this.pdfPage) {
+      this.setPdfPage(pageView.pdfPage);
+    }
     this.renderingState = RenderingStates.FINISHED;
     var ctx = this.getPageDrawContext();
 
@@ -330,6 +314,7 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() {
     _resetView: function () {
       this.thumbnails = [];
       this._pagesRotation = 0;
+      this._pagesRequests = [];
     },
 
     setDocument: function (pdfDocument) {
@@ -351,15 +336,37 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() {
         var pagesCount = pdfDocument.numPages;
         var viewport = firstPage.getViewport(1.0);
         for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-          var pageSource = new PDFPageSource(pdfDocument, pageNum);
           var thumbnail = new ThumbnailView(this.container, pageNum,
                                             viewport.clone(), this.linkService,
-                                            this.renderingQueue, pageSource);
+                                            this.renderingQueue);
           this.thumbnails.push(thumbnail);
         }
       }.bind(this));
     },
 
+    /**
+     * @param {PDFPageView} pageView
+     * @returns {PDFPage}
+     * @private
+     */
+    _ensurePdfPageLoaded: function (thumbView) {
+      if (thumbView.pdfPage) {
+        return Promise.resolve(thumbView.pdfPage);
+      }
+      var pageNumber = thumbView.id;
+      if (this._pagesRequests[pageNumber]) {
+        return this._pagesRequests[pageNumber];
+      }
+      var promise = this.pdfDocument.getPage(pageNumber).then(
+        function (pdfPage) {
+          thumbView.setPdfPage(pdfPage);
+          this._pagesRequests[pageNumber] = null;
+          return pdfPage;
+        }.bind(this));
+      this._pagesRequests[pageNumber] = promise;
+      return promise;
+    },
+
     ensureThumbnailVisible:
         function PDFThumbnailViewer_ensureThumbnailVisible(page) {
       // Ensure that the thumbnail of the current page is visible
@@ -373,7 +380,9 @@ var PDFThumbnailViewer = (function pdfThumbnailViewer() {
                                                              this.thumbnails,
                                                              this.scroll.down);
       if (thumbView) {
-        this.renderingQueue.renderView(thumbView);
+        this._ensurePdfPageLoaded(thumbView).then(function () {
+          this.renderingQueue.renderView(thumbView);
+        }.bind(this));
         return true;
       }
       return false;
diff --git a/web/viewer.js b/web/viewer.js
index 5528dd36c..802d554dd 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -1734,7 +1734,7 @@ document.addEventListener('pagerendered', function (e) {
   var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
   var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
                       getThumbnail(pageIndex);
-  thumbnailView.setImage(pageView.canvas);
+  thumbnailView.setImage(pageView);
 
 //#if (FIREFOX || MOZCENTRAL)
 //if (pageView.textLayer && pageView.textLayer.textDivs &&

From 9f384bbb41f780b258d832d1c7170b570aedac95 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Mon, 29 Sep 2014 11:05:28 -0500
Subject: [PATCH 05/10] Creates AnnotationsLayerBuilder.

---
 web/annotations_layer_builder.js | 156 +++++++++++++++++++++++++++++++
 web/interfaces.js                |  13 +++
 web/pdf_page_view.js             | 132 ++++----------------------
 web/pdf_viewer.js                |  21 ++++-
 web/viewer.html                  |   1 +
 5 files changed, 208 insertions(+), 115 deletions(-)
 create mode 100644 web/annotations_layer_builder.js

diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js
new file mode 100644
index 000000000..309f7c033
--- /dev/null
+++ b/web/annotations_layer_builder.js
@@ -0,0 +1,156 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*globals PDFJS, CustomStyle, mozL10n */
+
+'use strict';
+
+/**
+ * @typedef {Object} AnnotationsLayerBuilderOptions
+ * @property {HTMLDivElement} pageDiv
+ * @property {PDFPage} pdfPage
+ * @property {IPDFLinkService} linkService
+ */
+
+/**
+ * @class
+ */
+var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() {
+  /**
+   * @param {AnnotationsLayerBuilderOptions} options
+   * @constructs AnnotationsLayerBuilder
+   */
+  function AnnotationsLayerBuilder(options) {
+    this.pageDiv = options.pageDiv;
+    this.pdfPage = options.pdfPage;
+    this.linkService = options.linkService;
+
+    this.div = null;
+  }
+  AnnotationsLayerBuilder.prototype =
+      /** @lends AnnotationsLayerBuilder.prototype */ {
+
+    /**
+     * @param {PageViewport} viewport
+     */
+    setupAnnotations:
+        function AnnotationsLayerBuilder_setupAnnotations(viewport) {
+      function bindLink(link, dest) {
+        link.href = linkService.getDestinationHash(dest);
+        link.onclick = function annotationsLayerBuilderLinksOnclick() {
+          if (dest) {
+            linkService.navigateTo(dest);
+          }
+          return false;
+        };
+        if (dest) {
+          link.className = 'internalLink';
+        }
+      }
+
+      function bindNamedAction(link, action) {
+        link.href = linkService.getAnchorUrl('');
+        link.onclick = function annotationsLayerBuilderNamedActionOnClick() {
+          linkService.executeNamedAction(action);
+          return false;
+        };
+        link.className = 'internalLink';
+      }
+
+      var linkService = this.linkService;
+      var pdfPage = this.pdfPage;
+      var self = this;
+
+      pdfPage.getAnnotations().then(function (annotationsData) {
+        viewport = viewport.clone({ dontFlip: true });
+        var transform = viewport.transform;
+        var transformStr = 'matrix(' + transform.join(',') + ')';
+        var data, element, i, ii;
+
+        if (self.div) {
+          // If an annotationLayer already exists, refresh its children's
+          // transformation matrices
+          for (i = 0, ii = annotationsData.length; i < ii; i++) {
+            data = annotationsData[i];
+            element = self.div.querySelector(
+                '[data-annotation-id="' + data.id + '"]');
+            if (element) {
+              CustomStyle.setProp('transform', element, transformStr);
+            }
+          }
+          // See PDFPageView.reset()
+          self.div.removeAttribute('hidden');
+        } else {
+          for (i = 0, ii = annotationsData.length; i < ii; i++) {
+            data = annotationsData[i];
+            if (!data || !data.hasHtml) {
+              continue;
+            }
+
+            element = PDFJS.AnnotationUtils.getHtmlElement(data,
+              pdfPage.commonObjs);
+            element.setAttribute('data-annotation-id', data.id);
+            mozL10n.translate(element);
+
+            var rect = data.rect;
+            var view = pdfPage.view;
+            rect = PDFJS.Util.normalizeRect([
+              rect[0],
+                view[3] - rect[1] + view[1],
+              rect[2],
+                view[3] - rect[3] + view[1]
+            ]);
+            element.style.left = rect[0] + 'px';
+            element.style.top = rect[1] + 'px';
+            element.style.position = 'absolute';
+
+            CustomStyle.setProp('transform', element, transformStr);
+            var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
+            CustomStyle.setProp('transformOrigin', element, transformOriginStr);
+
+            if (data.subtype === 'Link' && !data.url) {
+              var link = element.getElementsByTagName('a')[0];
+              if (link) {
+                if (data.action) {
+                  bindNamedAction(link, data.action);
+                } else {
+                  bindLink(link, ('dest' in data) ? data.dest : null);
+                }
+              }
+            }
+
+            if (!self.div) {
+              var annotationLayerDiv = document.createElement('div');
+              annotationLayerDiv.className = 'annotationLayer';
+              self.pageDiv.appendChild(annotationLayerDiv);
+              self.div = annotationLayerDiv;
+            }
+
+            self.div.appendChild(element);
+          }
+        }
+      });
+    },
+
+    hide: function () {
+      if (!this.div) {
+        return;
+      }
+      this.div.setAttribute('hidden', 'true');
+    }
+  };
+  return AnnotationsLayerBuilder;
+})();
diff --git a/web/interfaces.js b/web/interfaces.js
index 91ac65d9b..1cf9ee8d7 100644
--- a/web/interfaces.js
+++ b/web/interfaces.js
@@ -97,3 +97,16 @@ IPDFTextLayerFactory.prototype = {
    */
   createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {}
 };
+
+/**
+ * @interface
+ */
+function IPDFAnnotationsLayerFactory() {}
+IPDFAnnotationsLayerFactory.prototype = {
+  /**
+   * @param {HTMLDivElement} pageDiv
+   * @param {PDFPage} pdfPage
+   * @returns {AnnotationsLayerBuilder}
+   */
+  createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {}
+};
diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 38556efc6..87228c3e5 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -14,8 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals RenderingStates, PDFJS, mozL10n, CustomStyle, getOutputScale, Stats,
-           CSS_UNITS */
+/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale,
+           Stats */
 
 'use strict';
 
@@ -25,10 +25,10 @@
  * @property {number} id - The page unique ID (normally its number).
  * @property {number} scale - The page scale display.
  * @property {PageViewport} defaultViewport - The page viewport.
- * @property {IPDFLinkService} linkService - The navigation/linking service.
  * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
  * @property {Cache} cache - The page cache.
  * @property {IPDFTextLayerFactory} textLayerFactory
+ * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory
  */
 
 /**
@@ -45,10 +45,10 @@ var PDFPageView = (function PDFPageViewClosure() {
     var id = options.id;
     var scale = options.scale;
     var defaultViewport = options.defaultViewport;
-    var linkService = options.linkService;
     var renderingQueue = options.renderingQueue;
     var cache = options.cache;
     var textLayerFactory = options.textLayerFactory;
+    var annotationsLayerFactory = options.annotationsLayerFactory;
 
     this.id = id;
     this.renderingId = 'page' + id;
@@ -59,10 +59,10 @@ var PDFPageView = (function PDFPageViewClosure() {
     this.pdfPageRotate = defaultViewport.rotation;
     this.hasRestrictedScaling = false;
 
-    this.linkService = linkService;
     this.renderingQueue = renderingQueue;
     this.cache = cache;
     this.textLayerFactory = textLayerFactory;
+    this.annotationsLayerFactory = annotationsLayerFactory;
 
     this.renderingState = RenderingStates.INITIAL;
     this.resume = null;
@@ -119,10 +119,12 @@ var PDFPageView = (function PDFPageViewClosure() {
       div.style.height = Math.floor(this.viewport.height) + 'px';
 
       var childNodes = div.childNodes;
+      var currentZoomLayer = this.zoomLayer || null;
+      var currentAnnotationNode = (keepAnnotations && this.annotationLayer &&
+                                   this.annotationLayer.div) || null;
       for (var i = div.childNodes.length - 1; i >= 0; i--) {
         var node = childNodes[i];
-        if ((this.zoomLayer && this.zoomLayer === node) ||
-            (keepAnnotations && this.annotationLayer === node)) {
+        if (currentZoomLayer === node || currentAnnotationNode === node) {
           continue;
         }
         div.removeChild(node);
@@ -133,7 +135,7 @@ var PDFPageView = (function PDFPageViewClosure() {
         if (this.annotationLayer) {
           // Hide annotationLayer until all elements are resized
           // so they are not displayed on the already-resized page
-          this.annotationLayer.setAttribute('hidden', 'true');
+          this.annotationLayer.hide();
         }
       } else {
         this.annotationLayer = null;
@@ -258,7 +260,7 @@ var PDFPageView = (function PDFPageViewClosure() {
       }
 
       if (redrawAnnotations && this.annotationLayer) {
-        this.setupAnnotations();
+        this.annotationLayer.setupAnnotations(this.viewport);
       }
     },
 
@@ -270,106 +272,6 @@ var PDFPageView = (function PDFPageViewClosure() {
       return this.viewport.height;
     },
 
-    setupAnnotations: function PDFPageView_setupAnnotations() {
-      function bindLink(link, dest) {
-        link.href = linkService.getDestinationHash(dest);
-        link.onclick = function pageViewSetupLinksOnclick() {
-          if (dest) {
-            linkService.navigateTo(dest);
-          }
-          return false;
-        };
-        if (dest) {
-          link.className = 'internalLink';
-        }
-      }
-
-      function bindNamedAction(link, action) {
-        link.href = linkService.getAnchorUrl('');
-        link.onclick = function pageViewSetupNamedActionOnClick() {
-          linkService.executeNamedAction(action);
-          return false;
-        };
-        link.className = 'internalLink';
-      }
-
-      var linkService = this.linkService;
-      var pageDiv = this.div;
-      var pdfPage = this.pdfPage;
-      var viewport = this.viewport;
-      var self = this;
-
-      pdfPage.getAnnotations().then(function(annotationsData) {
-        viewport = viewport.clone({ dontFlip: true });
-        var transform = viewport.transform;
-        var transformStr = 'matrix(' + transform.join(',') + ')';
-        var data, element, i, ii;
-
-        if (self.annotationLayer) {
-          // If an annotationLayer already exists, refresh its children's
-          // transformation matrices
-          for (i = 0, ii = annotationsData.length; i < ii; i++) {
-            data = annotationsData[i];
-            element = self.annotationLayer.querySelector(
-                '[data-annotation-id="' + data.id + '"]');
-            if (element) {
-              CustomStyle.setProp('transform', element, transformStr);
-            }
-          }
-          // See this.reset()
-          self.annotationLayer.removeAttribute('hidden');
-        } else {
-          for (i = 0, ii = annotationsData.length; i < ii; i++) {
-            data = annotationsData[i];
-            if (!data || !data.hasHtml) {
-              continue;
-            }
-
-            element = PDFJS.AnnotationUtils.getHtmlElement(data,
-              pdfPage.commonObjs);
-            element.setAttribute('data-annotation-id', data.id);
-            mozL10n.translate(element);
-
-            var rect = data.rect;
-            var view = pdfPage.view;
-            rect = PDFJS.Util.normalizeRect([
-              rect[0],
-                view[3] - rect[1] + view[1],
-              rect[2],
-                view[3] - rect[3] + view[1]
-            ]);
-            element.style.left = rect[0] + 'px';
-            element.style.top = rect[1] + 'px';
-            element.style.position = 'absolute';
-
-            CustomStyle.setProp('transform', element, transformStr);
-            var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
-            CustomStyle.setProp('transformOrigin', element, transformOriginStr);
-
-            if (data.subtype === 'Link' && !data.url) {
-              var link = element.getElementsByTagName('a')[0];
-              if (link) {
-                if (data.action) {
-                  bindNamedAction(link, data.action);
-                } else {
-                  bindLink(link, ('dest' in data) ? data.dest : null);
-                }
-              }
-            }
-
-            if (!self.annotationLayer) {
-              var annotationLayerDiv = document.createElement('div');
-              annotationLayerDiv.className = 'annotationLayer';
-              pageDiv.appendChild(annotationLayerDiv);
-              self.annotationLayer = annotationLayerDiv;
-            }
-
-            self.annotationLayer.appendChild(element);
-          }
-        }
-      });
-    },
-
     getPagePoint: function PDFPageView_getPagePoint(x, y) {
       return this.viewport.convertToPdfPoint(x, y);
     },
@@ -396,7 +298,7 @@ var PDFPageView = (function PDFPageViewClosure() {
       canvasWrapper.appendChild(canvas);
       if (this.annotationLayer) {
         // annotationLayer needs to stay on top
-        div.insertBefore(canvasWrapper, this.annotationLayer);
+        div.insertBefore(canvasWrapper, this.annotationLayer.div);
       } else {
         div.appendChild(canvasWrapper);
       }
@@ -443,7 +345,7 @@ var PDFPageView = (function PDFPageViewClosure() {
         textLayerDiv.style.height = canvas.style.height;
         if (this.annotationLayer) {
           // annotationLayer needs to stay on top
-          div.insertBefore(textLayerDiv, this.annotationLayer);
+          div.insertBefore(textLayerDiv, this.annotationLayer.div);
         } else {
           div.appendChild(textLayerDiv);
         }
@@ -538,7 +440,13 @@ var PDFPageView = (function PDFPageViewClosure() {
         }
       );
 
-      this.setupAnnotations();
+      if (this.annotationsLayerFactory) {
+        if (!this.annotationLayer) {
+          this.annotationLayer = this.annotationsLayerFactory.
+            createAnnotationsLayerBuilder(div, this.pdfPage);
+        }
+        this.annotationLayer.setupAnnotations(this.viewport);
+      }
       div.setAttribute('data-loaded', true);
 
       // Add the page to the cache at the start of drawing. That way it can be
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 6441eae9e..84483224c 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -17,7 +17,8 @@
  /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PDFPageView, UNKNOWN_SCALE,
            SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS,
            DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates,
-           PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue */
+           PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue,
+           AnnotationsLayerBuilder */
 
 'use strict';
 
@@ -33,6 +34,7 @@ var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
 //#include pdf_rendering_queue.js
 //#include pdf_page_view.js
 //#include text_layer_builder.js
+//#include annotations_layer_builder.js
 
 /**
  * @typedef {Object} PDFViewerOptions
@@ -244,10 +246,10 @@ var PDFViewer = (function pdfViewer() {
             id: pageNum,
             scale: scale,
             defaultViewport: viewport.clone(),
-            linkService: this.linkService,
             renderingQueue: this.renderingQueue,
             cache: this.cache,
-            textLayerFactory: textLayerFactory
+            textLayerFactory: textLayerFactory,
+            annotationsLayerFactory: this
           });
           bindOnAfterDraw(pageView);
           this.pages.push(pageView);
@@ -675,6 +677,19 @@ var PDFViewer = (function pdfViewer() {
       });
     },
 
+    /**
+     * @param {HTMLDivElement} pageDiv
+     * @param {PDFPage} pdfPage
+     * @returns {AnnotationsLayerBuilder}
+     */
+    createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {
+      return new AnnotationsLayerBuilder({
+        pageDiv: pageDiv,
+        pdfPage: pdfPage,
+        linkService: this.linkService
+      });
+    },
+
     setFindController: function (findController) {
       this.findController = findController;
     },
diff --git a/web/viewer.html b/web/viewer.html
index c2d3dff69..d6588d940 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -71,6 +71,7 @@ http://sourceforge.net/adobe/cmap/wiki/License/
     <script src="pdf_rendering_queue.js"></script>
     <script src="pdf_page_view.js"></script>
     <script src="text_layer_builder.js"></script>
+    <script src="annotations_layer_builder.js"></script>
     <script src="pdf_viewer.js"></script>
     <script src="thumbnail_view.js"></script>
     <script src="document_outline_view.js"></script>

From 22c62685b0f7e6d2009f3a6d0419b51817851c99 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Mon, 29 Sep 2014 11:15:06 -0500
Subject: [PATCH 06/10] Removes Stats dependency from PDFPageView.

---
 web/pdf_page_view.js | 15 +--------------
 web/pdf_viewer.js    |  1 -
 web/viewer.js        | 19 ++++++++++++++++---
 3 files changed, 17 insertions(+), 18 deletions(-)

diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 87228c3e5..3ebe41427 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -14,8 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale,
-           Stats */
+/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale  */
 
 'use strict';
 
@@ -392,7 +391,6 @@ var PDFPageView = (function PDFPageViewClosure() {
 
         self.error = error;
         self.stats = pdfPage.stats;
-        self.updateStats();
         if (self.onAfterDraw) {
           self.onAfterDraw();
         }
@@ -508,17 +506,6 @@ var PDFPageView = (function PDFPageViewClosure() {
         });
       };
     },
-
-    updateStats: function PDFPageView_updateStats() {
-      if (!this.stats) {
-        return;
-      }
-
-      if (PDFJS.pdfBug && Stats.enabled) {
-        var stats = this.stats;
-        Stats.add(this.id, stats);
-      }
-    },
   };
 
   return PDFPageView;
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 84483224c..42e9efaf2 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -107,7 +107,6 @@ var PDFViewer = (function pdfViewer() {
         return;
       }
 
-      this.pages[val - 1].updateStats();
       event.previousPageNumber = this._currentPageNumber;
       this._currentPageNumber = val;
       event.pageNumber = val;
diff --git a/web/viewer.js b/web/viewer.js
index 802d554dd..c00a43bcd 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -16,7 +16,7 @@
  */
 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, ProgressBar,
            DownloadManager, getFileName, scrollIntoView, getPDFFileNameFromURL,
-           PDFHistory, Preferences, SidebarView, ViewHistory,
+           PDFHistory, Preferences, SidebarView, ViewHistory, Stats,
            PDFThumbnailViewer, URL, noContextMenuHandler, SecondaryToolbar,
            PasswordPrompt, PresentationMode, HandTool, Promise,
            DocumentProperties, DocumentOutlineView, DocumentAttachmentsView,
@@ -1730,12 +1730,17 @@ function webViewerInitialized() {
 document.addEventListener('DOMContentLoaded', webViewerLoad, true);
 
 document.addEventListener('pagerendered', function (e) {
-  var pageIndex = e.detail.pageNumber - 1;
+  var pageNumber = e.detail.pageNumber;
+  var pageIndex = pageNumber - 1;
   var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
   var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
                       getThumbnail(pageIndex);
   thumbnailView.setImage(pageView);
 
+  if (PDFJS.pdfBug && Stats.enabled && pageView.stats) {
+    Stats.add(pageNumber, pageView.stats);
+  }
+
 //#if (FIREFOX || MOZCENTRAL)
 //if (pageView.textLayer && pageView.textLayer.textDivs &&
 //    pageView.textLayer.textDivs.length > 0 &&
@@ -1768,7 +1773,7 @@ document.addEventListener('pagerendered', function (e) {
 
   // If the page is still visible when it has finished rendering,
   // ensure that the page number input loading indicator is hidden.
-  if ((pageIndex + 1) === PDFViewerApplication.page) {
+  if (pageNumber === PDFViewerApplication.page) {
     var pageNumberInput = document.getElementById('pageNumber');
     pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
   }
@@ -1965,6 +1970,14 @@ window.addEventListener('pagechange', function pagechange(evt) {
   document.getElementById('firstPage').disabled = (page <= 1);
   document.getElementById('lastPage').disabled = (page >= numPages);
 
+  // we need to update stats
+  if (PDFJS.pdfBug && Stats.enabled) {
+    var pageView = PDFViewerApplication.pdfViewer.getPageView(page - 1);
+    if (pageView.stats) {
+      Stats.add(page, pageView.stats);
+    }
+  }
+
   // checking if the this.page was called from the updateViewarea function
   if (evt.updateInProgress) {
     return;

From b930228788c64414d6040bca6941cc734158df31 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Mon, 29 Sep 2014 11:32:45 -0500
Subject: [PATCH 07/10] Refactors Cache into PDFPageViewBuffer

---
 web/pdf_page_view.js | 12 ++++++------
 web/pdf_viewer.js    | 38 ++++++++++++++++++++++++++++++++------
 web/ui_utils.js      | 21 ---------------------
 3 files changed, 38 insertions(+), 33 deletions(-)

diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 3ebe41427..6c8ea8c29 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -25,7 +25,6 @@
  * @property {number} scale - The page scale display.
  * @property {PageViewport} defaultViewport - The page viewport.
  * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
- * @property {Cache} cache - The page cache.
  * @property {IPDFTextLayerFactory} textLayerFactory
  * @property {IPDFAnnotationsLayerFactory} annotationsLayerFactory
  */
@@ -45,7 +44,6 @@ var PDFPageView = (function PDFPageViewClosure() {
     var scale = options.scale;
     var defaultViewport = options.defaultViewport;
     var renderingQueue = options.renderingQueue;
-    var cache = options.cache;
     var textLayerFactory = options.textLayerFactory;
     var annotationsLayerFactory = options.annotationsLayerFactory;
 
@@ -59,13 +57,15 @@ var PDFPageView = (function PDFPageViewClosure() {
     this.hasRestrictedScaling = false;
 
     this.renderingQueue = renderingQueue;
-    this.cache = cache;
     this.textLayerFactory = textLayerFactory;
     this.annotationsLayerFactory = annotationsLayerFactory;
 
     this.renderingState = RenderingStates.INITIAL;
     this.resume = null;
 
+    this.onBeforeDraw = null;
+    this.onAfterDraw = null;
+
     this.textLayer = null;
 
     this.zoomLayer = null;
@@ -447,9 +447,9 @@ var PDFPageView = (function PDFPageViewClosure() {
       }
       div.setAttribute('data-loaded', true);
 
-      // Add the page to the cache at the start of drawing. That way it can be
-      // evicted from the cache and destroyed even if we pause its rendering.
-      this.cache.push(this);
+      if (self.onBeforeDraw) {
+        self.onBeforeDraw();
+      }
     },
 
     beforePrint: function PDFPageView_beforePrint() {
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 42e9efaf2..1ad1c0c7e 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -14,7 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
- /*globals watchScroll, Cache, DEFAULT_CACHE_SIZE, PDFPageView, UNKNOWN_SCALE,
+ /*globals watchScroll, PDFPageView, UNKNOWN_SCALE,
            SCROLLBAR_PADDING, VERTICAL_PADDING, MAX_AUTO_SCALE, CSS_UNITS,
            DEFAULT_SCALE, scrollIntoView, getVisibleElements, RenderingStates,
            PDFJS, Promise, TextLayerBuilder, PDFRenderingQueue,
@@ -30,6 +30,7 @@ var PresentationModeState = {
 };
 
 var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
+var DEFAULT_CACHE_SIZE = 10;
 
 //#include pdf_rendering_queue.js
 //#include pdf_page_view.js
@@ -52,6 +53,26 @@ var IGNORE_CURRENT_POSITION_ON_ZOOM = false;
  * @implements {IRenderableView}
  */
 var PDFViewer = (function pdfViewer() {
+  function PDFPageViewBuffer(size) {
+    var data = [];
+    this.push = function cachePush(view) {
+      var i = data.indexOf(view);
+      if (i >= 0) {
+        data.splice(i, 1);
+      }
+      data.push(view);
+      if (data.length > size) {
+        data.shift().destroy();
+      }
+    };
+    this.resize = function (newSize) {
+      size = newSize;
+      while (data.length > size) {
+        data.shift().destroy();
+      }
+    };
+  }
+
   /**
    * @constructs PDFViewer
    * @param {PDFViewerOptions} options
@@ -212,7 +233,13 @@ var PDFViewer = (function pdfViewer() {
       });
       this.onePageRendered = onePageRendered;
 
-      var bindOnAfterDraw = function (pageView) {
+      var bindOnAfterAndBeforeDraw = function (pageView) {
+        pageView.onBeforeDraw = function pdfViewLoadOnBeforeDraw() {
+          // Add the page to the buffer at the start of drawing. That way it can
+          // be evicted from the buffer and destroyed even if we pause its
+          // rendering.
+          self._buffer.push(this);
+        };
         // when page is painted, using the image as thumbnail base
         pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
           if (!isOnePageRenderedResolved) {
@@ -246,11 +273,10 @@ var PDFViewer = (function pdfViewer() {
             scale: scale,
             defaultViewport: viewport.clone(),
             renderingQueue: this.renderingQueue,
-            cache: this.cache,
             textLayerFactory: textLayerFactory,
             annotationsLayerFactory: this
           });
-          bindOnAfterDraw(pageView);
+          bindOnAfterAndBeforeDraw(pageView);
           this.pages.push(pageView);
         }
 
@@ -291,11 +317,11 @@ var PDFViewer = (function pdfViewer() {
     },
 
     _resetView: function () {
-      this.cache = new Cache(DEFAULT_CACHE_SIZE);
       this.pages = [];
       this._currentPageNumber = 1;
       this._currentScale = UNKNOWN_SCALE;
       this._currentScaleValue = null;
+      this._buffer = new PDFPageViewBuffer(DEFAULT_CACHE_SIZE);
       this.location = null;
       this._pagesRotation = 0;
       this._pagesRequests = [];
@@ -538,7 +564,7 @@ var PDFViewer = (function pdfViewer() {
 
       var suggestedCacheSize = Math.max(DEFAULT_CACHE_SIZE,
           2 * visiblePages.length + 1);
-      this.cache.resize(suggestedCacheSize);
+      this._buffer.resize(suggestedCacheSize);
 
       this.renderingQueue.renderHighestPriority(visible);
 
diff --git a/web/ui_utils.js b/web/ui_utils.js
index b4f2d7365..3c32c5939 100644
--- a/web/ui_utils.js
+++ b/web/ui_utils.js
@@ -22,7 +22,6 @@ var UNKNOWN_SCALE = 0;
 var MAX_AUTO_SCALE = 1.25;
 var SCROLLBAR_PADDING = 40;
 var VERTICAL_PADDING = 5;
-var DEFAULT_CACHE_SIZE = 10;
 
 // optimised CSS custom property getter/setter
 var CustomStyle = (function CustomStyleClosure() {
@@ -349,23 +348,3 @@ var ProgressBar = (function ProgressBarClosure() {
 
   return ProgressBar;
 })();
-
-var Cache = function cacheCache(size) {
-  var data = [];
-  this.push = function cachePush(view) {
-    var i = data.indexOf(view);
-    if (i >= 0) {
-      data.splice(i, 1);
-    }
-    data.push(view);
-    if (data.length > size) {
-      data.shift().destroy();
-    }
-  };
-  this.resize = function (newSize) {
-    size = newSize;
-    while (data.length > size) {
-      data.shift().destroy();
-    }
-  };
-};

From 2ac7ac4678519d8fcfa0f5e703531ba71bf0859d Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Wed, 17 Dec 2014 14:12:51 -0600
Subject: [PATCH 08/10] Removes lastScrollSource and isViewerInPresentationMode
 from TextLayerBuilderOptions

---
 web/interfaces.js          | 11 --------
 web/pdf_find_controller.js | 25 ++++++++++++++++-
 web/pdf_page_view.js       | 12 ++++++++
 web/pdf_viewer.js          | 11 +++-----
 web/text_layer_builder.js  | 56 ++++++++++++++++++--------------------
 5 files changed, 67 insertions(+), 48 deletions(-)

diff --git a/web/interfaces.js b/web/interfaces.js
index 1cf9ee8d7..b99338c49 100644
--- a/web/interfaces.js
+++ b/web/interfaces.js
@@ -73,17 +73,6 @@ IRenderableView.prototype = {
   resume: function () {},
 };
 
-/**
- * @interface
- */
-function ILastScrollSource() {}
-ILastScrollSource.prototype = {
-  /**
-   * @returns {number}
-   */
-  get lastScroll() {},
-};
-
 /**
  * @interface
  */
diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js
index c54fc8ade..0d3d1d5ae 100644
--- a/web/pdf_find_controller.js
+++ b/web/pdf_find_controller.js
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals PDFJS, FirefoxCom, Promise */
+/* globals PDFJS, FirefoxCom, Promise, scrollIntoView */
 
 'use strict';
 
@@ -24,6 +24,9 @@ var FindStates = {
   FIND_PENDING: 3
 };
 
+var FIND_SCROLL_OFFSET_TOP = -50;
+var FIND_SCROLL_OFFSET_LEFT = -400;
+
 /**
  * Provides "search" or "find" functionality for the PDF.
  * This object actually performs the search for a given string.
@@ -308,6 +311,26 @@ var PDFFindController = (function PDFFindControllerClosure() {
       }
     },
 
+    /**
+     * The method is called back from the text layer when match presentation
+     * is updated.
+     * @param {number} pageIndex - page index.
+     * @param {number} index - match index.
+     * @param {Array} elements - text layer div elements array.
+     * @param {number} beginIdx - start index of the div array for the match.
+     * @param {number} endIdx - end index of the div array for the match.
+     */
+    updateMatchPosition: function PDFFindController_updateMatchPosition(
+        pageIndex, index, elements, beginIdx, endIdx) {
+      if (this.selected.matchIdx === index &&
+          this.selected.pageIdx === pageIndex) {
+        scrollIntoView(elements[beginIdx], {
+          top: FIND_SCROLL_OFFSET_TOP,
+          left: FIND_SCROLL_OFFSET_LEFT
+        });
+      }
+    },
+
     nextPageMatch: function PDFFindController_nextPageMatch() {
       if (this.resumePageIdx !== null) {
         console.error('There can only be one pending page.');
diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 6c8ea8c29..50d3c2b9b 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -18,6 +18,8 @@
 
 'use strict';
 
+var TEXT_LAYER_RENDER_DELAY = 200; // ms
+
 /**
  * @typedef {Object} PDFPageViewOptions
  * @property {HTMLDivElement} container - The viewer element.
@@ -194,6 +196,15 @@ var PDFPageView = (function PDFPageViewClosure() {
       this.reset(true);
     },
 
+    /**
+     * Called when moved in the parent's container.
+     */
+    updatePosition: function PDFPageView_updatePosition() {
+      if (this.textLayer) {
+        this.textLayer.render(TEXT_LAYER_RENDER_DELAY);
+      }
+    },
+
     cssTransform: function PDFPageView_transform(canvas, redrawAnnotations) {
       // Scale canvas, canvas wrapper, and page container.
       var width = this.viewport.width;
@@ -429,6 +440,7 @@ var PDFPageView = (function PDFPageViewClosure() {
             self.pdfPage.getTextContent().then(
               function textContentResolved(textContent) {
                 textLayer.setTextContent(textContent);
+                textLayer.render(TEXT_LAYER_RENDER_DELAY);
               }
             );
           }
diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js
index 1ad1c0c7e..e68130ce5 100644
--- a/web/pdf_viewer.js
+++ b/web/pdf_viewer.js
@@ -49,7 +49,6 @@ var DEFAULT_CACHE_SIZE = 10;
 /**
  * Simple viewer control to display PDF content/pages.
  * @class
- * @implements {ILastScrollSource}
  * @implements {IRenderableView}
  */
 var PDFViewer = (function pdfViewer() {
@@ -92,7 +91,6 @@ var PDFViewer = (function pdfViewer() {
     }
 
     this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
-    this.lastScroll = 0;
     this.updateInProgress = false;
     this.presentationModeState = PresentationModeState.UNKNOWN;
     this._resetView();
@@ -333,12 +331,13 @@ var PDFViewer = (function pdfViewer() {
     },
 
     _scrollUpdate: function () {
-      this.lastScroll = Date.now();
-
       if (this.pagesCount === 0) {
         return;
       }
       this.update();
+      for (var i = 0, ii = this.pages.length; i < ii; i++) {
+        this.pages[i].updatePosition();
+      }
     },
 
     _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
@@ -696,9 +695,7 @@ var PDFViewer = (function pdfViewer() {
         textLayerDiv: textLayerDiv,
         pageIndex: pageIndex,
         viewport: viewport,
-        lastScrollSource: this,
-        isViewerInPresentationMode: isViewerInPresentationMode,
-        findController: this.findController
+        findController: isViewerInPresentationMode ? null : this.findController
       });
     },
 
diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js
index 5fac1c493..d300f6354 100644
--- a/web/text_layer_builder.js
+++ b/web/text_layer_builder.js
@@ -13,14 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals CustomStyle, scrollIntoView, PDFJS */
+/* globals CustomStyle, PDFJS */
 
 'use strict';
 
-var FIND_SCROLL_OFFSET_TOP = -50;
-var FIND_SCROLL_OFFSET_LEFT = -400;
 var MAX_TEXT_DIVS_TO_RENDER = 100000;
-var RENDER_DELAY = 200; // ms
 
 var NonWhitespaceRegexp = /\S/;
 
@@ -33,9 +30,6 @@ function isAllWhitespace(str) {
  * @property {HTMLDivElement} textLayerDiv - The text layer container.
  * @property {number} pageIndex - The page index.
  * @property {PageViewport} viewport - The viewport of the text layer.
- * @property {ILastScrollSource} lastScrollSource - The object that records when
- *   last time scroll happened.
- * @property {boolean} isViewerInPresentationMode
  * @property {PDFFindController} findController
  */
 
@@ -49,13 +43,11 @@ function isAllWhitespace(str) {
 var TextLayerBuilder = (function TextLayerBuilderClosure() {
   function TextLayerBuilder(options) {
     this.textLayerDiv = options.textLayerDiv;
-    this.layoutDone = false;
+    this.renderingDone = false;
     this.divContentDone = false;
     this.pageIdx = options.pageIndex;
     this.matches = [];
-    this.lastScrollSource = options.lastScrollSource || null;
     this.viewport = options.viewport;
-    this.isViewerInPresentationMode = options.isViewerInPresentationMode;
     this.textDivs = [];
     this.findController = options.findController || null;
   }
@@ -71,6 +63,7 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
       // No point in rendering many divs as it would make the browser
       // unusable even after the divs are rendered.
       if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
+        this.renderingDone = true;
         return;
       }
 
@@ -118,23 +111,29 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
       this.updateMatches();
     },
 
-    setupRenderLayoutTimer:
-        function TextLayerBuilder_setupRenderLayoutTimer() {
-      // Schedule renderLayout() if the user has been scrolling,
-      // otherwise run it right away.
-      var self = this;
-      var lastScroll = (this.lastScrollSource === null ?
-                        0 : this.lastScrollSource.lastScroll);
+    /**
+     * Renders the text layer.
+     * @param {number} timeout (optional) if specified, the rendering waits
+     *   for specified amount of ms.
+     */
+    render: function TextLayerBuilder_render(timeout) {
+      if (!this.divContentDone || this.renderingDone) {
+        return;
+      }
 
-      if (Date.now() - lastScroll > RENDER_DELAY) { // Render right away
+      if (this.renderTimer) {
+        clearTimeout(this.renderTimer);
+        this.renderTimer = null;
+      }
+
+      if (!timeout) { // Render right away
         this.renderLayer();
       } else { // Schedule
-        if (this.renderTimer) {
-          clearTimeout(this.renderTimer);
-        }
+        var self = this;
         this.renderTimer = setTimeout(function() {
-          self.setupRenderLayoutTimer();
-        }, RENDER_DELAY);
+          self.renderLayer();
+          self.renderTimer = null;
+        }, timeout);
       }
     },
 
@@ -204,7 +203,6 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
         this.appendText(textItems[i], textContent.styles);
       }
       this.divContentDone = true;
-      this.setupRenderLayoutTimer();
     },
 
     convertMatches: function TextLayerBuilder_convertMatches(matches) {
@@ -266,8 +264,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
       var bidiTexts = this.textContent.items;
       var textDivs = this.textDivs;
       var prevEnd = null;
+      var pageIdx = this.pageIdx;
       var isSelectedPage = (this.findController === null ?
-        false : (this.pageIdx === this.findController.selected.pageIdx));
+        false : (pageIdx === this.findController.selected.pageIdx));
       var selectedMatchIdx = (this.findController === null ?
                               -1 : this.findController.selected.matchIdx);
       var highlightAll = (this.findController === null ?
@@ -313,10 +312,9 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
         var isSelected = (isSelectedPage && i === selectedMatchIdx);
         var highlightSuffix = (isSelected ? ' selected' : '');
 
-        if (isSelected && !this.isViewerInPresentationMode) {
-          scrollIntoView(textDivs[begin.divIdx],
-                         { top: FIND_SCROLL_OFFSET_TOP,
-                           left: FIND_SCROLL_OFFSET_LEFT });
+        if (this.findController) {
+          this.findController.updateMatchPosition(pageIdx, i, textDivs,
+                                                  begin.divIdx, end.divIdx);
         }
 
         // Match inside new div.

From 2565e627a31dccd562133d2e0f91413f09f302a5 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Wed, 17 Dec 2014 14:47:14 -0600
Subject: [PATCH 09/10] Refactors draw method in PDFPageView; makes optional
 some PDFPageViewOptions options

---
 web/annotations_layer_builder.js | 19 +++++++++++++++++
 web/interfaces.js                |  4 ++--
 web/pdf_page_view.js             | 36 ++++++++++++++++++++++++--------
 web/pdf_rendering_queue.js       |  5 ++++-
 web/text_layer_builder.js        | 21 +++++++++++++++++++
 web/thumbnail_view.js            | 16 +++++++++-----
 6 files changed, 84 insertions(+), 17 deletions(-)

diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js
index 309f7c033..494ceffb0 100644
--- a/web/annotations_layer_builder.js
+++ b/web/annotations_layer_builder.js
@@ -154,3 +154,22 @@ var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() {
   };
   return AnnotationsLayerBuilder;
 })();
+
+/**
+ * @constructor
+ * @implements IPDFAnnotationsLayerFactory
+ */
+function DefaultAnnotationsLayerFactory() {}
+DefaultAnnotationsLayerFactory.prototype = {
+  /**
+   * @param {HTMLDivElement} pageDiv
+   * @param {PDFPage} pdfPage
+   * @returns {AnnotationsLayerBuilder}
+   */
+  createAnnotationsLayerBuilder: function (pageDiv, pdfPage) {
+    return new AnnotationsLayerBuilder({
+      pageDiv: pageDiv,
+      pdfPage: pdfPage
+    });
+  }
+};
diff --git a/web/interfaces.js b/web/interfaces.js
index b99338c49..37a7b19d0 100644
--- a/web/interfaces.js
+++ b/web/interfaces.js
@@ -67,9 +67,9 @@ IRenderableView.prototype = {
    */
   get renderingState() {},
   /**
-   * @param {function} callback - The draw completion callback.
+   * @returns {Promise} Resolved on draw completion.
    */
-  draw: function (callback) {},
+  draw: function () {},
   resume: function () {},
 };
 
diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index 50d3c2b9b..cb0725a3c 100644
--- a/web/pdf_page_view.js
+++ b/web/pdf_page_view.js
@@ -14,7 +14,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale  */
+/* globals RenderingStates, PDFJS, CustomStyle, CSS_UNITS, getOutputScale,
+           TextLayerBuilder, AnnotationsLayerBuilder, Promise */
 
 'use strict';
 
@@ -286,7 +287,7 @@ var PDFPageView = (function PDFPageViewClosure() {
       return this.viewport.convertToPdfPoint(x, y);
     },
 
-    draw: function PDFPageView_draw(callback) {
+    draw: function PDFPageView_draw() {
       if (this.renderingState !== RenderingStates.INITIAL) {
         console.error('Must be in new state before drawing');
       }
@@ -373,6 +374,12 @@ var PDFPageView = (function PDFPageViewClosure() {
         ctx.scale(outputScale.sx, outputScale.sy);
       }
 
+      var resolveRenderPromise, rejectRenderPromise;
+      var promise = new Promise(function (resolve, reject) {
+        resolveRenderPromise = resolve;
+        rejectRenderPromise = reject;
+      });
+
       // Rendering area
 
       var self = this;
@@ -385,6 +392,7 @@ var PDFPageView = (function PDFPageViewClosure() {
         }
 
         if (error === 'cancelled') {
+          rejectRenderPromise(error);
           return;
         }
 
@@ -412,14 +420,16 @@ var PDFPageView = (function PDFPageViewClosure() {
         });
         div.dispatchEvent(event);
 
-        callback();
+        if (!error) {
+          resolveRenderPromise(undefined);
+        } else {
+          rejectRenderPromise(error);
+        }
       }
 
-      var renderContext = {
-        canvasContext: ctx,
-        viewport: this.viewport,
-        // intent: 'default', // === 'display'
-        continueCallback: function pdfViewcContinueCallback(cont) {
+      var renderContinueCallback = null;
+      if (this.renderingQueue) {
+        renderContinueCallback = function renderContinueCallback(cont) {
           if (!self.renderingQueue.isHighestPriority(self)) {
             self.renderingState = RenderingStates.PAUSED;
             self.resume = function resumeCallback() {
@@ -429,7 +439,14 @@ var PDFPageView = (function PDFPageViewClosure() {
             return;
           }
           cont();
-        }
+        };
+      }
+
+      var renderContext = {
+        canvasContext: ctx,
+        viewport: this.viewport,
+        // intent: 'default', // === 'display'
+        continueCallback: renderContinueCallback
       };
       var renderTask = this.renderTask = this.pdfPage.render(renderContext);
 
@@ -462,6 +479,7 @@ var PDFPageView = (function PDFPageViewClosure() {
       if (self.onBeforeDraw) {
         self.onBeforeDraw();
       }
+      return promise;
     },
 
     beforePrint: function PDFPageView_beforePrint() {
diff --git a/web/pdf_rendering_queue.js b/web/pdf_rendering_queue.js
index c7b1b3aba..b17a1ee58 100644
--- a/web/pdf_rendering_queue.js
+++ b/web/pdf_rendering_queue.js
@@ -165,7 +165,10 @@ var PDFRenderingQueue = (function PDFRenderingQueueClosure() {
           break;
         case RenderingStates.INITIAL:
           this.highestPriorityPage = view.renderingId;
-          view.draw(this.renderHighestPriority.bind(this));
+          var continueRendering = function () {
+            this.renderHighestPriority();
+          }.bind(this);
+          view.draw().then(continueRendering, continueRendering);
           break;
       }
       return true;
diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js
index d300f6354..020dfdf1c 100644
--- a/web/text_layer_builder.js
+++ b/web/text_layer_builder.js
@@ -385,3 +385,24 @@ var TextLayerBuilder = (function TextLayerBuilderClosure() {
   };
   return TextLayerBuilder;
 })();
+
+/**
+ * @constructor
+ * @implements IPDFTextLayerFactory
+ */
+function DefaultTextLayerFactory() {}
+DefaultTextLayerFactory.prototype = {
+  /**
+   * @param {HTMLDivElement} textLayerDiv
+   * @param {number} pageIndex
+   * @param {PageViewport} viewport
+   * @returns {TextLayerBuilder}
+   */
+  createTextLayerBuilder: function (textLayerDiv, pageIndex, viewport) {
+    return new TextLayerBuilder({
+      textLayerDiv: textLayerDiv,
+      pageIndex: pageIndex,
+      viewport: viewport
+    });
+  }
+};
diff --git a/web/thumbnail_view.js b/web/thumbnail_view.js
index f8c207ee9..e07ab3ab9 100644
--- a/web/thumbnail_view.js
+++ b/web/thumbnail_view.js
@@ -139,17 +139,22 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport,
     return !this.hasImage;
   };
 
-  this.draw = function thumbnailViewDraw(callback) {
+  this.draw = function thumbnailViewDraw() {
     if (this.renderingState !== RenderingStates.INITIAL) {
       console.error('Must be in new state before drawing');
     }
 
     this.renderingState = RenderingStates.RUNNING;
     if (this.hasImage) {
-      callback();
-      return;
+      return Promise.resolve(undefined);
     }
 
+    var resolveRenderPromise, rejectRenderPromise;
+    var promise = new Promise(function (resolve, reject) {
+      resolveRenderPromise = resolve;
+      rejectRenderPromise = reject;
+    });
+
     var self = this;
     var ctx = this.getPageDrawContext();
     var drawViewport = this.viewport.clone({ scale: this.scale });
@@ -171,14 +176,15 @@ var ThumbnailView = function thumbnailView(container, id, defaultViewport,
     this.pdfPage.render(renderContext).promise.then(
       function pdfPageRenderCallback() {
         self.renderingState = RenderingStates.FINISHED;
-        callback();
+        resolveRenderPromise(undefined);
       },
       function pdfPageRenderError(error) {
         self.renderingState = RenderingStates.FINISHED;
-        callback();
+        rejectRenderPromise(error);
       }
     );
     this.hasImage = true;
+    return promise;
   };
 
   function getTempCanvas(width, height) {

From 513a3d8c91c2948fcc95d2f1f297d787f45fa416 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Fri, 19 Dec 2014 18:10:57 -0600
Subject: [PATCH 10/10] Replaces text selection example

---
 examples/components/pageviewer.html          |  46 +++++++++
 examples/components/pageviewer.js            |  58 +++++++++++
 examples/text-selection/css/minimal.css      |  43 --------
 examples/text-selection/index.html           |  26 -----
 examples/text-selection/js/minimal.js        |  97 -------------------
 examples/text-selection/pdf/TestDocument.pdf | Bin 35153 -> 0 bytes
 web/annotations_layer_builder.js             |   4 +-
 web/pdf_viewer.component.js                  |   9 +-
 web/text_layer_builder.css                   |   4 +
 web/viewer.css                               |   7 --
 10 files changed, 119 insertions(+), 175 deletions(-)
 create mode 100644 examples/components/pageviewer.html
 create mode 100644 examples/components/pageviewer.js
 delete mode 100644 examples/text-selection/css/minimal.css
 delete mode 100644 examples/text-selection/index.html
 delete mode 100644 examples/text-selection/js/minimal.js
 delete mode 100644 examples/text-selection/pdf/TestDocument.pdf

diff --git a/examples/components/pageviewer.html b/examples/components/pageviewer.html
new file mode 100644
index 000000000..ac49d273c
--- /dev/null
+++ b/examples/components/pageviewer.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!--
+Copyright 2014 Mozilla Foundation
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<html dir="ltr" mozdisallowselectionprint moznomarginboxes>
+<head>
+  <meta charset="utf-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
+  <meta name="google" content="notranslate">
+  <title>PDF.js page viewer using built components</title>
+
+  <style>
+    body {
+      background-color: #808080;
+      margin: 0;
+      padding: 0;
+    }
+  </style>
+
+  <link rel="stylesheet" href="../../build/components/pdf_viewer.css">
+
+  <!-- for legacy browsers -->
+  <script src="../../build/components/compatibility.js"></script>
+  <script src="../../build/pdf.js"></script>
+  <script src="../../build/components/pdf_viewer.js"></script>
+</head>
+
+<body tabindex="1">
+  <div id="pageContainer" class="pdfPage"></div>
+
+  <script src="pageviewer.js"></script>
+</body>
+</html>
+
diff --git a/examples/components/pageviewer.js b/examples/components/pageviewer.js
new file mode 100644
index 000000000..41c1b35da
--- /dev/null
+++ b/examples/components/pageviewer.js
@@ -0,0 +1,58 @@
+/* Copyright 2014 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+if (!PDFJS.PDFViewer || !PDFJS.getDocument) {
+  alert('Please build the library and components using\n' +
+        '  `node make generic components`');
+}
+
+// In cases when the pdf.worker.js is located at the different folder than the
+// pdf.js's one, or the pdf.js is executed via eval(), the workerSrc property
+// shall be specified.
+//
+// PDFJS.workerSrc = '../../build/pdf.worker.js';
+
+// Some PDFs need external cmaps.
+//
+// PDFJS.cMapUrl = '../../external/bcmaps/';
+// PDFJS.cMapPacked = true;
+
+var DEFAULT_URL = '../../web/compressed.tracemonkey-pldi-09.pdf';
+var PAGE_TO_VIEW = 1;
+var SCALE = 1.0;
+
+var container = document.getElementById('pageContainer');
+
+// Loading document.
+PDFJS.getDocument(DEFAULT_URL).then(function (pdfDocument) {
+  // Document loaded, retrieving the page.
+  return pdfDocument.getPage(PAGE_TO_VIEW).then(function (pdfPage) {
+    // Creating the page view with default parameters.
+    var pdfPageView = new PDFJS.PDFPageView({
+      container: container,
+      id: PAGE_TO_VIEW,
+      scale: SCALE,
+      defaultViewport: pdfPage.getViewport(SCALE),
+      // We can enable text/annotations layers, if needed
+      textLayerFactory: new PDFJS.DefaultTextLayerFactory(),
+      annotationsLayerFactory: new PDFJS.DefaultAnnotationsLayerFactory()
+    });
+    // Associates the actual page with the view, and drawing it
+    pdfPageView.setPdfPage(pdfPage);
+    return pdfPageView.draw();
+  });
+});
diff --git a/examples/text-selection/css/minimal.css b/examples/text-selection/css/minimal.css
deleted file mode 100644
index 6a1124484..000000000
--- a/examples/text-selection/css/minimal.css
+++ /dev/null
@@ -1,43 +0,0 @@
-body {
-    font-family: arial, verdana, sans-serif;
-}
-
-/* Allow absolute positioning of the canvas and textLayer in the page. They
-   will be the same size and will be placed on top of each other. */
-.pdfPage {
-    position: relative;
-    overflow: visible;
-    border: 1px solid #000000;
-}
-
-.pdfPage > canvas {
-    position: absolute;
-    top: 0;
-    left: 0;
-}
-
-/* CSS classes used by TextLayerBuilder to style the text layer divs */
-
-/* This stuff is important! Otherwise when you select the text,
-   the text in the divs will show up! */
-::selection { background:rgba(0,0,255,0.3); }
-::-moz-selection { background:rgba(0,0,255,0.3); }
-
-.textLayer {
-    position: absolute;
-    left: 0;
-    top: 0;
-    right: 0;
-    bottom: 0;
-    color: #000;
-    font-family: sans-serif;
-    overflow: hidden;
-}
-
-.textLayer > div {
-    color: transparent;
-    position: absolute;
-    line-height: 1;
-    white-space: pre;
-    cursor: text;
-}
diff --git a/examples/text-selection/index.html b/examples/text-selection/index.html
deleted file mode 100644
index fc1e43ba1..000000000
--- a/examples/text-selection/index.html
+++ /dev/null
@@ -1,26 +0,0 @@
-<html>
-    <head>
-        <title>Minimal pdf.js text-selection demo</title>
-        <link href="css/minimal.css" rel="stylesheet" media="screen" />
-
-        <!-- you will need to run "node make generic" first before you can use this -->
-        <script src="../../build/generic/build/pdf.js"></script>
-
-        <!-- These files are viewer components that you will need to get text-selection to work -->
-        <script src="../../web/ui_utils.js"></script>
-        <script src="../../web/text_layer_builder.js"></script>
-
-        <script>
-             // Specify the main script used to create a new PDF.JS web worker.
-             // In production, change this to point to the combined `pdf.js` file.
-         </script>
-
-        <script src="js/minimal.js"></script>
-    </head>
-    <body>
-        This is a minimal pdf.js text-selection demo. The existing minimal-example shows you how to render a PDF, but not
-        how to enable text-selection. This example shows you how to do both. <br /><br />
-        <div id="pdfContainer">
-        </div>
-    </body>
-</html>
diff --git a/examples/text-selection/js/minimal.js b/examples/text-selection/js/minimal.js
deleted file mode 100644
index e87bb837a..000000000
--- a/examples/text-selection/js/minimal.js
+++ /dev/null
@@ -1,97 +0,0 @@
-// Minimal PDF rendering and text-selection example using PDF.js by Vivin Suresh Paliath (http://vivin.net)
-// This example uses a built version of PDF.js that contains all modules that it requires.
-//
-// The problem with understanding text selection was that the text selection code has heavily intertwined
-// with viewer.html and viewer.js. I have extracted the parts I need out of viewer.js into a separate file
-// which contains the bare minimum required to implement text selection. The key component is TextLayerBuilder,
-// which is the object that handles the creation of text-selection divs. I have added this code as an external
-// resource.
-//
-// This demo uses a PDF that only has one page. You can render other pages if you wish, but the focus here is
-// just to show you how you can render a PDF with text selection. Hence the code only loads up one page.
-//
-// The CSS used here is also very important since it sets up the CSS for the text layer divs overlays that
-// you actually end up selecting.
-//
-// NOTE: The original example was changed to remove jQuery usage, re-structure and add more comments.
-
-window.onload = function () {
-  if (typeof PDFJS === 'undefined') {
-    alert('Built version of pdf.js is not found\nPlease run `node make generic`');
-    return;
-  }
-
-  var scale = 1.5; //Set this to whatever you want. This is basically the "zoom" factor for the PDF.
-  PDFJS.workerSrc = '../../build/generic/build/pdf.worker.js';
-
-  function loadPdf(pdfPath) {
-    var pdf = PDFJS.getDocument(pdfPath);
-    return pdf.then(renderPdf);
-  }
-
-  function renderPdf(pdf) {
-    return pdf.getPage(1).then(renderPage);
-  }
-
-  function renderPage(page) {
-    var viewport = page.getViewport(scale);
-
-    // Create and append the 'pdf-page' div to the pdf container.
-    var pdfPage = document.createElement('div');
-    pdfPage.className = 'pdfPage';
-    var pdfContainer = document.getElementById('pdfContainer');
-    pdfContainer.appendChild(pdfPage);
-
-    // Set the canvas height and width to the height and width of the viewport.
-    var canvas = document.createElement('canvas');
-    var context = canvas.getContext('2d');
-
-    // The following few lines of code set up scaling on the context, if we are
-    // on a HiDPI display.
-    var outputScale = getOutputScale(context);
-    canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
-    canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
-    context._scaleX = outputScale.sx;
-    context._scaleY = outputScale.sy;
-    if (outputScale.scaled) {
-      context.scale(outputScale.sx, outputScale.sy);
-    }
-
-    // The page, canvas and text layer elements will have the same size.
-    canvas.style.width = Math.floor(viewport.width) + 'px';
-    canvas.style.height = Math.floor(viewport.height) + 'px';
-
-    pdfPage.style.width = canvas.style.width;
-    pdfPage.style.height = canvas.style.height;
-    pdfPage.appendChild(canvas);
-
-    var textLayerDiv = document.createElement('div');
-    textLayerDiv.className = 'textLayer';
-    textLayerDiv.style.width = canvas.style.width;
-    textLayerDiv.style.height = canvas.style.height;
-    pdfPage.appendChild(textLayerDiv);
-
-    // Painting the canvas...
-    var renderContext = {
-      canvasContext: context,
-      viewport: viewport
-    };
-    var renderTask = page.render(renderContext);
-
-    // ... and at the same time, getting the text and creating the text layer.
-    var textLayerPromise = page.getTextContent().then(function (textContent) {
-      var textLayerBuilder = new TextLayerBuilder({
-        textLayerDiv: textLayerDiv,
-        viewport: viewport,
-        pageIndex: 0
-      });
-      textLayerBuilder.setTextContent(textContent);
-    });
-
-    // We might be interested when rendering complete and text layer is built.
-    return Promise.all([renderTask.promise, textLayerPromise]);
-  }
-
-  loadPdf('pdf/TestDocument.pdf');
-};
-
diff --git a/examples/text-selection/pdf/TestDocument.pdf b/examples/text-selection/pdf/TestDocument.pdf
deleted file mode 100644
index 843fd9d2bda87f634a58449d017d7624fe17c02b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 35153
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

diff --git a/web/annotations_layer_builder.js b/web/annotations_layer_builder.js
index 494ceffb0..b5ef814ec 100644
--- a/web/annotations_layer_builder.js
+++ b/web/annotations_layer_builder.js
@@ -103,7 +103,9 @@ var AnnotationsLayerBuilder = (function AnnotationsLayerBuilderClosure() {
             element = PDFJS.AnnotationUtils.getHtmlElement(data,
               pdfPage.commonObjs);
             element.setAttribute('data-annotation-id', data.id);
-            mozL10n.translate(element);
+            if (typeof mozL10n !== 'undefined') {
+              mozL10n.translate(element);
+            }
 
             var rect = data.rect;
             var view = pdfPage.view;
diff --git a/web/pdf_viewer.component.js b/web/pdf_viewer.component.js
index 22c483854..8c1223ab7 100644
--- a/web/pdf_viewer.component.js
+++ b/web/pdf_viewer.component.js
@@ -15,7 +15,9 @@
  * limitations under the License.
  */
 /*jshint globalstrict: false */
-/* globals PDFJS, PDFViewer */
+/* globals PDFJS, PDFViewer, PDFPageView, TextLayerBuilder,
+           DefaultTextLayerFactory, AnnotationsLayerBuilder,
+           DefaultAnnotationsLayerFactory */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
@@ -29,4 +31,9 @@ if (typeof PDFJS === 'undefined') {
 //#include pdf_viewer.js
 
   PDFJS.PDFViewer = PDFViewer;
+  PDFJS.PDFPageView = PDFPageView;
+  PDFJS.TextLayerBuilder = TextLayerBuilder;
+  PDFJS.DefaultTextLayerFactory = DefaultTextLayerFactory;
+  PDFJS.AnnotationsLayerBuilder = AnnotationsLayerBuilder;
+  PDFJS.DefaultAnnotationsLayerFactory = DefaultAnnotationsLayerFactory;
 }).call((typeof window === 'undefined') ? this : window);
diff --git a/web/text_layer_builder.css b/web/text_layer_builder.css
index 32f7c497b..f5a55df10 100644
--- a/web/text_layer_builder.css
+++ b/web/text_layer_builder.css
@@ -20,6 +20,7 @@
   right: 0;
   bottom: 0;
   overflow: hidden;
+  opacity: 0.2;
 }
 
 .textLayer > div {
@@ -57,3 +58,6 @@
 .textLayer .highlight.selected {
   background-color: rgb(0, 100, 0);
 }
+
+.textLayer ::selection { background: rgb(0,0,255); }
+.textLayer ::-moz-selection { background: rgb(0,0,255); }
diff --git a/web/viewer.css b/web/viewer.css
index 066b12aeb..5fcb3f0fd 100644
--- a/web/viewer.css
+++ b/web/viewer.css
@@ -1258,19 +1258,12 @@ html[dir='rtl'] .attachmentsItem > button {
   cursor: default;
 }
 
-
 /* TODO: file FF bug to support ::-moz-selection:window-inactive
    so we can override the opaque grey background when the window is inactive;
    see https://bugzilla.mozilla.org/show_bug.cgi?id=706209 */
 ::selection { background: rgba(0,0,255,0.3); }
 ::-moz-selection { background: rgba(0,0,255,0.3); }
 
-.textLayer ::selection { background: rgb(0,0,255); }
-.textLayer ::-moz-selection { background: rgb(0,0,255); }
-.textLayer {
-  opacity: 0.2;
-}
-
 #errorWrapper {
   background: none repeat scroll 0 0 #FF5555;
   color: white;