Merge m-c to inbound on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 17 May 2013 17:34:54 -0400
changeset 132287 d7155fcfc6a964f7b1ecdbe561f2bc559871e067
parent 132286 0152d497fbefdc95761a240a935d49b1076d7011 (current diff)
parent 132248 ecdfb8bb501eef34b62dec14b74ada075ab6452a (diff)
child 132288 3f1b8af8ff14404fe5bf4e6f5f393f832d8c7d75
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
milestone24.0a1
Merge m-c to inbound on a CLOSED TREE.
browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
browser/extensions/pdfjs/content/web/images/toolbarButton-fullscreen.png
toolkit/devtools/webconsole/test/test_nsiconsolemessage.html
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -4,34 +4,32 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [ "DeveloperToolbar", "CommandUtils" ];
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource:///modules/devtools/Commands.jsm");
 
 const Node = Components.interfaces.nsIDOMNode;
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
                                   "resource://gre/modules/devtools/Console.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "gcli",
                                   "resource://gre/modules/devtools/gcli.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
                                   "resource:///modules/devtools/BuiltinCommands.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener",
+XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
                                   "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
                                   "resource://gre/modules/devtools/Loader.jsm");
 
@@ -432,18 +430,18 @@ DeveloperToolbar.prototype._initErrorsCo
 {
   let tabId = aTab.linkedPanel;
   if (tabId in this._errorsCount) {
     this._updateErrorsCount();
     return;
   }
 
   let window = aTab.linkedBrowser.contentWindow;
-  let listener = new ConsoleServiceListener(window, {
-    onConsoleServiceMessage: this._onPageError.bind(this, tabId),
+  let listener = new PageErrorListener(window, {
+    onPageError: this._onPageError.bind(this, tabId),
   });
   listener.init();
 
   this._errorListeners[tabId] = listener;
   this._errorsCount[tabId] = 0;
   this._warningsCount[tabId] = 0;
 
   let messages = listener.getCachedMessages();
@@ -590,18 +588,17 @@ DeveloperToolbar.prototype.handleEvent =
  * @private
  * @param string aTabId the ID of the tab from where the page error comes.
  * @param object aPageError the page error object received from the
  * PageErrorListener.
  */
 DeveloperToolbar.prototype._onPageError =
 function DT__onPageError(aTabId, aPageError)
 {
-  if (!(aPageError instanceof Ci.nsIScriptError) ||
-      aPageError.category == "CSS Parser" ||
+  if (aPageError.category == "CSS Parser" ||
       aPageError.category == "CSS Loader") {
     return;
   }
   if ((aPageError.flags & aPageError.warningFlag) ||
       (aPageError.flags & aPageError.strictFlag)) {
     this._warningsCount[aTabId]++;
   } else {
     this._errorsCount[aTabId]++;
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -125,17 +125,16 @@ MOCHITEST_BROWSER_FILES = \
 	browser_webconsole_bug_837351_securityerrors.js \
 	browser_bug_865871_variables_view_close_on_esc_key.js \
 	browser_bug_865288_repeat_different_objects.js \
 	browser_jsterm_inspect.js \
 	browser_bug_869003_inspect_cross_domain_object.js \
 	browser_bug_862916_console_dir_and_filter_off.js \
 	browser_console_native_getters.js \
 	browser_bug_871156_ctrlw_close_tab.js \
-	browser_console_nsiconsolemessage.js \
 	head.js \
 	$(NULL)
 
 ifeq ($(OS_ARCH), Darwin)
 MOCHITEST_BROWSER_FILES += \
 	browser_webconsole_bug_804845_ctrl_key_nav.js \
         $(NULL)
 endif
deleted file mode 100644
--- a/browser/devtools/webconsole/test/browser_console_nsiconsolemessage.js
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-// Check that nsIConsoleMessages show in the Browser Console and in the Web
-// Console. See bug 859756.
-
-const TEST_URI = "data:text/html;charset=utf8,<title>bug859756</title>\n" +
-                 "<p>hello world\n<p>nsIConsoleMessages ftw!";
-
-let gWebConsole, gJSTerm, gVariablesView;
-
-function test()
-{
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-
-    // Test for cached nsIConsoleMessages.
-    sendMessage("test1 for bug859756", "cat2012");
-
-    openConsole(null, consoleOpened);
-  }, true);
-}
-
-function sendMessage(aMessage, aCategory)
-{
-  let windowID = WebConsoleUtils.getInnerWindowId(content);
-  let consoleMsg = Cc["@mozilla.org/consolemessage;1"]
-                   .createInstance(Ci.nsIConsoleMessage);
-  consoleMsg.initMessage(aMessage, aCategory, windowID);
-  Services.console.logMessage(consoleMsg);
-}
-
-function consoleOpened(hud)
-{
-  gWebConsole = hud;
-  gJSTerm = hud.jsterm;
-
-  // Send a message with no window ID.
-  Services.console.logStringMessage("do-not-show-me");
-
-  sendMessage("test2 for bug859756", "cat2013");
-
-  waitForMessages({
-    webconsole: hud,
-    messages: [
-      {
-        text: "test1 for bug859756",
-        category: CATEGORY_JS,
-      },
-      {
-        text: "test2 for bug859756",
-        category: CATEGORY_JS,
-      },
-    ],
-  }).then(onLogMessages);
-}
-
-function onLogMessages()
-{
-  let text = gWebConsole.outputNode.textContent;
-  is(text.indexOf("do-not-show-me"), -1,
-     "message without window ID is not displayed");
-  closeConsole(null, onWebConsoleClose);
-}
-
-function onWebConsoleClose()
-{
-  HUDConsoleUI.toggleBrowserConsole().then(onBrowserConsoleOpen);
-}
-
-function onBrowserConsoleOpen(hud)
-{
-  waitForMessages({
-    webconsole: hud,
-    messages: [
-      {
-        text: "test1 for bug859756",
-        category: CATEGORY_JS,
-      },
-      {
-        text: "test2 for bug859756",
-        category: CATEGORY_JS,
-      },
-      {
-        text: "do-not-show-me",
-        category: CATEGORY_JS,
-      },
-    ],
-  }).then(finishTest);
-}
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -951,24 +951,21 @@ WebConsoleFrame.prototype = {
   {
     if (!aRemoteMessages.length) {
       return;
     }
 
     aRemoteMessages.forEach(function(aMessage) {
       switch (aMessage._type) {
         case "PageError": {
-          let category = Utils.categoryForConsoleMessage(aMessage);
+          let category = Utils.categoryForScriptError(aMessage);
           this.outputMessage(category, this.reportPageError,
                              [category, aMessage]);
           break;
         }
-        case "LogMessage":
-          this.handleLogMessage(aMessage);
-          break;
         case "ConsoleAPI":
           this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
                              [aMessage]);
           break;
       }
     }, this);
   },
 
@@ -1186,37 +1183,21 @@ WebConsoleFrame.prototype = {
    * Handle PageError objects received from the server. This method outputs the
    * given error.
    *
    * @param nsIScriptError aPageError
    *        The error received from the server.
    */
   handlePageError: function WCF_handlePageError(aPageError)
   {
-    let category = Utils.categoryForConsoleMessage(aPageError);
+    let category = Utils.categoryForScriptError(aPageError);
     this.outputMessage(category, this.reportPageError, [category, aPageError]);
   },
 
   /**
-   * Handle log messages received from the server. This method outputs the given
-   * message.
-   *
-   * @param object aPacket
-   *        The message packet received from the server.
-   */
-  handleLogMessage: function WCF_handleLogMessage(aPacket)
-  {
-    let category = Utils.categoryForConsoleMessage(aPacket.category);
-    this.outputMessage(category, () => {
-      return this.createMessageNode(category, SEVERITY_LOG, aPacket.message,
-                                    null, null, null, null, aPacket.timeStamp);
-    });
-  },
-
-  /**
    * Log network event.
    *
    * @param object aActorId
    *        The network event actor ID to log.
    * @return nsIDOMElement|null
    *         The message element to display in the Web Console output.
    */
   logNetEvent: function WCF_logNetEvent(aActorId)
@@ -4351,27 +4332,27 @@ var Utils = {
                          aOutputNode.lastChild.clientHeight : 0;
     let scrollBox = aOutputNode.scrollBoxObject.element;
 
     return scrollBox.scrollTop + scrollBox.clientHeight >=
            scrollBox.scrollHeight - lastNodeHeight / 2;
   },
 
   /**
-   * Determine the category of a given nsIConsoleMessage.
+   * Determine the category of a given nsIScriptError.
    *
-   * @param nsIConsoleMessage aMessage
-   *        The message you want to determine the category for.
+   * @param nsIScriptError aScriptError
+   *        The script error you want to determine the category for.
    * @return CATEGORY_JS|CATEGORY_CSS|CATEGORY_SECURITY
-   *         Depending on the message kind CATEGORY_JS, CATEGORY_CSS, or
+   *         Depending on the script error CATEGORY_JS, CATEGORY_CSS, or
    *         CATEGORY_SECURITY can be returned.
    */
-  categoryForConsoleMessage: function Utils_categoryForConsoleMessage(aMessage)
+  categoryForScriptError: function Utils_categoryForScriptError(aScriptError)
   {
-    switch (aMessage.category) {
+    switch (aScriptError.category) {
       case "CSS Parser":
       case "CSS Loader":
         return CATEGORY_CSS;
 
       case "Mixed Content Blocker":
       case "CSP":
         return CATEGORY_SECURITY;
 
@@ -4522,17 +4503,16 @@ CommandController.prototype = {
  *        The target that the console will connect to.
  */
 function WebConsoleConnectionProxy(aWebConsole, aTarget)
 {
   this.owner = aWebConsole;
   this.target = aTarget;
 
   this._onPageError = this._onPageError.bind(this);
-  this._onLogMessage = this._onLogMessage.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onTabNavigated = this._onTabNavigated.bind(this);
   this._onAttachConsole = this._onAttachConsole.bind(this);
   this._onCachedMessages = this._onCachedMessages.bind(this);
   this._connectionTimeout = this._connectionTimeout.bind(this);
@@ -4626,17 +4606,16 @@ WebConsoleConnectionProxy.prototype = {
       this._connectTimer.cancel();
       this._connectTimer = null;
     }.bind(this), function _onFailure() {
       this._connectTimer = null;
     }.bind(this));
 
     let client = this.client = this.target.client;
 
-    client.addListener("logMessage", this._onLogMessage);
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("networkEvent", this._onNetworkEvent);
     client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
     this.target.on("will-navigate", this._onTabNavigated);
     this.target.on("navigate", this._onTabNavigated);
 
@@ -4748,33 +4727,16 @@ WebConsoleConnectionProxy.prototype = {
   _onPageError: function WCCP__onPageError(aType, aPacket)
   {
     if (this.owner && aPacket.from == this._consoleActor) {
       this.owner.handlePageError(aPacket.pageError);
     }
   },
 
   /**
-   * The "logMessage" message type handler. We redirect any message to the UI
-   * for displaying.
-   *
-   * @private
-   * @param string aType
-   *        Message type.
-   * @param object aPacket
-   *        The message received from the server.
-   */
-  _onLogMessage: function WCCP__onLogMessage(aType, aPacket)
-  {
-    if (this.owner && aPacket.from == this._consoleActor) {
-      this.owner.handleLogMessage(aPacket);
-    }
-  },
-
-  /**
    * The "consoleAPICall" message type handler. We redirect any message to
    * the UI for displaying.
    *
    * @private
    * @param string aType
    *        Message type.
    * @param object aPacket
    *        The message received from the server.
@@ -4891,17 +4853,16 @@ WebConsoleConnectionProxy.prototype = {
     this._disconnecter = Promise.defer();
 
     if (!this.client) {
       this._disconnecter.resolve(null);
       return this._disconnecter.promise;
     }
 
     this.client.removeListener("pageError", this._onPageError);
-    this.client.removeListener("logMessage", this._onLogMessage);
     this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
     this.client.removeListener("networkEvent", this._onNetworkEvent);
     this.client.removeListener("networkEventUpdate", this._onNetworkEventUpdate);
     this.client.removeListener("fileActivity", this._onFileActivity);
     this.target.off("will-navigate", this._onTabNavigated);
     this.target.off("navigate", this._onTabNavigated);
 
     this.client = null;
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.47
+Current extension version is: 0.8.169
 
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -11,17 +11,17 @@
  * 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.
  */
 /* jshint esnext:true */
 /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
-           dump */
+           dump, NetworkManager */
 
 'use strict';
 
 var EXPORTED_SYMBOLS = ['PdfStreamConverter'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
@@ -32,16 +32,17 @@ const PDFJS_EVENT_ID = 'pdf.js.message';
 const PDF_CONTENT_TYPE = 'application/pdf';
 const PREF_PREFIX = 'pdfjs';
 const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
 const MAX_DATABASE_LENGTH = 4096;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/NetUtil.jsm');
+Cu.import('resource://pdf.js/network.js');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
   'resource://gre/modules/PrivateBrowsingUtils.jsm');
 
 var Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
                                    '@mozilla.org/mime;1',
                                    'nsIMIMEService');
@@ -185,19 +186,18 @@ PdfDataListener.prototype = {
     }
     if (this.errorCode) {
       value(null, this.errorCode);
     }
   }
 };
 
 // All the priviledged actions.
-function ChromeActions(domWindow, dataListener, contentDispositionFilename) {
+function ChromeActions(domWindow, contentDispositionFilename) {
   this.domWindow = domWindow;
-  this.dataListener = dataListener;
   this.contentDispositionFilename = contentDispositionFilename;
 }
 
 ChromeActions.prototype = {
   isInPrivateBrowsing: function() {
     var docIsPrivate, privateBrowsing;
     try {
       docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(this.domWindow);
@@ -300,49 +300,16 @@ ChromeActions.prototype = {
   getDatabase: function() {
     if (this.isInPrivateBrowsing())
       return '{}';
     return getStringPref(PREF_PREFIX + '.database', '{}');
   },
   getLocale: function() {
     return getStringPref('general.useragent.locale', 'en-US');
   },
-  getLoadingType: function() {
-    return this.dataListener ? 'passive' : 'active';
-  },
-  initPassiveLoading: function() {
-    if (!this.dataListener)
-      return false;
-
-    var domWindow = this.domWindow;
-    this.dataListener.onprogress =
-      function ChromeActions_dataListenerProgress(loaded, total) {
-
-      domWindow.postMessage({
-        pdfjsLoadAction: 'progress',
-        loaded: loaded,
-        total: total
-      }, '*');
-    };
-
-    var self = this;
-    this.dataListener.oncomplete =
-      function ChromeActions_dataListenerComplete(data, errorCode) {
-
-      domWindow.postMessage({
-        pdfjsLoadAction: 'complete',
-        data: data,
-        errorCode: errorCode
-      }, '*');
-
-      delete self.dataListener;
-    };
-
-    return true;
-  },
   getStrings: function(data) {
     try {
       // Lazy initialization of localizedStrings
       if (!('localizedStrings' in this))
         this.localizedStrings = getLocalizedStrings('viewer.properties');
 
       var result = this.localizedStrings[data];
       return JSON.stringify(result || null);
@@ -431,16 +398,156 @@ ChromeActions.prototype = {
         (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
       return;
     }
     getChromeWindow(this.domWindow).gFindBar
                                    .updateControlState(result, findPrevious);
   }
 };
 
+var RangedChromeActions = (function RangedChromeActionsClosure() {
+  /**
+   * This is for range requests
+   */
+  function RangedChromeActions(
+              domWindow, contentDispositionFilename, originalRequest) {
+
+    ChromeActions.call(this, domWindow, contentDispositionFilename);
+
+    this.pdfUrl = originalRequest.URI.resolve('');
+    this.contentLength = originalRequest.contentLength;
+
+    // Pass all the headers from the original request through
+    var httpHeaderVisitor = {
+      headers: {},
+      visitHeader: function(aHeader, aValue) {
+        if (aHeader === 'Range') {
+          // When loading the PDF from cache, firefox seems to set the Range
+          // request header to fetch only the unfetched portions of the file
+          // (e.g. 'Range: bytes=1024-'). However, we want to set this header
+          // manually to fetch the PDF in chunks.
+          return;
+        }
+        this.headers[aHeader] = aValue;
+      }
+    };
+    originalRequest.visitRequestHeaders(httpHeaderVisitor);
+
+    var getXhr = function getXhr() {
+      const XMLHttpRequest = Components.Constructor(
+          '@mozilla.org/xmlextras/xmlhttprequest;1');
+      return new XMLHttpRequest();
+    };
+
+    this.networkManager = new NetworkManager(this.pdfUrl, {
+      httpHeaders: httpHeaderVisitor.headers,
+      getXhr: getXhr
+    });
+
+    var self = this;
+    // If we are in range request mode, this means we manually issued xhr
+    // requests, which we need to abort when we leave the page
+    domWindow.addEventListener('unload', function unload(e) {
+      self.networkManager.abortAllRequests();
+      domWindow.removeEventListener(e.type, unload);
+    });
+  }
+
+  RangedChromeActions.prototype = Object.create(ChromeActions.prototype);
+  var proto = RangedChromeActions.prototype;
+  proto.constructor = RangedChromeActions;
+
+  proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
+    this.domWindow.postMessage({
+      pdfjsLoadAction: 'supportsRangedLoading',
+      pdfUrl: this.pdfUrl,
+      length: this.contentLength
+    }, '*');
+
+    return true;
+  };
+
+  proto.requestDataRange = function RangedChromeActions_requestDataRange(args) {
+    var begin = args.begin;
+    var end = args.end;
+    var domWindow = this.domWindow;
+    // TODO(mack): Support error handler. We're not currently not handling
+    // errors from chrome code for non-range requests, so this doesn't
+    // seem high-pri
+    this.networkManager.requestRange(begin, end, {
+      onDone: function RangedChromeActions_onDone(args) {
+        domWindow.postMessage({
+          pdfjsLoadAction: 'range',
+          begin: args.begin,
+          chunk: args.chunk
+        }, '*');
+      },
+      onProgress: function RangedChromeActions_onProgress(evt) {
+        domWindow.postMessage({
+          pdfjsLoadAction: 'rangeProgress',
+          loaded: evt.loaded,
+        }, '*');
+      }
+    });
+  };
+
+  return RangedChromeActions;
+})();
+
+var StandardChromeActions = (function StandardChromeActionsClosure() {
+
+  /**
+   * This is for a single network stream
+   */
+  function StandardChromeActions(domWindow, contentDispositionFilename,
+                                 dataListener) {
+
+    ChromeActions.call(this, domWindow, contentDispositionFilename);
+    this.dataListener = dataListener;
+  }
+
+  StandardChromeActions.prototype = Object.create(ChromeActions.prototype);
+  var proto = StandardChromeActions.prototype;
+  proto.constructor = StandardChromeActions;
+
+  proto.initPassiveLoading =
+      function StandardChromeActions_initPassiveLoading() {
+
+    if (!this.dataListener) {
+      return false;
+    }
+
+    var self = this;
+
+    this.dataListener.onprogress = function ChromeActions_dataListenerProgress(
+                                      loaded, total) {
+      self.domWindow.postMessage({
+        pdfjsLoadAction: 'progress',
+        loaded: loaded,
+        total: total
+      }, '*');
+    };
+
+    this.dataListener.oncomplete = function ChromeActions_dataListenerComplete(
+                                      data, errorCode) {
+      self.domWindow.postMessage({
+        pdfjsLoadAction: 'complete',
+        data: data,
+        errorCode: errorCode
+      }, '*');
+
+      delete self.dataListener;
+    };
+
+    return true;
+  };
+
+  return StandardChromeActions;
+})();
+
 // Event listener to trigger chrome privedged code.
 function RequestListener(actions) {
   this.actions = actions;
 }
 // Receive an event and synchronously or asynchronously responds.
 RequestListener.prototype.receive = function(event) {
   var message = event.target;
   var doc = message.ownerDocument;
@@ -547,104 +654,150 @@ PdfStreamConverter.prototype = {
       Ci.nsIStreamConverter,
       Ci.nsIStreamListener,
       Ci.nsIRequestObserver
   ]),
 
   /*
    * This component works as such:
    * 1. asyncConvertData stores the listener
-   * 2. onStartRequest creates a new channel, streams the viewer and cancels
-   *    the request so pdf.js can do the request
-   * Since the request is cancelled onDataAvailable should not be called. The
-   * onStopRequest does nothing. The convert function just returns the stream,
-   * it's just the synchronous version of asyncConvertData.
+   * 2. onStartRequest creates a new channel, streams the viewer
+   * 3. If range requests are supported:
+   *      3.1. Suspends and cancels the request so we can issue range
+   *          requests instead.
+   *
+   *    If range rquests are not supported:
+   *      3.1. Read the stream as it's loaded in onDataAvailable to send
+   *           to the viewer
+   *
+   * The convert function just returns the stream, it's just the synchronous
+   * version of asyncConvertData.
    */
 
   // nsIStreamConverter::convert
   convert: function(aFromStream, aFromType, aToType, aCtxt) {
     throw Cr.NS_ERROR_NOT_IMPLEMENTED;
   },
 
   // nsIStreamConverter::asyncConvertData
   asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
     // Store the listener passed to us
     this.listener = aListener;
   },
 
   // nsIStreamListener::onDataAvailable
   onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
     if (!this.dataListener) {
-      // Do nothing since all the data loading is handled by the viewer.
       return;
     }
 
     var binaryStream = this.binaryStream;
     binaryStream.setInputStream(aInputStream);
-    this.dataListener.append(binaryStream.readByteArray(aCount));
+    var chunk = binaryStream.readByteArray(aCount);
+    this.dataListener.append(chunk);
   },
 
   // nsIRequestObserver::onStartRequest
   onStartRequest: function(aRequest, aContext) {
     // Setup the request so we can use it below.
+    var isHttpRequest = false;
+    try {
+      aRequest.QueryInterface(Ci.nsIHttpChannel);
+      isHttpRequest = true;
+    } catch (e) {}
+
+    var rangeRequest = false;
+    if (isHttpRequest) {
+      var contentEncoding = 'identity';
+      try {
+        contentEncoding = aRequest.getResponseHeader('Content-Encoding');
+      } catch (e) {}
+
+      var acceptRanges;
+      try {
+        acceptRanges = aRequest.getResponseHeader('Accept-Ranges');
+      } catch (e) {}
+
+      var hash = aRequest.URI.ref;
+      rangeRequest = contentEncoding === 'identity' &&
+                     acceptRanges === 'bytes' &&
+                     aRequest.contentLength >= 0 &&
+                     hash.indexOf('disableRange=true') < 0;
+    }
+
     aRequest.QueryInterface(Ci.nsIChannel);
+
     aRequest.QueryInterface(Ci.nsIWritablePropertyBag);
-    // Creating storage for PDF data
-    var contentLength = aRequest.contentLength;
-    var dataListener = new PdfDataListener(contentLength);
+
     var contentDispositionFilename;
     try {
       contentDispositionFilename = aRequest.contentDispositionFilename;
     } catch (e) {}
-    this.dataListener = dataListener;
-    this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
-                        .createInstance(Ci.nsIBinaryInputStream);
 
     // Change the content type so we don't get stuck in a loop.
     aRequest.setProperty('contentType', aRequest.contentType);
     aRequest.contentType = 'text/html';
 
+    if (!rangeRequest) {
+      // Creating storage for PDF data
+      var contentLength = aRequest.contentLength;
+      this.dataListener = new PdfDataListener(contentLength);
+      this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
+                          .createInstance(Ci.nsIBinaryInputStream);
+    } else {
+      // Suspend the request so we're not consuming any of the stream,
+      // but we can't cancel the request yet. Otherwise, the original
+      // listener will think we do not want to go the new PDF url
+      aRequest.suspend();
+    }
+
     // Create a new channel that is viewer loaded as a resource.
     var ioService = Services.io;
     var channel = ioService.newChannel(
                     PDF_VIEWER_WEB_PAGE, null, null);
 
     var listener = this.listener;
+    var dataListener = this.dataListener;
     // Proxy all the request observer calls, when it gets to onStopRequest
     // we can get the dom window.  We also intentionally pass on the original
     // request(aRequest) below so we don't overwrite the original channel and
     // trigger an assertion.
     var proxy = {
       onStartRequest: function(request, context) {
         listener.onStartRequest(aRequest, context);
       },
       onDataAvailable: function(request, context, inputStream, offset, count) {
         listener.onDataAvailable(aRequest, context, inputStream, offset, count);
       },
       onStopRequest: function(request, context, statusCode) {
         // We get the DOM window here instead of before the request since it
         // may have changed during a redirect.
         var domWindow = getDOMWindow(channel);
-        // Double check the url is still the correct one.
-        if (domWindow.document.documentURIObject.equals(aRequest.URI)) {
-          var actions = new ChromeActions(domWindow, dataListener,
-                                          contentDispositionFilename);
-          var requestListener = new RequestListener(actions);
-          domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
-            requestListener.receive(event);
-          }, false, true);
-          if (actions.supportsIntegratedFind()) {
-            var chromeWindow = getChromeWindow(domWindow);
-            var findEventManager = new FindEventManager(chromeWindow.gFindBar,
-                                                        domWindow,
-                                                        chromeWindow);
-            findEventManager.bind();
-          }
+        var actions;
+        if (rangeRequest) {
+          // We are going to be issuing range requests, so cancel the
+          // original request
+          aRequest.resume();
+          aRequest.cancel(Cr.NS_BINDING_ABORTED);
+          actions = new RangedChromeActions(domWindow,
+              contentDispositionFilename, aRequest);
         } else {
-          log('Dom window url did not match request url.');
+          actions = new StandardChromeActions(
+              domWindow, contentDispositionFilename, dataListener);
+        }
+        var requestListener = new RequestListener(actions);
+        domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
+          requestListener.receive(event);
+        }, false, true);
+        if (actions.supportsIntegratedFind()) {
+          var chromeWindow = getChromeWindow(domWindow);
+          var findEventManager = new FindEventManager(chromeWindow.gFindBar,
+                                                      domWindow,
+                                                      chromeWindow);
+          findEventManager.bind();
         }
         listener.onStopRequest(aRequest, context, statusCode);
       }
     };
 
     // Keep the URL the same so the browser sees it as the same.
     channel.originalURI = aRequest.URI;
     channel.loadGroup = aRequest.loadGroup;
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -11,18 +11,18 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 var PDFJS = {};
-PDFJS.version = '0.8.47';
-PDFJS.build = 'added3d';
+PDFJS.version = '0.8.169';
+PDFJS.build = '869b878';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -34,125 +34,658 @@ PDFJS.build = 'added3d';
  *     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 assertWellFormed, calculateMD5, Catalog, error, info, isArray,
-           isArrayBuffer, isDict, isName, isStream, isString, Lexer,
-           Linearization, NullStream, PartialEvaluator, shadow, Stream,
-           StreamsSequenceStream, stringToPDFString, TODO, Util, warn, XRef */
+/* globals assert, MissingDataException, isInt, NetworkManager, Promise,
+           isEmptyObj */
 
 'use strict';
 
+var ChunkedStream = (function ChunkedStreamClosure() {
+  function ChunkedStream(length, chunkSize) {
+    this.bytes = new Uint8Array(length);
+    this.start = 0;
+    this.pos = 0;
+    this.end = length;
+    this.chunkSize = chunkSize;
+    this.loadedChunks = [];
+    this.numChunksLoaded = 0;
+    this.numChunks = Math.ceil(length / chunkSize);
+  }
+
+  // required methods for a stream. if a particular stream does not
+  // implement these, an error should be thrown
+  ChunkedStream.prototype = {
+
+    getMissingChunks: function ChunkedStream_getMissingChunks() {
+      var chunks = [];
+      for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
+        if (!(chunk in this.loadedChunks)) {
+          chunks.push(chunk);
+        }
+      }
+      return chunks;
+    },
+
+    allChunksLoaded: function ChunkedStream_allChunksLoaded() {
+      return this.numChunksLoaded === this.numChunks;
+    },
+
+    onReceiveData: function(begin, chunk) {
+      var end = begin + chunk.byteLength;
+
+      assert(begin % this.chunkSize === 0, 'Bad begin offset: ' + begin);
+      // Using this.length is inaccurate here since this.start can be moved
+      // See ChunkedStream.moveStart()
+      var length = this.bytes.length;
+      assert(end % this.chunkSize === 0 || end === length,
+        'Bad end offset: ' + end);
+
+      this.bytes.set(new Uint8Array(chunk), begin);
+      var chunkSize = this.chunkSize;
+      var beginChunk = Math.floor(begin / chunkSize);
+      var endChunk = Math.floor((end - 1) / chunkSize) + 1;
+
+      for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+        if (!(chunk in this.loadedChunks)) {
+          this.loadedChunks[chunk] = true;
+          ++this.numChunksLoaded;
+        }
+      }
+    },
+
+    ensureRange: function ChunkedStream_ensureRange(begin, end) {
+      if (begin >= end) {
+        return;
+      }
+
+      var chunkSize = this.chunkSize;
+      var beginChunk = Math.floor(begin / chunkSize);
+      var endChunk = Math.floor((end - 1) / chunkSize) + 1;
+      for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+        if (!(chunk in this.loadedChunks)) {
+          throw new MissingDataException(begin, end);
+        }
+      }
+    },
+
+    nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
+      for (var chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
+        if (!(chunk in this.loadedChunks)) {
+          return chunk;
+        }
+      }
+      // Wrap around to beginning
+      for (var chunk = 0; chunk < beginChunk; ++chunk) {
+        if (!(chunk in this.loadedChunks)) {
+          return chunk;
+        }
+      }
+      return null;
+    },
+
+    hasChunk: function ChunkedStream_hasChunk(chunk) {
+      return chunk in this.loadedChunks;
+    },
+
+    get length() {
+      return this.end - this.start;
+    },
+
+    getByte: function ChunkedStream_getByte() {
+      var pos = this.pos;
+      if (pos >= this.end) {
+        return null;
+      }
+      this.ensureRange(pos, pos + 1);
+      return this.bytes[this.pos++];
+    },
+
+    // returns subarray of original buffer
+    // should only be read
+    getBytes: function ChunkedStream_getBytes(length) {
+      var bytes = this.bytes;
+      var pos = this.pos;
+      var strEnd = this.end;
+
+      if (!length) {
+        this.ensureRange(pos, strEnd);
+        return bytes.subarray(pos, strEnd);
+      }
+
+      var end = pos + length;
+      if (end > strEnd)
+        end = strEnd;
+      this.ensureRange(pos, end);
+
+      this.pos = end;
+      return bytes.subarray(pos, end);
+    },
+
+    getByteRange: function ChunkedStream_getBytes(begin, end) {
+      this.ensureRange(begin, end);
+      return this.bytes.subarray(begin, end);
+    },
+
+    lookChar: function ChunkedStream_lookChar() {
+      var pos = this.pos;
+      if (pos >= this.end)
+        return null;
+      this.ensureRange(pos, pos + 1);
+      return String.fromCharCode(this.bytes[pos]);
+    },
+
+    getChar: function ChunkedStream_getChar() {
+      var pos = this.pos;
+      if (pos >= this.end)
+        return null;
+      this.ensureRange(pos, pos + 1);
+      return String.fromCharCode(this.bytes[this.pos++]);
+    },
+
+    skip: function ChunkedStream_skip(n) {
+      if (!n)
+        n = 1;
+      this.pos += n;
+    },
+
+    reset: function ChunkedStream_reset() {
+      this.pos = this.start;
+    },
+
+    moveStart: function ChunkedStream_moveStart() {
+      this.start = this.pos;
+    },
+
+    makeSubStream: function ChunkedStream_makeSubStream(start, length, dict) {
+      function ChunkedStreamSubstream() {}
+      ChunkedStreamSubstream.prototype = Object.create(this);
+      var subStream = new ChunkedStreamSubstream();
+      subStream.pos = subStream.start = start;
+      subStream.end = start + length || this.end;
+      subStream.dict = dict;
+      return subStream;
+    },
+
+    isStream: true
+  };
+
+  return ChunkedStream;
+})();
+
+var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
+
+  function ChunkedStreamManager(length, chunkSize, url, args) {
+    var self = this;
+    this.stream = new ChunkedStream(length, chunkSize);
+    this.length = length;
+    this.chunkSize = chunkSize;
+    this.url = url;
+    this.disableAutoFetch = args.disableAutoFetch;
+    var msgHandler = this.msgHandler = args.msgHandler;
+
+    if (args.chunkedViewerLoading) {
+      msgHandler.on('OnDataRange', this.onReceiveData.bind(this));
+      msgHandler.on('OnDataProgress', this.onProgress.bind(this));
+      this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
+        msgHandler.send('RequestDataRange', { begin: begin, end: end });
+      };
+    } else {
+
+      var getXhr = function getXhr() {
+        return new XMLHttpRequest();
+      };
+      this.networkManager = new NetworkManager(this.url, {
+        getXhr: getXhr,
+        httpHeaders: args.httpHeaders
+      });
+      this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
+        this.networkManager.requestRange(begin, end, {
+          onDone: this.onReceiveData.bind(this),
+          onProgress: this.onProgress.bind(this)
+        });
+      };
+    }
+
+    this.currRequestId = 0;
+
+    this.chunksNeededByRequest = {};
+    this.requestsByChunk = {};
+    this.callbacksByRequest = {};
+
+    this.loadedStream = new Promise();
+  }
+
+  ChunkedStreamManager.prototype = {
+
+    onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
+      return this.loadedStream;
+    },
+
+    // Get all the chunks that are not yet loaded and groups them into
+    // contiguous ranges to load in as few requests as possible
+    requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
+      var missingChunks = this.stream.getMissingChunks();
+      var chunksToRequest = [];
+      for (var i = 0, n = missingChunks.length; i < n; ++i) {
+        var chunk = missingChunks[i];
+        if (!(chunk in this.requestsByChunk)) {
+          this.requestsByChunk[chunk] = [];
+          chunksToRequest.push(chunk);
+        }
+      }
+      var groupedChunks = this.groupChunks(chunksToRequest);
+      for (var i = 0, n = groupedChunks.length; i < n; ++i) {
+        var groupedChunk = groupedChunks[i];
+        var begin = groupedChunk.beginChunk * this.chunkSize;
+        var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
+        this.sendRequest(begin, end);
+      }
+
+      return this.loadedStream;
+    },
+
+    getStream: function ChunkedStreamManager_getStream() {
+      return this.stream;
+    },
+
+    // Loads any chunks in the requested range that are not yet loaded
+    requestRange: function ChunkedStreamManager_requestRange(
+                      begin, end, callback) {
+
+      end = Math.min(end, this.length);
+
+      var beginChunk = this.getBeginChunk(begin);
+      var endChunk = this.getEndChunk(end);
+
+      var requestId = this.currRequestId++;
+
+      var chunksNeeded;
+      this.chunksNeededByRequest[requestId] = chunksNeeded = {};
+      for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+        if (!this.stream.hasChunk(chunk)) {
+          chunksNeeded[chunk] = true;
+        }
+      }
+
+      if (isEmptyObj(chunksNeeded)) {
+        callback();
+        return;
+      }
+
+      this.callbacksByRequest[requestId] = callback;
+
+      var chunksToRequest = [];
+      for (var chunk in chunksNeeded) {
+        chunk = chunk | 0;
+        if (!(chunk in this.requestsByChunk)) {
+          this.requestsByChunk[chunk] = [];
+          chunksToRequest.push(chunk);
+        }
+        this.requestsByChunk[chunk].push(requestId);
+      }
+
+      if (!chunksToRequest.length) {
+        return;
+      }
+
+      var groupedChunksToRequest = this.groupChunks(chunksToRequest);
+
+      for (var i = 0; i < groupedChunksToRequest.length; ++i) {
+        var groupedChunk = groupedChunksToRequest[i];
+        var begin = groupedChunk.beginChunk * this.chunkSize;
+        var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
+        this.sendRequest(begin, end);
+      }
+    },
+
+    // Groups a sorted array of chunks into as few continguous larger
+    // chunks as possible
+    groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
+      var groupedChunks = [];
+      var beginChunk;
+      var prevChunk;
+      for (var i = 0; i < chunks.length; ++i) {
+        var chunk = chunks[i];
+
+        if (!beginChunk) {
+          beginChunk = chunk;
+        }
+
+        if (prevChunk && prevChunk + 1 !== chunk) {
+          groupedChunks.push({
+            beginChunk: beginChunk, endChunk: prevChunk + 1});
+          beginChunk = chunk;
+        }
+        if (i + 1 === chunks.length) {
+          groupedChunks.push({
+            beginChunk: beginChunk, endChunk: chunk + 1});
+        }
+
+        prevChunk = chunk;
+      }
+      return groupedChunks;
+    },
+
+    onProgress: function ChunkedStreamManager_onProgress(args) {
+      var bytesLoaded = this.stream.numChunksLoaded * this.chunkSize +
+                        args.loaded;
+      this.msgHandler.send('DocProgress', {
+        loaded: bytesLoaded,
+        total: this.length
+      });
+    },
+
+    onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
+      var chunk = args.chunk;
+      var begin = args.begin;
+      var end = begin + chunk.byteLength;
+
+      var beginChunk = this.getBeginChunk(begin);
+      var endChunk = this.getEndChunk(end);
+
+      this.stream.onReceiveData(begin, chunk);
+      if (this.stream.allChunksLoaded()) {
+        this.loadedStream.resolve(this.stream);
+      }
+
+      var loadedRequests = [];
+      for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
+
+        // The server might return more chunks than requested
+        var requestIds = this.requestsByChunk[chunk] || [];
+        delete this.requestsByChunk[chunk];
+
+        for (var i = 0; i < requestIds.length; ++i) {
+          var requestId = requestIds[i];
+          var chunksNeeded = this.chunksNeededByRequest[requestId];
+          if (chunk in chunksNeeded) {
+            delete chunksNeeded[chunk];
+          }
+
+          if (!isEmptyObj(chunksNeeded)) {
+            continue;
+          }
+
+          loadedRequests.push(requestId);
+        }
+      }
+
+      // If there are no pending requests, automatically fetch the next
+      // unfetched chunk of the PDF
+      if (!this.disableAutoFetch && isEmptyObj(this.requestsByChunk)) {
+        var nextEmptyChunk;
+        if (this.stream.numChunksLoaded === 1) {
+          // This is a special optimization so that after fetching the first
+          // chunk, rather than fetching the second chunk, we fetch the last
+          // chunk.
+          var lastChunk = this.stream.numChunks - 1;
+          if (!this.stream.hasChunk(lastChunk)) {
+            nextEmptyChunk = lastChunk;
+          }
+        } else {
+          nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
+        }
+        if (isInt(nextEmptyChunk)) {
+          var nextEmptyByte = nextEmptyChunk * this.chunkSize;
+          this.requestRange(nextEmptyByte, nextEmptyByte + this.chunkSize,
+              function() {});
+        }
+      }
+
+      for (var i = 0; i < loadedRequests.length; ++i) {
+        var requestId = loadedRequests[i];
+        var callback = this.callbacksByRequest[requestId];
+        delete this.callbacksByRequest[requestId];
+        callback();
+      }
+
+      this.msgHandler.send('DocProgress', {
+        loaded: this.stream.numChunksLoaded * this.chunkSize,
+        total: this.length
+      });
+    },
+
+    getBeginChunk: function ChunkedStreamManager_getBeginChunk(begin) {
+      var chunk = Math.floor(begin / this.chunkSize);
+      return chunk;
+    },
+
+    getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
+      if (end % this.chunkSize === 0) {
+        return end / this.chunkSize;
+      }
+
+      // 0 -> 0
+      // 1 -> 1
+      // 99 -> 1
+      // 100 -> 1
+      // 101 -> 2
+      var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
+      return chunk;
+    }
+  };
+
+  return ChunkedStreamManager;
+})();
+
+
+
+// TODO(mack): Make use of PDFJS.Util.inherit() when it becomes available
+var BasePdfManager = (function BasePdfManagerClosure() {
+  function BasePdfManager() {
+    throw new Error('Cannot initialize BaseManagerManager');
+  }
+
+  BasePdfManager.prototype = {
+    onLoadedStream: function BasePdfManager_onLoadedStream() {
+      throw new NotImplementedException();
+    },
+
+    ensureModel: function BasePdfManager_ensureModel(prop, args) {
+      return this.ensure(this.pdfModel, prop, args);
+    },
+
+    ensureXRef: function BasePdfManager_ensureXRef(prop, args) {
+      return this.ensure(this.pdfModel.xref, prop, args);
+    },
+
+    ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
+      return this.ensure(this.pdfModel.catalog, prop, args);
+    },
+
+    getPage: function BasePdfManager_pagePage(pageIndex) {
+      return this.pdfModel.getPage(pageIndex);
+    },
+
+    ensure: function BasePdfManager_ensure(obj, prop, args) {
+      return new NotImplementedException();
+    },
+
+    requestRange: function BasePdfManager_ensure(begin, end) {
+      return new NotImplementedException();
+    },
+
+    requestLoadedStream: function BasePdfManager_requestLoadedStream() {
+      return new NotImplementedException();
+    },
+
+    updatePassword: function BasePdfManager_updatePassword(password) {
+      this.pdfModel.xref.password = this.password = password;
+      if (this.passwordChangedPromise) {
+        this.passwordChangedPromise.resolve();
+      }
+    }
+  };
+
+  return BasePdfManager;
+})();
+
+var LocalPdfManager = (function LocalPdfManagerClosure() {
+  function LocalPdfManager(data, password) {
+    var stream = new Stream(data);
+    this.pdfModel = new PDFDocument(this, stream, password);
+    this.loadedStream = new Promise();
+    this.loadedStream.resolve(stream);
+  }
+
+  LocalPdfManager.prototype = Object.create(BasePdfManager.prototype);
+  LocalPdfManager.prototype.constructor = LocalPdfManager;
+
+  LocalPdfManager.prototype.ensure =
+      function LocalPdfManager_ensure(obj, prop, args) {
+    var promise = new Promise();
+    try {
+      var value = obj[prop];
+      var result;
+      if (typeof(value) === 'function') {
+        result = value.apply(obj, args);
+      } else {
+        result = value;
+      }
+      promise.resolve(result);
+    } catch (e) {
+      console.log(e.stack);
+      promise.reject(e);
+    }
+    return promise;
+  };
+
+  LocalPdfManager.prototype.requestRange =
+      function LocalPdfManager_requestRange(begin, end) {
+    var promise = new Promise();
+    promise.resolve();
+    return promise;
+  };
+
+  LocalPdfManager.prototype.requestLoadedStream =
+      function LocalPdfManager_requestLoadedStream() {
+  };
+
+  LocalPdfManager.prototype.onLoadedStream =
+      function LocalPdfManager_getLoadedStream() {
+    return this.loadedStream;
+  };
+
+  return LocalPdfManager;
+})();
+
+var NetworkPdfManager = (function NetworkPdfManagerClosure() {
+
+  var CHUNK_SIZE = 65536;
+
+  function NetworkPdfManager(args, msgHandler) {
+
+    this.msgHandler = msgHandler;
+
+    var params = {
+      msgHandler: msgHandler,
+      httpHeaders: args.httpHeaders,
+      chunkedViewerLoading: args.chunkedViewerLoading,
+      disableAutoFetch: args.disableAutoFetch
+    };
+    this.streamManager = new ChunkedStreamManager(args.length, CHUNK_SIZE,
+                                                  args.url, params);
+
+    this.pdfModel = new PDFDocument(this, this.streamManager.getStream(),
+                                    args.password);
+  }
+
+  NetworkPdfManager.prototype = Object.create(BasePdfManager.prototype);
+  NetworkPdfManager.prototype.constructor = NetworkPdfManager;
+
+  NetworkPdfManager.prototype.ensure =
+      function NetworkPdfManager_ensure(obj, prop, args) {
+    var promise = new Promise();
+    this.ensureHelper(promise, obj, prop, args);
+    return promise;
+  };
+
+  NetworkPdfManager.prototype.ensureHelper =
+      function NetworkPdfManager_ensureHelper(promise, obj, prop, args) {
+    try {
+      var result;
+      var value = obj[prop];
+      if (typeof(value) === 'function') {
+        result = value.apply(obj, args);
+      } else {
+        result = value;
+      }
+      promise.resolve(result);
+    } catch(e) {
+      if (!(e instanceof MissingDataException)) {
+        console.log(e.stack);
+        promise.reject(e);
+        return;
+      }
+
+      this.streamManager.requestRange(e.begin, e.end, function() {
+        this.ensureHelper(promise, obj, prop, args);
+      }.bind(this));
+    }
+  };
+
+  NetworkPdfManager.prototype.requestRange =
+      function NetworkPdfManager_requestRange(begin, end) {
+    var promise = new Promise();
+    this.streamManager.requestRange(begin, end, function() {
+      promise.resolve();
+    });
+    return promise;
+  };
+
+  NetworkPdfManager.prototype.requestLoadedStream =
+      function NetworkPdfManager_requestLoadedStream() {
+    this.streamManager.requestAllChunks();
+  };
+
+  NetworkPdfManager.prototype.onLoadedStream =
+      function NetworkPdfManager_getLoadedStream() {
+    return this.streamManager.onLoadedStream();
+  };
+
+  return NetworkPdfManager;
+})();
+
+
+
 var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
 var ERRORS = 0, WARNINGS = 1, INFOS = 5;
 var verbosity = WARNINGS;
 
 // The global PDFJS object exposes the API
 // In production, it will be declared outside a global wrapper
 // In development, it will be declared here
 if (!globalScope.PDFJS) {
   globalScope.PDFJS = {};
 }
 
-// getPdf()
-// Convenience function to perform binary Ajax GET
-// Usage: getPdf('http://...', callback)
-//        getPdf({
-//                 url:String ,
-//                 [,progress:Function, error:Function]
-//               },
-//               callback)
-function getPdf(arg, callback) {
-  var params = arg;
-  if (typeof arg === 'string')
-    params = { url: arg };
-  var xhr = new XMLHttpRequest();
-  xhr.open('GET', params.url);
-
-  var headers = params.headers;
-  if (headers) {
-    for (var property in headers) {
-      if (typeof headers[property] === 'undefined')
-        continue;
-
-      xhr.setRequestHeader(property, params.headers[property]);
-    }
-  }
-
-  xhr.mozResponseType = xhr.responseType = 'arraybuffer';
-
-  var protocol = params.url.substring(0, params.url.indexOf(':') + 1);
-  xhr.expected = (protocol === 'http:' || protocol === 'https:') ? 200 : 0;
-
-  if ('progress' in params)
-    xhr.onprogress = params.progress || undefined;
-
-  var calledErrorBack = false;
-
-  if ('error' in params) {
-    xhr.onerror = function errorBack() {
-      if (!calledErrorBack) {
-        calledErrorBack = true;
-        params.error();
-      }
-    };
-  }
-
-  xhr.onreadystatechange = function getPdfOnreadystatechange(e) {
-    if (xhr.readyState === 4) {
-      if (xhr.status === xhr.expected) {
-        var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
-                    xhr.responseArrayBuffer || xhr.response);
-        callback(data);
-      } else if (params.error && !calledErrorBack) {
-        calledErrorBack = true;
-        params.error(e);
-      }
-    }
-  };
-  xhr.send(null);
-}
-globalScope.PDFJS.getPdf = getPdf;
 globalScope.PDFJS.pdfBug = false;
 
 
 var Page = (function PageClosure() {
 
-  function getDefaultAnnotationAppearance(annotationDict) {
-    var appearanceState = annotationDict.get('AP');
-    if (!isDict(appearanceState)) {
-      return;
-    }
-
-    var appearance;
-    var appearances = appearanceState.get('N');
-    if (isDict(appearances)) {
-      var as = annotationDict.get('AS');
-      if (as && appearances.has(as.name)) {
-        appearance = appearances.get(as.name);
-      }
-    } else {
-      appearance = appearances;
-    }
-    return appearance;
-  }
-
-  function Page(xref, pageIndex, pageDict, ref) {
+  function Page(pdfManager, xref, pageIndex, pageDict, ref) {
+    this.pdfManager = pdfManager;
     this.pageIndex = pageIndex;
     this.pageDict = pageDict;
     this.xref = xref;
     this.ref = ref;
+    this.idCounters = {
+      font: 0,
+      obj: 0
+    };
   }
 
   Page.prototype = {
     getPageProp: function Page_getPageProp(key) {
       return this.pageDict.get(key);
     },
     inheritPageProp: function Page_inheritPageProp(key) {
       var dict = this.pageDict;
@@ -161,17 +694,17 @@ var Page = (function PageClosure() {
         dict = dict.get('Parent');
         if (!dict)
           break;
         obj = dict.get(key);
       }
       return obj;
     },
     get content() {
-      return shadow(this, 'content', this.getPageProp('Contents'));
+      return this.getPageProp('Contents');
     },
     get resources() {
       return shadow(this, 'resources', this.inheritPageProp('Resources'));
     },
     get mediaBox() {
       var obj = this.inheritPageProp('MediaBox');
       // Reset invalid media box to letter size.
       if (!isArray(obj) || obj.length !== 4)
@@ -189,18 +722,18 @@ var Page = (function PageClosure() {
       // extend beyond the boundaries of the media box. If they do, they are
       // effectively reduced to their intersection with the media box."
       cropBox = Util.intersect(cropBox, mediaBox);
       if (!cropBox)
         return shadow(this, 'view', mediaBox);
 
       return shadow(this, 'view', cropBox);
     },
-    get annotations() {
-      return shadow(this, 'annotations', this.inheritPageProp('Annots'));
+    get annotationRefs() {
+      return shadow(this, 'annotationRefs', this.inheritPageProp('Annots'));
     },
     get rotate() {
       var rotate = this.inheritPageProp('Rotate') || 0;
       // Normalize rotation so it's a multiple of 90 and between 0 and 270
       if (rotate % 90 !== 0) {
         rotate = 0;
       } else if (rotate >= 360) {
         rotate = rotate % 360;
@@ -208,322 +741,198 @@ var Page = (function PageClosure() {
         // The spec doesn't cover negatives, assume its counterclockwise
         // rotation. The following is the other implementation of modulo.
         rotate = ((rotate % 360) + 360) % 360;
       }
       return shadow(this, 'rotate', rotate);
     },
     getContentStream: function Page_getContentStream() {
       var content = this.content;
+      var stream;
       if (isArray(content)) {
         // fetching items
         var xref = this.xref;
         var i, n = content.length;
         var streams = [];
         for (i = 0; i < n; ++i)
           streams.push(xref.fetchIfRef(content[i]));
-        content = new StreamsSequenceStream(streams);
+        stream = new StreamsSequenceStream(streams);
       } else if (isStream(content)) {
-        content.reset();
-      } else if (!content) {
+        stream = content;
+      } else {
         // replacing non-existent page content with empty one
-        content = new NullStream();
-      }
-      return content;
-    },
-    getOperatorList: function Page_getOperatorList(handler, dependency) {
-      var xref = this.xref;
-      var contentStream = this.getContentStream();
-      var resources = this.resources;
-      var pe = this.pe = new PartialEvaluator(
-                                xref, handler, this.pageIndex,
-                                'p' + this.pageIndex + '_');
-
-      var list = pe.getOperatorList(contentStream, resources, dependency);
-
-      var annotations = this.getAnnotationsForDraw();
-      var annotationEvaluator = new PartialEvaluator(
-        xref, handler, this.pageIndex,
-        'p' + this.pageIndex + '_annotation');
-      var annotationsList = annotationEvaluator.getAnnotationsOperatorList(
-          annotations, dependency);
-
-      Util.concatenateToArray(list.fnArray, annotationsList.fnArray);
-      Util.concatenateToArray(list.argsArray, annotationsList.argsArray);
-
-      pe.optimizeQueue(list);
-      return list;
+        stream = new NullStream();
+      }
+      return stream;
+    },
+    getOperatorList: function Page_getOperatorList(handler) {
+      var self = this;
+      var promise = new Promise();
+
+      function reject(e) {
+        promise.reject(e);
+      }
+
+      var pageListPromise = new Promise();
+
+      var pdfManager = this.pdfManager;
+      var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
+                                                   []);
+      var resourcesPromise = pdfManager.ensure(this, 'resources');
+
+      var partialEvaluator = new PartialEvaluator(
+            pdfManager, this.xref, handler,
+            this.pageIndex, 'p' + this.pageIndex + '_',
+            this.idCounters);
+
+      var dataPromises = Promise.all(
+          [contentStreamPromise, resourcesPromise], reject);
+      dataPromises.then(function(data) {
+        var contentStream = data[0];
+        var resources = data[1];
+
+        pdfManager.ensure(partialEvaluator, 'getOperatorList',
+                          [contentStream, resources]).then(
+          function(opListPromise) {
+            opListPromise.then(function(data) {
+              pageListPromise.resolve(data);
+            });
+          },
+          reject
+        );
+      });
+
+      var annotationsPromise = pdfManager.ensure(this, 'annotations');
+      Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
+        var pageData = datas[0];
+        var pageQueue = pageData.queue;
+        var annotations = datas[1];
+
+        var ensurePromises = [];
+        for (var i = 0, n = annotations.length; i < n; ++i) {
+          var ensurePromise = pdfManager.ensure(annotations[i],
+                                                'getOperatorList',
+                                                [partialEvaluator]);
+          ensurePromises.push(ensurePromise);
+        }
+
+        Promise.all(ensurePromises).then(function(listPromises) {
+          Promise.all(listPromises).then(function(datas) {
+            for (var i = 0, n = datas.length; i < n; ++i) {
+              var annotationData = datas[i];
+              var annotationQueue = annotationData.queue;
+              Util.concatenateToArray(pageQueue.fnArray,
+                                      annotationQueue.fnArray);
+              Util.concatenateToArray(pageQueue.argsArray,
+                                      annotationQueue.argsArray);
+              Util.extendObj(pageData.dependencies,
+                             annotationData.dependencies);
+            }
+
+            PartialEvaluator.optimizeQueue(pageQueue);
+
+            promise.resolve(pageData);
+          }, reject);
+        }, reject);
+      }, reject);
+
+      return promise;
     },
     extractTextContent: function Page_extractTextContent() {
       var handler = {
         on: function nullHandlerOn() {},
         send: function nullHandlerSend() {}
       };
 
-      var xref = this.xref;
-      var contentStream = this.getContentStream();
-      var resources = xref.fetchIfRef(this.resources);
-
-      var pe = new PartialEvaluator(
-                     xref, handler, this.pageIndex,
-                     'p' + this.pageIndex + '_');
-      return pe.getTextContent(contentStream, resources);
-    },
-    getLinks: function Page_getLinks() {
-      var links = [];
-      var annotations = this.getAnnotations();
-      var i, n = annotations.length;
-      for (i = 0; i < n; ++i) {
-        if (annotations[i].type != 'Link')
-          continue;
-        links.push(annotations[i]);
-      }
-      return links;
-    },
-
-    getAnnotations: function Page_getAnnotations() {
-      var annotations = this.getAnnotationsBase();
-      var items = [];
-      for (var i = 0, length = annotations.length; i < length; ++i) {
-        items.push(annotations[i].item);
-      }
-      return items;
-    },
-
-    getAnnotationsForDraw: function Page_getAnnotationsForDraw() {
-      var annotations = this.getAnnotationsBase();
-      var items = [];
-      for (var i = 0, length = annotations.length; i < length; ++i) {
-        var item = annotations[i].item;
-        var annotationDict = annotations[i].dict;
-
-        item.annotationFlags = annotationDict.get('F');
-
-        var appearance = getDefaultAnnotationAppearance(annotationDict);
-        if (appearance &&
-            // TODO(mack): The proper implementation requires that the
-            // appearance stream overrides Name, but we're currently
-            // doing it the other way around for 'Text' annotations since we
-            // have special rendering for it
-            item.type !== 'Text') {
-
-          item.appearance = appearance;
-          var appearanceDict = appearance.dict;
-          item.resources = appearanceDict.get('Resources');
-          item.bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
-          item.matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
-        }
-
-        var border = annotationDict.get('BS');
-        if (isDict(border) && !item.appearance) {
-          var borderWidth = border.has('W') ? border.get('W') : 1;
-          if (borderWidth !== 0) {
-            item.border = {
-              width: borderWidth,
-              type: border.get('S') || 'S',
-              rgb: annotationDict.get('C') || [0, 0, 1]
-            };
-          }
-        }
-
-        items.push(item);
-      }
-
-      return items;
-    },
-
-    getAnnotationsBase: function Page_getAnnotationsBase() {
-      var xref = this.xref;
-      function getInheritableProperty(annotation, name) {
-        var item = annotation;
-        while (item && !item.has(name)) {
-          item = item.get('Parent');
-        }
-        if (!item)
-          return null;
-        return item.get(name);
-      }
-      function isValidUrl(url) {
-        if (!url)
-          return false;
-        var colon = url.indexOf(':');
-        if (colon < 0)
-          return false;
-        var protocol = url.substr(0, colon);
-        switch (protocol) {
-          case 'http':
-          case 'https':
-          case 'ftp':
-          case 'mailto':
-            return true;
-          default:
-            return false;
-        }
-      }
-
-      var annotations = this.annotations || [];
-      var i, n = annotations.length;
-      var items = [];
-      for (i = 0; i < n; ++i) {
-        var annotationRef = annotations[i];
-        var annotation = xref.fetchIfRef(annotationRef);
-        if (!isDict(annotation))
-          continue;
-        var subtype = annotation.get('Subtype');
-        if (!isName(subtype))
-          continue;
-
-        var item = {};
-        item.type = subtype.name;
-        var rect = annotation.get('Rect');
-        item.rect = Util.normalizeRect(rect);
-
-        var includeAnnotation = true;
-        switch (subtype.name) {
-          case 'Link':
-            var a = annotation.get('A');
-            if (a) {
-              switch (a.get('S').name) {
-                case 'URI':
-                  var url = a.get('URI');
-                  // TODO: pdf spec mentions urls can be relative to a Base
-                  // entry in the dictionary.
-                  if (!isValidUrl(url))
-                    url = '';
-                  item.url = url;
-                  break;
-                case 'GoTo':
-                  item.dest = a.get('D');
-                  break;
-                case 'GoToR':
-                  var url = a.get('F');
-                  if (isDict(url)) {
-                    // We assume that the 'url' is a Filspec dictionary
-                    // and fetch the url without checking any further
-                    url = url.get('F') || '';
-                  }
-
-                  // TODO: pdf reference says that GoToR
-                  // can also have 'NewWindow' attribute
-                  if (!isValidUrl(url))
-                    url = '';
-                  item.url = url;
-                  item.dest = a.get('D');
-                  break;
-                default:
-                  TODO('unrecognized link type: ' + a.get('S').name);
-              }
-            } else if (annotation.has('Dest')) {
-              // simple destination link
-              var dest = annotation.get('Dest');
-              item.dest = isName(dest) ? dest.name : dest;
-            }
-            break;
-          case 'Widget':
-            var fieldType = getInheritableProperty(annotation, 'FT');
-            if (!isName(fieldType))
-              break;
-
-            // Do not display digital signatures since we do not currently
-            // validate them.
-            if (fieldType.name === 'Sig') {
-              includeAnnotation = false;
-              break;
-            }
-
-            item.fieldType = fieldType.name;
-            // Building the full field name by collecting the field and
-            // its ancestors 'T' properties and joining them using '.'.
-            var fieldName = [];
-            var namedItem = annotation, ref = annotationRef;
-            while (namedItem) {
-              var parent = namedItem.get('Parent');
-              var parentRef = namedItem.getRaw('Parent');
-              var name = namedItem.get('T');
-              if (name) {
-                fieldName.unshift(stringToPDFString(name));
-              } else {
-                // The field name is absent, that means more than one field
-                // with the same name may exist. Replacing the empty name
-                // with the '`' plus index in the parent's 'Kids' array.
-                // This is not in the PDF spec but necessary to id the
-                // the input controls.
-                var kids = parent.get('Kids');
-                var j, jj;
-                for (j = 0, jj = kids.length; j < jj; j++) {
-                  var kidRef = kids[j];
-                  if (kidRef.num == ref.num && kidRef.gen == ref.gen)
-                    break;
-                }
-                fieldName.unshift('`' + j);
-              }
-              namedItem = parent;
-              ref = parentRef;
-            }
-            item.fullName = fieldName.join('.');
-            var alternativeText = stringToPDFString(annotation.get('TU') || '');
-            item.alternativeText = alternativeText;
-            var da = getInheritableProperty(annotation, 'DA') || '';
-            var m = /([\d\.]+)\sTf/.exec(da);
-            if (m)
-              item.fontSize = parseFloat(m[1]);
-            item.textAlignment = getInheritableProperty(annotation, 'Q');
-            item.flags = getInheritableProperty(annotation, 'Ff') || 0;
-            break;
-          case 'Text':
-            var content = annotation.get('Contents');
-            var title = annotation.get('T');
-            item.content = stringToPDFString(content || '');
-            item.title = stringToPDFString(title || '');
-            item.name = !annotation.has('Name') ? 'Note' :
-              annotation.get('Name').name;
-            break;
-          default:
-            var appearance = getDefaultAnnotationAppearance(annotation);
-            if (!appearance) {
-              TODO('unimplemented annotation type: ' + subtype.name);
-            }
-            break;
-        }
-        if (includeAnnotation) {
-          items.push({
-            item: item,
-            dict: annotation
+      var self = this;
+
+      var textContentPromise = new Promise();
+
+      var pdfManager = this.pdfManager;
+      var contentStreamPromise = pdfManager.ensure(this, 'getContentStream',
+                                                   []);
+      var resourcesPromise = new Promise();
+      pdfManager.ensure(this, 'resources').then(function(resources) {
+        pdfManager.ensure(self.xref, 'fetchIfRef', [resources]).then(
+          function(resources) {
+            resourcesPromise.resolve(resources);
+          }
+        );
+      });
+
+      var dataPromises = Promise.all([contentStreamPromise,
+                                      resourcesPromise]);
+      dataPromises.then(function(data) {
+        var contentStream = data[0];
+        var resources = data[1];
+        var partialEvaluator = new PartialEvaluator(
+              pdfManager, self.xref, handler,
+              self.pageIndex, 'p' + self.pageIndex + '_',
+              self.idCounters);
+
+        partialEvaluator.getTextContent(
+            contentStream, resources).then(function(bidiTexts) {
+          textContentPromise.resolve({
+            bidiTexts: bidiTexts
           });
-        }
-      }
-      return items;
+        });
+      });
+
+      return textContentPromise;
+    },
+
+    getAnnotationsData: function Page_getAnnotationsData() {
+      var annotations = this.annotations;
+      var annotationsData = [];
+      for (var i = 0, n = annotations.length; i < n; ++i) {
+        annotationsData.push(annotations[i].getData());
+      }
+      return annotationsData;
+    },
+
+    get annotations() {
+      var annotations = [];
+      var annotationRefs = this.annotationRefs || [];
+      for (var i = 0, n = annotationRefs.length; i < n; ++i) {
+        var annotationRef = annotationRefs[i];
+        var annotation = Annotation.fromRef(this.xref, annotationRef);
+        if (annotation) {
+          annotations.push(annotation);
+        }
+      }
+      return shadow(this, 'annotations', annotations);
     }
   };
 
   return Page;
 })();
 
 /**
  * The `PDFDocument` holds all the data of the PDF file. Compared to the
  * `PDFDoc`, this one doesn't have any job management code.
  * Right now there exists one PDFDocument on the main thread + one object
  * for each worker. If there is no worker support enabled, there are two
  * `PDFDocument` objects on the main thread created.
  */
 var PDFDocument = (function PDFDocumentClosure() {
-  function PDFDocument(arg, password) {
+  function PDFDocument(pdfManager, arg, password) {
     if (isStream(arg))
-      init.call(this, arg, password);
+      init.call(this, pdfManager, arg, password);
     else if (isArrayBuffer(arg))
-      init.call(this, new Stream(arg), password);
+      init.call(this, pdfManager, new Stream(arg), password);
     else
       error('PDFDocument: Unknown argument type');
   }
 
-  function init(stream, password) {
+  function init(pdfManager, stream, password) {
     assertWellFormed(stream.length > 0, 'stream must have data');
+    this.pdfManager = pdfManager;
     this.stream = stream;
-    this.setup(password);
-    this.acroForm = this.catalog.catDict.get('AcroForm');
+    var xref = new XRef(this.stream, password);
+    this.xref = xref;
   }
 
   function find(stream, needle, limit, backwards) {
     var pos = stream.pos;
     var end = stream.end;
     var str = '';
     if (pos + limit > end)
       limit = end - pos;
@@ -551,25 +960,35 @@ var PDFDocument = (function PDFDocumentC
         CreationDate: isString,
         ModDate: isString,
         Trapped: isName
       });
     }
   };
 
   PDFDocument.prototype = {
+    parse: function PDFDocument_parse(recoveryMode) {
+      this.setup(recoveryMode);
+      this.acroForm = this.catalog.catDict.get('AcroForm');
+    },
+
     get linearization() {
       var length = this.stream.length;
       var linearization = false;
       if (length) {
         try {
           linearization = new Linearization(this.stream);
-          if (linearization.length != length)
+          if (linearization.length != length) {
             linearization = false;
+          }
         } catch (err) {
+          if (err instanceof MissingDataException) {
+            throw err;
+          }
+
           warn('The linearization data is not available ' +
                'or unreadable pdf data is found');
           linearization = false;
         }
       }
       // shadow the prototype getter with a data property
       return shadow(this, 'linearization', linearization);
     },
@@ -638,32 +1057,31 @@ var PDFDocument = (function PDFDocumentC
           version += ch;
         }
         // removing "%PDF-"-prefix
         this.pdfFormatVersion = version.substring(5);
         return;
       }
       // May not be a PDF file, continue anyway.
     },
-    setup: function PDFDocument_setup(password) {
-      this.checkHeader();
-      var xref = new XRef(this.stream,
-                          this.startXRef,
-                          this.mainXRefEntriesOffset,
-                          password);
-      this.xref = xref;
-      this.catalog = new Catalog(xref);
+    parseStartXRef: function PDFDocument_parseStartXRef() {
+      var startXRef = this.startXRef;
+      this.xref.setStartXRef(startXRef);
+    },
+    setup: function PDFDocument_setup(recoveryMode) {
+      this.xref.parse(recoveryMode);
+      this.catalog = new Catalog(this.pdfManager, this.xref);
     },
     get numPages() {
       var linearization = this.linearization;
       var num = linearization ? linearization.numPages : this.catalog.numPages;
       // shadow the prototype getter
       return shadow(this, 'numPages', num);
     },
-    getDocumentInfo: function PDFDocument_getDocumentInfo() {
+    get documentInfo() {
       var docInfo = {
         PDFFormatVersion: this.pdfFormatVersion,
         IsAcroFormPresent: !!this.acroForm
       };
       if (this.xref.trailer.has('Info')) {
         var infoDict = this.xref.trailer.get('Info');
 
         var validEntries = DocumentInfoValidators.entries;
@@ -676,19 +1094,19 @@ var PDFDocument = (function PDFDocumentC
               docInfo[key] = typeof value !== 'string' ? value :
                              stringToPDFString(value);
             } else {
               info('Bad value in document info for "' + key + '"');
             }
           }
         }
       }
-      return shadow(this, 'getDocumentInfo', docInfo);
-    },
-    getFingerprint: function PDFDocument_getFingerprint() {
+      return shadow(this, 'documentInfo', docInfo);
+    },
+    get fingerprint() {
       var xref = this.xref, fileID;
       if (xref.trailer.has('ID')) {
         fileID = '';
         var id = xref.trailer.get('ID')[0];
         id.split('').forEach(function(el) {
           fileID += Number(el.charCodeAt(0)).toString(16);
         });
       } else {
@@ -697,20 +1115,25 @@ var PDFDocument = (function PDFDocumentC
         var data = this.stream.bytes.subarray(0, 100);
         var hash = calculateMD5(data, 0, data.length);
         fileID = '';
         for (var i = 0, length = hash.length; i < length; i++) {
           fileID += Number(hash[i]).toString(16);
         }
       }
 
-      return shadow(this, 'getFingerprint', fileID);
-    },
-    getPage: function PDFDocument_getPage(n) {
-      return this.catalog.getPage(n);
+      return shadow(this, 'fingerprint', fileID);
+    },
+
+    traversePages: function PDFDocument_traversePages() {
+      this.catalog.traversePages();
+    },
+
+    getPage: function PDFDocument_getPage(pageIndex) {
+      return this.catalog.getPage(pageIndex);
     }
   };
 
   return PDFDocument;
 })();
 
 
 
@@ -829,16 +1252,21 @@ var LogManager = PDFJS.LogManager = (fun
 function shadow(obj, prop, value) {
   Object.defineProperty(obj, prop, { value: value,
                                      enumerable: true,
                                      configurable: true,
                                      writable: false });
   return value;
 }
 
+var PasswordResponses = PDFJS.PasswordResponses = {
+  NEED_PASSWORD: 1,
+  INCORRECT_PASSWORD: 2
+};
+
 var PasswordException = (function PasswordExceptionClosure() {
   function PasswordException(msg, code) {
     this.name = 'PasswordException';
     this.message = msg;
     this.code = code;
   }
 
   PasswordException.prototype = new Error();
@@ -879,16 +1307,55 @@ var MissingPDFException = (function Miss
   }
 
   MissingPDFException.prototype = new Error();
   MissingPDFException.constructor = MissingPDFException;
 
   return MissingPDFException;
 })();
 
+var NotImplementedException = (function NotImplementedExceptionClosure() {
+  function NotImplementedException(msg) {
+    this.message = msg;
+  }
+
+  NotImplementedException.prototype = new Error();
+  NotImplementedException.prototype.name = 'NotImplementedException';
+  NotImplementedException.constructor = NotImplementedException;
+
+  return NotImplementedException;
+})();
+
+var MissingDataException = (function MissingDataExceptionClosure() {
+  function MissingDataException(begin, end) {
+    this.begin = begin;
+    this.end = end;
+    this.message = 'Missing data [begin, end)';
+  }
+
+  MissingDataException.prototype = new Error();
+  MissingDataException.prototype.name = 'MissingDataException';
+  MissingDataException.constructor = MissingDataException;
+
+  return MissingDataException;
+})();
+
+var XRefParseException = (function XRefParseExceptionClosure() {
+  function XRefParseException(msg) {
+    this.message = msg;
+  }
+
+  XRefParseException.prototype = new Error();
+  XRefParseException.prototype.name = 'XRefParseException';
+  XRefParseException.constructor = XRefParseException;
+
+  return XRefParseException;
+})();
+
+
 function bytesToString(bytes) {
   var str = '';
   var length = bytes.length;
   for (var n = 0; n < length; ++n)
     str += String.fromCharCode(bytes[n]);
   return str;
 }
 
@@ -913,16 +1380,28 @@ var Util = PDFJS.Util = (function UtilCl
     var cs = new DeviceCmykCS();
     Util.makeCssCmyk = function makeCssCmyk(cmyk) {
       var rgb = cs.getRgb(cmyk, 0);
       return Util.makeCssRgb(rgb);
     };
     return Util.makeCssCmyk(cmyk);
   };
 
+  // Concatenates two transformation matrices together and returns the result.
+  Util.transform = function Util_transform(m1, m2) {
+    return [
+      m1[0] * m2[0] + m1[2] * m2[1],
+      m1[1] * m2[0] + m1[3] * m2[1],
+      m1[0] * m2[2] + m1[2] * m2[3],
+      m1[1] * m2[2] + m1[3] * m2[3],
+      m1[0] * m2[4] + m1[2] * m2[5] + m1[4],
+      m1[1] * m2[4] + m1[3] * m2[5] + m1[5]
+    ];
+  };
+
   // For 2d affine transforms
   Util.applyTransform = function Util_applyTransform(p, m) {
     var xt = p[0] * m[0] + p[1] * m[2] + m[4];
     var yt = p[0] * m[1] + p[1] * m[3] + m[5];
     return [xt, yt];
   };
 
   Util.applyInverseTransform = function Util_applyInverseTransform(p, m) {
@@ -1048,55 +1527,88 @@ var Util = PDFJS.Util = (function UtilCl
 
     return result;
   };
 
   Util.sign = function Util_sign(num) {
     return num < 0 ? -1 : 1;
   };
 
+  // TODO(mack): Rename appendToArray
   Util.concatenateToArray = function concatenateToArray(arr1, arr2) {
-    return Array.prototype.push.apply(arr1, arr2);
+    Array.prototype.push.apply(arr1, arr2);
+  };
+
+  Util.prependToArray = function concatenateToArray(arr1, arr2) {
+    Array.prototype.unshift.apply(arr1, arr2);
+  };
+
+  Util.extendObj = function extendObj(obj1, obj2) {
+    for (var key in obj2) {
+      obj1[key] = obj2[key];
+    }
+  };
+
+  Util.getInheritableProperty = function Util_getInheritableProperty(dict,
+                                                                     name) {
+    while (dict && !dict.has(name)) {
+      dict = dict.get('Parent');
+    }
+    if (!dict) {
+      return null;
+    }
+    return dict.get(name);
+  };
+
+  Util.inherit = function Util_inherit(sub, base, prototype) {
+    sub.prototype = Object.create(base.prototype);
+    sub.prototype.constructor = sub;
+    for (var prop in prototype) {
+      sub.prototype[prop] = prototype[prop];
+    }
   };
 
   return Util;
 })();
 
 var PageViewport = PDFJS.PageViewport = (function PageViewportClosure() {
-  function PageViewport(viewBox, scale, rotation, offsetX, offsetY) {
+  function PageViewport(viewBox, scale, rotation, offsetX, offsetY, dontFlip) {
     this.viewBox = viewBox;
     this.scale = scale;
     this.rotation = rotation;
     this.offsetX = offsetX;
     this.offsetY = offsetY;
 
     // creating transform to convert pdf coordinate system to the normal
     // canvas like coordinates taking in account scale and rotation
     var centerX = (viewBox[2] + viewBox[0]) / 2;
     var centerY = (viewBox[3] + viewBox[1]) / 2;
     var rotateA, rotateB, rotateC, rotateD;
-    switch (rotation % 360) {
-      case -180:
+    rotation = rotation % 360;
+    rotation = rotation < 0 ? rotation + 360 : rotation;
+    switch (rotation) {
       case 180:
         rotateA = -1; rotateB = 0; rotateC = 0; rotateD = 1;
         break;
-      case -270:
       case 90:
         rotateA = 0; rotateB = 1; rotateC = 1; rotateD = 0;
         break;
-      case -90:
       case 270:
         rotateA = 0; rotateB = -1; rotateC = -1; rotateD = 0;
         break;
-      //case 360:
       //case 0:
       default:
         rotateA = 1; rotateB = 0; rotateC = 0; rotateD = -1;
         break;
     }
+
+    if (dontFlip) {
+      rotateC = -rotateC; rotateD = -rotateD;
+    }
+
     var offsetCanvasX, offsetCanvasY;
     var width, height;
     if (rotateA === 0) {
       offsetCanvasX = Math.abs(centerY - viewBox[1]) * scale + offsetX;
       offsetCanvasY = Math.abs(centerX - viewBox[0]) * scale + offsetY;
       width = Math.abs(viewBox[3] - viewBox[1]) * scale;
       height = Math.abs(viewBox[2] - viewBox[0]) * scale;
     } else {
@@ -1122,17 +1634,17 @@ var PageViewport = PDFJS.PageViewport = 
     this.fontScale = scale;
   }
   PageViewport.prototype = {
     clone: function PageViewPort_clone(args) {
       args = args || {};
       var scale = 'scale' in args ? args.scale : this.scale;
       var rotation = 'rotation' in args ? args.rotation : this.rotation;
       return new PageViewport(this.viewBox.slice(), scale, rotation,
-                              this.offsetX, this.offsetY);
+                              this.offsetX, this.offsetY, args.dontFlip);
     },
     convertToViewportPoint: function PageViewport_convertToViewportPoint(x, y) {
       return Util.applyTransform([x, y], this.transform);
     },
     convertToViewportRectangle:
       function PageViewport_convertToViewportRectangle(rect) {
       var tl = Util.applyTransform([rect[0], rect[1]], this.transform);
       var br = Util.applyTransform([rect[2], rect[3]], this.transform);
@@ -1172,16 +1684,23 @@ function stringToPDFString(str) {
   }
   return str2;
 }
 
 function stringToUTF8String(str) {
   return decodeURIComponent(escape(str));
 }
 
+function isEmptyObj(obj) {
+  for (var key in obj) {
+    return false;
+  }
+  return true;
+}
+
 function isBool(v) {
   return typeof v == 'boolean';
 }
 
 function isInt(v) {
   return typeof v == 'number' && ((v | 0) == v);
 }
 
@@ -1245,18 +1764,18 @@ function isPDFFunction(v) {
   else
     return false;
   return fnDict.has('FunctionType');
 }
 
 /**
  * 'Promise' object.
  * Each object that is stored in PDFObjects is based on a Promise object that
- * contains the status of the object and the data. There migth be situations,
- * where a function want to use the value of an object, but it isn't ready at
+ * contains the status of the object and the data. There might be situations
+ * where a function wants to use the value of an object, but it isn't ready at
  * that time. To get a notification, once the object is ready to be used, s.o.
  * can add a callback using the `then` method on the promise that then calls
  * the callback once the object gets resolved.
  * A promise can get resolved only once and only once the data of the promise
  * can be set. If any of these happens twice or the data is required before
  * it was set, an exception is throw.
  */
 var Promise = PDFJS.Promise = (function PromiseClosure() {
@@ -1293,26 +1812,36 @@ var Promise = PDFJS.Promise = (function 
   Promise.all = function Promise_all(promises) {
     var deferred = new Promise();
     var unresolved = promises.length;
     var results = [];
     if (unresolved === 0) {
       deferred.resolve(results);
       return deferred;
     }
+    function reject(reason) {
+      if (deferred.isRejected) {
+        return;
+      }
+      results = [];
+      deferred.reject(reason);
+    }
     for (var i = 0, ii = promises.length; i < ii; ++i) {
       var promise = promises[i];
       promise.then((function(i) {
         return function(value) {
+          if (deferred.isRejected) {
+            return;
+          }
           results[i] = value;
           unresolved--;
           if (unresolved === 0)
             deferred.resolve(results);
         };
-      })(i));
+      })(i), reject);
     }
     return deferred;
   };
   Promise.prototype = {
     hasData: false,
 
     set data(value) {
       if (value === undefined) {
@@ -1383,32 +1912,31 @@ var Promise = PDFJS.Promise = (function 
       var errbacks = this.errbacks;
 
       for (var i = 0, ii = errbacks.length; i < ii; i++) {
         errbacks[i].call(null, reason, exception);
       }
     },
 
     then: function Promise_then(callback, errback, progressback) {
-      if (!callback) {
-        error('Requiring callback' + this.name);
-      }
-
       // If the promise is already resolved, call the callback directly.
-      if (this.isResolved) {
+      if (this.isResolved && callback) {
         var data = this.data;
         callback.call(null, data);
       } else if (this.isRejected && errback) {
         var error = this.error;
         var exception = this.exception;
         errback.call(null, error, exception);
       } else {
-        this.callbacks.push(callback);
-        if (errback)
+        if (callback) {
+          this.callbacks.push(callback);
+        }
+        if (errback) {
           this.errbacks.push(errback);
+        }
       }
 
       if (progressback)
         this.progressbacks.push(progressback);
     }
   };
 
   return Promise;
@@ -1486,19 +2014,30 @@ PDFJS.createBlob = function createBlob(d
  * @param {string|TypedAray|object} source Can be an url to where a PDF is
  * located, a typed array (Uint8Array) already populated with data or
  * and parameter object with the following possible fields:
  *  - url   - The URL of the PDF.
  *  - data  - A typed array with PDF data.
  *  - httpHeaders - Basic authentication headers.
  *  - password - For decrypting password-protected PDFs.
  *
+ * @param {object} pdfDataRangeTransport is optional. It is used if you want
+ * to manually serve range requests for data in the PDF. See viewer.js for
+ * an example of pdfDataRangeTransport's interface.
+ *
+ * @param {function} passwordCallback is optional. It is used to request a
+ * password if wrong or no password was provided. The callback receives two
+ * parameters: function that needs to be called with new password and reason
+ * (see {PasswordResponses}).
+ *
  * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
  */
-PDFJS.getDocument = function getDocument(source) {
+PDFJS.getDocument = function getDocument(source,
+                                         pdfDataRangeTransport,
+                                         passwordCallback) {
   var workerInitializedPromise, workerReadyPromise, transport;
 
   if (typeof source === 'string') {
     source = { url: source };
   } else if (isArrayBuffer(source)) {
     source = { data: source };
   } else if (typeof source !== 'object') {
     error('Invalid parameter in getDocument, need either Uint8Array, ' +
@@ -1515,18 +2054,20 @@ PDFJS.getDocument = function getDocument
       params[key] = combineUrl(window.location.href, source[key]);
       continue;
     }
     params[key] = source[key];
   }
 
   workerInitializedPromise = new PDFJS.Promise();
   workerReadyPromise = new PDFJS.Promise();
-  transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise);
+  transport = new WorkerTransport(workerInitializedPromise,
+      workerReadyPromise, pdfDataRangeTransport);
   workerInitializedPromise.then(function transportInitialized() {
+    transport.passwordCallback = passwordCallback;
     transport.fetchDocument(params);
   });
   return workerReadyPromise;
 };
 
 /**
  * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
  * properties that can be read synchronously.
@@ -1565,20 +2106,17 @@ var PDFDocumentProxy = (function PDFDocu
     getPage: function PDFDocumentProxy_getPage(number) {
       return this.transport.getPage(number);
     },
     /**
      * @return {Promise} A promise that is resolved with a lookup table for
      * mapping named destinations to reference numbers.
      */
     getDestinations: function PDFDocumentProxy_getDestinations() {
-      var promise = new PDFJS.Promise();
-      var destinations = this.pdfInfo.destinations;
-      promise.resolve(destinations);
-      return promise;
+      return this.transport.getDestinations();
     },
     /**
      * @return {Promise} A promise that is resolved with an array of all the
      * JavaScript strings in the name tree.
      */
     getJavaScript: function PDFDocumentProxy_getDestinations() {
       var promise = new PDFJS.Promise();
       var js = this.pdfInfo.javaScript;
@@ -1631,16 +2169,23 @@ var PDFDocumentProxy = (function PDFDocu
      * @return {Promise} A promise that is resolved with a TypedArray that has
      * the raw data from the PDF.
      */
     getData: function PDFDocumentProxy_getData() {
       var promise = new PDFJS.Promise();
       this.transport.getData(promise);
       return promise;
     },
+    /**
+     * @return {Promise} A promise that is resolved when the document's data
+     * is loaded
+     */
+    dataLoaded: function PDFDocumentProxy_dataLoaded() {
+      return this.transport.dataLoaded();
+    },
     destroy: function PDFDocumentProxy_destroy() {
       this.transport.destroy();
     }
   };
   return PDFDocumentProxy;
 })();
 
 var PDFPageProxy = (function PDFPageProxyClosure() {
@@ -1913,24 +2458,29 @@ var PDFPageProxy = (function PDFPageProx
     }
   };
   return PDFPageProxy;
 })();
 /**
  * For internal use only.
  */
 var WorkerTransport = (function WorkerTransportClosure() {
-  function WorkerTransport(workerInitializedPromise, workerReadyPromise) {
+  function WorkerTransport(workerInitializedPromise, workerReadyPromise,
+      pdfDataRangeTransport) {
+    this.pdfDataRangeTransport = pdfDataRangeTransport;
+
     this.workerReadyPromise = workerReadyPromise;
     this.commonObjs = new PDFObjects();
 
     this.pageCache = [];
     this.pagePromises = [];
     this.embeddedFontsUsed = false;
 
+    this.passwordCallback = null;
+
     // If worker support isn't disabled explicit and the browser has worker
     // support, create a new web worker and test if it/the browser fullfills
     // all requirements to run parts of pdf.js in a web worker.
     // Right now, the requirement is, that an Uint8Array is still an Uint8Array
     // as it arrives on the worker. Chrome added this with version 15.
     if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
       var workerSrc = PDFJS.workerSrc;
       if (typeof workerSrc === 'undefined') {
@@ -1967,21 +2517,24 @@ var WorkerTransport = (function WorkerTr
     // Either workers are disabled, not supported or have thrown an exception.
     // Thus, we fallback to a faked worker.
     globalScope.PDFJS.disableWorker = true;
     this.setupFakeWorker();
     workerInitializedPromise.resolve();
   }
   WorkerTransport.prototype = {
     destroy: function WorkerTransport_destroy() {
-      if (this.worker)
-        this.worker.terminate();
-
       this.pageCache = [];
       this.pagePromises = [];
+      var self = this;
+      this.messageHandler.send('Terminate', null, function () {
+        if (self.worker) {
+          self.worker.terminate();
+        }
+      });
     },
     setupFakeWorker: function WorkerTransport_setupFakeWorker() {
       warn('Setting up fake worker.');
       // If we don't use a worker, just post/sendMessage to the main thread.
       var fakeWorker = {
         postMessage: function WorkerTransport_postMessage(obj) {
           fakeWorker.onmessage({data: obj});
         },
@@ -1995,28 +2548,61 @@ var WorkerTransport = (function WorkerTr
       // the main thread sends to it self.
       WorkerMessageHandler.setup(messageHandler);
     },
 
     setupMessageHandler:
       function WorkerTransport_setupMessageHandler(messageHandler) {
       this.messageHandler = messageHandler;
 
+      function updatePassword(password) {
+        messageHandler.send('UpdatePassword', password);
+      }
+
+      var pdfDataRangeTransport = this.pdfDataRangeTransport;
+      if (pdfDataRangeTransport) {
+        pdfDataRangeTransport.addRangeListener(function(begin, chunk) {
+          messageHandler.send('OnDataRange', {
+            begin: begin,
+            chunk: chunk
+          });
+        });
+
+        pdfDataRangeTransport.addProgressListener(function(loaded) {
+          messageHandler.send('OnDataProgress', {
+            loaded: loaded
+          });
+        });
+
+        messageHandler.on('RequestDataRange',
+          function transportDataRange(data) {
+            pdfDataRangeTransport.requestDataRange(data.begin, data.end);
+          }, this);
+      }
+
       messageHandler.on('GetDoc', function transportDoc(data) {
         var pdfInfo = data.pdfInfo;
         var pdfDocument = new PDFDocumentProxy(pdfInfo, this);
         this.pdfDocument = pdfDocument;
         this.workerReadyPromise.resolve(pdfDocument);
       }, this);
 
       messageHandler.on('NeedPassword', function transportPassword(data) {
+        if (this.passwordCallback) {
+          return this.passwordCallback(updatePassword,
+                                       PasswordResponses.NEED_PASSWORD);
+        }
         this.workerReadyPromise.reject(data.exception.message, data.exception);
       }, this);
 
       messageHandler.on('IncorrectPassword', function transportBadPass(data) {
+        if (this.passwordCallback) {
+          return this.passwordCallback(updatePassword,
+                                       PasswordResponses.INCORRECT_PASSWORD);
+        }
         this.workerReadyPromise.reject(data.exception.message, data.exception);
       }, this);
 
       messageHandler.on('InvalidPDF', function transportInvalidPDF(data) {
         this.workerReadyPromise.reject(data.exception.name, data.exception);
       }, this);
 
       messageHandler.on('MissingPDF', function transportMissingPDF(data) {
@@ -2098,16 +2684,20 @@ var WorkerTransport = (function WorkerTr
             }
             break;
           default:
             error('Got unknown object type ' + type);
         }
       }, this);
 
       messageHandler.on('DocProgress', function transportDocProgress(data) {
+        // TODO(mack): The progress event should be resolved on a different
+        // promise that tracks progress of whole file, since workerReadyPromise
+        // is for file being ready to render, not for when file is fully
+        // downloaded
         this.workerReadyPromise.progress({
           loaded: data.loaded,
           total: data.total
         });
       }, this);
 
       messageHandler.on('DocError', function transportDocError(data) {
         this.workerReadyPromise.reject(data);
@@ -2153,38 +2743,61 @@ var WorkerTransport = (function WorkerTr
           promise.resolve({ data: buf, width: width, height: height});
         }).bind(this);
         var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
         img.src = src;
       });
     },
 
     fetchDocument: function WorkerTransport_fetchDocument(source) {
-      this.messageHandler.send('GetDocRequest', {source: source});
+      source.disableAutoFetch = PDFJS.disableAutoFetch;
+      source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
+      this.messageHandler.send('GetDocRequest', {
+        source: source,
+        disableRange: PDFJS.disableRange
+      });
     },
 
     getData: function WorkerTransport_getData(promise) {
       this.messageHandler.send('GetData', null, function(data) {
         promise.resolve(data);
       });
     },
 
+    dataLoaded: function WorkerTransport_dataLoaded() {
+      var promise = new PDFJS.Promise();
+      this.messageHandler.send('DataLoaded', null, function(args) {
+        promise.resolve(args);
+      });
+      return promise;
+    },
+
     getPage: function WorkerTransport_getPage(pageNumber, promise) {
       var pageIndex = pageNumber - 1;
       if (pageIndex in this.pagePromises)
         return this.pagePromises[pageIndex];
       var promise = new PDFJS.Promise('Page ' + pageNumber);
       this.pagePromises[pageIndex] = promise;
       this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
       return promise;
     },
 
     getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
       this.messageHandler.send('GetAnnotationsRequest',
         { pageIndex: pageIndex });
+    },
+
+    getDestinations: function WorkerTransport_getDestinations() {
+      var promise = new PDFJS.Promise();
+      this.messageHandler.send('GetDestinations', null,
+        function transportDestinations(destinations) {
+          promise.resolve(destinations);
+        }
+      );
+      return promise;
     }
   };
   return WorkerTransport;
 
 })();
 
 
 // <canvas> contexts store most of the state we need natively.
@@ -2198,17 +2811,17 @@ var TextRenderingMode = {
   FILL_ADD_TO_PATH: 4,
   STROKE_ADD_TO_PATH: 5,
   FILL_STROKE_ADD_TO_PATH: 6,
   ADD_TO_PATH: 7,
   ADD_TO_PATH_FLAG: 4
 };
 
 // Minimal font size that would be used during canvas fillText operations.
-var MIN_FONT_SIZE = 1;
+var MIN_FONT_SIZE = 16;
 
 function createScratchCanvas(width, height) {
   var canvas = document.createElement('canvas');
   canvas.width = width;
   canvas.height = height;
   return canvas;
 }
 
@@ -2219,16 +2832,17 @@ function addContextCurrentTransform(ctx)
     ctx._scaleX = ctx._scaleX || 1.0;
     ctx._scaleY = ctx._scaleY || 1.0;
     ctx._originalSave = ctx.save;
     ctx._originalRestore = ctx.restore;
     ctx._originalRotate = ctx.rotate;
     ctx._originalScale = ctx.scale;
     ctx._originalTranslate = ctx.translate;
     ctx._originalTransform = ctx.transform;
+    ctx._originalSetTransform = ctx.setTransform;
 
     ctx._transformMatrix = [ctx._scaleX, 0, 0, ctx._scaleY, 0, 0];
     ctx._transformStack = [];
 
     Object.defineProperty(ctx, 'mozCurrentTransform', {
       get: function getCurrentTransform() {
         return this._transformMatrix;
       }
@@ -2300,16 +2914,22 @@ function addContextCurrentTransform(ctx)
         m[1] * c + m[3] * d,
         m[0] * e + m[2] * f + m[4],
         m[1] * e + m[3] * f + m[5]
       ];
 
       ctx._originalTransform(a, b, c, d, e, f);
     };
 
+    ctx.setTransform = function ctxSetTransform(a, b, c, d, e, f) {
+      this._transformMatrix = [a, b, c, d, e, f];
+
+      ctx._originalSetTransform(a, b, c, d, e, f);
+    };
+
     ctx.rotate = function ctxRotate(angle) {
       var cosValue = Math.cos(angle);
       var sinValue = Math.sin(angle);
 
       var m = this._transformMatrix;
       this._transformMatrix = [
         m[0] * cosValue + m[2] * sinValue,
         m[1] * cosValue + m[3] * sinValue,
@@ -2381,16 +3001,17 @@ var CanvasGraphics = (function CanvasGra
   // before it stops and shedules a continue of execution.
   var EXECUTION_TIME = 15;
 
   function CanvasGraphics(canvasCtx, commonObjs, objs, textLayer, imageLayer) {
     this.ctx = canvasCtx;
     this.current = new CanvasExtraState();
     this.stateStack = [];
     this.pendingClip = null;
+    this.pendingEOFill = false;
     this.res = null;
     this.xobjs = null;
     this.commonObjs = commonObjs;
     this.objs = objs;
     this.textLayer = textLayer;
     this.imageLayer = imageLayer;
     this.groupStack = [];
     if (canvasCtx) {
@@ -2861,54 +3482,72 @@ var CanvasGraphics = (function CanvasGra
     closeStroke: function CanvasGraphics_closeStroke() {
       this.closePath();
       this.stroke();
     },
     fill: function CanvasGraphics_fill(consumePath) {
       consumePath = typeof consumePath !== 'undefined' ? consumePath : true;
       var ctx = this.ctx;
       var fillColor = this.current.fillColor;
+      var needRestore = false;
 
       if (fillColor && fillColor.hasOwnProperty('type') &&
           fillColor.type === 'Pattern') {
         ctx.save();
         ctx.fillStyle = fillColor.getPattern(ctx);
-        ctx.fill();
+        needRestore = true;
+      }
+
+      if (this.pendingEOFill) {
+        if ('mozFillRule' in this.ctx) {
+          this.ctx.mozFillRule = 'evenodd';
+          this.ctx.fill();
+          this.ctx.mozFillRule = 'nonzero';
+        } else {
+          try {
+            this.ctx.fill('evenodd');
+          } catch (ex) {
+            // shouldn't really happen, but browsers might think differently
+            this.ctx.fill();
+          }
+        }
+        this.pendingEOFill = false;
+      } else {
+        this.ctx.fill();
+      }
+
+      if (needRestore) {
         ctx.restore();
-      } else {
-        ctx.fill();
-      }
-      if (consumePath)
+      }
+      if (consumePath) {
         this.consumePath();
+      }
     },
     eoFill: function CanvasGraphics_eoFill() {
-      var savedFillRule = this.setEOFillRule();
+      this.pendingEOFill = true;
       this.fill();
-      this.restoreFillRule(savedFillRule);
     },
     fillStroke: function CanvasGraphics_fillStroke() {
       this.fill(false);
       this.stroke(false);
 
       this.consumePath();
     },
     eoFillStroke: function CanvasGraphics_eoFillStroke() {
-      var savedFillRule = this.setEOFillRule();
+      this.pendingEOFill = true;
       this.fillStroke();
-      this.restoreFillRule(savedFillRule);
     },
     closeFillStroke: function CanvasGraphics_closeFillStroke() {
       this.closePath();
       this.fillStroke();
     },
     closeEOFillStroke: function CanvasGraphics_closeEOFillStroke() {
-      var savedFillRule = this.setEOFillRule();
+      this.pendingEOFill = true;
       this.closePath();
       this.fillStroke();
-      this.restoreFillRule(savedFillRule);
     },
     endPath: function CanvasGraphics_endPath() {
       this.consumePath();
     },
 
     // Clipping
     clip: function CanvasGraphics_clip() {
       this.pendingClip = NORMAL_CLIP;
@@ -3185,16 +3824,17 @@ var CanvasGraphics = (function CanvasGra
         for (var i = 0; i < glyphsLength; ++i) {
           var glyph = glyphs[i];
           if (glyph === null) {
             // word break
             x += current.fontDirection * wordSpacing;
             continue;
           }
 
+          var restoreNeeded = false;
           var character = glyph.fontChar;
           var vmetric = glyph.vmetric || defaultVMetrics;
           if (vertical) {
             var vx = glyph.vmetric ? vmetric[1] : glyph.width * 0.5;
             vx = -vx * fontSize * current.fontMatrix[0];
             var vy = vmetric[2] * fontSize * current.fontMatrix[0];
           }
           var width = vmetric ? -vmetric[0] : glyph.width;
@@ -3210,16 +3850,32 @@ var CanvasGraphics = (function CanvasGra
             } else {
               scaledX = x / fontSizeScale;
               scaledY = 0;
             }
             if (accent) {
               scaledAccentX = scaledX + accent.offset.x / fontSizeScale;
               scaledAccentY = scaledY - accent.offset.y / fontSizeScale;
             }
+
+            if (font.remeasure && width > 0) {
+              // some standard fonts may not have the exact width, trying to
+              // rescale per character
+              var measuredWidth = ctx.measureText(character).width * 1000 /
+                current.fontSize * current.fontSizeScale;
+              var characterScaleX = width / measuredWidth;
+              restoreNeeded = true;
+              ctx.save();
+              ctx.scale(characterScaleX, 1);
+              scaledX /= characterScaleX;
+              if (accent) {
+                scaledAccentX /= characterScaleX;
+              }
+            }
+
             switch (textRenderingMode) {
               default: // other unsupported rendering modes
               case TextRenderingMode.FILL:
               case TextRenderingMode.FILL_ADD_TO_PATH:
                 ctx.fillText(character, scaledX, scaledY);
                 if (accent) {
                   ctx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
                 }
@@ -3251,16 +3907,20 @@ var CanvasGraphics = (function CanvasGra
                 clipCtx.fillText(accent.fontChar, scaledAccentX, scaledAccentY);
               }
             }
           }
 
           x += charWidth;
 
           canvasWidth += charWidth;
+
+          if (restoreNeeded) {
+            ctx.restore();
+          }
         }
         if (vertical) {
           current.y -= x * textHScale;
         } else {
           current.x += x * textHScale;
         }
         ctx.restore();
       }
@@ -3563,17 +4223,17 @@ var CanvasGraphics = (function CanvasGra
       // - draw as usual
       // - remove the backdrop alpha:
       // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
       // value of your transparency group and 'alphaBackdrop' the alpha of the
       // backdrop
       // - remove background color:
       // colorNew = color - alphaNew *colorBackdrop /(1 - alphaNew)
       if (!group.isolated) {
-        TODO('Support non-isolated groups.');
+        info('TODO: Support non-isolated groups.');
       }
 
       // TODO knockout - supposedly possible with the clever use of compositing
       // modes.
       if (group.knockout) {
         TODO('Support knockout groups.');
       }
 
@@ -3584,19 +4244,20 @@ var CanvasGraphics = (function CanvasGra
       assert(group.bbox, 'Bounding box is required.');
 
       // Based on the current transform figure out how big the bounding box
       // will actually be.
       var bounds = Util.getAxialAlignedBoundingBox(
                     group.bbox,
                     currentCtx.mozCurrentTransform);
       // Use ceil in case we're between sizes so we don't create canvas that is
-      // too small.
-      var drawnWidth = Math.ceil(bounds[2] - bounds[0]);
-      var drawnHeight = Math.ceil(bounds[3] - bounds[1]);
+      // too small and make the canvas at least 1x1 pixels.
+      var drawnWidth = Math.max(Math.ceil(bounds[2] - bounds[0]), 1);
+      var drawnHeight = Math.max(Math.ceil(bounds[3] - bounds[1]), 1);
+
       var scratchCanvas = createScratchCanvas(drawnWidth, drawnHeight);
       var groupCtx = scratchCanvas.getContext('2d');
       addContextCurrentTransform(groupCtx);
       // Since we created a new canvas that is just the size of the bounding box
       // we have to translate the group ctx.
       var offsetX = bounds[0];
       var offsetY = bounds[1];
       groupCtx.translate(-offsetX, -offsetY);
@@ -3630,34 +4291,22 @@ var CanvasGraphics = (function CanvasGra
       } else {
         this.ctx.mozImageSmoothingEnabled = false;
       }
       this.ctx.drawImage(groupCtx.canvas, 0, 0);
       this.restore();
     },
 
     beginAnnotation: function CanvasGraphics_beginAnnotation(rect, transform,
-                                                             matrix, border) {
+                                                             matrix) {
       this.save();
 
       if (rect && isArray(rect) && 4 == rect.length) {
         var width = rect[2] - rect[0];
         var height = rect[3] - rect[1];
-
-        if (border) {
-          // TODO(mack): Support different border styles
-          this.save();
-          var rgb = border.rgb;
-          this.setStrokeRGBColor(rgb[0], rgb[1], rgb[2]);
-          this.setLineWidth(border.width);
-          this.rectangle(rect[0], rect[1], width, height);
-          this.stroke();
-          this.restore();
-        }
-
         this.rectangle(rect[0], rect[1], width, height);
         this.clip();
         this.endPath();
       }
 
       this.transform.apply(this, transform);
       this.transform.apply(this, matrix);
     },
@@ -3861,39 +4510,36 @@ var CanvasGraphics = (function CanvasGra
     endCompat: function CanvasGraphics_endCompat() {
       // TODO stop ignoring undefined operators
     },
 
     // Helper functions
 
     consumePath: function CanvasGraphics_consumePath() {
       if (this.pendingClip) {
-        var savedFillRule = null;
-        if (this.pendingClip == EO_CLIP)
-          savedFillRule = this.setEOFillRule();
-
-        this.ctx.clip();
-
+        if (this.pendingClip == EO_CLIP) {
+          if ('mozFillRule' in this.ctx) {
+            this.ctx.mozFillRule = 'evenodd';
+            this.ctx.clip();
+            this.ctx.mozFillRule = 'nonzero';
+          } else {
+            try {
+              this.ctx.clip('evenodd');
+            } catch (ex) {
+              // shouldn't really happen, but browsers might think differently
+              this.ctx.clip();
+            }
+          }
+        } else {
+          this.ctx.clip();
+        }
         this.pendingClip = null;
-        if (savedFillRule !== null)
-          this.restoreFillRule(savedFillRule);
       }
       this.ctx.beginPath();
     },
-    // We generally keep the canvas context set for
-    // nonzero-winding, and just set evenodd for the operations
-    // that need them.
-    setEOFillRule: function CanvasGraphics_setEOFillRule() {
-      var savedFillRule = this.ctx.mozFillRule;
-      this.ctx.mozFillRule = 'evenodd';
-      return savedFillRule;
-    },
-    restoreFillRule: function CanvasGraphics_restoreFillRule(rule) {
-      this.ctx.mozFillRule = rule;
-    },
     getSinglePixelWidth: function CanvasGraphics_getSinglePixelWidth(scale) {
       var inverse = this.ctx.mozCurrentTransformInverse;
       // max of the current horizontal and vertical scale
       return Math.sqrt(Math.max(
         (inverse[0] * inverse[0] + inverse[1] * inverse[1]),
         (inverse[2] * inverse[2] + inverse[3] * inverse[3])));
     },
     getCanvasPosition: function CanvasGraphics_getCanvasPosition(x, y) {
@@ -4034,21 +4680,31 @@ var RefSet = (function RefSetClosure() {
       this.dict['R' + ref.num + '.' + ref.gen] = ref;
     }
   };
 
   return RefSet;
 })();
 
 var Catalog = (function CatalogClosure() {
-  function Catalog(xref) {
+  function Catalog(pdfManager, xref) {
+    this.pdfManager = pdfManager;
     this.xref = xref;
-    var obj = xref.getCatalogObj();
-    assertWellFormed(isDict(obj), 'catalog object is not a dictionary');
-    this.catDict = obj;
+    this.catDict = xref.getCatalogObj();
+    assertWellFormed(isDict(this.catDict),
+      'catalog object is not a dictionary');
+
+    // Stores state as we traverse the pages catalog so that we can resume
+    // parsing if an exception is thrown
+    this.traversePagesQueue = [{
+      pagesDict: this.toplevelPagesDict,
+      posInKids: 0
+    }];
+    this.pagePromises = [];
+    this.currPageIndex = 0;
   }
 
   Catalog.prototype = {
     get metadata() {
       var streamRef = this.catDict.getRaw('Metadata');
       if (!isRef(streamRef))
         return shadow(this, 'metadata', null);
 
@@ -4142,37 +4798,16 @@ var Catalog = (function CatalogClosure()
       var obj = this.toplevelPagesDict.get('Count');
       assertWellFormed(
         isInt(obj),
         'page count in top level pages object is not an integer'
       );
       // shadow the prototype getter
       return shadow(this, 'num', obj);
     },
-    traverseKids: function Catalog_traverseKids(pagesDict) {
-      var pageCache = this.pageCache;
-      var kids = pagesDict.get('Kids');
-      assertWellFormed(isArray(kids),
-                       'page dictionary kids object is not an array');
-      for (var i = 0, ii = kids.length; i < ii; ++i) {
-        var kid = kids[i];
-        assertWellFormed(isRef(kid),
-                        'page dictionary kid is not a reference');
-        var obj = this.xref.fetch(kid);
-        if (isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids'))) {
-          pageCache.push(new Page(this.xref, pageCache.length, obj, kid));
-        } else { // must be a child page dictionary
-          assertWellFormed(
-            isDict(obj),
-            'page dictionary kid reference points to wrong type of object'
-          );
-          this.traverseKids(obj);
-        }
-      }
-    },
     get destinations() {
       function fetchDestination(dest) {
         return isDict(dest) ? dest.get('D') : dest;
       }
 
       var xref = this.xref;
       var dests = {}, nameTreeRef, nameDictionaryRef;
       var obj = this.catDict.get('Names');
@@ -4230,99 +4865,125 @@ var Catalog = (function CatalogClosure()
           if (isStream(js)) {
             js = bytesToString(js.getBytes());
           }
           javaScript.push(stringToPDFString(js));
         }
       }
       return shadow(this, 'javaScript', javaScript);
     },
-    getPage: function Catalog_getPage(n) {
-      var pageCache = this.pageCache;
-      if (!pageCache) {
-        pageCache = this.pageCache = [];
-        this.traverseKids(this.toplevelPagesDict);
-      }
-      return this.pageCache[n - 1];
+
+    getPage: function Catalog_getPage(pageIndex) {
+      if (!(pageIndex in this.pagePromises)) {
+        this.pagePromises[pageIndex] = new Promise();
+      }
+      return this.pagePromises[pageIndex];
+    },
+
+    // Traverses pages in DFS order so that pages are processed in increasing
+    // order
+    traversePages: function Catalog_traversePages() {
+      var queue = this.traversePagesQueue;
+      while (queue.length) {
+        var queueItem = queue[queue.length - 1];
+        var pagesDict = queueItem.pagesDict;
+
+        var kids = pagesDict.get('Kids');
+        assert(isArray(kids), 'page dictionary kids object is not an array');
+        if (queueItem.posInKids >= kids.length) {
+          queue.pop();
+          continue;
+        }
+        var kidRef = kids[queueItem.posInKids];
+        assert(isRef(kidRef), 'page dictionary kid is not a reference');
+
+        var kid = this.xref.fetch(kidRef);
+        if (isDict(kid, 'Page') || (isDict(kid) && !kid.has('Kids'))) {
+          var pageIndex = this.currPageIndex++;
+          var page = new Page(this.pdfManager, this.xref, pageIndex, kid,
+                              kidRef);
+          if (!(pageIndex in this.pagePromises)) {
+            this.pagePromises[pageIndex] = new Promise();
+          }
+          this.pagePromises[pageIndex].resolve(page);
+
+        } else { // must be a child page dictionary
+          assert(
+            isDict(kid),
+            'page dictionary kid reference points to wrong type of object'
+          );
+
+          queue.push({
+            pagesDict: kid,
+            posInKids: 0
+          });
+        }
+        ++queueItem.posInKids;
+      }
     }
   };
 
   return Catalog;
 })();
 
 var XRef = (function XRefClosure() {
-  function XRef(stream, startXRef, mainXRefEntriesOffset, password) {
+  function XRef(stream, password) {
+
     this.stream = stream;
     this.entries = [];
     this.xrefstms = {};
-    var trailerDict = this.readXRef(startXRef);
-    trailerDict.assignXref(this);
-    this.trailer = trailerDict;
     // prepare the XRef cache
     this.cache = [];
-
-    var encrypt = trailerDict.get('Encrypt');
-    if (encrypt) {
-      var ids = trailerDict.get('ID');
-      var fileId = (ids && ids.length) ? ids[0] : '';
-      this.encrypt = new CipherTransformFactory(encrypt, fileId, password);
-    }
-
-    // get the root dictionary (catalog) object
-    if (!(this.root = trailerDict.get('Root')))
-      error('Invalid root reference');
+    this.password = password;
   }
 
   XRef.prototype = {
-    readXRefTable: function XRef_readXRefTable(parser) {
-      // Example of cross-reference table:
-      // xref
-      // 0 1                    <-- subsection header (first obj #, obj count)
-      // 0000000000 65535 f     <-- actual object (offset, generation #, f/n)
-      // 23 2                   <-- subsection header ... and so on ...
-      // 0000025518 00002 n
-      // 0000025635 00000 n
-      // trailer
-      // ...
-
-      // Outer loop is over subsection headers
-      var obj;
-      while (!isCmd(obj = parser.getObj(), 'trailer')) {
-        var first = obj,
-            count = parser.getObj();
-
-        if (!isInt(first) || !isInt(count))
-          error('Invalid XRef table: wrong types in subsection header');
-
-        // Inner loop is over objects themselves
-        for (var i = 0; i < count; i++) {
-          var entry = {};
-          entry.offset = parser.getObj();
-          entry.gen = parser.getObj();
-          var type = parser.getObj();
-
-          if (isCmd(type, 'f'))
-            entry.free = true;
-          else if (isCmd(type, 'n'))
-            entry.uncompressed = true;
-
-          // Validate entry obj
-          if (!isInt(entry.offset) || !isInt(entry.gen) ||
-              !(entry.free || entry.uncompressed)) {
-            error('Invalid entry in XRef subsection: ' + first + ', ' + count);
-          }
-
-          if (!this.entries[i + first])
-            this.entries[i + first] = entry;
-        }
-      }
-
-      // Sanity check: as per spec, first object must be free
-      if (this.entries[0] && !this.entries[0].free)
-        error('Invalid XRef table: unexpected first object');
+    setStartXRef: function XRef_setStartXRef(startXRef) {
+      // Store the starting positions of xref tables as we process them
+      // so we can recover from missing data errors
+      this.startXRefQueue = [startXRef];
+    },
+
+    parse: function XRef_parse(recoveryMode) {
+      var trailerDict;
+      if (!recoveryMode) {
+        trailerDict = this.readXRef();
+      } else {
+        warn('Indexing all PDF objects');
+        trailerDict = this.indexObjects();
+      }
+      trailerDict.assignXref(this);
+      this.trailer = trailerDict;
+      var encrypt = trailerDict.get('Encrypt');
+      if (encrypt) {
+        var ids = trailerDict.get('ID');
+        var fileId = (ids && ids.length) ? ids[0] : '';
+        this.encrypt = new CipherTransformFactory(
+            encrypt, fileId, this.password);
+      }
+
+      // get the root dictionary (catalog) object
+      if (!(this.root = trailerDict.get('Root'))) {
+        error('Invalid root reference');
+      }
+    },
+
+    processXRefTable: function XRef_processXRefTable(parser) {
+      if (!('tableState' in this)) {
+        // Stores state of the table as we process it so we can resume
+        // from middle of table in case of missing data error
+        this.tableState = {
+          entryNum: 0,
+          streamPos: parser.lexer.stream.pos,
+          parserBuf1: parser.buf1,
+          parserBuf2: parser.buf2
+        };
+      }
+
+      var obj = this.readXRefTable(parser);
 
       // Sanity check
       if (!isCmd(obj, 'trailer'))
         error('Invalid XRef table: could not find trailer dictionary');
 
       // Read trailer dictionary, e.g.
       // trailer
       //    << /Size 22
@@ -4331,37 +4992,150 @@ var XRef = (function XRefClosure() {
       //      /ID [ <81b14aafa313db63dbd6f981e49f94f4> ]
       //    >>
       // The parser goes through the entire stream << ... >> and provides
       // a getter interface for the key-value table
       var dict = parser.getObj();
       if (!isDict(dict))
         error('Invalid XRef table: could not parse trailer dictionary');
 
+      delete this.tableState;
+
       return dict;
     },
+
+    readXRefTable: function XRef_readXRefTable(parser) {
+      // Example of cross-reference table:
+      // xref
+      // 0 1                    <-- subsection header (first obj #, obj count)
+      // 0000000000 65535 f     <-- actual object (offset, generation #, f/n)
+      // 23 2                   <-- subsection header ... and so on ...
+      // 0000025518 00002 n
+      // 0000025635 00000 n
+      // trailer
+      // ...
+
+      var stream = parser.lexer.stream;
+      var tableState = this.tableState;
+      stream.pos = tableState.streamPos;
+      parser.buf1 = tableState.parserBuf1;
+      parser.buf2 = tableState.parserBuf2;
+
+      // Outer loop is over subsection headers
+      var obj;
+
+      while (true) {
+        if (!('firstEntryNum' in tableState) || !('entryCount' in tableState)) {
+          if (isCmd(obj = parser.getObj(), 'trailer')) {
+            break;
+          }
+          tableState.firstEntryNum = obj;
+          tableState.entryCount = parser.getObj();
+        }
+
+        var first = tableState.firstEntryNum;
+        var count = tableState.entryCount;
+        if (!isInt(first) || !isInt(count))
+          error('Invalid XRef table: wrong types in subsection header');
+
+        // Inner loop is over objects themselves
+        for (var i = tableState.entryNum; i < count; i++) {
+          tableState.streamPos = stream.pos;
+          tableState.entryNum = i;
+          tableState.parserBuf1 = parser.buf1;
+          tableState.parserBuf2 = parser.buf2;
+
+          var entry = {};
+          entry.offset = parser.getObj();
+          entry.gen = parser.getObj();
+          var type = parser.getObj();
+
+          if (isCmd(type, 'f'))
+            entry.free = true;
+          else if (isCmd(type, 'n'))
+            entry.uncompressed = true;
+
+          // Validate entry obj
+          if (!isInt(entry.offset) || !isInt(entry.gen) ||
+              !(entry.free || entry.uncompressed)) {
+            console.log(entry.offset, entry.gen, entry.free,
+                entry.uncompressed);
+            error('Invalid entry in XRef subsection: ' + first + ', ' + count);
+          }
+
+          if (!this.entries[i + first])
+            this.entries[i + first] = entry;
+        }
+
+        tableState.entryNum = 0;
+        tableState.streamPos = stream.pos;
+        tableState.parserBuf1 = parser.buf1;
+        tableState.parserBuf2 = parser.buf2;
+        delete tableState.firstEntryNum;
+        delete tableState.entryCount;
+      }
+
+      // Sanity check: as per spec, first object must be free
+      if (this.entries[0] && !this.entries[0].free)
+        error('Invalid XRef table: unexpected first object');
+
+      return obj;
+    },
+
+    processXRefStream: function XRef_processXRefStream(stream) {
+      if (!('streamState' in this)) {
+        // Stores state of the stream as we process it so we can resume
+        // from middle of stream in case of missing data error
+        var streamParameters = stream.dict;
+        var byteWidths = streamParameters.get('W');
+        var range = streamParameters.get('Index');
+        if (!range) {
+          range = [0, streamParameters.get('Size')];
+        }
+
+        this.streamState = {
+          entryRanges: range,
+          byteWidths: byteWidths,
+          entryNum: 0,
+          streamPos: stream.pos
+        };
+      }
+      this.readXRefStream(stream);
+      delete this.streamState;
+
+      return stream.dict;
+    },
+
     readXRefStream: function XRef_readXRefStream(stream) {
-      var streamParameters = stream.parameters;
-      var byteWidths = streamParameters.get('W');
-      var range = streamParameters.get('Index');
-      if (!range)
-        range = [0, streamParameters.get('Size')];
       var i, j;
-      while (range.length > 0) {
-        var first = range[0], n = range[1];
+      var streamState = this.streamState;
+      stream.pos = streamState.streamPos;
+
+      var byteWidths = streamState.byteWidths;
+      var typeFieldWidth = byteWidths[0];
+      var offsetFieldWidth = byteWidths[1];
+      var generationFieldWidth = byteWidths[2];
+
+      var entryRanges = streamState.entryRanges;
+      while (entryRanges.length > 0) {
+
+        var first = entryRanges[0];
+        var n = entryRanges[1];
+
         if (!isInt(first) || !isInt(n))
           error('Invalid XRef range fields: ' + first + ', ' + n);
-        var typeFieldWidth = byteWidths[0];
-        var offsetFieldWidth = byteWidths[1];
-        var generationFieldWidth = byteWidths[2];
+
         if (!isInt(typeFieldWidth) || !isInt(offsetFieldWidth) ||
             !isInt(generationFieldWidth)) {
           error('Invalid XRef entry fields length: ' + first + ', ' + n);
         }
-        for (i = 0; i < n; ++i) {
+        for (i = streamState.entryNum; i < n; ++i) {
+          streamState.entryNum = i;
+          streamState.streamPos = stream.pos;
+
           var type = 0, offset = 0, generation = 0;
           for (j = 0; j < typeFieldWidth; ++j)
             type = (type << 8) | stream.getByte();
           // if type field is absent, its default value = 1
           if (typeFieldWidth === 0)
             type = 1;
           for (j = 0; j < offsetFieldWidth; ++j)
             offset = (offset << 8) | stream.getByte();
@@ -4380,19 +5154,21 @@ var XRef = (function XRefClosure() {
             case 2:
               break;
             default:
               error('Invalid XRef entry type: ' + type);
           }
           if (!this.entries[first + i])
             this.entries[first + i] = entry;
         }
-        range.splice(0, 2);
-      }
-      return streamParameters;
+
+        streamState.entryNum = 0;
+        streamState.streamPos = stream.pos;
+        entryRanges.splice(0, 2);
+      }
     },
     indexObjects: function XRef_indexObjects() {
       // Simple scan through the PDF content to find objects,
       // trailers and XRef streams.
       function readToken(data, offset) {
         var token = '', ch = data[offset];
         while (ch !== 13 && ch !== 10) {
           if (++offset >= data.length)
@@ -4470,17 +5246,18 @@ var XRef = (function XRefClosure() {
           }
 
           position += contentLength;
         } else
           position += token.length + 1;
       }
       // reading XRef streams
       for (var i = 0, ii = xrefStms.length; i < ii; ++i) {
-          this.readXRef(xrefStms[i], true);
+        this.startXRefQueue.push(xrefStms[i]);
+        this.readXRef(/* recoveryMode */ true);
       }
       // finding main trailer
       var dict;
       for (var i = 0, ii = trailers.length; i < ii; ++i) {
         stream.pos = trailers[i];
         var parser = new Parser(new Lexer(stream), true, null);
         var obj = parser.getObj();
         if (!isCmd(obj, 'trailer'))
@@ -4494,92 +5271,118 @@ var XRef = (function XRefClosure() {
       }
       // no tailer with 'ID', taking last one (if exists)
       if (dict)
         return dict;
       // nothing helps
       // calling error() would reject worker with an UnknownErrorException.
       throw new InvalidPDFException('Invalid PDF structure');
     },
-    readXRef: function XRef_readXRef(startXRef, recoveryMode) {
+
+    readXRef: function XRef_readXRef(recoveryMode) {
       var stream = this.stream;
-      stream.pos = startXRef;
 
       try {
-        var parser = new Parser(new Lexer(stream), true, null);
-        var obj = parser.getObj();
-        var dict;
-
-        // Get dictionary
-        if (isCmd(obj, 'xref')) {
-          // Parse end-of-file XRef
-          dict = this.readXRefTable(parser);
-
-          // Recursively get other XRefs 'XRefStm', if any
-          obj = dict.get('XRefStm');
+        while (this.startXRefQueue.length) {
+          var startXRef = this.startXRefQueue[0];
+
+          stream.pos = startXRef;
+
+          var parser = new Parser(new Lexer(stream), true, null);
+          var obj = parser.getObj();
+          var dict;
+
+          // Get dictionary
+          if (isCmd(obj, 'xref')) {
+
+            // Parse end-of-file XRef
+            dict = this.processXRefTable(parser);
+            if (!this.topDict) {
+              this.topDict = dict;
+            }
+
+            // Recursively get other XRefs 'XRefStm', if any
+            obj = dict.get('XRefStm');
+            if (isInt(obj)) {
+              var pos = obj;
+              // ignore previously loaded xref streams
+              // (possible infinite recursion)
+              if (!(pos in this.xrefstms)) {
+                this.xrefstms[pos] = 1;
+                this.startXRefQueue.push(pos);
+              }
+            }
+          } else if (isInt(obj)) {
+
+            // Parse in-stream XRef
+            if (!isInt(parser.getObj()) ||
+                !isCmd(parser.getObj(), 'obj') ||
+                !isStream(obj = parser.getObj())) {
+              error('Invalid XRef stream');
+            }
+            dict = this.processXRefStream(obj);
+            if (!this.topDict) {
+              this.topDict = dict;
+            }
+
+            if (!dict)
+              error('Failed to read XRef stream');
+          }
+
+          // Recursively get previous dictionary, if any
+          obj = dict.get('Prev');
           if (isInt(obj)) {
-            var pos = obj;
-            // ignore previously loaded xref streams
-            // (possible infinite recursion)
-            if (!(pos in this.xrefstms)) {
-              this.xrefstms[pos] = 1;
-              this.readXRef(pos);
-            }
-          }
-        } else if (isInt(obj)) {
-          // Parse in-stream XRef
-          if (!isInt(parser.getObj()) ||
-              !isCmd(parser.getObj(), 'obj') ||
-              !isStream(obj = parser.getObj())) {
-            error('Invalid XRef stream');
-          }
-          dict = this.readXRefStream(obj);
-          if (!dict)
-            error('Failed to read XRef stream');
-        }
-
-        // Recursively get previous dictionary, if any
-        obj = dict.get('Prev');
-        if (isInt(obj))
-          this.readXRef(obj, recoveryMode);
-        else if (isRef(obj)) {
-          // The spec says Prev must not be a reference, i.e. "/Prev NNN"
-          // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
-          this.readXRef(obj.num, recoveryMode);
-        }
-
-        return dict;
+            this.startXRefQueue.push(obj);
+          } else if (isRef(obj)) {
+            // The spec says Prev must not be a reference, i.e. "/Prev NNN"
+            // This is a fallback for non-compliant PDFs, i.e. "/Prev NNN 0 R"
+            this.startXRefQueue.push(obj.num);
+          }
+
+          this.startXRefQueue.shift();
+        }
+
+        return this.topDict;
       } catch (e) {
+        if (e instanceof MissingDataException) {
+          throw e;
+        }
+
         log('(while reading XRef): ' + e);
       }
 
       if (recoveryMode)
         return;
-
-      warn('Indexing all PDF objects');
-      return this.indexObjects();
-    },
+      throw new XRefParseException();
+    },
+
     getEntry: function XRef_getEntry(i) {
       var e = this.entries[i];
       if (e === null)
         return null;
       return e.free || !e.offset ? null : e; // returns null if entry is free
     },
     fetchIfRef: function XRef_fetchIfRef(obj) {
       if (!isRef(obj))
         return obj;
       return this.fetch(obj);
     },
     fetch: function XRef_fetch(ref, suppressEncryption) {
       assertWellFormed(isRef(ref), 'ref object is not a reference');
       var num = ref.num;
-      if (num in this.cache)
-        return this.cache[num];
-
-      var e = this.getEntry(num);
+      var e;
+      if (num in this.cache) {
+        e = this.cache[num];
+        if (e instanceof Stream) {
+          return e.makeSubStream(e.start, e.length, e.dict);
+        }
+        return e;
+      }
+
+      e = this.getEntry(num);
 
       // the referenced entry can be free
       if (e === null)
         return (this.cache[num] = e);
 
       var gen = ref.gen;
       var stream, parser;
       if (e.uncompressed) {
@@ -4611,29 +5414,36 @@ var XRef = (function XRefClosure() {
             // almost all streams must be encrypted, but sometimes
             // they are not probably due to some broken generators
             // re-trying without encryption
             return this.fetch(ref, true);
           }
         } else {
           e = parser.getObj();
         }
-        // Don't cache streams since they are mutable (except images).
-        if (!isStream(e) || e instanceof JpegStream)
+        if (!isStream(e) || e instanceof JpegStream) {
+          this.cache[num] = e;
+        } else if (e instanceof Stream) {
+          e = e.makeSubStream(e.start, e.length, e.dict);
           this.cache[num] = e;
+        } else if ('readBlock' in e) {
+          e.getBytes();
+          e = e.makeSubStream(0, e.bufferLength, e.dict);
+          this.cache[num] = e;
+        }
         return e;
       }
 
       // compressed entry
       var tableOffset = e.offset;
       stream = this.fetch(new Ref(tableOffset, 0));
       if (!isStream(stream))
         error('bad ObjStm stream');
-      var first = stream.parameters.get('First');
-      var n = stream.parameters.get('N');
+      var first = stream.dict.get('First');
+      var n = stream.dict.get('N');
       if (!isInt(first) || !isInt(n)) {
         error('invalid first and n parameters for ObjStm stream');
       }
       parser = new Parser(new Lexer(stream), false, this);
       parser.allowStreams = true;
       var i, entries = [], nums = [];
       // read the object numbers to populate cache
       for (i = 0; i < n; ++i) {
@@ -4835,16 +5645,504 @@ var PDFObjects = (function PDFObjectsClo
       this.objs = {};
     }
   };
   return PDFObjects;
 })();
 
 
 
+var Annotation = (function AnnotationClosure() {
+  // 12.5.5: Algorithm: Appearance streams
+  function getTransformMatrix(rect, bbox, matrix) {
+    var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
+    var minX = bounds[0];
+    var minY = bounds[1];
+    var maxX = bounds[2];
+    var maxY = bounds[3];
+
+    if (minX === maxX || minY === maxY) {
+      // From real-life file, bbox was [0, 0, 0, 0]. In this case,
+      // just apply the transform for rect
+      return [1, 0, 0, 1, rect[0], rect[1]];
+    }
+
+    var xRatio = (rect[2] - rect[0]) / (maxX - minX);
+    var yRatio = (rect[3] - rect[1]) / (maxY - minY);
+    return [
+      xRatio,
+      0,
+      0,
+      yRatio,
+      rect[0] - minX * xRatio,
+      rect[1] - minY * yRatio
+    ];
+  }
+
+  function getDefaultAppearance(dict) {
+    var appearanceState = dict.get('AP');
+    if (!isDict(appearanceState)) {
+      return;
+    }
+
+    var appearance;
+    var appearances = appearanceState.get('N');
+    if (isDict(appearances)) {
+      var as = dict.get('AS');
+      if (as && appearances.has(as.name)) {
+        appearance = appearances.get(as.name);
+      }
+    } else {
+      appearance = appearances;
+    }
+    return appearance;
+  }
+
+  function Annotation(params) {
+    if (params.data) {
+      this.data = params.data;
+      return;
+    }
+
+    var dict = params.dict;
+    var data = this.data = {};
+
+    data.subtype = dict.get('Subtype').name;
+    var rect = dict.get('Rect');
+    data.rect = Util.normalizeRect(rect);
+    data.annotationFlags = dict.get('F');
+
+    var border = dict.get('BS');
+    if (isDict(border)) {
+      var borderWidth = border.has('W') ? border.get('W') : 1;
+      data.border = {
+        width: borderWidth,
+        type: border.get('S') || 'S',
+        rgb: dict.get('C') || [0, 0, 1]
+      };
+    }
+
+    this.appearance = getDefaultAppearance(dict);
+  }
+
+  Annotation.prototype = {
+
+    getData: function Annotation_getData() {
+      return this.data;
+    },
+
+    hasHtml: function Annotation_hasHtml() {
+      return false;
+    },
+
+    getHtmlElement: function Annotation_getHtmlElement(commonObjs) {
+      throw new NotImplementedException(
+        'getHtmlElement() should be implemented in subclass');
+    },
+
+    getEmptyContainer: function Annotaiton_getEmptyContainer(tagName, rect) {
+      assert(!isWorker,
+        'getEmptyContainer() should be called from main thread');
+
+      rect = rect || this.data.rect;
+      var element = document.createElement(tagName);
+      element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
+      element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
+      return element;
+    },
+
+    isViewable: function Annotation_isViewable() {
+      var data = this.data;
+      return !!(
+        data &&
+        (!data.annotationFlags ||
+         !(data.annotationFlags & 0x22)) && // Hidden or NoView
+        data.rect                            // rectangle is nessessary
+      );
+    },
+
+    getOperatorList: function Annotation_appendToOperatorList(evaluator) {
+
+      var promise = new Promise();
+
+      if (!this.appearance) {
+        promise.resolve({
+          queue: {
+            fnArray: [],
+            argsArray: []
+          },
+          dependency: {}
+        });
+        return promise;
+      }
+
+      var data = this.data;
+
+      var appearanceDict = this.appearance.dict;
+      var resources = appearanceDict.get('Resources');
+      var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
+      var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
+      var transform = getTransformMatrix(data.rect, bbox, matrix);
+
+      var border = data.border;
+
+      var listPromise = evaluator.getOperatorList(this.appearance, resources);
+      listPromise.then(function(appearanceStreamData) {
+        var fnArray = appearanceStreamData.queue.fnArray;
+        var argsArray = appearanceStreamData.queue.argsArray;
+
+        fnArray.unshift('beginAnnotation');
+        argsArray.unshift([data.rect, transform, matrix]);
+
+        fnArray.push('endAnnotation');
+        argsArray.push([]);
+
+        promise.resolve(appearanceStreamData);
+      });
+
+      return promise;
+    }
+  };
+
+  Annotation.getConstructor =
+      function Annotation_getConstructor(subtype, fieldType) {
+
+    if (!subtype) {
+      return;
+    }
+
+    // TODO(mack): Implement FreeText annotations
+    if (subtype === 'Link') {
+      return LinkAnnotation;
+    } else if (subtype === 'Text') {
+      return TextAnnotation;
+    } else if (subtype === 'Widget') {
+      if (!fieldType) {
+        return;
+      }
+
+      return WidgetAnnotation;
+    } else {
+      return Annotation;
+    }
+  };
+
+  // TODO(mack): Support loading annotation from data
+  Annotation.fromData = function Annotation_fromData(data) {
+    var subtype = data.subtype;
+    var fieldType = data.fieldType;
+    var Constructor = Annotation.getConstructor(subtype, fieldType);
+    if (Constructor) {
+      return new Constructor({ data: data });
+    }
+  };
+
+  Annotation.fromRef = function Annotation_fromRef(xref, ref) {
+
+    var dict = xref.fetchIfRef(ref);
+    if (!isDict(dict)) {
+      return;
+    }
+
+    var subtype = dict.get('Subtype');
+    subtype = isName(subtype) ? subtype.name : '';
+    if (!subtype) {
+      return;
+    }
+
+    var fieldType = Util.getInheritableProperty(dict, 'FT');
+    fieldType = isName(fieldType) ? fieldType.name : '';
+
+    var Constructor = Annotation.getConstructor(subtype, fieldType);
+    if (!Constructor) {
+      return;
+    }
+
+    var params = {
+      dict: dict,
+      ref: ref,
+    };
+
+    var annotation = new Constructor(params);
+
+    if (annotation.isViewable()) {
+      return annotation;
+    } else {
+      TODO('unimplemented annotation type: ' + subtype);
+    }
+  };
+
+  return Annotation;
+})();
+PDFJS.Annotation = Annotation;
+
+
+var WidgetAnnotation = (function WidgetAnnotationClosure() {
+
+  function WidgetAnnotation(params) {
+    Annotation.call(this, params);
+
+    if (params.data) {
+      return;
+    }
+
+    var dict = params.dict;
+    var data = this.data;
+
+    data.fieldValue = stringToPDFString(
+      Util.getInheritableProperty(dict, 'V') || '');
+    data.alternativeText = stringToPDFString(dict.get('TU') || '');
+    data.defaultAppearance = Util.getInheritableProperty(dict, 'DA') || '';
+    var fieldType = Util.getInheritableProperty(dict, 'FT');
+    data.fieldType = isName(fieldType) ? fieldType.name : '';
+    data.fieldFlags = Util.getInheritableProperty(dict, 'Ff') || 0;
+    this.fieldResources = Util.getInheritableProperty(dict, 'DR') || new Dict();
+
+    // Building the full field name by collecting the field and
+    // its ancestors 'T' data and joining them using '.'.
+    var fieldName = [];
+    var namedItem = dict;
+    var ref = params.ref;
+    while (namedItem) {
+      var parent = namedItem.get('Parent');
+      var parentRef = namedItem.getRaw('Parent');
+      var name = namedItem.get('T');
+      if (name) {
+        fieldName.unshift(stringToPDFString(name));
+      } else {
+        // The field name is absent, that means more than one field
+        // with the same name may exist. Replacing the empty name
+        // with the '`' plus index in the parent's 'Kids' array.
+        // This is not in the PDF spec but necessary to id the
+        // the input controls.
+        var kids = parent.get('Kids');
+        var j, jj;
+        for (j = 0, jj = kids.length; j < jj; j++) {
+          var kidRef = kids[j];
+          if (kidRef.num == ref.num && kidRef.gen == ref.gen)
+            break;
+        }
+        fieldName.unshift('`' + j);
+      }
+      namedItem = parent;
+      ref = parentRef;
+    }
+    data.fullName = fieldName.join('.');
+  }
+
+  var parent = Annotation.prototype;
+  Util.inherit(WidgetAnnotation, Annotation, {
+    isViewable: function WidgetAnnotation_isViewable() {
+      if (this.data.fieldType === 'Sig') {
+        TODO('unimplemented annotation type: Widget signature');
+        return false;
+      }
+
+      return parent.isViewable.call(this);
+    }
+  });
+
+  return WidgetAnnotation;
+})();
+
+var TextAnnotation = (function TextAnnotationClosure() {
+  function TextAnnotation(params) {
+    Annotation.call(this, params);
+
+    if (params.data) {
+      return;
+    }
+
+    var dict = params.dict;
+    var data = this.data;
+
+    var content = dict.get('Contents');
+    var title = dict.get('T');
+    data.content = stringToPDFString(content || '');
+    data.title = stringToPDFString(title || '');
+    data.name = !dict.has('Name') ? 'Note' : dict.get('Name').name;
+  }
+
+  var ANNOT_MIN_SIZE = 10;
+  var IMAGE_DIR = './images/';
+
+  Util.inherit(TextAnnotation, Annotation, {
+
+    getOperatorList: function TextAnnotation_getOperatorList(evaluator) {
+      var promise = new Promise();
+      promise.resolve({
+        queue: {
+          fnArray: [],
+          argsArray: []
+        },
+        dependency: {}
+      });
+      return promise;
+    },
+
+    hasHtml: function TextAnnotation_hasHtml() {
+      return true;
+    },
+
+    getHtmlElement: function TextAnnotation_getHtmlElement(commonObjs) {
+      assert(!isWorker, 'getHtmlElement() shall be called from main thread');
+
+      var item = this.data;
+      var rect = item.rect;
+
+      // sanity check because of OOo-generated PDFs
+      if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
+        rect[3] = rect[1] + ANNOT_MIN_SIZE;
+      }
+      if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
+        rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
+      }
+
+      var container = this.getEmptyContainer('section', rect);
+      container.className = 'annotText';
+
+      var image = document.createElement('img');
+      image.style.width = container.style.width;
+      image.style.height = container.style.height;
+      var iconName = item.name;
+      image.src = IMAGE_DIR + 'annotation-' +
+        iconName.toLowerCase() + '.svg';
+      image.alt = '[{{type}} Annotation]';
+      image.dataset.l10nId = 'text_annotation_type';
+      image.dataset.l10nArgs = JSON.stringify({type: iconName});
+      var content = document.createElement('div');
+      content.setAttribute('hidden', true);
+      var title = document.createElement('h1');
+      var text = document.createElement('p');
+      content.style.left = Math.floor(rect[2] - rect[0]) + 'px';
+      content.style.top = '0px';
+      title.textContent = item.title;
+
+      if (!item.content && !item.title) {
+        content.setAttribute('hidden', true);
+      } else {
+        var e = document.createElement('span');
+        var lines = item.content.split(/(?:\r\n?|\n)/);
+        for (var i = 0, ii = lines.length; i < ii; ++i) {
+          var line = lines[i];
+          e.appendChild(document.createTextNode(line));
+          if (i < (ii - 1))
+            e.appendChild(document.createElement('br'));
+        }
+        text.appendChild(e);
+        image.addEventListener('mouseover', function annotationImageOver() {
+          container.style.zIndex += 1;
+          content.removeAttribute('hidden');
+        }, false);
+
+        image.addEventListener('mouseout', function annotationImageOut() {
+          container.style.zIndex -= 1;
+          content.setAttribute('hidden', true);
+        }, false);
+      }
+
+      content.appendChild(title);
+      content.appendChild(text);
+      container.appendChild(image);
+      container.appendChild(content);
+
+      return container;
+    }
+  });
+
+  return TextAnnotation;
+})();
+
+var LinkAnnotation = (function LinkAnnotationClosure() {
+  function isValidUrl(url) {
+    if (!url)
+      return false;
+    var colon = url.indexOf(':');
+    if (colon < 0)
+      return false;
+    var protocol = url.substr(0, colon);
+    switch (protocol) {
+      case 'http':
+      case 'https':
+      case 'ftp':
+      case 'mailto':
+        return true;
+      default:
+        return false;
+    }
+  }
+
+  function LinkAnnotation(params) {
+    Annotation.call(this, params);
+
+    if (params.data) {
+      return;
+    }
+
+    var dict = params.dict;
+    var data = this.data;
+
+    var action = dict.get('A');
+    if (action) {
+      var linkType = action.get('S').name;
+      if (linkType === 'URI') {
+        var url = action.get('URI');
+        // TODO: pdf spec mentions urls can be relative to a Base
+        // entry in the dictionary.
+        if (!isValidUrl(url)) {
+          url = '';
+        }
+        data.url = url;
+      } else if (linkType === 'GoTo') {
+        data.dest = action.get('D');
+      } else if (linkType === 'GoToR') {
+        var urlDict = action.get('F');
+        if (isDict(urlDict)) {
+          // We assume that the 'url' is a Filspec dictionary
+          // and fetch the url without checking any further
+          url = urlDict.get('F') || '';
+        }
+
+        // TODO: pdf reference says that GoToR
+        // can also have 'NewWindow' attribute
+        if (!isValidUrl(url)) {
+          url = '';
+        }
+        data.url = url;
+        data.dest = action.get('D');
+      } else {
+        TODO('unrecognized link type: ' + linkType);
+      }
+    } else if (dict.has('Dest')) {
+      // simple destination link
+      var dest = dict.get('Dest');
+      data.dest = isName(dest) ? dest.name : dest;
+    }
+  }
+
+  Util.inherit(LinkAnnotation, Annotation, {
+    hasOperatorList: function LinkAnnotation_hasOperatorList() {
+      return false;
+    },
+
+    hasHtml: function LinkAnnotation_hasHtml() {
+      return true;
+    },
+
+    getHtmlElement: function LinkAnnotation_getHtmlElement(commonObjs) {
+      var element = this.getEmptyContainer('a');
+      element.href = this.data.url || '';
+      return element;
+    }
+  });
+
+  return LinkAnnotation;
+})();
+
+
 var PDFFunction = (function PDFFunctionClosure() {
   var CONSTRUCT_SAMPLED = 0;
   var CONSTRUCT_INTERPOLATED = 2;
   var CONSTRUCT_STICHED = 3;
   var CONSTRUCT_POSTSCRIPT = 4;
 
   return {
     getSampleArray: function PDFFunction_getSampleArray(size, outputSize, bps,
@@ -14037,28 +15335,30 @@ var CipherTransformFactory = (function C
     var passwordBytes;
     if (password)
       passwordBytes = stringToBytes(password);
 
     var encryptionKey = prepareKeyData(fileIdBytes, passwordBytes,
                                        ownerPassword, userPassword, flags,
                                        revision, keyLength, encryptMetadata);
     if (!encryptionKey && !password) {
-      throw new PasswordException('No password given', 'needpassword');
+      throw new PasswordException('No password given',
+                                  PasswordResponses.NEED_PASSWORD);
     } else if (!encryptionKey && password) {
       // Attempting use the password as an owner password
       var decodedPassword = decodeUserPassword(passwordBytes, ownerPassword,
                                                revision, keyLength);
       encryptionKey = prepareKeyData(fileIdBytes, decodedPassword,
                                      ownerPassword, userPassword, flags,
                                      revision, keyLength, encryptMetadata);
     }
 
     if (!encryptionKey)
-      throw new PasswordException('Incorrect Password', 'incorrectpassword');
+      throw new PasswordException('Incorrect Password',
+                                  PasswordResponses.INCORRECT_PASSWORD);
 
     this.encryptionKey = encryptionKey;
 
     if (algorithm == 4) {
       this.cf = dict.get('CF');
       this.stmf = dict.get('StmF') || identityName;
       this.strf = dict.get('StrF') || identityName;
       this.eff = dict.get('EFF') || this.strf;
@@ -14129,26 +15429,27 @@ var CipherTransformFactory = (function C
   };
 
   return CipherTransformFactory;
 })();
 
 
 
 var PartialEvaluator = (function PartialEvaluatorClosure() {
-  function PartialEvaluator(xref, handler, pageIndex, uniquePrefix) {
+  function PartialEvaluator(pdfManager, xref, handler, pageIndex,
+                            uniquePrefix, idCounters) {
     this.state = new EvalState();
     this.stateStack = [];
 
+    this.pdfManager = pdfManager;
     this.xref = xref;
     this.handler = handler;
     this.pageIndex = pageIndex;
     this.uniquePrefix = uniquePrefix;
-    this.objIdCounter = 0;
-    this.fontIdCounter = 0;
+    this.idCounters = idCounters;
   }
 
   // Specifies properties for each command
   //
   // If variableArgs === true: [0, `numArgs`] expected
   // If variableArgs === false: exactly `numArgs` expected
   var OP_MAP = {
     // Graphic state
@@ -14253,780 +15554,886 @@ var PartialEvaluator = (function Partial
     fal: null,
     fals: null,
     'false': null,
     nu: null,
     nul: null,
     'null': null
   };
 
+  var TILING_PATTERN = 1, SHADING_PATTERN = 2;
+
+  function createOperatorList(fnArray, argsArray, dependencies) {
+    return {
+      queue: {
+        fnArray: fnArray || [],
+        argsArray: argsArray || []
+      },
+      dependencies: dependencies || {}
+    };
+  }
+
   PartialEvaluator.prototype = {
-    loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
-                                                 resources, dependency) {
-      var fontRes = resources.get('Font');
-
-      assert(fontRes, 'fontRes not available');
-
-      ++this.fontIdCounter;
-
-      font = xref.fetchIfRef(font) || fontRes.get(fontName);
-      if (!isDict(font)) {
-        return {
-          translated: new ErrorFont('Font ' + fontName + ' is not available'),
-          loadedName: 'g_font_' + this.uniquePrefix + this.fontIdCounter
+
+    buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
+                                                                 xobj, smask) {
+      var self = this;
+      var promise = new Promise();
+      var fnArray = [];
+      var argsArray = [];
+
+      var matrix = xobj.dict.get('Matrix');
+      var bbox = xobj.dict.get('BBox');
+      var group = xobj.dict.get('Group');
+      if (group) {
+        var groupOptions = {
+          matrix: matrix,
+          bbox: bbox,
+          smask: !!smask,
+          isolated: false,
+          knockout: false
         };
-      }
-
-      var loadedName = font.loadedName;
-      if (!loadedName) {
-        // keep track of each font we translated so the caller can
-        // load them asynchronously before calling display on a page
-        loadedName = 'g_font_' + this.uniquePrefix + this.fontIdCounter;
-        font.loadedName = loadedName;
-
-        var translated;
-        try {
-          translated = this.translateFont(font, xref, resources,
-                                          dependency);
-        } catch (e) {
-          translated = new ErrorFont(e instanceof Error ? e.message : e);
-        }
-        font.translated = translated;
-
-        var data = translated;
-        if (data.loadCharProcs) {
-          delete data.loadCharProcs;
-
-          var charProcs = font.get('CharProcs').getAll();
-          var fontResources = font.get('Resources') || resources;
-          var charProcOperatorList = {};
-          for (var key in charProcs) {
-            var glyphStream = charProcs[key];
-            charProcOperatorList[key] =
-              this.getOperatorList(glyphStream, fontResources, dependency);
-          }
-          data.charProcOperatorList = charProcOperatorList;
-        }
-      }
-      return font;
-    },
-
-    getOperatorList: function PartialEvaluator_getOperatorList(stream,
-                                                               resources,
-                                                               dependency,
-                                                               queue) {
-
+
+        var groupSubtype = group.get('S');
+        if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
+          groupOptions.isolated = group.get('I') || false;
+          groupOptions.knockout = group.get('K') || false;
+          // There is also a group colorspace, but since we put everything in
+          // RGB I'm not sure we need it.
+        }
+        fnArray.push('beginGroup');
+        argsArray.push([groupOptions]);
+      }
+
+      fnArray.push('paintFormXObjectBegin');
+      argsArray.push([matrix, bbox]);
+
+      // Pass in the current `queue` object. That means the `fnArray`
+      // and the `argsArray` in this scope is reused and new commands
+      // are added to them.
+      var opListPromise = this.getOperatorList(xobj,
+          xobj.dict.get('Resources') || resources);
+      opListPromise.then(function(data) {
+        var queue = data.queue;
+        var dependencies = data.dependencies;
+        Util.prependToArray(queue.fnArray, fnArray);
+        Util.prependToArray(queue.argsArray, argsArray);
+        self.insertDependencies(queue, dependencies);
+
+        queue.fnArray.push('paintFormXObjectEnd');
+        queue.argsArray.push([]);
+
+        if (group) {
+          queue.fnArray.push('endGroup');
+          queue.argsArray.push([groupOptions]);
+        }
+
+        promise.resolve({
+          queue: queue,
+          dependencies: dependencies
+        });
+      });
+
+      return promise;
+    },
+
+    buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(
+                                resources, image, inline) {
       var self = this;
-      var xref = this.xref;
-      var handler = this.handler;
-      var pageIndex = this.pageIndex;
+      var dict = image.dict;
+      var w = dict.get('Width', 'W');
+      var h = dict.get('Height', 'H');
+
+      var dependencies = {};
+      var retData = {
+        dependencies: dependencies
+      };
+
+      var imageMask = dict.get('ImageMask', 'IM') || false;
+      if (imageMask) {
+        // This depends on a tmpCanvas beeing filled with the
+        // current fillStyle, such that processing the pixel
+        // data can't be done here. Instead of creating a
+        // complete PDFImage, only read the information needed
+        // for later.
+
+        var width = dict.get('Width', 'W');
+        var height = dict.get('Height', 'H');
+        var bitStrideLength = (width + 7) >> 3;
+        var imgArray = image.getBytes(bitStrideLength * height);
+        var decode = dict.get('Decode', 'D');
+        var inverseDecode = !!decode && decode[0] > 0;
+
+        retData.fn = 'paintImageMaskXObject';
+        retData.args = [imgArray, inverseDecode, width, height];
+        return retData;
+      }
+
+      var softMask = dict.get('SMask', 'SM') || false;
+      var mask = dict.get('Mask') || false;
+
+      var SMALL_IMAGE_DIMENSIONS = 200;
+      // Inlining small images into the queue as RGB data
+      if (inline && !softMask && !mask &&
+          !(image instanceof JpegStream) &&
+          (w + h) < SMALL_IMAGE_DIMENSIONS) {
+        var imageObj = new PDFImage(this.xref, resources, image,
+                                    inline, null, null);
+        var imgData = imageObj.getImageData();
+        retData.fn = 'paintInlineImageXObject';
+        retData.args = [imgData];
+        return retData;
+      }
+
+      // If there is no imageMask, create the PDFImage and a lot
+      // of image processing can be done here.
       var uniquePrefix = this.uniquePrefix || '';
-
-      function insertDependency(depList) {
-        fnArray.push('dependency');
-        argsArray.push(depList);
-        for (var i = 0, ii = depList.length; i < ii; i++) {
-          var dep = depList[i];
-          if (dependency.indexOf(dep) == -1) {
-            dependency.push(depList[i]);
-          }
-        }
-      }
-
-      function handleSetFont(fontName, font) {
-        font = self.loadFont(fontName, font, xref, resources, dependency);
-
+      var objId = 'img_' + uniquePrefix + (++this.idCounters.obj);
+      dependencies[objId] = true;
+      retData.args = [objId, w, h];
+
+      if (!softMask && !mask && image instanceof JpegStream &&
+          image.isNativelySupported(this.xref, resources)) {
+        // These JPEGs don't need any more processing so we can just send it.
+        retData.fn = 'paintJpegXObject';
+        this.handler.send(
+            'obj', [objId, this.pageIndex, 'JpegStream', image.getIR()]);
+        return retData;
+      }
+
+      retData.fn = 'paintImageXObject';
+
+      PDFImage.buildImage(function(imageObj) {
+          var imgData = imageObj.getImageData();
+          self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]);
+        }, self.handler, self.xref, resources, image, inline);
+
+      return retData;
+    },
+
+    handleTilingType: function PartialEvaluator_handleTilingType(
+                          fn, args, resources, pattern, patternDict) {
+      var self = this;
+      // Create an IR of the pattern code.
+      var promise = new Promise();
+      var opListPromise = this.getOperatorList(pattern,
+          patternDict.get('Resources') || resources);
+      opListPromise.then(function(data) {
+        var opListData = createOperatorList([], [], data.dependencies);
+        var queue = opListData.queue;
+
+        // Add the dependencies that are required to execute the
+        // operatorList.
+        self.insertDependencies(queue, data.dependencies);
+        queue.fnArray.push(fn);
+        queue.argsArray.push(
+          TilingPattern.getIR(data.queue, patternDict, args));
+        promise.resolve(opListData);
+      });
+
+      return promise;
+    },
+
+    handleSetFont: function PartialEvaluator_handleSetFont(
+                      resources, fontArgs, font) {
+
+      var promise = new Promise();
+      // TODO(mack): Not needed?
+      var fontName;
+      if (fontArgs) {
+        fontArgs = fontArgs.slice();
+        fontName = fontArgs[0].name;
+      }
+      var self = this;
+      var fontPromise = this.loadFont(fontName, font, this.xref, resources);
+      fontPromise.then(function(data) {
+        var font = data.font;
         var loadedName = font.loadedName;
         if (!font.sent) {
-          var data = font.translated.exportData();
-
-          handler.send('commonobj', [
-              loadedName,
-              'Font',
-              data
+          var fontData = font.translated.exportData();
+
+          self.handler.send('commonobj', [
+            loadedName,
+            'Font',
+            fontData
           ]);
           font.sent = true;
         }
 
         // Ensure the font is ready before the font is set
         // and later on used for drawing.
         // OPTIMIZE: This should get insert to the operatorList only once per
         // page.
-        insertDependency([loadedName]);
-        return loadedName;
-      }
-
-      function buildFormXObject(xobj, smask) {
-        var matrix = xobj.dict.get('Matrix');
-        var bbox = xobj.dict.get('BBox');
-        var group = xobj.dict.get('Group');
-        if (group) {
-          var groupOptions = {
-            matrix: matrix,
-            bbox: bbox,
-            smask: !!smask,
-            isolated: false,
-            knockout: false
-          };
-
-          var groupSubtype = group.get('S');
-          if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
-            groupOptions.isolated = group.get('I') || false;
-            groupOptions.knockout = group.get('K') || false;
-            // There is also a group colorspace, but since we put everything in
-            // RGB I'm not sure we need it.
-          }
-          fnArray.push('beginGroup');
-          argsArray.push([groupOptions]);
-        }
-
-        fnArray.push('paintFormXObjectBegin');
-        argsArray.push([matrix, bbox]);
-
-        // This adds the operatorList of the xObj to the current queue.
-        var depIdx = dependencyArray.length;
-
-        // Pass in the current `queue` object. That means the `fnArray`
-        // and the `argsArray` in this scope is reused and new commands
-        // are added to them.
-        self.getOperatorList(xobj,
-            xobj.dict.get('Resources') || resources,
-            dependencyArray, queue);
-
-        // Add the dependencies that are required to execute the
-        // operatorList.
-        insertDependency(dependencyArray.slice(depIdx));
-
-        fnArray.push('paintFormXObjectEnd');
-        argsArray.push([]);
-
-        if (group) {
-          fnArray.push('endGroup');
-          argsArray.push([groupOptions]);
-        }
-      }
-
-      function buildPaintImageXObject(image, inline) {
-        var dict = image.dict;
-        var w = dict.get('Width', 'W');
-        var h = dict.get('Height', 'H');
-
-        var imageMask = dict.get('ImageMask', 'IM') || false;
-        if (imageMask) {
-          // This depends on a tmpCanvas beeing filled with the
-          // current fillStyle, such that processing the pixel
-          // data can't be done here. Instead of creating a
-          // complete PDFImage, only read the information needed
-          // for later.
-
-          var width = dict.get('Width', 'W');
-          var height = dict.get('Height', 'H');
-          var bitStrideLength = (width + 7) >> 3;
-          var imgArray = image.getBytes(bitStrideLength * height);
-          var decode = dict.get('Decode', 'D');
-          var inverseDecode = !!decode && decode[0] > 0;
-
-          fn = 'paintImageMaskXObject';
-          args = [imgArray, inverseDecode, width, height];
-          return;
-        }
-
-        var softMask = dict.get('SMask', 'SM') || false;
-        var mask = dict.get('Mask') || false;
-
-        var SMALL_IMAGE_DIMENSIONS = 200;
-        // Inlining small images into the queue as RGB data
-        if (inline && !softMask && !mask &&
-            !(image instanceof JpegStream) &&
-            (w + h) < SMALL_IMAGE_DIMENSIONS) {
-          var imageObj = new PDFImage(xref, resources, image,
-                                      inline, null, null);
-          var imgData = imageObj.getImageData();
-          fn = 'paintInlineImageXObject';
-          args = [imgData];
-          return;
-        }
-
-        // If there is no imageMask, create the PDFImage and a lot
-        // of image processing can be done here.
-        var objId = 'img_' + uniquePrefix + (++self.objIdCounter);
-        insertDependency([objId]);
-        args = [objId, w, h];
-
-        if (!softMask && !mask && image instanceof JpegStream &&
-            image.isNativelySupported(xref, resources)) {
-          // These JPEGs don't need any more processing so we can just send it.
-          fn = 'paintJpegXObject';
-          handler.send('obj', [objId, pageIndex, 'JpegStream', image.getIR()]);
-          return;
-        }
-
-        fn = 'paintImageXObject';
-
-        PDFImage.buildImage(function(imageObj) {
-            var imgData = imageObj.getImageData();
-            handler.send('obj', [objId, pageIndex, 'Image', imgData]);
-          }, handler, xref, resources, image, inline);
-      }
-
-      if (!queue) {
-        queue = {
-          transparency: false
+        var fnArray = [];
+        var argsArray = [];
+        var queue = {
+          fnArray: fnArray,
+          argsArray: argsArray
         };
-      }
-
-      if (!queue.argsArray) {
-        queue.argsArray = [];
-      }
-      if (!queue.fnArray) {
-        queue.fnArray = [];
-      }
-
-      var fnArray = queue.fnArray, argsArray = queue.argsArray;
-      var dependencyArray = dependency || [];
+        var dependencies = data.dependencies;
+        dependencies[loadedName] = true;
+        self.insertDependencies(queue, dependencies);
+        if (fontArgs) {
+          fontArgs[0] = loadedName;
+          fnArray.push('setFont');
+          argsArray.push(fontArgs);
+        }
+        promise.resolve({
+          loadedName: loadedName,
+          queue: queue,
+          dependencies: dependencies
+        });
+      });
+      return promise;
+    },
+
+    insertDependencies: function PartialEvaluator_insertDependencies(
+                            queue, dependencies) {
+
+      var fnArray = queue.fnArray;
+      var argsArray = queue.argsArray;
+      var depList = Object.keys(dependencies);
+      if (depList.length) {
+        fnArray.push('dependency');
+        argsArray.push(depList);
+      }
+    },
+
+    setGState: function PartialEvaluator_setGState(resources, gState) {
+
+      var self = this;
+      var opListData = createOperatorList();
+      var queue = opListData.queue;
+      var fnArray = queue.fnArray;
+      var argsArray = queue.argsArray;
+      var dependencies = opListData.dependencies;
+
+      // TODO(mack): This should be rewritten so that this function returns
+      // what should be added to the queue during each iteration
+      function setGStateForKey(gStateObj, key, value) {
+        switch (key) {
+          case 'Type':
+            break;
+          case 'LW':
+          case 'LC':
+          case 'LJ':
+          case 'ML':
+          case 'D':
+          case 'RI':
+          case 'FL':
+          case 'CA':
+          case 'ca':
+            gStateObj.push([key, value]);
+            break;
+          case 'Font':
+            var promise = new Promise();
+            self.handleSetFont(resources, null, value[0]).then(function(data) {
+              var gState = ['Font', data.loadedName, value[1]];
+              promise.resolve({
+                gState: gState,
+                queue: data.queue,
+                dependencies: data.dependencies
+              });
+            });
+            gStateObj.push(['promise', promise]);
+            break;
+          case 'BM':
+            if (!isName(value) || value.name !== 'Normal') {
+              queue.transparency = true;
+            }
+            gStateObj.push([key, value]);
+            break;
+          case 'SMask':
+            // We support the default so don't trigger the TODO.
+            if (!isName(value) || value.name != 'None')
+              TODO('graphic state operator ' + key);
+            break;
+          // Only generate info log messages for the following since
+          // they are unlikey to have a big impact on the rendering.
+          case 'OP':
+          case 'op':
+          case 'OPM':
+          case 'BG':
+          case 'BG2':
+          case 'UCR':
+          case 'UCR2':
+          case 'TR':
+          case 'TR2':
+          case 'HT':
+          case 'SM':
+          case 'SA':
+          case 'AIS':
+          case 'TK':
+            // TODO implement these operators.
+            info('graphic state operator ' + key);
+            break;
+          default:
+            info('Unknown graphic state operator ' + key);
+            break;
+        }
+      }
+
+      // This array holds the converted/processed state data.
+      var gStateObj = [];
+      var gStateMap = gState.map;
+      for (var key in gStateMap) {
+        var value = gStateMap[key];
+        setGStateForKey(gStateObj, key, value);
+      }
+
+      var promises = [];
+      var indices = [];
+      for (var i = 0, n = gStateObj.length; i < n; ++i) {
+        var value = gStateObj[i];
+        if (value[0] === 'promise') {
+          promises.push(value[1]);
+          indices.push(i);
+        }
+      }
+
+      var promise = new Promise();
+      Promise.all(promises).then(function(datas) {
+        for (var i = 0, n = datas.length; i < n; ++i) {
+          var data = datas[i];
+          var index = indices[i];
+          gStateObj[index] = data.gState;
+          var subQueue = data.queue;
+          Util.concatenateToArray(fnArray, subQueue.fnArray);
+          Util.concatenateToArray(argsArray, subQueue.argsArray);
+          queue.transparency = subQueue.transparency || queue.transparency;
+          Util.extendObj(dependencies, data.dependencies);
+        }
+        fnArray.push('setGState');
+        argsArray.push([gStateObj]);
+        promise.resolve(opListData);
+      });
+
+      return promise;
+    },
+
+    loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
+                                                 resources) {
+      var promise = new Promise();
+
+      var fontRes = resources.get('Font');
+      if (!fontRes) {
+        warn('fontRes not available');
+      }
+
+      font = xref.fetchIfRef(font) || (fontRes && fontRes.get(fontName));
+      if (!isDict(font)) {
+        ++this.idCounters.font;
+        promise.resolve({
+          font: {
+            translated: new ErrorFont('Font ' + fontName + ' is not available'),
+            loadedName: 'g_font_' + this.uniquePrefix + this.idCounters.obj
+          },
+          dependencies: {}
+        });
+        return promise;
+      }
+
+      if (font.loaded) {
+        promise.resolve({
+          font: font,
+          dependencies: {}
+        });
+        return promise;
+      }
+
+      // keep track of each font we translated so the caller can
+      // load them asynchronously before calling display on a page
+      font.loadedName = 'g_font_' + this.uniquePrefix +
+                        (this.idCounters.font + 1);
+
+      if (!font.translated) {
+        var translated;
+        try {
+          translated = this.translateFont(font, xref);
+        } catch (e) {
+          if (e instanceof MissingDataException) {
+            throw e;
+          }
+          translated = new ErrorFont(e instanceof Error ? e.message : e);
+        }
+        font.translated = translated;
+      }
+
+      if (font.translated.loadCharProcs) {
+        var charProcs = font.get('CharProcs').getAll();
+        var fontResources = font.get('Resources') || resources;
+        var opListPromises = [];
+        var charProcKeys = Object.keys(charProcs);
+        for (var i = 0, n = charProcKeys.length; i < n; ++i) {
+          var key = charProcKeys[i];
+          var glyphStream = charProcs[key];
+          opListPromises.push(
+            this.getOperatorList(glyphStream, fontResources));
+        }
+        Promise.all(opListPromises).then(function(datas) {
+          var charProcOperatorList = {};
+          var dependencies = {};
+          for (var i = 0, n = charProcKeys.length; i < n; ++i) {
+            var key = charProcKeys[i];
+            var data = datas[i];
+            charProcOperatorList[key] = data.queue;
+            Util.extendObj(dependencies, data.dependencies);
+          }
+          font.translated.charProcOperatorList = charProcOperatorList;
+          font.loaded = true;
+          ++this.idCounters.font;
+          promise.resolve({
+            font: font,
+            dependencies: dependencies
+          });
+        }.bind(this));
+      } else {
+        ++this.idCounters.font;
+        font.loaded = true;
+        promise.resolve({
+          font: font,
+          dependencies: {}
+        });
+      }
+      return promise;
+    },
+
+    getOperatorList: function PartialEvaluator_getOperatorList(stream,
+                                                               resources) {
+
+      var self = this;
+      var xref = this.xref;
+      var handler = this.handler;
+
+      var fnArray = [];
+      var argsArray = [];
+      var queue = {
+        transparency: false,
+        fnArray: fnArray,
+        argsArray: argsArray
+      };
+      var dependencies = {};
 
       resources = resources || new Dict();
       var xobjs = resources.get('XObject') || new Dict();
       var patterns = resources.get('Pattern') || new Dict();
       // TODO(mduan): pass array of knownCommands rather than OP_MAP
       // dictionary
       var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
-      var res = resources;
-      var args = [], obj;
-      var TILING_PATTERN = 1, SHADING_PATTERN = 2;
-
-      while (true) {
-        obj = parser.getObj();
-        if (isEOF(obj)) {
-          break;
-        }
-
-        if (isCmd(obj)) {
-          var cmd = obj.cmd;
-
-          // Check that the command is valid
-          var opSpec = OP_MAP[cmd];
-          if (!opSpec) {
-            warn('Unknown command "' + cmd + '"');
-            continue;
-          }
-
-          var fn = opSpec.fnName;
-
-          // Validate the number of arguments for the command
-          if (opSpec.variableArgs) {
-            if (args.length > opSpec.numArgs) {
-              info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
-                  '] args, but received ' + args.length + ' args');
-            }
-          } else {
-            if (args.length < opSpec.numArgs) {
-              // If we receive too few args, it's not possible to possible
-              // to execute the command, so skip the command
-              info('Command ' + fn + ': because expected ' + opSpec.numArgs +
-                  ' args, but received ' + args.length + ' args; skipping');
-              args = [];
-              continue;
-            } else if (args.length > opSpec.numArgs) {
-              info('Command ' + fn + ': expected ' + opSpec.numArgs +
-                  ' args, but received ' + args.length + ' args');
-            }
-          }
-
-          // TODO figure out how to type-check vararg functions
-
-          if ((cmd == 'SCN' || cmd == 'scn') && !args[args.length - 1].code) {
-            // compile tiling patterns
-            var patternName = args[args.length - 1];
-            // SCN/scn applies patterns along with normal colors
-            if (isName(patternName)) {
-              var pattern = patterns.get(patternName.name);
-              if (pattern) {
-                var dict = isStream(pattern) ? pattern.dict : pattern;
-                var typeNum = dict.get('PatternType');
-
-                if (typeNum == TILING_PATTERN) {
-                  // Create an IR of the pattern code.
-                  var depIdx = dependencyArray.length;
-                  var operatorList = this.getOperatorList(pattern,
-                      dict.get('Resources') || resources, dependencyArray);
-
-                  // Add the dependencies that are required to execute the
-                  // operatorList.
-                  insertDependency(dependencyArray.slice(depIdx));
-
-                  args = TilingPattern.getIR(operatorList, dict, args);
+
+      var promise = new Promise();
+      function parseCommands() {
+        try {
+          parser.restoreState();
+          var args = [];
+          while (true) {
+
+            var obj = parser.getObj();
+
+            if (isEOF(obj)) {
+              break;
+            }
+
+            if (isCmd(obj)) {
+              var cmd = obj.cmd;
+
+              // Check that the command is valid
+              var opSpec = OP_MAP[cmd];
+              if (!opSpec) {
+                warn('Unknown command "' + cmd + '"');
+                continue;
+              }
+
+              var fn = opSpec.fnName;
+
+              // Validate the number of arguments for the command
+              if (opSpec.variableArgs) {
+                if (args.length > opSpec.numArgs) {
+                  info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
+                      '] args, but received ' + args.length + ' args');
                 }
-                else if (typeNum == SHADING_PATTERN) {
-                  var shading = dict.get('Shading');
-                  var matrix = dict.get('Matrix');
-                  var pattern = Pattern.parseShading(shading, matrix, xref,
-                                                     res);
-                  args = pattern.getIR();
-                } else {
-                  error('Unkown PatternType ' + typeNum);
-                }
-              }
-            }
-          } else if (cmd == 'Do' && !args[0].code) {
-            // eagerly compile XForm objects
-            var name = args[0].name;
-            var xobj = xobjs.get(name);
-            if (xobj) {
-              assertWellFormed(isStream(xobj), 'XObject should be a stream');
-
-              var type = xobj.dict.get('Subtype');
-              assertWellFormed(
-                isName(type),
-                'XObject should have a Name subtype'
-              );
-
-              if ('Form' == type.name) {
-                buildFormXObject(xobj);
-                args = [];
-                continue;
-              } else if ('Image' == type.name) {
-                buildPaintImageXObject(xobj, false);
               } else {
-                error('Unhandled XObject subtype ' + type.name);
-              }
-            }
-          } else if (cmd == 'Tf') { // eagerly collect all fonts
-            args[0] = handleSetFont(args[0].name);
-          } else if (cmd == 'EI') {
-            buildPaintImageXObject(args[0], true);
-          }
-
-          switch (fn) {
-            // Parse the ColorSpace data to a raw format.
-            case 'setFillColorSpace':
-            case 'setStrokeColorSpace':
-              args = [ColorSpace.parseToIR(args[0], xref, resources)];
-              break;
-            case 'shadingFill':
-              var shadingRes = res.get('Shading');
-              if (!shadingRes)
-                error('No shading resource found');
-
-              var shading = shadingRes.get(args[0].name);
-              if (!shading)
-                error('No shading object found');
-
-              var shadingFill = Pattern.parseShading(shading, null, xref, res);
-              var patternIR = shadingFill.getIR();
-              args = [patternIR];
-              fn = 'shadingFill';
-              break;
-            case 'setGState':
-              var dictName = args[0];
-              var extGState = resources.get('ExtGState');
-
-              if (!isDict(extGState) || !extGState.has(dictName.name))
-                break;
-
-              var gsState = extGState.get(dictName.name);
-
-              // This array holds the converted/processed state data.
-              var gsStateObj = [];
-
-              gsState.forEach(
-                function canvasGraphicsSetGStateForEach(key, value) {
-                  switch (key) {
-                    case 'Type':
-                      break;
-                    case 'LW':
-                    case 'LC':
-                    case 'LJ':
-                    case 'ML':
-                    case 'D':
-                    case 'RI':
-                    case 'FL':
-                    case 'CA':
-                    case 'ca':
-                      gsStateObj.push([key, value]);
-                      break;
-                    case 'Font':
-                      gsStateObj.push([
-                        'Font',
-                        handleSetFont(null, value[0]),
-                        value[1]
-                      ]);
-                      break;
-                    case 'BM':
-                      if (!isName(value) || value.name !== 'Normal') {
-                        queue.transparency = true;
-                      }
-                      gsStateObj.push([key, value]);
-                      break;
-                    case 'SMask':
-                      // We support the default so don't trigger the TODO.
-                      if (!isName(value) || value.name != 'None')
-                        TODO('graphic state operator ' + key);
-                      break;
-                    // Only generate info log messages for the following since
-                    // they are unlikey to have a big impact on the rendering.
-                    case 'OP':
-                    case 'op':
-                    case 'OPM':
-                    case 'BG':
-                    case 'BG2':
-                    case 'UCR':
-                    case 'UCR2':
-                    case 'TR':
-                    case 'TR2':
-                    case 'HT':
-                    case 'SM':
-                    case 'SA':
-                    case 'AIS':
-                    case 'TK':
-                      // TODO implement these operators.
-                      info('graphic state operator ' + key);
-                      break;
-                    default:
-                      info('Unknown graphic state operator ' + key);
-                      break;
+                if (args.length < opSpec.numArgs) {
+                  // If we receive too few args, it's not possible to possible
+                  // to execute the command, so skip the command
+                  info('Command ' + fn + ': because expected ' +
+                       opSpec.numArgs + ' args, but received ' + args.length +
+                       ' args; skipping');
+                  args = [];
+                  continue;
+                } else if (args.length > opSpec.numArgs) {
+                  info('Command ' + fn + ': expected ' + opSpec.numArgs +
+                      ' args, but received ' + args.length + ' args');
+                }
+              }
+
+              // TODO figure out how to type-check vararg functions
+
+              if ((cmd == 'SCN' || cmd == 'scn') &&
+                   !args[args.length - 1].code) {
+                // compile tiling patterns
+                var patternName = args[args.length - 1];
+                // SCN/scn applies patterns along with normal colors
+                var pattern;
+                if (isName(patternName) &&
+                    (pattern = patterns.get(patternName.name))) {
+
+                  var dict = isStream(pattern) ? pattern.dict : pattern;
+                  var typeNum = dict.get('PatternType');
+
+                  if (typeNum == TILING_PATTERN) {
+                    var patternPromise = self.handleTilingType(
+                        fn, args, resources, pattern, dict);
+                    fn = 'promise';
+                    args = [patternPromise];
+                  } else if (typeNum == SHADING_PATTERN) {
+                    var shading = dict.get('Shading');
+                    var matrix = dict.get('Matrix');
+                    var pattern = Pattern.parseShading(shading, matrix, xref,
+                                                        resources);
+                    args = pattern.getIR();
+                  } else {
+                    error('Unkown PatternType ' + typeNum);
+                  }
+                }
+              } else if (cmd == 'Do' && !args[0].code) {
+                // eagerly compile XForm objects
+                var name = args[0].name;
+                var xobj = xobjs.get(name);
+                if (xobj) {
+                  assertWellFormed(
+                      isStream(xobj), 'XObject should be a stream');
+
+                  var type = xobj.dict.get('Subtype');
+                  assertWellFormed(
+                    isName(type),
+                    'XObject should have a Name subtype'
+                  );
+
+                  if ('Form' == type.name) {
+                    fn = 'promise';
+                    args = [self.buildFormXObject(resources, xobj)];
+                  } else if ('Image' == type.name) {
+                    var data = self.buildPaintImageXObject(
+                        resources, xobj, false);
+                    Util.extendObj(dependencies, data.dependencies);
+                    self.insertDependencies(queue, data.dependencies);
+                    fn = data.fn;
+                    args = data.args;
+                  } else {
+                    error('Unhandled XObject subtype ' + type.name);
                   }
                 }
-              );
-              args = [gsStateObj];
-              break;
-          } // switch
-
-          fnArray.push(fn);
-          argsArray.push(args);
-          args = [];
-        } else if (obj !== null && obj !== undefined) {
-          args.push(obj instanceof Dict ? obj.getAll() : obj);
-          assertWellFormed(args.length <= 33, 'Too many arguments');
-        }
-      }
-
-      return queue;
-    },
-
-    getAnnotationsOperatorList:
-        function PartialEvaluator_getAnnotationsOperatorList(annotations,
-                                                             dependency) {
-      // 12.5.5: Algorithm: Appearance streams
-      function getTransformMatrix(rect, bbox, matrix) {
-        var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
-        var minX = bounds[0];
-        var minY = bounds[1];
-        var maxX = bounds[2];
-        var maxY = bounds[3];
-        var width = rect[2] - rect[0];
-        var height = rect[3] - rect[1];
-        var xRatio = width / (maxX - minX);
-        var yRatio = height / (maxY - minY);
-        return [
-          xRatio,
-          0,
-          0,
-          yRatio,
-          rect[0] - minX * xRatio,
-          rect[1] - minY * yRatio
-        ];
-      }
-
-      var fnArray = [];
-      var argsArray = [];
-      // deal with annotations
-      for (var i = 0, length = annotations.length; i < length; ++i) {
-        var annotation = annotations[i];
-
-        // check whether we can visualize annotation
-        if (!annotation ||
-            !annotation.annotationFlags ||
-            (annotation.annotationFlags & 0x0022) || // Hidden or NoView
-            !annotation.rect ||                      // rectangle is nessessary
-            !annotation.appearance) {                // appearance is nessessary
-          continue;
-        }
-
-        // apply rectangle
-        var rect = annotation.rect;
-        var bbox = annotation.bbox;
-        var matrix = annotation.matrix;
-        var transform = getTransformMatrix(rect, bbox, matrix);
-        var border = annotation.border;
-
-        fnArray.push('beginAnnotation');
-        argsArray.push([rect, transform, matrix, border]);
-
-        if (annotation.appearance) {
-          var list = this.getOperatorList(annotation.appearance,
-            annotation.resources, dependency);
-
-          Util.concatenateToArray(fnArray, list.fnArray);
-          Util.concatenateToArray(argsArray, list.argsArray);
-        }
-
-        fnArray.push('endAnnotation');
-        argsArray.push([]);
-      }
-
-      return {
-        fnArray: fnArray,
-        argsArray: argsArray
-      };
-    },
-
-    optimizeQueue: function PartialEvaluator_optimizeQueue(queue) {
-      var fnArray = queue.fnArray, argsArray = queue.argsArray;
-      // grouping paintInlineImageXObject's into paintInlineImageXObjectGroup
-      // searching for (save, transform, paintInlineImageXObject, restore)+
-      var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
-      var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
-      var MAX_WIDTH = 1000;
-      var IMAGE_PADDING = 1;
-      for (var i = 0, ii = fnArray.length; i < ii; i++) {
-        if (fnArray[i] === 'paintInlineImageXObject' &&
-            fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
-            fnArray[i + 1] === 'restore') {
-          var j = i - 2;
-          for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
-          }
-          var count = Math.min((i - j) >> 2,
-                               MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
-          if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
-            continue;
-          }
-          // assuming that heights of those image is too small (~1 pixel)
-          // packing as much as possible by lines
-          var maxX = 0;
-          var map = [], maxLineHeight = 0;
-          var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING;
-          for (var q = 0; q < count; q++) {
-            var transform = argsArray[j + (q << 2) + 1];
-            var img = argsArray[j + (q << 2) + 2][0];
-            if (currentX + img.width > MAX_WIDTH) {
-              // starting new line
-              maxX = Math.max(maxX, currentX);
-              currentY += maxLineHeight + 2 * IMAGE_PADDING;
-              currentX = 0;
-              maxLineHeight = 0;
-            }
-            map.push({
-              transform: transform,
-              x: currentX, y: currentY,
-              w: img.width, h: img.height
+              } else if (cmd == 'Tf') { // eagerly collect all fonts
+                fn = 'promise';
+                args = [self.handleSetFont(resources, args)];
+              } else if (cmd == 'EI') {
+                var data = self.buildPaintImageXObject(
+                    resources, args[0], true);
+                Util.extendObj(dependencies, data.dependencies);
+                self.insertDependencies(queue, data.dependencies);
+                fn = data.fn;
+                args = data.args;
+              }
+
+              switch (fn) {
+                // Parse the ColorSpace data to a raw format.
+                case 'setFillColorSpace':
+                case 'setStrokeColorSpace':
+                  args = [ColorSpace.parseToIR(args[0], xref, resources)];
+                  break;
+                case 'shadingFill':
+                  var shadingRes = resources.get('Shading');
+                  if (!shadingRes)
+                    error('No shading resource found');
+
+                  var shading = shadingRes.get(args[0].name);
+                  if (!shading)
+                    error('No shading object found');
+
+                  var shadingFill = Pattern.parseShading(
+                      shading, null, xref, resources);
+                  var patternIR = shadingFill.getIR();
+                  args = [patternIR];
+                  fn = 'shadingFill';
+                  break;
+                case 'setGState':
+                  var dictName = args[0];
+                  var extGState = resources.get('ExtGState');
+
+                  if (!isDict(extGState) || !extGState.has(dictName.name))
+                    break;
+
+                  var gState = extGState.get(dictName.name);
+                  fn = 'promise';
+                  args = [self.setGState(resources, gState)];
+              } // switch
+
+              fnArray.push(fn);
+              argsArray.push(args);
+              args = [];
+              parser.saveState();
+            } else if (obj !== null && obj !== undefined) {
+              args.push(obj instanceof Dict ? obj.getAll() : obj);
+              assertWellFormed(args.length <= 33, 'Too many arguments');
+            }
+          }
+
+          var subQueuePromises = [];
+          for (var i = 0; i < fnArray.length; ++i) {
+            if (fnArray[i] === 'promise') {
+              subQueuePromises.push(argsArray[i][0]);
+            }
+          }
+          Promise.all(subQueuePromises).then(function(datas) {
+            // TODO(mack): Optimize by using repositioning elements
+            // in original queue rather than creating new queue
+
+            for (var i = 0, n = datas.length; i < n; ++i) {
+              var data = datas[i];
+              var subQueue = data.queue;
+              queue.transparency = subQueue.transparency || queue.transparency;
+              Util.extendObj(dependencies, data.dependencies);
+            }
+
+            var newFnArray = [];
+            var newArgsArray = [];
+            var currOffset = 0;
+            var subQueueIdx = 0;
+            for (var i = 0, n = fnArray.length; i < n; ++i) {
+              var offset = i + currOffset;
+              if (fnArray[i] === 'promise') {
+                var data = datas[subQueueIdx++];
+                var subQueue = data.queue;
+                var subQueueFnArray = subQueue.fnArray;
+                var subQueueArgsArray = subQueue.argsArray;
+                for (var j = 0, nn = subQueueFnArray.length; j < nn; ++j) {
+                  newFnArray[offset + j] = subQueueFnArray[j];
+                  newArgsArray[offset + j] = subQueueArgsArray[j];
+                }
+                currOffset += subQueueFnArray.length - 1;
+              } else {
+                newFnArray[offset] = fnArray[i];
+                newArgsArray[offset] = argsArray[i];
+              }
+            }
+
+            promise.resolve({
+              queue: {
+                fnArray: newFnArray,
+                argsArray: newArgsArray,
+                transparency: queue.transparency
+              },
+              dependencies: dependencies
             });
-            currentX += img.width + 2 * IMAGE_PADDING;
-            maxLineHeight = Math.max(maxLineHeight, img.height);
-          }
-          var imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
-          var imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
-          var imgData = new Uint8Array(imgWidth * imgHeight * 4);
-          var imgRowSize = imgWidth << 2;
-          for (var q = 0; q < count; q++) {
-            var data = argsArray[j + (q << 2) + 2][0].data;
-            // copy image by lines and extends pixels into padding
-            var rowSize = map[q].w << 2;
-            var dataOffset = 0;
-            var offset = (map[q].x + map[q].y * imgWidth) << 2;
-            imgData.set(
-              data.subarray(0, rowSize), offset - imgRowSize);
-            for (var k = 0, kk = map[q].h; k < kk; k++) {
-              imgData.set(
-                data.subarray(dataOffset, dataOffset + rowSize), offset);
-              dataOffset += rowSize;
-              offset += imgRowSize;
-            }
-            imgData.set(
-              data.subarray(dataOffset - rowSize, dataOffset), offset);
-            while (offset >= 0) {
-              data[offset - 4] = data[offset];
-              data[offset - 3] = data[offset + 1];
-              data[offset - 2] = data[offset + 2];
-              data[offset - 1] = data[offset + 3];
-              data[offset + rowSize] = data[offset + rowSize - 4];
-              data[offset + rowSize + 1] = data[offset + rowSize - 3];
-              data[offset + rowSize + 2] = data[offset + rowSize - 2];
-              data[offset + rowSize + 3] = data[offset + rowSize - 1];
-              offset -= imgRowSize;
-            }
-          }
-          // replacing queue items
-          fnArray.splice(j, count * 4, ['paintInlineImageXObjectGroup']);
-          argsArray.splice(j, count * 4,
-            [{width: imgWidth, height: imgHeight, data: imgData}, map]);
-          i = j;
-          ii = fnArray.length;
-        }
-      }
-      // grouping paintImageMaskXObject's into paintImageMaskXObjectGroup
-      // searching for (save, transform, paintImageMaskXObject, restore)+
-      var MIN_IMAGES_IN_MASKS_BLOCK = 10;
-      var MAX_IMAGES_IN_MASKS_BLOCK = 100;
-      for (var i = 0, ii = fnArray.length; i < ii; i++) {
-        if (fnArray[i] === 'paintImageMaskXObject' &&
-            fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
-            fnArray[i + 1] === 'restore') {
-          var j = i - 2;
-          for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
-          }
-          var count = Math.min((i - j) >> 2,
-                               MAX_IMAGES_IN_MASKS_BLOCK);
-          if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
-            continue;
-          }
-          var images = [];
-          for (var q = 0; q < count; q++) {
-            var transform = argsArray[j + (q << 2) + 1];
-            var maskParams = argsArray[j + (q << 2) + 2];
-            images.push({data: maskParams[0], width: maskParams[2],
-              height: maskParams[3], transform: transform,
-              inverseDecode: maskParams[1]});
-          }
-          // replacing queue items
-          fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);
-          argsArray.splice(j, count * 4, [images]);
-          i = j;
-          ii = fnArray.length;
-        }
-      }
+          });
+        } catch (e) {
+          if (!(e instanceof MissingDataException)) {
+            throw e;
+          }
+
+          self.pdfManager.requestRange(e.begin, e.end).then(parseCommands);
+        }
+      }
+      parser.saveState();
+      parseCommands();
+
+      return promise;
     },
 
     getTextContent: function PartialEvaluator_getTextContent(
-                                                    stream, resources, state) {
-      var bidiTexts;
+                                                    stream, resources) {
+
       var SPACE_FACTOR = 0.35;
       var MULTI_SPACE_FACTOR = 1.5;
-
-      if (!state) {
-        bidiTexts = [];
-        state = {
-          bidiTexts: bidiTexts
-        };
-      } else {
-        bidiTexts = state.bidiTexts;
-      }
-
       var self = this;
-      var xref = this.xref;
-
-      function handleSetFont(fontName, fontRef) {
-        return self.loadFont(fontName, fontRef, xref, resources, null);
-      }
-
-      resources = xref.fetchIfRef(resources) || new Dict();
+
+      var statePromise = new Promise();
+
+      function handleSetFont(fontName, fontRef, resources) {
+        var promise = new Promise();
+        self.loadFont(fontName, fontRef, self.xref, resources).then(
+          function(data) {
+            promise.resolve(data.font.translated);
+          }
+        );
+        return promise;
+      }
+
+      function getBidiText(str, startLevel, vertical) {
+        if (str) {
+          return PDFJS.bidi(str, -1, vertical);
+        }
+      }
+
+      resources = this.xref.fetchIfRef(resources) || new Dict();
       // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
       var xobjs = null;
 
       var parser = new Parser(new Lexer(stream), false);
-      var res = resources;
-      var args = [], obj;
-
-      var chunk = '';
-      var font = null;
-      while (!isEOF(obj = parser.getObj())) {
-        if (isCmd(obj)) {
-          var cmd = obj.cmd;
-          switch (cmd) {
-            // TODO: Add support for SAVE/RESTORE and XFORM here.
-            case 'Tf':
-              font = handleSetFont(args[0].name).translated;
-              break;
-            case 'TJ':
-              var items = args[0];
-              for (var j = 0, jj = items.length; j < jj; j++) {
-                if (typeof items[j] === 'string') {
-                  chunk += fontCharsToUnicode(items[j], font);
-                } else if (items[j] < 0 && font.spaceWidth > 0) {
-                  var fakeSpaces = -items[j] / font.spaceWidth;
-                  if (fakeSpaces > MULTI_SPACE_FACTOR) {
-                    fakeSpaces = Math.round(fakeSpaces);
-                    while (fakeSpaces--) {
-                      chunk += ' ';
+
+      var chunkPromises = [];
+      var fontPromise;
+      function parseCommands() {
+        try {
+          parser.restoreState();
+          var args = [];
+
+          while (true) {
+            var obj = parser.getObj();
+            if (isEOF(obj)) {
+              break;
+            }
+
+            if (isCmd(obj)) {
+              var cmd = obj.cmd;
+              switch (cmd) {
+                // TODO: Add support for SAVE/RESTORE and XFORM here.
+                case 'Tf':
+                  fontPromise = handleSetFont(args[0].name, null, resources);
+                  //.translated;
+                  break;
+                case 'TJ':
+                  var chunkPromise = new Promise();
+                  chunkPromises.push(chunkPromise);
+                  fontPromise.then(function(items, chunkPromise, font) {
+                    var chunk = '';
+                    for (var j = 0, jj = items.length; j < jj; j++) {
+                      if (typeof items[j] === 'string') {
+                        chunk += fontCharsToUnicode(items[j], font);
+                      } else if (items[j] < 0 && font.spaceWidth > 0) {
+                        var fakeSpaces = -items[j] / font.spaceWidth;
+                        if (fakeSpaces > MULTI_SPACE_FACTOR) {
+                          fakeSpaces = Math.round(fakeSpaces);
+                          while (fakeSpaces--) {
+                            chunk += ' ';
+                          }
+                        } else if (fakeSpaces > SPACE_FACTOR) {
+                          chunk += ' ';
+                        }
+                      }
                     }
-                  } else if (fakeSpaces > SPACE_FACTOR) {
-                    chunk += ' ';
+                    chunkPromise.resolve(
+                        getBidiText(chunk, -1, font.vertical));
+                  }.bind(null, args[0], chunkPromise));
+                  break;
+                case 'Tj':
+                  var chunkPromise = new Promise();
+                  chunkPromises.push(chunkPromise);
+                  fontPromise.then(function(charCodes, chunkPromise, font) {
+                    var chunk = fontCharsToUnicode(charCodes, font);
+                    chunkPromise.resolve(
+                        getBidiText(chunk, -1, font.vertical));
+                  }.bind(null, args[0], chunkPromise));
+                  break;
+                case '\'':
+                  // For search, adding a extra white space for line breaks
+                  // would be better here, but that causes too much spaces in
+                  // the text-selection divs.
+                  var chunkPromise = new Promise();
+                  chunkPromises.push(chunkPromise);
+                  fontPromise.then(function(charCodes, chunkPromise, font) {
+                    var chunk = fontCharsToUnicode(charCodes, font);
+                    chunkPromise.resolve(
+                        getBidiText(chunk, -1, font.vertical));
+                  }.bind(null, args[0], chunkPromise));
+                  break;
+                case '"':
+                  // Note comment in "'"
+                  var chunkPromise = new Promise();
+                  chunkPromises.push(chunkPromise);
+                  fontPromise.then(function(charCodes, chunkPromise, font) {
+                    var chunk = fontCharsToUnicode(charCodes, font);
+                    chunkPromise.resolve(
+                        getBidiText(chunk, -1, font.vertical));
+                  }.bind(null, args[2], chunkPromise));
+                  break;
+                case 'Do':
+                  if (args[0].code) {
+                    break;
                   }
-                }
-              }
-              break;
-            case 'Tj':
-              chunk += fontCharsToUnicode(args[0], font);
-              break;
-            case '\'':
-              // For search, adding a extra white space for line breaks would be
-              // better here, but that causes too much spaces in the
-              // text-selection divs.
-              chunk += fontCharsToUnicode(args[0], font);
-              break;
-            case '"':
-              // Note comment in "'"
-              chunk += fontCharsToUnicode(args[2], font);
-              break;
-            case 'Do':
-              // Set the chunk such that the following if won't add something
-              // to the state.
-              chunk = '';
-
-              if (args[0].code) {
-                break;
-              }
-
-              if (!xobjs) {
-                xobjs = resources.get('XObject') || new Dict();
-              }
-
-              var name = args[0].name;
-              var xobj = xobjs.get(name);
-              if (!xobj)
-                break;
-              assertWellFormed(isStream(xobj), 'XObject should be a stream');
-
-              var type = xobj.dict.get('Subtype');
-              assertWellFormed(
-                isName(type),
-                'XObject should have a Name subtype'
-              );
-
-              if ('Form' !== type.name)
-                break;
-
-              state = this.getTextContent(
-                xobj,
-                xobj.dict.get('Resources') || resources,
-                state
-              );
-              break;
-            case 'gs':
-              var dictName = args[0];
-              var extGState = resources.get('ExtGState');
-
-              if (!isDict(extGState) || !extGState.has(dictName.name))
-                break;
-
-              var gsState = extGState.get(dictName.name);
-
-              for (var i = 0; i < gsState.length; i++) {
-                if (gsState[i] === 'Font') {
-                  font = handleSetFont(args[0].name).translated;
-                }
-              }
-              break;
-          } // switch
-
-          if (chunk !== '') {
-            var bidiText = PDFJS.bidi(chunk, -1, font.vertical);
-            bidiTexts.push(bidiText);
-
-            chunk = '';
-          }
-
-          args = [];
-        } else if (obj !== null && obj !== undefined) {
-          assertWellFormed(args.length <= 33, 'Too many arguments');
-          args.push(obj);
-        }
-      } // while
-
-      return state;
+
+                  if (!xobjs) {
+                    xobjs = resources.get('XObject') || new Dict();
+                  }
+
+                  var name = args[0].name;
+                  var xobj = xobjs.get(name);
+                  if (!xobj)
+                    break;
+                  assertWellFormed(isStream(xobj),
+                                   'XObject should be a stream');
+
+                  var type = xobj.dict.get('Subtype');
+                  assertWellFormed(
+                    isName(type),
+                    'XObject should have a Name subtype'
+                  );
+
+                  if ('Form' !== type.name)
+                    break;
+
+                  var chunkPromise = self.getTextContent(
+                    xobj,
+                    xobj.dict.get('Resources') || resources
+                  );
+                  chunkPromises.push(chunkPromise);
+                  break;
+                case 'gs':
+                  var dictName = args[0];
+                  var extGState = resources.get('ExtGState');
+
+                  if (!isDict(extGState) || !extGState.has(dictName.name))
+                    break;
+
+                  var gsState = extGState.get(dictName.name);
+
+                  for (var i = 0; i < gsState.length; i++) {
+                    if (gsState[i] === 'Font') {
+                      fontPromise = handleSetFont(
+                          args[0].name, null, resources);
+                    }
+                  }
+                  break;
+              } // switch
+
+              args = [];
+              parser.saveState();
+            } else if (obj !== null && obj !== undefined) {
+              assertWellFormed(args.length <= 33, 'Too many arguments');
+              args.push(obj);
+            }
+          } // while
+
+          Promise.all(chunkPromises).then(function(datas) {
+            var bidiTexts = [];
+            for (var i = 0, n = datas.length; i < n; ++i) {
+              var bidiText = datas[i];
+              if (!bidiText) {
+                continue;
+              } else if (isArray(bidiText)) {
+                Util.concatenateToArray(bidiTexts, bidiText);
+              } else {
+                bidiTexts.push(bidiText);
+              }
+            }
+            statePromise.resolve(bidiTexts);
+          });
+        } catch (e) {
+          if (!(e instanceof MissingDataException)) {
+            throw e;
+          }
+
+          self.pdfManager.requestRange(e.begin, e.end).then(parseCommands);
+        }
+      }
+      parser.saveState();
+      parseCommands();
+
+      return statePromise;
     },
 
     extractDataStructures: function
       partialEvaluatorExtractDataStructures(dict, baseDict,
                                             xref, properties) {
       // 9.10.2
       var toUnicode = dict.get('ToUnicode') ||
         baseDict.get('ToUnicode');
@@ -15044,57 +16451,74 @@ var PartialEvaluator = (function Partial
           };
         }
 
         var cidToGidMap = dict.get('CIDToGIDMap');
         if (isStream(cidToGidMap))
           properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
       }
 
+      // Based on 9.6.6 of the spec the encoding can come from multiple places
+      // but should be prioritized in the following order:
+      // 1. Encoding dictionary
+      // 2. Encoding within font file (Type1 or Type1C)
+      // 3. Default (depends on font type)
+      // Differences applied to the above.
+      // Note: we don't fill in the encoding from the font file(2) here but use
+      // the flag overridableEncoding to signal that the font can override the
+      // encoding if it has one built in.
+      var overridableEncoding = true;
+      var hasEncoding = false;
       var flags = properties.flags;
       var differences = [];
-      var baseEncoding = Encodings.StandardEncoding;
+      var baseEncoding = properties.type === 'TrueType' ?
+                          Encodings.WinAnsiEncoding :
+                          Encodings.StandardEncoding;
       // The Symbolic attribute can be misused for regular fonts
       // Heuristic: we have to check if the font is a standard one also
       if (!!(flags & FontFlags.Symbolic)) {
         baseEncoding = !properties.file ? Encodings.symbolsEncoding :
                                           Encodings.MacRomanEncoding;
       }
-      var hasEncoding = dict.has('Encoding');
-      if (hasEncoding) {
+      if (dict.has('Encoding')) {
         var encoding = dict.get('Encoding');
         if (isDict(encoding)) {
           var baseName = encoding.get('BaseEncoding');
-          if (baseName)
+          if (baseName) {
+            overridableEncoding = false;
+            hasEncoding = true;
             baseEncoding = Encodings[baseName.name];
-          else
-            hasEncoding = false; // base encoding was not provided
+          }
 
           // Load the differences between the base and original
           if (encoding.has('Differences')) {
+            hasEncoding = true;
             var diffEncoding = encoding.get('Differences');
             var index = 0;
             for (var j = 0, jj = diffEncoding.length; j < jj; j++) {
               var data = diffEncoding[j];
               if (isNum(data))
                 index = data;
               else
                 differences[index++] = data.name;
             }
           }
         } else if (isName(encoding)) {
+          overridableEncoding = false;
+          hasEncoding = true;
           baseEncoding = Encodings[encoding.name];
         } else {
           error('Encoding is not a Name nor a Dict');
         }
       }
 
       properties.differences = differences;
       properties.baseEncoding = baseEncoding;
       properties.hasEncoding = hasEncoding;
+      properties.overridableEncoding = overridableEncoding;
     },
 
     readToUnicode: function PartialEvaluator_readToUnicode(toUnicode, xref,
                                                            properties) {
       var cmapObj = toUnicode;
       var charToUnicode = [];
       if (isName(cmapObj)) {
         var isIdentityMap = cmapObj.name.substr(0, 9) == 'Identity-';
@@ -15358,19 +16782,17 @@ var PartialEvaluator = (function Partial
       return {
         defaultWidth: defaultWidth,
         monospace: monospace,
         widths: widths
       };
     },
 
     translateFont: function PartialEvaluator_translateFont(dict,
-                                                           xref,
-                                                           resources,
-                                                           dependency) {
+                                                           xref) {
       var baseDict = dict;
       var type = dict.get('Subtype');
       assertWellFormed(isName(type), 'invalid font Subtype');
 
       var composite = false;
       if (type.name == 'Type0') {
         // If font is a composite
         //  - get the descendant font
@@ -15446,17 +16868,17 @@ var PartialEvaluator = (function Partial
       if (isString(baseFont)) {
         baseFont = new Name(baseFont);
       }
 
       if (type.name !== 'Type3') {
         var fontNameStr = fontName && fontName.name;
         var baseFontStr = baseFont && baseFont.name;
         if (fontNameStr !== baseFontStr) {
-          warn('The FontDescriptor\'s FontName is "' + fontNameStr +
+          info('The FontDescriptor\'s FontName is "' + fontNameStr +
                '" but should be the same as the Font\'s BaseFont "' +
                baseFontStr + '"');
         }
       }
       fontName = fontName || baseFont;
 
       assertWellFormed(isName(fontName), 'invalid font name');
 
@@ -15509,16 +16931,135 @@ var PartialEvaluator = (function Partial
       if (type.name === 'Type3') {
         properties.coded = true;
       }
 
       return new Font(fontName.name, fontFile, properties);
     }
   };
 
+  PartialEvaluator.optimizeQueue =
+      function PartialEvaluator_optimizeQueue(queue) {
+
+    var fnArray = queue.fnArray, argsArray = queue.argsArray;
+    // grouping paintInlineImageXObject's into paintInlineImageXObjectGroup
+    // searching for (save, transform, paintInlineImageXObject, restore)+
+    var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
+    var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
+    var MAX_WIDTH = 1000;
+    var IMAGE_PADDING = 1;
+    for (var i = 0, ii = fnArray.length; i < ii; i++) {
+      if (fnArray[i] === 'paintInlineImageXObject' &&
+          fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
+          fnArray[i + 1] === 'restore') {
+        var j = i - 2;
+        for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
+        }
+        var count = Math.min((i - j) >> 2,
+                             MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
+        if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
+          continue;
+        }
+        // assuming that heights of those image is too small (~1 pixel)
+        // packing as much as possible by lines
+        var maxX = 0;
+        var map = [], maxLineHeight = 0;
+        var currentX = IMAGE_PADDING, currentY = IMAGE_PADDING;
+        for (var q = 0; q < count; q++) {
+          var transform = argsArray[j + (q << 2) + 1];
+          var img = argsArray[j + (q << 2) + 2][0];
+          if (currentX + img.width > MAX_WIDTH) {
+            // starting new line
+            maxX = Math.max(maxX, currentX);
+            currentY += maxLineHeight + 2 * IMAGE_PADDING;
+            currentX = 0;
+            maxLineHeight = 0;
+          }
+          map.push({
+            transform: transform,
+            x: currentX, y: currentY,
+            w: img.width, h: img.height
+          });
+          currentX += img.width + 2 * IMAGE_PADDING;
+          maxLineHeight = Math.max(maxLineHeight, img.height);
+        }
+        var imgWidth = Math.max(maxX, currentX) + IMAGE_PADDING;
+        var imgHeight = currentY + maxLineHeight + IMAGE_PADDING;
+        var imgData = new Uint8Array(imgWidth * imgHeight * 4);
+        var imgRowSize = imgWidth << 2;
+        for (var q = 0; q < count; q++) {
+          var data = argsArray[j + (q << 2) + 2][0].data;
+          // copy image by lines and extends pixels into padding
+          var rowSize = map[q].w << 2;
+          var dataOffset = 0;
+          var offset = (map[q].x + map[q].y * imgWidth) << 2;
+          imgData.set(
+            data.subarray(0, rowSize), offset - imgRowSize);
+          for (var k = 0, kk = map[q].h; k < kk; k++) {
+            imgData.set(
+              data.subarray(dataOffset, dataOffset + rowSize), offset);
+            dataOffset += rowSize;
+            offset += imgRowSize;
+          }
+          imgData.set(
+            data.subarray(dataOffset - rowSize, dataOffset), offset);
+          while (offset >= 0) {
+            data[offset - 4] = data[offset];
+            data[offset - 3] = data[offset + 1];
+            data[offset - 2] = data[offset + 2];
+            data[offset - 1] = data[offset + 3];
+            data[offset + rowSize] = data[offset + rowSize - 4];
+            data[offset + rowSize + 1] = data[offset + rowSize - 3];
+            data[offset + rowSize + 2] = data[offset + rowSize - 2];
+            data[offset + rowSize + 3] = data[offset + rowSize - 1];
+            offset -= imgRowSize;
+          }
+        }
+        // replacing queue items
+        fnArray.splice(j, count * 4, ['paintInlineImageXObjectGroup']);
+        argsArray.splice(j, count * 4,
+          [{width: imgWidth, height: imgHeight, data: imgData}, map]);
+        i = j;
+        ii = fnArray.length;
+      }
+    }
+    // grouping paintImageMaskXObject's into paintImageMaskXObjectGroup
+    // searching for (save, transform, paintImageMaskXObject, restore)+
+    var MIN_IMAGES_IN_MASKS_BLOCK = 10;
+    var MAX_IMAGES_IN_MASKS_BLOCK = 100;
+    for (var i = 0, ii = fnArray.length; i < ii; i++) {
+      if (fnArray[i] === 'paintImageMaskXObject' &&
+          fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
+          fnArray[i + 1] === 'restore') {
+        var j = i - 2;
+        for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
+        }
+        var count = Math.min((i - j) >> 2,
+                             MAX_IMAGES_IN_MASKS_BLOCK);
+        if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
+          continue;
+        }
+        var images = [];
+        for (var q = 0; q < count; q++) {
+          var transform = argsArray[j + (q << 2) + 1];
+          var maskParams = argsArray[j + (q << 2) + 2];
+          images.push({data: maskParams[0], width: maskParams[2],
+            height: maskParams[3], transform: transform,
+            inverseDecode: maskParams[1]});
+        }
+        // replacing queue items
+        fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);
+        argsArray.splice(j, count * 4, [images]);
+        i = j;
+        ii = fnArray.length;
+      }
+    }
+  };
+
+
   return PartialEvaluator;
 })();
 
 var EvalState = (function EvalStateClosure() {
   function EvalState() {
     // Are soft masks and alpha values shapes or opacities?
     this.alphaIsShape = false;
     this.fontSize = 0;
@@ -17760,16 +19301,19 @@ var Font = (function FontClosure() {
       this.bold = (fontName.search(/bold/gi) != -1);
       this.italic = (fontName.search(/oblique/gi) != -1) ||
                     (fontName.search(/italic/gi) != -1);
 
       // Use 'name' instead of 'fontName' here because the original
       // name ArialBlack for example will be replaced by Helvetica.
       this.black = (name.search(/Black/g) != -1);
 
+      // if at least one width is present, remeasure all chars when exists
+      this.remeasure = Object.keys(this.widths).length > 0;
+
       this.encoding = properties.baseEncoding;
       this.noUnicodeAdaptation = true;
       this.loadedName = fontName.split('-')[0];
       this.loading = false;
       return;
     }
 
     // Some fonts might use wrong font types for Type1C or CIDFontType0C
@@ -18405,17 +19949,17 @@ var Font = (function FontClosure() {
           if (!glyphName)
             continue;
           var unicode = glyphs[i].unicode;
           glyphNameMap[glyphName] = unicode;
           var code = glyphs[i].code;
           encoding[code] = glyphName;
         }
         properties.glyphNameMap = glyphNameMap;
-        if (!properties.hasEncoding)
+        if (properties.overridableEncoding)
           properties.baseEncoding = encoding;
       }
 
       /**
        * Read the appropriate subtable from the cmap according to 9.6.6.4 from
        * PDF spec
        */
       function readCmapTable(cmap, font, hasEncoding, isSymbolicFont) {
@@ -18964,112 +20508,200 @@ var Font = (function FontClosure() {
           }
         }
         return names;
       }
 
       var TTOpsStackDeltas = [
         0, 0, 0, 0, 0, 0, 0, 0, -2, -2, -2, -2, 0, 0, -2, -5,
         -1, -1, -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, -1, -1,
-        1, -1, -999, 0, 1, 0, 0, -2, 0, -1, -2, -1, -999, -999, -1, -1,
+        1, -1, -999, 0, 1, 0, -1, -2, 0, -1, -2, -1, -1, 0, -1, -1,
         0, 0, -999, -999, -1, -1, -1, -1, -2, -999, -2, -2, -2, 0, -2, -2,
         0, 0, -2, 0, -2, 0, 0, 0, -2, -1, -1, 1, 1, 0, 0, -1,
         -1, -1, -1, -1, -1, -1, 0, 0, -1, 0, -1, -1, 0, -999, -1, -1,
-        -1, -1, -1, -1, 0, 0, 0, 0, -1, -1, 0, 0, 0, 0, 0, 0,
+        -1, -1, -1, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
         -2, -999, -999, -999, -999, -999, -1, -1, -2, -2, 0, 0, 0, 0, -1, -1,
-        -999, -2, -2, 0, 0, -1, -2, -2, 0, -999, 0, 0, 0, -1, -2];
+        -999, -2, -2, 0, 0, -1, -2, -2, 0, 0, 0, -1, -1, -1, -2];
         // 0xC0-DF == -1 and 0xE0-FF == -2
 
       function sanitizeTTProgram(table, ttContext) {
         var data = table.data;
         var i = 0, n, lastEndf = 0, lastDeff = 0;
         var stack = [];
+        var callstack = [];
+        var functionsCalled = [];
         var tooComplexToFollowFunctions =
           ttContext.tooComplexToFollowFunctions;
+        var inFDEF = false, ifLevel = 0, inELSE = 0;
         for (var ii = data.length; i < ii;) {
           var op = data[i++];
           // The TrueType instruction set docs can be found at
           // https://developer.apple.com/fonts/TTRefMan/RM05/Chap5.html
           if (op === 0x40) { // NPUSHB - pushes n bytes
             n = data[i++];
-            for (var j = 0; j < n; j++) {
-              stack.push(data[i++]);
+            if (inFDEF || inELSE) {
+              i += n;
+            } else {
+              for (var j = 0; j < n; j++) {
+                stack.push(data[i++]);
+              }
             }
           } else if (op === 0x41) { // NPUSHW - pushes n words
             n = data[i++];
-            for (var j = 0; j < n; j++) {
-              var b = data[i++];
-              stack.push((b << 8) | data[i++]);
+            if (inFDEF || inELSE) {
+              i += n * 2;
+            } else {
+              for (var j = 0; j < n; j++) {
+                var b = data[i++];
+                stack.push((b << 8) | data[i++]);
+              }
             }
           } else if ((op & 0xF8) === 0xB0) { // PUSHB - pushes bytes
             n = op - 0xB0 + 1;
-            for (var j = 0; j < n; j++) {
-              stack.push(data[i++]);
+            if (inFDEF || inELSE) {
+              i += n;
+            } else {
+              for (var j = 0; j < n; j++) {
+                stack.push(data[i++]);
+              }
             }
           } else if ((op & 0xF8) === 0xB8) { // PUSHW - pushes words
             n = op - 0xB8 + 1;
-            for (var j = 0; j < n; j++) {
-              var b = data[i++];
-              stack.push((b << 8) | data[i++]);
+            if (inFDEF || inELSE) {
+              i += n * 2;
+            } else {
+              for (var j = 0; j < n; j++) {
+                var b = data[i++];
+                stack.push((b << 8) | data[i++]);
+              }
             }
           } else if (op === 0x2B && !tooComplexToFollowFunctions) { // CALL
-            // collecting inforamtion about which functions are used
-            var funcId = stack[stack.length - 1];
-            ttContext.functionsUsed[funcId] = true;
-            if (i >= 2 && data[i - 2] === 0x2B) {
-              // all data in stack, calls are performed in sequence
-              tooComplexToFollowFunctions = true;
+            if (!inFDEF && !inELSE) {
+              // collecting inforamtion about which functions are used
+              var funcId = stack[stack.length - 1];
+              ttContext.functionsUsed[funcId] = true;
+              if (funcId in ttContext.functionsStackDeltas) {
+                stack.length += ttContext.functionsStackDeltas[funcId];
+              } else if (funcId in ttContext.functionsDefined &&
+                         functionsCalled.indexOf(funcId) < 0) {
+                callstack.push({data: data, i: i, stackTop: stack.length - 1});
+                functionsCalled.push(funcId);
+                var pc = ttContext.functionsDefined[funcId];
+                data = pc.data;
+                i = pc.i;
+              }
             }
           } else if (op === 0x2C && !tooComplexToFollowFunctions) { // FDEF
+            if (inFDEF || inELSE) {
+              warn('TT: nested FDEFs not allowed');
+              tooComplexToFollowFunctions = true;
+            }
+            inFDEF = true;
             // collecting inforamtion about which functions are defined
             lastDeff = i;
-            var funcId = stack[stack.length - 1];
-            ttContext.functionsDefined[funcId] = true;
-            if (i >= 2 && data[i - 2] === 0x2D) {
-              // all function ids in stack, FDEF/ENDF perfomed in sequence
+            var funcId = stack.pop();
+            ttContext.functionsDefined[funcId] = {data: data, i: i};
+          } else if (op === 0x2D) { // ENDF - end of function
+            if (inFDEF) {
+              inFDEF = false;
+              lastEndf = i;
+            } else {
+              var pc = callstack.pop();
+              var funcId = functionsCalled.pop();
+              data = pc.data;
+              i = pc.i;
+              ttContext.functionsStackDeltas[funcId] =
+                stack.length - pc.stackTop;
+            }
+          } else if (op === 0x89) { // IDEF - instruction definition
+            if (inFDEF || inELSE) {
+              warn('TT: nested IDEFs not allowed');
               tooComplexToFollowFunctions = true;
             }
-          } else if (op === 0x2D) { // ENDF - end of function
-            lastEndf = i;
-          } else if (op === 0x89) { // IDEF - instruction definition
+            inFDEF = true;
             // recording it as a function to track ENDF
             lastDeff = i;
+          } else if (op === 0x58) { // IF
+            ++ifLevel;
+          } else if (op === 0x1B) { // ELSE
+            inELSE = ifLevel;
+          } else if (op === 0x59) { // EIF
+            if (inELSE === ifLevel) {
+              inELSE = 0;
+            }
+            --ifLevel;
+          } else if (op === 0x1C) { // JMPR
+            var offset = stack[stack.length - 1];
+            // only jumping forward to prevent infinite loop
+            if (offset > 0) { i += offset - 1; }
           }
           // Adjusting stack not extactly, but just enough to get function id
-          var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
-            op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
-          while (stackDelta < 0 && stack.length > 0) {
-            stack.pop();
-            stackDelta++;
-          }
-          while (stackDelta > 0) {
-            stack.push(NaN); // pushing any number into stack
-            stackDelta--;
+          if (!inFDEF && !inELSE) {
+            var stackDelta = op <= 0x8E ? TTOpsStackDeltas[op] :
+              op >= 0xC0 && op <= 0xDF ? -1 : op >= 0xE0 ? -2 : 0;
+            if (op >= 0x71 && op <= 0x75) {
+              n = stack.pop();
+              if (n === n) {
+                stackDelta = -n * 2;
+              }
+            }
+            while (stackDelta < 0 && stack.length > 0) {
+              stack.pop();
+              stackDelta++;
+            }
+            while (stackDelta > 0) {
+              stack.push(NaN); // pushing any number into stack
+              stackDelta--;
+            }
           }
         }
         ttContext.tooComplexToFollowFunctions = tooComplexToFollowFunctions;
         var content = [data];
         if (i > data.length) {
           content.push(new Uint8Array(i - data.length));
         }
         if (lastDeff > lastEndf) {
+          warn('TT: complementing a missing function tail');
           // new function definition started, but not finished
           // complete function by [CLEAR, ENDF]
           content.push(new Uint8Array([0x22, 0x2D]));
         }
-        if (ttContext.defineMissingFunctions && !tooComplexToFollowFunctions) {
+        foldTTTable(table, content);
+      }
+
+      function addTTDummyFunctions(table, ttContext, maxFunctionDefs) {
+        var content = [table.data];
+        if (!ttContext.tooComplexToFollowFunctions) {
+          var undefinedFunctions = [];
           for (var j = 0, jj = ttContext.functionsUsed.length; j < jj; j++) {
             if (!ttContext.functionsUsed[j] || ttContext.functionsDefined[j]) {
               continue;
             }
+            undefinedFunctions.push(j);
+            if (j >= maxFunctionDefs) {
+              continue;
+            }
             // function is used, but not defined
-            // creating empty one [PUSHB, function-id, FDEF, ENDF]
-            content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
-          }
-        }
+            if (j < 256) {
+              // creating empty one [PUSHB, function-id, FDEF, ENDF]
+              content.push(new Uint8Array([0xB0, j, 0x2C, 0x2D]));
+            } else {
+              // creating empty one [PUSHW, function-id, FDEF, ENDF]
+              content.push(
+                new Uint8Array([0xB8, j >> 8, j & 255, 0x2C, 0x2D]));
+            }
+          }
+          if (undefinedFunctions.length > 0) {
+            warn('TT: undefined functions: ' + undefinedFunctions);
+          }
+        }
+        foldTTTable(table, content);
+      }
+
+      function foldTTTable(table, content) {
         if (content.length > 1) {
           // concatenating the content items
           var newLength = 0;
           for (var j = 0, jj = content.length; j < jj; j++) {
             newLength += content[j].length;
           }
           newLength = (newLength + 3) & ~3;
           var result = new Uint8Array(newLength);
@@ -19082,25 +20714,27 @@ var Font = (function FontClosure() {
           table.length = newLength;
         }
       }
 
       function sanitizeTTPrograms(fpgm, prep) {
         var ttContext = {
           functionsDefined: [],
           functionsUsed: [],
+          functionsStackDeltas: [],
           tooComplexToFollowFunctions: false
         };
+        if (fpgm) {
+          sanitizeTTProgram(fpgm, ttContext);
+        }
         if (prep) {
-          // collecting prep functions info first
           sanitizeTTProgram(prep, ttContext);
         }
         if (fpgm) {
-          ttContext.defineMissingFunctions = true;
-          sanitizeTTProgram(fpgm, ttContext);
+          addTTDummyFunctions(fpgm, ttContext, maxFunctionDefs);
         }
       }
 
       // Check that required tables are present
       var requiredTables = ['OS/2', 'cmap', 'head', 'hhea',
                              'hmtx', 'maxp', 'name', 'post'];
 
       var header = readOpenTypeHeader(font);
@@ -19160,22 +20794,27 @@ var Font = (function FontClosure() {
 
       // The new numbers of tables will be the last one plus the num
       // of missing tables
       createOpenTypeHeader(header.version, ttf, numTables);
 
       // Ensure the hmtx table contains the advance width and
       // sidebearings information for numGlyphs in the maxp table
       font.pos = (font.start || 0) + maxp.offset;
-      var version = int16(font.getBytes(4));
+      var version = int32(font.getBytes(4));
       var numGlyphs = int16(font.getBytes(2));
+      var maxFunctionDefs = 0;
+      if (version >= 0x00010000 && maxp.length >= 22) {
+        font.pos += 14;
+        var maxFunctionDefs = int16(font.getBytes(2));
+      }
 
       sanitizeMetrics(font, hhea, hmtx, numGlyphs);
 
-      sanitizeTTPrograms(fpgm, prep);
+      sanitizeTTPrograms(fpgm, prep, maxFunctionDefs);
 
       if (head) {
         sanitizeHead(head, numGlyphs, loca.length);
       }
 
       var isGlyphLocationsLong = int16([head.data[50], head.data[51]]);
       if (head && loca && glyf) {
         sanitizeGlyphLocations(loca, glyf, numGlyphs, isGlyphLocationsLong);
@@ -19878,16 +21517,17 @@ var Font = (function FontClosure() {
             var cid = this.unicodeToCID[charcode] || charcode;
             width = this.widths[cid];
             vmetric = this.vmetrics && this.vmetrics[cid];
             fontCharCode = charcode;
             break;
           }
           fontCharCode = this.toFontChar[charcode] || charcode;
           break;
+        case 'MMType1': // XXX at the moment only "standard" fonts are supported
         case 'Type1':
           var glyphName = this.differences[charcode] || this.encoding[charcode];
           if (!isNum(width))
             width = this.widths[glyphName];
           if (this.noUnicodeAdaptation) {
             fontCharCode = mapPrivateUseChars(GlyphsUnicode[glyphName] ||
               charcode);
             break;
@@ -20646,17 +22286,17 @@ var Type1Parser = (function Type1ParserC
                   var index = this.readInt();
                   this.getToken(); // read in '/'
                   var glyph = this.getToken();
                   encoding[index] = glyph;
                   this.getToken(); // read the in 'put'
                 }
               }
             }
-            if (!properties.hasEncoding && encoding) {
+            if (properties.overridableEncoding && encoding) {
               properties.baseEncoding = encoding;
               break;
             }
             break;
         }
       }
     }
   };
@@ -20906,17 +22546,25 @@ Type1Font.prototype = {
       'ForceBold',
       'StdHW',
       'StdVW'
     ];
     for (var i = 0, ii = fields.length; i < ii; i++) {
       var field = fields[i];
       if (!properties.privateData.hasOwnProperty(field))
         continue;
-      privateDict.setByName(field, properties.privateData[field]);
+      var value = properties.privateData[field];
+      if (isArray(value)) {
+        // All of the private dictionary array data in CFF must be stored as
+        // "delta-encoded" numbers.
+        for (var j = value.length - 1; j > 0; j--) {
+          value[j] -= value[j - 1]; // ... difference from previous value
+        }
+      }
+      privateDict.setByName(field, value);
     }
     cff.topDict.privateDict = privateDict;
 
     var subrIndex = new CFFIndex();
     for (var i = 0, ii = subrs.length; i < ii; i++) {
       subrIndex.add(subrs[i]);
     }
     privateDict.subrsIndex = subrIndex;
@@ -20926,59 +22574,75 @@ Type1Font.prototype = {
   }
 };
 
 var CFFFont = (function CFFFontClosure() {
   function CFFFont(file, properties) {
     this.properties = properties;
 
     var parser = new CFFParser(file, properties);
-    var cff = parser.parse();
-    var compiler = new CFFCompiler(cff);
-    this.readExtra(cff);
+    this.cff = parser.parse();
+    var compiler = new CFFCompiler(this.cff);
+    this.readExtra();
     try {
       this.data = compiler.compile();
     } catch (e) {
       warn('Failed to compile font ' + properties.loadedName);
       // There may have just been an issue with the compiler, set the data
       // anyway and hope the font loaded.
       this.data = file;
     }
   }
 
   CFFFont.prototype = {
-    readExtra: function CFFFont_readExtra(cff) {
+    readExtra: function CFFFont_readExtra() {
       // charstrings contains info about glyphs (one element per glyph
       // containing mappings for {unicode, width})
-      var charset = cff.charset.charset;
-      var encoding = cff.encoding ? cff.encoding.encoding : null;
-      var charstrings = this.getCharStrings(charset, encoding);
+      var charstrings = this.getCharStrings();
 
       // create the mapping between charstring and glyph id
       var glyphIds = [];
       for (var i = 0, ii = charstrings.length; i < ii; i++)
         glyphIds.push(charstrings[i].gid);
 
       this.charstrings = charstrings;
       this.glyphIds = glyphIds;
-      this.seacs = cff.seacs;
-    },
-    getCharStrings: function CFFFont_getCharStrings(charsets, encoding) {
+      this.seacs = this.cff.seacs;
+    },
+    getCharStrings: function CFFFont_getCharStrings() {
+      var cff = this.cff;
+      var charsets = cff.charset.charset;
+      var encoding = cff.encoding ? cff.encoding.encoding : null;
       var charstrings = [];
       var unicodeUsed = [];
       var unassignedUnicodeItems = [];
       var inverseEncoding = [];
-      // CID fonts don't have an encoding.
-      if (encoding !== null)
+      var gidStart = 0;
+      // Even though the CFF font may not actually be a CID font is could have
+      // CID information in the font descriptor.
+      if (this.properties.cidSystemInfo) {
+        // According to section 9.7.4.2 if the font is actually a CID font then
+        // we should use the charset to map CIDs to GIDs. If it is not actually
+        // a CID font then CIDs can be mapped directly to GIDs.
+        if (this.cff.isCIDFont) {
+          inverseEncoding = charsets;
+        } else {
+          for (var i = 0, ii = charsets.length; i < charsets.length; i++) {
+            inverseEncoding.push(i);
+          }
+        }
+      } else {
         for (var charcode in encoding)
           inverseEncoding[encoding[charcode]] = charcode | 0;
-      else
-        inverseEncoding = charsets;
-      var i = charsets[0] == '.notdef' ? 1 : 0;
-      for (var ii = charsets.length; i < ii; i++) {
+        if (charsets[0] === '.notedef') {
+          gidStart = 1;
+        }
+      }
+
+      for (var i = gidStart, ii = charsets.length; i < ii; i++) {
         var glyph = charsets[i];
 
         var code = inverseEncoding[i];
         if (!code || isSpecialUnicode(code)) {
           unassignedUnicodeItems.push(i);
           continue;
         }
         charstrings.push({
@@ -22013,16 +23677,43 @@ var CFFCompiler = (function CFFCompilerC
 
       // Compile the five entries that must be in order.
       var header = this.compileHeader(cff.header);
       output.add(header);
 
       var nameIndex = this.compileNameIndex(cff.names);
       output.add(nameIndex);
 
+      if (cff.isCIDFont) {
+        // The spec is unclear on how font matrices should relate to each other
+        // when there is one in the main top dict and the sub top dicts.
+        // Windows handles this differently than linux and osx so we have to
+        // normalize to work on all.
+        // Rules based off of some mailing list discussions:
+        // - If main font has a matrix and subfont doesn't, use the main matrix.
+        // - If no main font matrix and there is a subfont matrix, use the
+        //   subfont matrix.
+        // - If both have matrices, concat together.
+        // - If neither have matrices, use default.
+        // To make this work on all platforms we move the top matrix into each
+        // sub top dict and concat if necessary.
+        if (cff.topDict.hasName('FontMatrix')) {
+          var base = cff.topDict.getByName('FontMatrix');
+          cff.topDict.removeByName('FontMatrix');
+          for (var i = 0, ii = cff.fdArray.length; i < ii; i++) {
+            var subDict = cff.fdArray[i];
+            var matrix = base.slice(0);
+            if (subDict.hasName('FontMatrix')) {
+              matrix = Util.transform(matrix, subDict.getByName('FontMatrix'));
+            }
+            subDict.setByName('FontMatrix', matrix);
+          }
+        }
+      }
+
       var compiled = this.compileTopDicts([cff.topDict],
                                           output.length,
                                           cff.isCIDFont);
       output.add(compiled.output);
       var topDictTracker = compiled.trackers[0];
 
       var stringIndex = this.compileStringIndex(cff.strings.strings);
       output.add(stringIndex);
@@ -22084,16 +23775,24 @@ var CFFCompiler = (function CFFCompilerC
     encodeNumber: function CFFCompiler_encodeNumber(value) {
       if (parseFloat(value) == parseInt(value, 10) && !isNaN(value)) // isInt
         return this.encodeInteger(value);
       else
         return this.encodeFloat(value);
     },
     encodeFloat: function CFFCompiler_encodeFloat(num) {
       var value = num.toString();
+
+      // rounding inaccurate doubles
+      var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value);
+      if (m) {
+        var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length));
+        value = (Math.round(num * epsilon) / epsilon).toString();
+      }
+
       var nibbles = '';
       for (var i = 0, ii = value.length; i < ii; ++i) {
         var a = value[i];
         if (a === 'e') {
           nibbles += value[++i] === '-' ? 'c' : 'b';
         } else if (a === '.') {
           nibbles += 'a';
         } else if (a === '-') {
@@ -29986,16 +31685,31 @@ var Parser = (function ParserClosure() {
   function Parser(lexer, allowStreams, xref) {
     this.lexer = lexer;
     this.allowStreams = allowStreams;
     this.xref = xref;
     this.refill();
   }
 
   Parser.prototype = {
+    saveState: function Parser_saveState() {
+      this.state = {
+        buf1: this.buf1,
+        buf2: this.buf2,
+        streamPos: this.lexer.stream.pos
+      };
+    },
+
+    restoreState: function Parser_restoreState() {
+      var state = this.state;
+      this.buf1 = state.buf1;
+      this.buf2 = state.buf2;
+      this.lexer.stream.pos = state.streamPos;
+    },
+
     refill: function Parser_refill() {
       this.buf1 = this.lexer.getObj();
       this.buf2 = this.lexer.getObj();
     },
     shift: function Parser_shift() {
       if (isCmd(this.buf2, 'ID')) {
         this.buf1 = this.buf2;
         this.buf2 = null;
@@ -30020,18 +31734,21 @@ var Parser = (function ParserClosure() {
           error('End of file inside array');
         this.shift();
         return array;
       }
       if (isCmd(this.buf1, '<<')) { // dictionary or stream
         this.shift();
         var dict = new Dict(this.xref);
         while (!isCmd(this.buf1, '>>') && !isEOF(this.buf1)) {
-          if (!isName(this.buf1))
-            error('Dictionary key must be a name object');
+          if (!isName(this.buf1)) {
+            info('Malformed dictionary, key must be a name object');
+            this.shift();
+            continue;
+          }
 
           var key = this.buf1.name;
           this.shift();
           if (isEOF(this.buf1))
             break;
           dict.set(key, this.getObj(cipherTransform));
         }
         if (isEOF(this.buf1))
@@ -30112,17 +31829,17 @@ var Parser = (function ParserClosure() {
         }
       }
 
       var length = (stream.pos - 4) - startPos;
       var imageStream = stream.makeSubStream(startPos, length, dict);
       if (cipherTransform)
         imageStream = cipherTransform.createStream(imageStream);
       imageStream = this.filter(imageStream, dict, length);
-      imageStream.parameters = dict;
+      imageStream.dict = dict;
 
       this.buf2 = Cmd.get('EI');
       this.shift();
 
       return imageStream;
     },
     fetchIfRef: function Parser_fetchIfRef(obj) {
       // not relying on the xref.fetchIfRef -- xref might not be set
@@ -30148,17 +31865,17 @@ var Parser = (function ParserClosure() {
       if (!isCmd(this.buf1, 'endstream'))
         error('Missing endstream');
       this.shift();
 
       stream = stream.makeSubStream(pos, length, dict);
       if (cipherTransform)
         stream = cipherTransform.createStream(stream);
       stream = this.filter(stream, dict, length);
-      stream.parameters = dict;
+      stream.dict = dict;
       return stream;
     },
     filter: function Parser_filter(stream, dict, length) {
       var filter = this.fetchIfRef(dict.get('Filter', 'F'));
       var params = this.fetchIfRef(dict.get('DecodeParms', 'DP'));
       if (isName(filter))
         return this.makeFilter(stream, filter.name, length, params);
       if (isArray(filter)) {
@@ -30629,16 +32346,20 @@ var Linearization = (function Linearizat
     }
   };
 
   return Linearization;
 })();
 
 
 
+// This global variable is used to minimize the memory usage when patterns are
+// used.
+var temporaryPatternCanvas = null;
+
 var PatternType = {
   AXIAL: 2,
   RADIAL: 3
 };
 
 var Pattern = (function PatternClosure() {
   // Constructor should define this.getPattern
   function Pattern() {
@@ -30877,78 +32598,34 @@ Shadings.Dummy = (function DummyClosure(
   return Dummy;
 })();
 
 var TilingPattern = (function TilingPatternClosure() {
   var PaintType = {
     COLORED: 1,
     UNCOLORED: 2
   };
-  var MAX_PATTERN_SIZE = 4096;
+
+  var MAX_PATTERN_SIZE = 8192;
 
   function TilingPattern(IR, color, ctx, objs, commonObjs) {
-    var operatorList = IR[2];
+    this.name = IR[1][0].name;
+    this.operatorList = IR[2];
     this.matrix = IR[3] || [1, 0, 0, 1, 0, 0];
-    var bbox = IR[4];
-    var xstep = IR[5];
-    var ystep = IR[6];
-    var paintType = IR[7];
-    var tilingType = IR[8];
-
-    TODO('TilingType: ' + tilingType);
-
+    this.bbox = IR[4];
+    this.xstep = IR[5];
+    this.ystep = IR[6];
+    this.paintType = IR[7];
+    this.tilingType = IR[8];
+    this.color = color;
+    this.objs = objs;
+    this.commonObjs = commonObjs;
     this.curMatrix = ctx.mozCurrentTransform;
-    this.ctx = ctx;
     this.type = 'Pattern';
-
-    var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
-
-    var topLeft = [x0, y0];
-    // we want the canvas to be as large as the step size
-    var botRight = [x0 + xstep, y0 + ystep];
-
-    var width = botRight[0] - topLeft[0];
-    var height = botRight[1] - topLeft[1];
-
-    // Obtain scale from matrix and current transformation matrix.
-    var matrixScale = Util.singularValueDecompose2dScale(this.matrix);
-    var curMatrixScale = Util.singularValueDecompose2dScale(this.curMatrix);
-    var combinedScale = [matrixScale[0] * curMatrixScale[0],
-                         matrixScale[1] * curMatrixScale[1]];
-
-    // MAX_PATTERN_SIZE is used to avoid OOM situation.
-    // Use width and height values that are as close as possible to the end
-    // result when the pattern is used. Too low value makes the pattern look
-    // blurry. Too large value makes it look too crispy.
-    width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])),
-                     MAX_PATTERN_SIZE);
-
-    height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])),
-                      MAX_PATTERN_SIZE);
-
-    var tmpCanvas = createScratchCanvas(width, height);
-
-    // set the new canvas element context as the graphics context
-    var tmpCtx = tmpCanvas.getContext('2d');
-    var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs);
-
-    this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
-
-    this.setScale(width, height, xstep, ystep);
-    this.transformToScale(graphics);
-
-    // transform coordinates to pattern space
-    var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
-    graphics.transform.apply(graphics, tmpTranslate);
-
-    this.clipBbox(graphics, bbox, x0, y0, x1, y1);
-
-    graphics.executeOperatorList(operatorList);
-
-    this.canvas = tmpCanvas;
+    this.ctx = ctx;
   }
 
   TilingPattern.getIR = function TilingPattern_getIR(operatorList, dict, args) {
     var matrix = dict.get('Matrix');
     var bbox = dict.get('BBox');
     var xstep = dict.get('XStep');
     var ystep = dict.get('YStep');
     var paintType = dict.get('PaintType');
@@ -30956,16 +32633,79 @@ var TilingPattern = (function TilingPatt
 
     return [
       'TilingPattern', args, operatorList, matrix, bbox, xstep, ystep,
       paintType, tilingType
     ];
   };
 
   TilingPattern.prototype = {
+    createPatternCanvas: function TilinPattern_createPatternCanvas(tmpCanvas) {
+      var operatorList = this.operatorList;
+      var bbox = this.bbox;
+      var xstep = this.xstep;
+      var ystep = this.ystep;
+      var paintType = this.paintType;
+      var tilingType = this.tilingType;
+      var color = this.color;
+      var objs = this.objs;
+      var commonObjs = this.commonObjs;
+      var ctx = this.ctx;
+
+      TODO('TilingType: ' + tilingType);
+
+      var x0 = bbox[0], y0 = bbox[1], x1 = bbox[2], y1 = bbox[3];
+
+      var topLeft = [x0, y0];
+      // we want the canvas to be as large as the step size
+      var botRight = [x0 + xstep, y0 + ystep];
+
+      var width = botRight[0] - topLeft[0];
+      var height = botRight[1] - topLeft[1];
+
+      // Obtain scale from matrix and current transformation matrix.
+      var matrixScale = Util.singularValueDecompose2dScale(this.matrix);
+      var curMatrixScale = Util.singularValueDecompose2dScale(this.curMatrix);
+      var combinedScale = [matrixScale[0] * curMatrixScale[0],
+                           matrixScale[1] * curMatrixScale[1]];
+
+      // MAX_PATTERN_SIZE is used to avoid OOM situation.
+      // Use width and height values that are as close as possible to the end
+      // result when the pattern is used. Too low value makes the pattern look
+      // blurry. Too large value makes it look too crispy.
+      width = Math.min(Math.ceil(Math.abs(width * combinedScale[0])),
+                       MAX_PATTERN_SIZE);
+
+      height = Math.min(Math.ceil(Math.abs(height * combinedScale[1])),
+                        MAX_PATTERN_SIZE);
+
+      tmpCanvas.width = width;
+      tmpCanvas.height = height;
+
+      // set the new canvas element context as the graphics context
+      var tmpCtx = tmpCanvas.getContext('2d');
+      // for simulated mozCurrentTransform canvas (normaly setting width/height
+      // will reset the matrix)
+      tmpCtx.setTransform(1, 0, 0, 1, 0, 0);
+      var graphics = new CanvasGraphics(tmpCtx, commonObjs, objs);
+
+      this.setFillAndStrokeStyleToContext(tmpCtx, paintType, color);
+
+      this.setScale(width, height, xstep, ystep);
+      this.transformToScale(graphics);
+
+      // transform coordinates to pattern space
+      var tmpTranslate = [1, 0, 0, 1, -topLeft[0], -topLeft[1]];
+      graphics.transform.apply(graphics, tmpTranslate);
+
+      this.clipBbox(graphics, bbox, x0, y0, x1, y1);
+
+      graphics.executeOperatorList(operatorList);
+    },
+
     setScale: function TilingPattern_setScale(width, height, xstep, ystep) {
       this.scale = [width / xstep, height / ystep];
     },
 
     transformToScale: function TilingPattern_transformToScale(graphics) {
       var scale = this.scale;
       var tmpScale = [scale[0], 0, 0, scale[1], 0, 0];
       graphics.transform.apply(graphics, tmpScale);
@@ -31001,25 +32741,29 @@ var TilingPattern = (function TilingPatt
           context.strokeStyle = cssColor;
           break;
         default:
           error('Unsupported paint type: ' + paintType);
       }
     },
 
     getPattern: function TilingPattern_getPattern() {
-      var matrix = this.matrix;
-      var curMatrix = this.curMatrix;
+      // The temporary canvas is created only because the memory is released
+      // more quickly than creating multiple temporary canvases.
+      if (temporaryPatternCanvas === null) {
+        temporaryPatternCanvas = createScratchCanvas(1, 1);
+      }
+      this.createPatternCanvas(temporaryPatternCanvas);
+
       var ctx = this.ctx;
-
-      ctx.setTransform.apply(ctx, curMatrix);
-      ctx.transform.apply(ctx, matrix);
+      ctx.setTransform.apply(ctx, this.curMatrix);
+      ctx.transform.apply(ctx, this.matrix);
       this.scaleToContext();
 
-      return ctx.createPattern(this.canvas, 'repeat');
+      return ctx.createPattern(temporaryPatternCanvas, 'repeat');
     }
   };
 
   return TilingPattern;
 })();
 
 
 
@@ -31644,16 +33388,20 @@ var PredictorStream = (function Predicto
 
     var bufferLength = this.bufferLength;
     var buffer = this.ensureBuffer(bufferLength + rowBytes);
 
     var bits = this.bits;
     var colors = this.colors;
 
     var rawBytes = this.stream.getBytes(rowBytes);
+    this.eof = !rawBytes.length;
+    if (this.eof) {
+      return;
+    }
 
     var inbuf = 0, outbuf = 0;
     var inbits = 0, outbits = 0;
     var pos = bufferLength;
 
     if (bits === 1) {
       for (var i = 0; i < rowBytes; ++i) {
         var c = rawBytes[i];
@@ -31704,16 +33452,20 @@ var PredictorStream = (function Predicto
   PredictorStream.prototype.readBlockPng =
     function predictorStreamReadBlockPng() {
 
     var rowBytes = this.rowBytes;
     var pixBytes = this.pixBytes;
 
     var predictor = this.stream.getByte();
     var rawBytes = this.stream.getBytes(rowBytes);
+    this.eof = !rawBytes.length;
+    if (this.eof) {
+      return;
+    }
 
     var bufferLength = this.bufferLength;
     var buffer = this.ensureBuffer(bufferLength + rowBytes);
 
     var prevRow = buffer.subarray(bufferLength - rowBytes, bufferLength);
     if (prevRow.length === 0)
       prevRow = new Uint8Array(rowBytes);
 
@@ -31852,16 +33604,17 @@ var JpegStream = (function JpegStreamClo
       if (this.colorTransform != -1)
         jpegImage.colorTransform = this.colorTransform;
       jpegImage.parse(this.bytes);
       var width = jpegImage.width;
       var height = jpegImage.height;
       var data = jpegImage.getData(width, height);
       this.buffer = data;
       this.bufferLength = data.length;
+      this.eof = true;
     } catch (e) {
       error('JPEG error: ' + e);
     }
   };
   JpegStream.prototype.getIR = function JpegStream_getIR() {
     return bytesToString(this.bytes);
   };
   JpegStream.prototype.getChar = function JpegStream_getChar() {
@@ -31987,16 +33740,17 @@ var JpxStream = (function JpxStreamClosu
             dataPosition += rowFeed;
           }
           break;
       }
     }
 
     this.buffer = data;
     this.bufferLength = data.length;
+    this.eof = true;
   };
   JpxStream.prototype.getChar = function JpxStream_getChar() {
     error('internal error: getChar is not valid on JpxStream');
   };
 
   return JpxStream;
 })();
 
@@ -32031,16 +33785,17 @@ var Jbig2Stream = (function Jbig2StreamC
     var dataLength = data.length;
 
     // JBIG2 had black as 1 and white as 0, inverting the colors
     for (var i = 0; i < dataLength; i++)
       data[i] ^= 0xFF;
 
     this.buffer = data;
     this.bufferLength = dataLength;
+    this.eof = true;
   };
   Jbig2Stream.prototype.getChar = function Jbig2Stream_getChar() {
     error('internal error: getChar is not valid on Jbig2Stream');
   };
 
   return Jbig2Stream;
 })();
 
@@ -33450,68 +35205,141 @@ MessageHandler.prototype = {
       message.callbackId = callbackId;
     }
     this.comObj.postMessage(message);
   }
 };
 
 var WorkerMessageHandler = {
   setup: function wphSetup(handler) {
-    var pdfModel = null;
-
-    function loadDocument(pdfData, pdfModelSource) {
-      // Create only the model of the PDFDoc, which is enough for
-      // processing the content of the pdf.
-      var pdfPassword = pdfModelSource.password;
-      try {
-        pdfModel = new PDFDocument(new Stream(pdfData), pdfPassword);
-      } catch (e) {
-        if (e instanceof PasswordException) {
-          if (e.code === 'needpassword') {
-            handler.send('NeedPassword', {
-              exception: e
-            });
-          } else if (e.code === 'incorrectpassword') {
-            handler.send('IncorrectPassword', {
-              exception: e
-            });
-          }
-
-          return;
-        } else if (e instanceof InvalidPDFException) {
-          handler.send('InvalidPDF', {
-            exception: e
+    var pdfManager;
+
+    function loadDocument(recoveryMode) {
+      var loadDocumentPromise = new Promise();
+
+      var parseSuccess = function parseSuccess() {
+        var numPagesPromise = pdfManager.ensureModel('numPages');
+        var fingerprintPromise = pdfManager.ensureModel('fingerprint');
+        var outlinePromise = pdfManager.ensureCatalog('documentOutline');
+        var infoPromise = pdfManager.ensureModel('documentInfo');
+        var metadataPromise = pdfManager.ensureCatalog('metadata');
+        var encryptedPromise = pdfManager.ensureXRef('encrypt');
+        var javaScriptPromise = pdfManager.ensureCatalog('javaScript');
+        Promise.all([numPagesPromise, fingerprintPromise, outlinePromise,
+          infoPromise, metadataPromise, encryptedPromise,
+          javaScriptPromise]).then(
+            function onDocReady(results) {
+
+          var doc = {
+            numPages: results[0],
+            fingerprint: results[1],
+            outline: results[2],
+            info: results[3],
+            metadata: results[4],
+            encrypted: !!results[5],
+            javaScript: results[6]
+          };
+          loadDocumentPromise.resolve(doc);
+        },
+        parseFailure);
+      };
+
+      var parseFailure = function parseFailure(e) {
+        loadDocumentPromise.reject(e);
+      };
+
+      pdfManager.ensureModel('checkHeader', []).then(function() {
+        pdfManager.ensureModel('parseStartXRef', []).then(function() {
+          pdfManager.ensureModel('parse', [recoveryMode]).then(
+              parseSuccess, parseFailure);
+        }, parseFailure);
+      }, parseFailure);
+
+      return loadDocumentPromise;
+    }
+
+    function getPdfManager(data) {
+      var pdfManagerPromise = new Promise();
+
+      var source = data.source;
+      var disableRange = data.disableRange;
+      if (source.data) {
+        pdfManager = new LocalPdfManager(source.data, source.password);
+        pdfManagerPromise.resolve();
+        return pdfManagerPromise;
+      } else if (source.chunkedViewerLoading) {
+        pdfManager = new NetworkPdfManager(source, handler);
+        pdfManagerPromise.resolve();
+        return pdfManagerPromise;
+      }
+
+      var networkManager = new NetworkManager(source.url, {
+        httpHeaders: source.httpHeaders
+      });
+      var fullRequestXhrId = networkManager.requestFull({
+        onHeadersReceived: function onHeadersReceived() {
+          if (disableRange) {
+            return;
+          }
+
+          var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
+          if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
+            return;
+          }
+
+          var contentEncoding =
+            fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
+          if (contentEncoding !== 'identity') {
+            return;
+          }
+
+          var length = fullRequestXhr.getResponseHeader('Content-Length');
+          length = parseInt(length, 10);
+          if (!isInt(length)) {
+            return;
+          }
+
+          // NOTE: by cancelling the full request, and then issuing range
+          // requests, there will be an issue for sites where you can only
+          // request the pdf once. However, if this is the case, then the
+          // server should not be returning that it can support range requests.
+          networkManager.abortRequest(fullRequestXhrId);
+
+          source.length = length;
+          pdfManager = new NetworkPdfManager(source, handler);
+          pdfManagerPromise.resolve(pdfManager);
+        },
+
+        onDone: function onDone(args) {
+          // the data is array, instantiating directly from it
+          pdfManager = new LocalPdfManager(args.chunk, source.password);
+          pdfManagerPromise.resolve();
+        },
+
+        onError: function onError(status) {
+          if (status == 404) {
+            var exception = new MissingPDFException( 'Missing PDF "' +
+                source.url + '".');
+            handler.send('MissingPDF', { exception: exception });
+          } else {
+            handler.send('DocError', 'Unexpected server response (' +
+                status + ') while retrieving PDF "' +
+                source.url + '".');
+          }
+        },
+
+        onProgress: function onProgress(evt) {
+          handler.send('DocProgress', {
+            loaded: evt.loaded,
+            total: evt.lengthComputable ? evt.total : void(0)
           });
-
-          return;
-        } else if (e instanceof MissingPDFException) {
-          handler.send('MissingPDF', {
-            exception: e
-          });
-
-          return;
-        } else {
-          handler.send('UnknownError', {
-            exception: new UnknownErrorException(e.message, e.toString())
-          });
-
-          return;
-        }
-      }
-      var doc = {
-        numPages: pdfModel.numPages,
-        fingerprint: pdfModel.getFingerprint(),
-        destinations: pdfModel.catalog.destinations,
-        javaScript: pdfModel.catalog.javaScript,
-        outline: pdfModel.catalog.documentOutline,
-        info: pdfModel.getDocumentInfo(),
-        metadata: pdfModel.catalog.metadata,
-        encrypted: !!pdfModel.xref.encrypt
-      };
-      handler.send('GetDoc', {pdfInfo: doc});
+        }
+      });
+
+      return pdfManagerPromise;
     }
 
     handler.on('test', function wphSetupTest(data) {
       // check if Uint8Array can be sent to worker
       if (!(data instanceof Uint8Array)) {
         handler.send('test', false);
         return;
       }
@@ -33527,150 +35355,212 @@ var WorkerMessageHandler = {
       if (!responseExists) {
         handler.send('test', false);
         return;
       }
       handler.send('test', true);
     });
 
     handler.on('GetDocRequest', function wphSetupDoc(data) {
-      var source = data.source;
-      if (source.data) {
-        // the data is array, instantiating directly from it
-        loadDocument(source.data, source);
-        return;
-      }
-
-      PDFJS.getPdf(
-        {
-          url: source.url,
-          progress: function getPDFProgress(evt) {
-            handler.send('DocProgress', {
-              loaded: evt.loaded,
-              total: evt.lengthComputable ? evt.total : void(0)
+
+      var onSuccess = function(doc) {
+        handler.send('GetDoc', { pdfInfo: doc });
+        pdfManager.ensureModel('traversePages', []).then(null, onFailure);
+      };
+
+      var onFailure = function(e) {
+        if (e instanceof PasswordException) {
+          if (e.code === PasswordResponses.NEED_PASSWORD) {
+            handler.send('NeedPassword', {
+              exception: e
+            });
+          } else if (e.code === PasswordResponses.INCORRECT_PASSWORD) {
+            handler.send('IncorrectPassword', {
+              exception: e
             });
-          },
-          error: function getPDFError(e) {
-            if (e.target.status == 404) {
-              handler.send('MissingPDF', {
-                exception: new MissingPDFException(
-                  'Missing PDF \"' + source.url + '\".')});
-            } else {
-              handler.send('DocError', 'Unexpected server response (' +
-                            e.target.status + ') while retrieving PDF \"' +
-                            source.url + '\".');
-            }
-          },
-          headers: source.httpHeaders
-        },
-        function getPDFLoad(data) {
-          loadDocument(data, source);
+          }
+        } else if (e instanceof InvalidPDFException) {
+          handler.send('InvalidPDF', {
+            exception: e
+          });
+        } else if (e instanceof MissingPDFException) {
+          handler.send('MissingPDF', {
+            exception: e
+          });
+        } else {
+          handler.send('UnknownError', {
+            exception: new UnknownErrorException(e.message, e.toString())
+          });
+        }
+      };
+
+      getPdfManager(data).then(function pdfManagerReady() {
+        loadDocument(false).then(onSuccess, function loadFailure(ex) {
+          // Try again with recoveryMode == true
+          if (!(ex instanceof XRefParseException)) {
+            if (ex instanceof PasswordException) {
+              // after password exception prepare to receive a new password
+              // to repeat loading
+              pdfManager.passwordChangedPromise = new Promise();
+              pdfManager.passwordChangedPromise.then(pdfManagerReady);
+            }
+
+            onFailure(ex);
+            return;
+          }
+
+          pdfManager.requestLoadedStream();
+          pdfManager.onLoadedStream().then(function() {
+            loadDocument(true).then(onSuccess, onFailure);
+          });
         });
+      });
     });
 
     handler.on('GetPageRequest', function wphSetupGetPage(data) {
-      var pageNumber = data.pageIndex + 1;
-      var pdfPage = pdfModel.getPage(pageNumber);
-      var page = {
-        pageIndex: data.pageIndex,
-        rotate: pdfPage.rotate,
-        ref: pdfPage.ref,
-        view: pdfPage.view
-      };
-      handler.send('GetPage', {pageInfo: page});
+      var pageIndex = data.pageIndex;
+      pdfManager.getPage(pageIndex).then(function(page) {
+        var rotatePromise = pdfManager.ensure(page, 'rotate');
+        var refPromise = pdfManager.ensure(page, 'ref');
+        var viewPromise = pdfManager.ensure(page, 'view');
+
+        Promise.all([rotatePromise, refPromise, viewPromise]).then(
+            function(results) {
+          var page = {
+            pageIndex: data.pageIndex,
+            rotate: results[0],
+            ref: results[1],
+            view: results[2]
+          };
+
+          handler.send('GetPage', { pageInfo: page });
+        });
+      });
     });
 
+    handler.on('GetDestinations',
+      function wphSetupGetDestinations(data, promise) {
+        pdfManager.ensureCatalog('destinations').then(function(destinations) {
+          promise.resolve(destinations);
+        });
+      }
+    );
+
     handler.on('GetData', function wphSetupGetData(data, promise) {
-      promise.resolve(pdfModel.stream.bytes);
+      pdfManager.requestLoadedStream();
+      pdfManager.onLoadedStream().then(function(stream) {
+        promise.resolve(stream.bytes);
+      });
+    });
+
+    handler.on('DataLoaded', function wphSetupDataLoaded(data, promise) {
+      pdfManager.onLoadedStream().then(function(stream) {
+        promise.resolve({ length: stream.bytes.byteLength });
+      });
+    });
+
+    handler.on('UpdatePassword', function wphSetupUpdatePassword(data) {
+      pdfManager.updatePassword(data);
     });
 
     handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
-      var pdfPage = pdfModel.getPage(data.pageIndex + 1);
-      handler.send('GetAnnotations', {
-        pageIndex: data.pageIndex,
-        annotations: pdfPage.getAnnotations()
+      pdfManager.getPage(data.pageIndex).then(function(page) {
+        pdfManager.ensure(page, 'getAnnotationsData', []).then(
+          function(annotationsData) {
+            handler.send('GetAnnotations', {
+              pageIndex: data.pageIndex,
+              annotations: annotationsData
+            });
+          }
+        );
       });
     });
 
     handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
-      var pageNum = data.pageIndex + 1;
-
-      var start = Date.now();
-
-      var dependency = [];
-      var operatorList = null;
-      try {
-        var page = pdfModel.getPage(pageNum);
+      pdfManager.getPage(data.pageIndex).then(function(page) {
+
+        var pageNum = data.pageIndex + 1;
+        var start = Date.now();
         // Pre compile the pdf page and fetch the fonts/images.
-        operatorList = page.getOperatorList(handler, dependency);
-      } catch (e) {
-        var minimumStackMessage =
-            'worker.js: while trying to getPage() and getOperatorList()';
-
-        var wrappedException;
-
-        // Turn the error into an obj that can be serialized
-        if (typeof e === 'string') {
-          wrappedException = {
-            message: e,
-            stack: minimumStackMessage
-          };
-        } else if (typeof e === 'object') {
-          wrappedException = {
-            message: e.message || e.toString(),
-            stack: e.stack || minimumStackMessage
-          };
-        } else {
-          wrappedException = {
-            message: 'Unknown exception type: ' + (typeof e),
-            stack: minimumStackMessage
-          };
-        }
-
-        handler.send('PageError', {
-          pageNum: pageNum,
-          error: wrappedException
+        page.getOperatorList(handler).then(function(opListData) {
+
+          var operatorList = opListData.queue;
+          var dependency = Object.keys(opListData.dependencies);
+
+          // The following code does quite the same as
+          // Page.prototype.startRendering, but stops at one point and sends the
+          // result back to the main thread.
+
+          log('page=%d - getOperatorList: time=%dms, len=%d', pageNum,
+              Date.now() - start, operatorList.fnArray.length);
+
+          // Filter the dependecies for fonts.
+          var fonts = {};
+          for (var i = 0, ii = dependency.length; i < ii; i++) {
+            var dep = dependency[i];
+            if (dep.indexOf('g_font_') === 0) {
+              fonts[dep] = true;
+            }
+          }
+          handler.send('RenderPage', {
+            pageIndex: data.pageIndex,
+            operatorList: operatorList,
+            depFonts: Object.keys(fonts)
+          });
+        }, function(e) {
+
+          var minimumStackMessage =
+              'worker.js: while trying to getPage() and getOperatorList()';
+
+          var wrappedException;
+
+          // Turn the error into an obj that can be serialized
+          if (typeof e === 'string') {
+            wrappedException = {
+              message: e,
+              stack: minimumStackMessage
+            };
+          } else if (typeof e === 'object') {
+            wrappedException = {
+              message: e.message || e.toString(),
+              stack: e.stack || minimumStackMessage
+            };
+          } else {
+            wrappedException = {
+              message: 'Unknown exception type: ' + (typeof e),
+              stack: minimumStackMessage
+            };
+          }
+
+          handler.send('PageError', {
+            pageNum: pageNum,
+            error: wrappedException
+          });
         });
-        return;
-      }
-
-      log('page=%d - getOperatorList: time=%dms, len=%d', pageNum,
-                              Date.now() - start, operatorList.fnArray.length);
-
-      // Filter the dependecies for fonts.
-      var fonts = {};
-      for (var i = 0, ii = dependency.length; i < ii; i++) {
-        var dep = dependency[i];
-        if (dep.indexOf('g_font_') === 0) {
-          fonts[dep] = true;
-        }
-      }
-      handler.send('RenderPage', {
-        pageIndex: data.pageIndex,
-        operatorList: operatorList,
-        depFonts: Object.keys(fonts)
       });
     }, this);
 
     handler.on('GetTextContent', function wphExtractText(data, promise) {
-      var pageNum = data.pageIndex + 1;
-      var start = Date.now();
-
-      var textContent = '';
-      try {
-        var page = pdfModel.getPage(pageNum);
-        textContent = page.extractTextContent();
-        promise.resolve(textContent);
-      } catch (e) {
-        // Skip errored pages
-        promise.reject(e);
-      }
-
-      log('text indexing: page=%d - time=%dms',
-                      pageNum, Date.now() - start);
+      pdfManager.getPage(data.pageIndex).then(function(page) {
+        var pageNum = data.pageIndex + 1;
+        var start = Date.now();
+        page.extractTextContent().then(function(textContent) {
+          promise.resolve(textContent);
+          log('text indexing: page=%d - time=%dms', pageNum,
+              Date.now() - start);
+        }, function (e) {
+          // Skip errored pages
+          promise.reject(e);
+        });
+      });
+    });
+
+    handler.on('Terminate', function wphTerminate(data, promise) {
+      pdfManager.streamManager.networkManager.abortAllRequests();
+      promise.resolve();
     });
   }
 };
 
 var consoleTimer = {};
 
 var workerConsole = {
   log: function log() {
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/content/network.js
@@ -0,0 +1,230 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2012 Mozilla Foundation
+ *
+ * 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.
+ */
+
+// NOTE: Be careful what goes in this file, as it is also used from the context
+// of the addon. So using warn/error in here will break the addon.
+
+'use strict';
+
+
+  
+  Components.utils.import('resource://gre/modules/Services.jsm');
+  
+  var EXPORTED_SYMBOLS = ['NetworkManager'];
+  
+  function log(aMsg) {
+    var msg = 'network.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
+    Services.console.logStringMessage(msg);
+    // TODO(mack): dump() doesn't seem to work here...
+    dump(msg + '\n');
+  }
+
+var NetworkManager = (function NetworkManagerClosure() {
+
+  var OK_RESPONSE = 200;
+  var PARTIAL_CONTENT_RESPONSE = 206;
+
+  function NetworkManager(url, args) {
+    this.url = url;
+    args = args || {};
+    this.httpHeaders = args.httpHeaders || {};
+    this.getXhr = args.getXhr ||
+      function NetworkManager_getXhr() {
+        return new XMLHttpRequest();
+      };
+
+    this.currXhrId = 0;
+    this.pendingRequests = {};
+    this.loadedRequests = {};
+  }
+
+  function getArrayBuffer(xhr) {
+    var data = (xhr.mozResponseArrayBuffer || xhr.mozResponse ||
+                xhr.responseArrayBuffer || xhr.response);
+    if (typeof data !== 'string') {
+      return data;
+    }
+    var length = data.length;
+    var buffer = new Uint8Array(length);
+    for (var i = 0; i < length; i++) {
+      buffer[i] = data.charCodeAt(i) & 0xFF;
+    }
+    return buffer;
+  }
+
+  NetworkManager.prototype = {
+    requestRange: function NetworkManager_requestRange(begin, end, listeners) {
+      var args = {
+        begin: begin,
+        end: end
+      };
+      for (var prop in listeners) {
+        args[prop] = listeners[prop];
+      }
+      return this.request(args);
+    },
+
+    requestFull: function NetworkManager_requestRange(listeners) {
+      return this.request(listeners);
+    },
+
+    request: function NetworkManager_requestRange(args) {
+      var xhr = this.getXhr();
+      var xhrId = this.currXhrId++;
+      var pendingRequest = this.pendingRequests[xhrId] = {
+        xhr: xhr
+      };
+
+      xhr.open('GET', this.url);
+      for (var property in this.httpHeaders) {
+        var value = this.httpHeaders[property];
+        if (typeof value === 'undefined') {
+          continue;
+        }
+        xhr.setRequestHeader(property, value);
+      }
+      if ('begin' in args && 'end' in args) {
+        var rangeStr = args.begin + '-' + (args.end - 1);
+        xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
+        pendingRequest.expectedStatus = 206;
+      } else {
+        pendingRequest.expectedStatus = 200;
+      }
+
+      xhr.mozResponseType = xhr.responseType = 'arraybuffer';
+
+      if (args.onProgress) {
+        xhr.onprogress = args.onProgress;
+      }
+      if (args.onError) {
+        xhr.onerror = function(evt) {
+          args.onError(xhr.status);
+        };
+      }
+      xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
+
+      pendingRequest.onHeadersReceived = args.onHeadersReceived;
+      pendingRequest.onDone = args.onDone;
+      pendingRequest.onError = args.onError;
+
+      xhr.send(null);
+
+      return xhrId;
+    },
+
+    onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
+      var pendingRequest = this.pendingRequests[xhrId];
+      if (!pendingRequest) {
+        // Maybe abortRequest was called...
+        return;
+      }
+
+      var xhr = pendingRequest.xhr;
+      if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
+        pendingRequest.onHeadersReceived();
+        delete pendingRequest.onHeadersReceived;
+      }
+
+      if (xhr.readyState !== 4) {
+        return;
+      }
+
+      if (!(xhrId in this.pendingRequests)) {
+        // The XHR request might have been aborted in onHeadersReceived()
+        // callback, in which case we should abort request
+        return;
+      }
+
+      delete this.pendingRequests[xhrId];
+
+      if (xhr.status === 0) {
+        if (pendingRequest.onError) {
+          pendingRequest.onError(xhr.status);
+        }
+        return;
+      }
+
+      // From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
+      // "A server MAY ignore the Range header". This means it's possible to
+      // get a 200 rather than a 206 response from a range request.
+      var ok_response_on_range_request =
+          xhr.status === OK_RESPONSE &&
+          pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
+
+      if (!ok_response_on_range_request &&
+          xhr.status !== pendingRequest.expectedStatus) {
+        if (pendingRequest.onError) {
+          pendingRequest.onError(xhr.status);
+        }
+        return;
+      }
+
+      this.loadedRequests[xhrId] = true;
+
+      var chunk = getArrayBuffer(xhr);
+      if (xhr.status === PARTIAL_CONTENT_RESPONSE) {
+        var rangeHeader = xhr.getResponseHeader('Content-Range');
+        var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
+        var begin = parseInt(matches[1], 10);
+        pendingRequest.onDone({
+          begin: begin,
+          chunk: chunk
+        });
+      } else {
+        pendingRequest.onDone({
+          begin: 0,
+          chunk: chunk
+        });
+      }
+    },
+
+    hasPendingRequests: function NetworkManager_hasPendingRequests() {
+      for (var xhrId in this.pendingRequests) {
+        return true;
+      }
+      return false;
+    },
+
+    getRequestXhr: function NetworkManager_getXhr(xhrId) {
+      return this.pendingRequests[xhrId].xhr;
+    },
+
+    isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
+      return xhrId in this.pendingRequests;
+    },
+
+    isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
+      return xhrId in this.loadedRequests;
+    },
+
+    abortAllRequests: function NetworkManager_abortAllRequests() {
+      for (var xhrId in this.pendingRequests) {
+        this.abortRequest(xhrId | 0);
+      }
+    },
+
+    abortRequest: function NetworkManager_abortRequest(xhrId) {
+      var xhr = this.pendingRequests[xhrId].xhr;
+      delete this.pendingRequests[xhrId];
+      xhr.abort();
+    }
+  };
+
+  return NetworkManager;
+})();
+
+
deleted file mode 100644
index fa730955054b440a9e32ce282a3361cf13f198fa..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
index 0000000000000000000000000000000000000000..fa730955054b440a9e32ce282a3361cf13f198fa
GIT binary patch
literal 491
zc$@+20Tlj;P)<h;3K|Lk000e1NJLTq000mG000mO1ONa4wfZ;e0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBUzjY&j7R47wLQB5yHK@>gp@kF&{Rb5bN
zGzijG&{UKVshtpNClNu)%Ekhbw2`1J>Z6HtgGN0xKEhh;{0EDsrSU_qGw%`SWF~jc
zxpQak3{VFTy2*App+P}o)NW`Vy70=Y$@eYTjRgDq)}ID15R?~1J&mx+C#(qTX;VcI
z7?3B$uiL;R>R}-rT@%F{Q^f$#FZUOjQcfPeZ$<}E18p$$rORXa`Z6Q;=m%OKw=v~i
zDrMy^zYYg%xi!g`vmMKrqSFdWO=yItW9DoVJ*XuE>5afFEf*aJP=P8AjY%zNU{t}`
z&6S2q!}*1UI~}}%g2r}h|1FKpX|M(R^hMUF<}P<`VJOHX(}C-46~4}~;<03|R$zne
zd{Hvx6aP5;bX<T>PIK`>N>asx-f>p5`o;?TlKMQ-6LJb45R#;%<gl>UHjic=P>Ck@
z+{d0AQcWVHUUFA4oxlhWu<(*~7(R)cs(MMk>=;Zw$5hvZ{W->}h6+FUwIhgP8WFVd
hn-r{!B2=PuoIg4=n|)8MY<2(u002ovPDHLkV1hfJ)Vlxx
--- a/browser/extensions/pdfjs/content/web/l10n.js
+++ b/browser/extensions/pdfjs/content/web/l10n.js
@@ -100,12 +100,15 @@
     getLanguage: function() { return gLanguage; },
 
     // get the direction (ltr|rtl) of the current language
     getDirection: function() {
       // http://www.w3.org/International/questions/qa-scripts
       // Arabic, Hebrew, Farsi, Pashto, Urdu
       var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
       return (rtlList.indexOf(gLanguage) >= 0) ? 'rtl' : 'ltr';
-    }
+    },
+
+    // translate an element or document fragment
+    translate: translateFragment
   };
 })(this);
 
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -232,71 +232,132 @@ html[dir='rtl'] #sidebarContent {
   overflow: auto;
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.05);
   position: absolute;
   top: 32px;
   right: 0;
   bottom: 0;
   left: 0;
 }
+.loadingInProgress #viewerContainer {
+  top: 39px;
+}
 
 .toolbar {
-  position: absolute;
+  position: relative;
   left: 0;
   right: 0;
-  height: 32px;
   z-index: 9999;
   cursor: default;
 }
 
 #toolbarContainer {
   width: 100%;
 }
 
 #toolbarSidebar {
   width: 200px;
   height: 32px;
-  background-image: url(images/texture.png),
-                    -webkit-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
-  background-image: url(images/texture.png),
-                    -moz-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
-  background-image: url(images/texture.png),
-                    -ms-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
-  background-image: url(images/texture.png),
-                    -o-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
+  background-color: #424242; /* fallback */
   background-image: url(images/texture.png),
                     linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
   box-shadow: inset -1px 0 0 rgba(0, 0, 0, 0.25),
 
               inset 0 -1px 0 hsla(0,0%,100%,.05),
               0 1px 0 hsla(0,0%,0%,.15),
               0 0 1px hsla(0,0%,0%,.1);
 }
 
-#toolbarViewer, .findbar {
+#toolbarContainer, .findbar {
   position: relative;
   height: 32px;
-  background-color: #474747; /* IE9 */
-  background-image: url(images/texture.png),
-                    -webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
-  background-image: url(images/texture.png),
-                    -moz-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
-  background-image: url(images/texture.png),
-                    -ms-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
-  background-image: url(images/texture.png),
-                    -o-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
+  background-color: #474747; /* fallback */
   background-image: url(images/texture.png),
                     linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
   box-shadow: inset 1px 0 0 hsla(0,0%,100%,.08),
               inset 0 1px 1px hsla(0,0%,0%,.15),
               inset 0 -1px 0 hsla(0,0%,100%,.05),
               0 1px 0 hsla(0,0%,0%,.15),
               0 1px 1px hsla(0,0%,0%,.1);
 }
 
+#toolbarViewer {
+  height: 32px;
+}
+
+#loadingBar {
+  position: relative;
+  width: 100%;
+  height: 6px;
+  background-color: #333;
+  border-bottom: 1px solid #333;
+}
+
+#loadingBar .progress {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 0%;
+  height: 100%;
+  background-color: #ddd;
+  overflow: hidden;
+  -moz-transition: width 200ms;
+  -ms-transition: width 200ms;
+  -webkit-transition: width 200ms;
+  transition: width 200ms;
+}
+
+@-moz-keyframes progressIndeterminate {
+  0% { left: 0%; }
+  50% { left: 100%; }
+  100% { left: 100%; }
+}
+
+@-ms-keyframes progressIndeterminate {
+  0% { left: 0%; }
+  50% { left: 100%; }
+  100% { left: 100%; }
+}
+
+@-webkit-keyframes progressIndeterminate {
+  0% { left: 0%; }
+  50% { left: 100%; }
+  100% { left: 100%; }
+}
+
+@keyframes progressIndeterminate {
+  0% { left: 0%; }
+  50% { left: 100%; }
+  100% { left: 100%; }
+}
+
+#loadingBar .progress.indeterminate {
+  background-color: #999;
+  -moz-transition: none;
+  -ms-transition: none;
+  -webkit-transition: none;
+  transition: none;
+}
+
+#loadingBar .indeterminate .glimmer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 50px;
+
+  background-image: linear-gradient(to right, #999 0%, #fff 50%, #999 100%);
+  background-size: 100% 100% no-repeat;
+
+  -moz-animation: progressIndeterminate 2s linear infinite;
+  -ms-animation: progressIndeterminate 2s linear infinite;
+  -webkit-animation: progressIndeterminate 2s linear infinite;
+  animation: progressIndeterminate 2s linear infinite;
+}
+
 .findbar {
   top: 32px;
   position: absolute;
   z-index: 10000;
   height: 32px;
 
   min-width: 16px;
   padding: 0px 6px 0px 6px;
@@ -457,20 +518,16 @@ html[dir='rtl'] .splitToolbarButton > .t
   margin: 0;
 }
 
 .splitToolbarButton:hover > .toolbarButton,
 .splitToolbarButton:focus > .toolbarButton,
 .splitToolbarButton.toggled > .toolbarButton,
 .toolbarButton.textButton {
   background-color: hsla(0,0%,0%,.12);
-  background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   border: 1px solid hsla(0,0%,0%,.35);
   border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.15) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   -webkit-transition-property: background-color, border-color, box-shadow;
@@ -595,36 +652,28 @@ html[dir='rtl'] .toolbarButton,
 html[dir='rtl'] .dropdownToolbarButton {
   margin: 3px 0 4px 2px;
 }
 
 .toolbarButton:hover,
 .toolbarButton:focus,
 .dropdownToolbarButton {
   background-color: hsla(0,0%,0%,.12);
-  background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   border: 1px solid hsla(0,0%,0%,.35);
   border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.15) inset,
               0 1px 0 hsla(0,0%,100%,.05);
 }
 
 .toolbarButton:hover:active,
 .dropdownToolbarButton:hover:active {
   background-color: hsla(0,0%,0%,.2);
-  background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.4) hsla(0,0%,0%,.45);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   -webkit-transition-property: background-color, border-color, box-shadow;
   -webkit-transition-duration: 10ms;
   -webkit-transition-timing-function: linear;
@@ -640,20 +689,16 @@ html[dir='rtl'] .dropdownToolbarButton {
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 10ms;
   transition-timing-function: linear;
 }
 
 .toolbarButton.toggled,
 .splitToolbarButton.toggled > .toolbarButton.toggled {
   background-color: hsla(0,0%,0%,.3);
-  background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -ms-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -o-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.45) hsla(0,0%,0%,.5);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   -webkit-transition-property: background-color, border-color, box-shadow;
   -webkit-transition-duration: 10ms;
   -webkit-transition-timing-function: linear;
@@ -815,19 +860,19 @@ html[dir='rtl'] .toolbarButton.pageDown:
   content: url(images/toolbarButton-zoomOut.png);
 }
 
 .toolbarButton.zoomIn::before {
   display: inline-block;
   content: url(images/toolbarButton-zoomIn.png);
 }
 
-.toolbarButton.fullscreen::before {
+.toolbarButton.presentationMode::before {
   display: inline-block;
-  content: url(images/toolbarButton-fullscreen.png);
+  content: url(images/toolbarButton-presentationMode.png);
 }
 
 .toolbarButton.print::before {
   display: inline-block;
   content: url(images/toolbarButton-print.png);
   left: 6px;
 }
 
@@ -875,17 +920,17 @@ html[dir='rtl'] .toolbarButton.pageDown:
 
 
 .toolbarField {
   padding: 3px 6px;
   margin: 4px 0 4px 0;
   border: 1px solid transparent;
   border-radius: 2px;
   background-color: hsla(0,0%,100%,.09);
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+  background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   border: 1px solid hsla(0,0%,0%,.35);
   border-color: hsla(0,0%,0%,.32) hsla(0,0%,0%,.38) hsla(0,0%,0%,.42);
   box-shadow: 0 1px 0 hsla(0,0%,0%,.05) inset,
               0 1px 0 hsla(0,0%,100%,.05);
   color: hsl(0,0%,95%);
   font-size: 12px;
   line-height: 14px;
@@ -972,32 +1017,32 @@ html[dir='rtl'] .toolbarButton.pageDown:
 a:focus > .thumbnail > .thumbnailSelectionRing > .thumbnailImage,
 .thumbnail:hover > .thumbnailSelectionRing > .thumbnailImage {
   opacity: .9;
 }
 
 a:focus > .thumbnail > .thumbnailSelectionRing,
 .thumbnail:hover > .thumbnailSelectionRing {
   background-color: hsla(0,0%,100%,.15);
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+  background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.2) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,.9);
 }
 
 .thumbnail.selected > .thumbnailSelectionRing > .thumbnailImage {
   box-shadow: 0 0 0 1px hsla(0,0%,0%,.5);
   opacity: 1;
 }
 
 .thumbnail.selected > .thumbnailSelectionRing {
   background-color: hsla(0,0%,100%,.3);
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+  background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,1);
 }
 
 #outlineView {
@@ -1038,27 +1083,27 @@ html[dir='ltr'] .outlineItem > a {
 }
 
 html[dir='rtl'] .outlineItem > a {
   padding: 2px 10px 5px 0;
 }
 
 .outlineItem > a:hover {
   background-color: hsla(0,0%,100%,.02);
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+  background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.2) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,.9);
 }
 
 .outlineItem.selected {
   background-color: hsla(0,0%,100%,.08);
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
+  background-image: linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
   background-clip: padding-box;
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.1) inset,
               0 0 1px hsla(0,0%,0%,.2);
   color: hsla(0,0%,100%,1);
 }
 
 .noResults {
@@ -1118,92 +1163,16 @@ canvas {
   display: block;
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
   background: url('images/loading-icon.gif') center no-repeat;
 }
 
-#loadingBox {
-  position: absolute;
-  top: 50%;
-  margin-top: -25px;
-  left: 0;
-  right: 0;
-  text-align: center;
-  color: #ddd;
-  font-size: 14px;
-}
-
-#loadingBar {
-  display: inline-block;
-  clear: both;
-  margin: 0px;
-  margin-top: 5px;
-  line-height: 0;
-  border-radius: 2px;
-  width: 200px;
-  height: 25px;
-
-  background-color: hsla(0,0%,0%,.3);
-  background-image: -moz-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  background-image: -webkit-linear-gradient(hsla(0,0%,100%,.05), hsla(0,0%,100%,0));
-  border: 1px solid #000;
-  box-shadow: 0 1px 1px hsla(0,0%,0%,.1) inset,
-              0 0 1px hsla(0,0%,0%,.2) inset,
-              0 0 1px 1px rgba(255, 255, 255, 0.1);
-}
-
-#loadingBar .progress {
-  display: inline-block;
-  float: left;
-
-  background: #666;
-  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#b2b2b2), color-stop(100%,#898989));
-  background: -webkit-linear-gradient(top, #b2b2b2 0%,#898989 100%);
-  background: -moz-linear-gradient(top, #b2b2b2 0%,#898989 100%);
-  background: -ms-linear-gradient(top, #b2b2b2 0%,#898989 100%);
-  background: -o-linear-gradient(top, #b2b2b2 0%,#898989 100%);
-  background: linear-gradient(top, #b2b2b2 0%,#898989 100%);
-
-  border-top-left-radius: 2px;
-  border-bottom-left-radius: 2px;
-
-  width: 0%;
-  height: 100%;
-}
-
-#loadingBar .progress.full {
-  border-top-right-radius: 2px;
-  border-bottom-right-radius: 2px;
-}
-
-#loadingBar .progress.indeterminate {
-  width: 100%;
-  height: 25px;
-  background-image: -moz-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
-  background-image: -webkit-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
-  background-image: -ms-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
-  background-image: -o-linear-gradient( 30deg, #404040, #404040 15%, #898989, #404040 85%, #404040);
-  background-size: 75px 25px;
-  -moz-animation: progressIndeterminate 1s linear infinite;
-  -webkit-animation: progressIndeterminate 1s linear infinite;
-}
-
-@-moz-keyframes progressIndeterminate {
-  from { background-position: 0px 0px; }
-  to { background-position: 75px 0px; }
-}
-
-@-webkit-keyframes progressIndeterminate {
-  from { background-position: 0px 0px; }
-  to { background-position: 75px 0px; }
-}
-
 .textLayer {
   position: absolute;
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
   color: #000;
   font-family: sans-serif;
@@ -1275,17 +1244,16 @@ canvas {
 }
 
 #errorWrapper {
   background: none repeat scroll 0 0 #FF5555;
   color: white;
   left: 0;
   position: absolute;
   right: 0;
-  top: 32px;
   z-index: 1000;
   padding: 3px;
   font-size: 0.8em;
 }
 
 #errorMessageLeft {
   float: left;
 }
@@ -1375,16 +1343,21 @@ canvas {
   margin: 0;
 }
 
 #printContainer {
   display: none;
 }
 
 @media print {
+  /* General rules for printing. */
+  body {
+    background: transparent none;
+  }
+
   /* Rules for browsers that don't support mozPrintCallback. */
   #sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer {
     display: none;
   }
 
   #mainContainer, #viewerContainer, .page, .page canvas {
     position: static;
     padding: 0;
@@ -1425,19 +1398,22 @@ canvas {
   html[dir='rtl'] #outerContainer.sidebarOpen .outerCenter {
     float: right;
     right: 180px;
   }
 }
 
 @media all and (max-width: 770px) {
   #sidebarContainer {
-    top: 33px;
+    top: 32px;
     z-index: 100;
   }
+  .loadingInProgress #sidebarContainer {
+    top: 39px;
+  }
   #sidebarContent {
     top: 32px;
     background-color: hsla(0,0%,0%,.7);
   }
 
   html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
     left: 0px;
   }
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -31,18 +31,18 @@ limitations under the License.
 
 
 
 
     <script type="text/javascript" src="debugger.js"></script>
     <script type="text/javascript" src="viewer.js"></script>
   </head>
 
-  <body>
-    <div id="outerContainer">
+  <body tabindex="1">
+    <div id="outerContainer" class="loadingInProgress">
 
       <div id="sidebarContainer">
         <div id="toolbarSidebar">
           <div class="splitToolbarButton toggled">
             <button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" tabindex="2" data-l10n-id="thumbs">
                <span data-l10n-id="thumbs_label">Thumbnails</span>
             </button>
             <button id="viewOutline" class="toolbarButton group" title="Show Document Outline" tabindex="3" data-l10n-id="outline">
@@ -100,17 +100,17 @@ limitations under the License.
                 <label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
                 <input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="8">
                 </input>
                 <span id="numPages" class="toolbarLabel"></span>
               </div>
               <div id="toolbarViewerRight">
                 <input id="fileInput" class="fileInput" type="file" oncontextmenu="return false;" style="visibility: hidden; position: fixed; right: 0; top: 0" />
 
-                <button id="fullscreen" class="toolbarButton fullscreen hiddenSmallView" title="Switch to Presentation Mode" tabindex="12" data-l10n-id="presentation_mode">
+                <button id="presentationMode" class="toolbarButton presentationMode hiddenSmallView" title="Switch to Presentation Mode" tabindex="12" data-l10n-id="presentation_mode">
                   <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
                 </button>
 
                 <button id="openFile" class="toolbarButton openFile hiddenSmallView" title="Open File" tabindex="13" data-l10n-id="open_file">
                    <span data-l10n-id="open_file_label">Open</span>
                 </button>
 
                 <button id="print" class="toolbarButton print" title="Print" tabindex="14" data-l10n-id="print">
@@ -121,21 +121,21 @@ limitations under the License.
                   <span data-l10n-id="download_label">Download</span>
                 </button>
                 <!-- <div class="toolbarButtonSpacer"></div> -->
                 <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="16" data-l10n-id="bookmark"><span data-l10n-id="bookmark_label">Current View</span></a>
               </div>
               <div class="outerCenter">
                 <div class="innerCenter" id="toolbarViewerMiddle">
                   <div class="splitToolbarButton">
-                    <button class="toolbarButton zoomOut" id="zoom_out" title="Zoom Out" tabindex="9" data-l10n-id="zoom_out">
+                    <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="9" data-l10n-id="zoom_out">
                       <span data-l10n-id="zoom_out_label">Zoom Out</span>
                     </button>
                     <div class="splitToolbarButtonSeparator"></div>
-                    <button class="toolbarButton zoomIn" id="zoom_in" title="Zoom In" tabindex="10" data-l10n-id="zoom_in">
+                    <button id="zoomIn" class="toolbarButton zoomIn" title="Zoom In" tabindex="10" data-l10n-id="zoom_in">
                       <span data-l10n-id="zoom_in_label">Zoom In</span>
                      </button>
                   </div>
                   <span id="scaleSelectContainer" class="dropdownToolbarButton">
                      <select id="scaleSelect" title="Zoom" oncontextmenu="return false;" tabindex="11" data-l10n-id="zoom">
                       <option id="pageAutoOption" value="auto" selected="selected" data-l10n-id="page_scale_auto">Automatic Zoom</option>
                       <option id="pageActualOption" value="page-actual" data-l10n-id="page_scale_actual">Actual Size</option>
                       <option id="pageFitOption" value="page-fit" data-l10n-id="page_scale_fit">Fit Page</option>
@@ -147,39 +147,40 @@ limitations under the License.
                       <option value="1.25">125%</option>
                       <option value="1.5">150%</option>
                       <option value="2">200%</option>
                     </select>
                   </span>
                 </div>
               </div>
             </div>
+            <div id="loadingBar">
+              <div class="progress">
+                <div class="glimmer">
+                </div>
+              </div>
+            </div>
           </div>
         </div>
 
         <menu type="context" id="viewerContextMenu">
-          <menuitem label="First Page" id="first_page"
+          <menuitem id="firstPage" label="First Page"
                     data-l10n-id="first_page" ></menuitem>
-          <menuitem label="Last Page" id="last_page"
+          <menuitem id="lastPage" label="Last Page"
                     data-l10n-id="last_page" ></menuitem>
-          <menuitem label="Rotate Counter-Clockwise" id="page_rotate_ccw"
+          <menuitem id="pageRotateCcw" label="Rotate Counter-Clockwise"
                     data-l10n-id="page_rotate_ccw" ></menuitem>
-          <menuitem label="Rotate Clockwise" id="page_rotate_cw"
+          <menuitem id="pageRotateCw" label="Rotate Clockwise"
                     data-l10n-id="page_rotate_cw" ></menuitem>
         </menu>
 
-        <div id="viewerContainer" tabindex="1">
+        <div id="viewerContainer">
           <div id="viewer" contextmenu="viewerContextMenu"></div>
         </div>
 
-        <div id="loadingBox">
-          <div id="loading"></div>
-          <div id="loadingBar"><div class="progress"></div></div>
-        </div>
-
         <div id="errorWrapper" hidden='true'>
           <div id="errorMessageLeft">
             <span id="errorMessage"></span>
             <button id="errorShowMore" onclick="" oncontextmenu="return false;" data-l10n-id="error_more_info">
               More Information
             </button>
             <button id="errorShowLess" onclick="" oncontextmenu="return false;" data-l10n-id="error_less_info" hidden='true'>
               Less Information
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -23,19 +23,17 @@ var DEFAULT_SCALE = 'auto';
 var DEFAULT_SCALE_DELTA = 1.1;
 var UNKNOWN_SCALE = 0;
 var CACHE_SIZE = 20;
 var CSS_UNITS = 96.0 / 72.0;
 var SCROLLBAR_PADDING = 40;
 var VERTICAL_PADDING = 5;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 4.0;
-var IMAGE_DIR = './images/';
 var SETTINGS_MEMORY = 20;
-var ANNOT_MIN_SIZE = 10;
 var RenderingStates = {
   INITIAL: 0,
   RUNNING: 1,
   PAUSED: 2,
   FINISHED: 3
 };
 var FindStates = {
   FIND_FOUND: 0,
@@ -103,34 +101,30 @@ var ProgressBar = (function ProgressBarC
 
     // Get options, with sensible defaults
     this.height = opts.height || 100;
     this.width = opts.width || 100;
     this.units = opts.units || '%';
 
     // Initialize heights
     this.div.style.height = this.height + this.units;
+    this.percent = 0;
   }
 
   ProgressBar.prototype = {
 
     updateBar: function ProgressBar_updateBar() {
       if (this._indeterminate) {
         this.div.classList.add('indeterminate');
+        this.div.style.width = this.width + this.units;
         return;
       }
 
+      this.div.classList.remove('indeterminate');
       var progressSize = this.width * this._percent / 100;
-
-      if (this._percent > 95)
-        this.div.classList.add('full');
-      else
-        this.div.classList.remove('full');
-      this.div.classList.remove('indeterminate');
-
       this.div.style.width = progressSize + this.units;
     },
 
     get percent() {
       return this._percent;
     },
 
     set percent(val) {
@@ -737,17 +731,17 @@ var PDFView = {
   container: null,
   thumbnailContainer: null,
   initialized: false,
   fellback: false,
   pdfDocument: null,
   sidebarOpen: false,
   pageViewScroll: null,
   thumbnailViewScroll: null,
-  isFullscreen: false,
+  isPresentationMode: false,
   previousScale: null,
   pageRotation: 0,
   mouseScrollTimeStamp: 0,
   mouseScrollDelta: 0,
   lastScroll: 0,
   previousPageNumber: 1,
 
   // called once when the document is loaded
@@ -916,21 +910,22 @@ var PDFView = {
     return value;
   },
 
   get supportsFullscreen() {
     var doc = document.documentElement;
     var support = doc.requestFullscreen || doc.mozRequestFullScreen ||
                   doc.webkitRequestFullScreen;
 
-    // Disable fullscreen button if we're in an iframe
-    if (window.parent !== window)
+    // Disable presentation mode button if we're in an iframe
+    if (window.parent !== window) {
       support = false;
-
-    Object.defineProperty(this, 'supportsFullScreen', { value: support,
+    }
+
+    Object.defineProperty(this, 'supportsFullscreen', { value: support,
                                                         enumerable: true,
                                                         configurable: true,
                                                         writable: false });
     return support;
   },
 
   get supportsIntegratedFind() {
     var support = false;
@@ -967,22 +962,67 @@ var PDFView = {
     return div.scrollWidth > div.clientWidth;
   },
 
   initPassiveLoading: function pdfViewInitPassiveLoading() {
     if (!PDFView.loadingBar) {
       PDFView.loadingBar = new ProgressBar('#loadingBar', {});
     }
 
-    window.addEventListener('message', function window_message(e) {
+    var pdfDataRangeTransport = {
+      rangeListeners: [],
+      progressListeners: [],
+
+      addRangeListener: function PdfDataRangeTransport_addRangeListener(
+                                   listener) {
+        this.rangeListeners.push(listener);
+      },
+
+      addProgressListener: function PdfDataRangeTransport_addProgressListener(
+                                      listener) {
+        this.progressListeners.push(listener);
+      },
+
+      onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
+        var listeners = this.rangeListeners;
+        for (var i = 0, n = listeners.length; i < n; ++i) {
+          listeners[i](begin, chunk);
+        }
+      },
+
+      onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
+        var listeners = this.progressListeners;
+        for (var i = 0, n = listeners.length; i < n; ++i) {
+          listeners[i](loaded);
+        }
+      },
+
+      requestDataRange: function PdfDataRangeTransport_requestDataRange(
+                                  begin, end) {
+        FirefoxCom.request('requestDataRange', { begin: begin, end: end });
+      }
+    };
+
+    window.addEventListener('message', function windowMessage(e) {
       var args = e.data;
 
       if (typeof args !== 'object' || !('pdfjsLoadAction' in args))
         return;
       switch (args.pdfjsLoadAction) {
+        case 'supportsRangedLoading':
+          PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
+            length: args.length
+          });
+          break;
+        case 'range':
+          pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
+          break;
+        case 'rangeProgress':
+          pdfDataRangeTransport.onDataProgress(args.loaded);
+          break;
         case 'progress':
           PDFView.progress(args.loaded / args.total);
           break;
         case 'complete':
           if (!args.data) {
             PDFView.error(mozL10n.get('loading_error', null,
                           'An error occurred while loading the PDF.'), e);
             break;
@@ -1004,68 +1044,76 @@ var PDFView = {
       this.setTitle(url);
     }
   },
 
   setTitle: function pdfViewSetTitle(title) {
     document.title = title;
   },
 
-  open: function pdfViewOpen(url, scale, password) {
+  // TODO(mack): This function signature should really be pdfViewOpen(url, args)
+  open: function pdfViewOpen(url, scale, password,
+                             pdfDataRangeTransport, args) {
     var parameters = {password: password};
     if (typeof url === 'string') { // URL
       this.setTitleUsingUrl(url);
       parameters.url = url;
     } else if (url && 'byteLength' in url) { // ArrayBuffer
       parameters.data = url;
     }
+    if (args) {
+      for (var prop in args) {
+        parameters[prop] = args[prop];
+      }
+    }
 
     if (!PDFView.loadingBar) {
       PDFView.loadingBar = new ProgressBar('#loadingBar', {});
     }
 
     this.pdfDocument = null;
     var self = this;
     self.loading = true;
-    PDFJS.getDocument(parameters).then(
+    var passwordNeeded = function passwordNeeded(updatePassword, reason) {
+      var promptString = mozL10n.get('request_password', null,
+                                'PDF is protected by a password:');
+
+      if (reason === PDFJS.PasswordResponses.INCORRECT_PASSWORD) {
+        promptString += '\n' + mozL10n.get('invalid_password', null,
+                                'Invalid Password.');
+      }
+
+      password = prompt(promptString);
+      if (password && password.length > 0) {
+        return updatePassword(password);
+      }
+    };
+
+    PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded).then(
       function getDocumentCallback(pdfDocument) {
         self.load(pdfDocument, scale);
         self.loading = false;
       },
       function getDocumentError(message, exception) {
-        if (exception && exception.name === 'PasswordException') {
-          if (exception.code === 'needpassword') {
-            var promptString = mozL10n.get('request_password', null,
-                                      'PDF is protected by a password:');
-            password = prompt(promptString);
-            if (password && password.length > 0) {
-              return PDFView.open(url, scale, password);
-            }
-          }
-        }
-
         var loadingErrorMessage = mozL10n.get('loading_error', null,
           'An error occurred while loading the PDF.');
 
         if (exception && exception.name === 'InvalidPDFException') {
           // change error message also for other builds
           var loadingErrorMessage = mozL10n.get('invalid_file_error', null,
                                         'Invalid or corrupted PDF file.');
         }
 
         if (exception && exception.name === 'MissingPDFException') {
           // special message for missing PDF's
           var loadingErrorMessage = mozL10n.get('missing_file_error', null,
                                         'Missing PDF file.');
 
         }
 
-        var loadingIndicator = document.getElementById('loading');
-        loadingIndicator.textContent = mozL10n.get('loading_error_indicator',
-          null, 'Error');
         var moreInfo = {
           message: message
         };
         self.error(loadingErrorMessage, moreInfo);
         self.loading = false;
       },
       function getDocumentProgress(progressData) {
         self.progress(progressData.loaded / progressData.total);
@@ -1113,33 +1161,41 @@ var PDFView = {
     FirefoxCom.request('fallback', url, function response(download) {
       if (!download)
         return;
       PDFView.download();
     });
   },
 
   navigateTo: function pdfViewNavigateTo(dest) {
-    if (typeof dest === 'string')
-      dest = this.destinations[dest];
-    if (!(dest instanceof Array))
-      return; // invalid destination
-    // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
-    var destRef = dest[0];
-    var pageNumber = destRef instanceof Object ?
-      this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] : (destRef + 1);
-    if (pageNumber > this.pages.length)
-      pageNumber = this.pages.length;
-    if (pageNumber) {
-      this.page = pageNumber;
-      var currentPage = this.pages[pageNumber - 1];
-      if (!this.isFullscreen) { // Avoid breaking fullscreen mode.
-        currentPage.scrollIntoView(dest);
+    var self = this;
+    PDFJS.Promise.all([this.pagesPromise,
+                       this.destinationsPromise]).then(function() {
+      if (typeof dest === 'string') {
+        dest = self.destinations[dest];
+      }
+      if (!(dest instanceof Array)) {
+        return; // invalid destination
       }
-    }
+      // dest array looks like that: <page-ref> </XYZ|FitXXX> <args..>
+      var destRef = dest[0];
+      var pageNumber = destRef instanceof Object ?
+        self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
+        (destRef + 1);
+      if (pageNumber) {
+        if (pageNumber > self.pages.length) {
+          pageNumber = self.pages.length;
+        }
+        self.page = pageNumber;
+        if (!self.isPresentationMode) { // Avoid breaking presentation mode.
+          var currentPage = self.pages[pageNumber - 1];
+          currentPage.scrollIntoView(dest);
+        }
+      }
+    });
   },
 
   getDestinationHash: function pdfViewGetDestinationHash(dest) {
     if (typeof dest === 'string')
       return PDFView.getAnchorUrl('#' + escape(dest));
     if (dest instanceof Array) {
       var destRef = dest[0]; // see navigateTo method for dest format
       var pageNumber = destRef instanceof Object ?
@@ -1214,45 +1270,50 @@ var PDFView = {
         if (moreInfo.lineNumber) {
           moreInfoText += '\n' +
             mozL10n.get('error_line', {line: moreInfo.lineNumber},
             'Line: {{line}}');
         }
       }
     }
 
-    var loadingBox = document.getElementById('loadingBox');
-    loadingBox.setAttribute('hidden', 'true');
-
     console.error(message + '\n' + moreInfoText);
     this.fallback();
   },
 
   progress: function pdfViewProgress(level) {
     var percent = Math.round(level * 100);
-    PDFView.loadingBar.percent = percent;
+    // When we transition from full request to range requests, it's possible
+    // that we discard some of the loaded data. This can cause the loading
+    // bar to move backwards. So prevent this by only updating the bar if it
+    // increases.
+    if (percent > PDFView.loadingBar.percent) {
+      PDFView.loadingBar.percent = percent;
+    }
   },
 
   load: function pdfViewLoad(pdfDocument, scale) {
     function bindOnAfterDraw(pageView, thumbnailView) {
       // when page is painted, using the image as thumbnail base
       pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
         thumbnailView.setImage(pageView.canvas);
       };
     }
 
     this.pdfDocument = pdfDocument;
 
     var errorWrapper = document.getElementById('errorWrapper');
     errorWrapper.setAttribute('hidden', 'true');
 
-    var loadingBox = document.getElementById('loadingBox');
-    loadingBox.setAttribute('hidden', 'true');
-    var loadingIndicator = document.getElementById('loading');
-    loadingIndicator.textContent = '';
+    pdfDocument.dataLoaded().then(function() {
+      var loadingBar = document.getElementById('loadingBar');
+      loadingBar.classList.add('hidden');
+      var outerContainer = document.getElementById('outerContainer');
+      outerContainer.classList.remove('loadingInProgress');
+    });
 
     var thumbsView = document.getElementById('thumbnailView');
     thumbsView.parentNode.scrollTop = 0;
 
     while (thumbsView.hasChildNodes())
       thumbsView.removeChild(thumbsView.lastChild);
 
     if ('_loadingInterval' in thumbsView)
@@ -1274,17 +1335,17 @@ var PDFView = {
     this.pageRotation = 0;
 
     var pages = this.pages = [];
     this.pageText = [];
     this.startedTextExtraction = false;
     var pagesRefMap = this.pagesRefMap = {};
     var thumbnails = this.thumbnails = [];
 
-    var pagesPromise = new PDFJS.Promise();
+    var pagesPromise = this.pagesPromise = new PDFJS.Promise();
     var self = this;
 
     var firstPagePromise = pdfDocument.getPage(1);
 
     // Fetch a single page so we can get a viewport that will be the default
     // viewport for all pages
     firstPagePromise.then(function(pdfPage) {
       var viewport = pdfPage.getViewport(scale || 1.0);
@@ -1340,16 +1401,23 @@ var PDFView = {
         var zoom = store.get('zoom', PDFView.currentScale);
         var left = store.get('scrollLeft', '0');
         var top = store.get('scrollTop', '0');
 
         storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
                     left + ',' + top;
       }
       self.setInitialView(storedHash, scale);
+
+      // Make all navigation keys work on document load,
+      // unless the viewer is embedded in another page.
+      if (window.parent === window) {
+        PDFView.container.focus();
+        PDFView.container.blur();
+      }
     });
 
     pagesPromise.then(function() {
       if (PDFView.supportsPrinting) {
         pdfDocument.getJavaScript().then(function(javaScript) {
           if (javaScript.length) {
             console.warn('Warning: JavaScript is not supported');
             PDFView.fallback();
@@ -1364,35 +1432,30 @@ var PDFView = {
               });
               return;
             }
           }
         });
       }
     });
 
-    var destinationsPromise = pdfDocument.getDestinations();
+    var destinationsPromise =
+      this.destinationsPromise = pdfDocument.getDestinations();
     destinationsPromise.then(function(destinations) {
       self.destinations = destinations;
     });
 
     // outline depends on destinations and pagesRefMap
     var promises = [pagesPromise, destinationsPromise,
                     PDFView.animationStartedPromise];
     PDFJS.Promise.all(promises).then(function() {
       pdfDocument.getOutline().then(function(outline) {
         self.outline = new DocumentOutlineView(outline);
         document.getElementById('viewOutline').disabled = !outline;
       });
-
-      // Make all navigation keys work on document load,
-      // unless the viewer is embedded in another page.
-      if (window.parent.location === window.location) {
-        PDFView.container.focus();
-      }
     });
 
     pdfDocument.getMetadata().then(function(data) {
       var info = data.info, metadata = data.metadata;
       self.documentInfo = info;
       self.metadata = metadata;
 
       // Provides some basic debug information
@@ -1611,20 +1674,20 @@ var PDFView = {
 
         if (outlineButton.getAttribute('disabled'))
           return;
         break;
     }
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
-    if (!this.isFullscreen) {
+    if (!this.isPresentationMode) {
       return this.getVisibleElements(this.container, this.pages, true);
     } else {
-      // The algorithm in getVisibleElements is broken in fullscreen mode.
+      // The algorithm in getVisibleElements is broken in presentation mode.
       var visible = [], page = this.page;
       var currentPage = this.pages[page - 1];
       visible.push({ id: currentPage.id, view: currentPage });
 
       return { first: currentPage, last: currentPage, views: visible};
     }
   },
 
@@ -1682,17 +1745,17 @@ var PDFView = {
   // Helper function to parse query string (e.g. ?param1=value&parm2=...).
   parseQueryString: function pdfViewParseQueryString(query) {
     var parts = query.split('&');
     var params = {};
     for (var i = 0, ii = parts.length; i < parts.length; ++i) {
       var param = parts[i].split('=');
       var key = param[0];
       var value = param.length > 1 ? param[1] : null;
-      params[unescape(key)] = unescape(value);
+      params[decodeURIComponent(key)] = decodeURIComponent(value);
     }
     return params;
   },
 
   beforePrint: function pdfViewSetupBeforePrint() {
     if (!this.supportsPrinting) {
       var printMessage = mozL10n.get('printing_not_supported', null,
           'Warning: Printing is not fully supported by this browser.');
@@ -1726,58 +1789,59 @@ var PDFView = {
   },
 
   afterPrint: function pdfViewSetupAfterPrint() {
     var div = document.getElementById('printContainer');
     while (div.hasChildNodes())
       div.removeChild(div.lastChild);
   },
 
-  fullscreen: function pdfViewFullscreen() {
-    var isFullscreen = document.fullscreenElement || document.mozFullScreen ||
-        document.webkitIsFullScreen;
-
-    if (isFullscreen) {
+  presentationMode: function pdfViewPresentationMode() {
+    var isPresentationMode = document.fullscreenElement ||
+                             document.mozFullScreen ||
+                             document.webkitIsFullScreen;
+
+    if (isPresentationMode) {
       return false;
     }
 
     var wrapper = document.getElementById('viewerContainer');
     if (document.documentElement.requestFullscreen) {
       wrapper.requestFullscreen();
     } else if (document.documentElement.mozRequestFullScreen) {
       wrapper.mozRequestFullScreen();
     } else if (document.documentElement.webkitRequestFullScreen) {
       wrapper.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
     } else {
       return false;
     }
 
-    this.isFullscreen = true;
+    this.isPresentationMode = true;
     var currentPage = this.pages[this.page - 1];
     this.previousScale = this.currentScaleValue;
     this.parseScale('page-fit', true);
 
-    // Wait for fullscreen to take effect
+    // Wait for presentation mode to take effect
     setTimeout(function() {
       currentPage.scrollIntoView();
     }, 0);
 
     this.showPresentationControls();
     return true;
   },
 
-  exitFullscreen: function pdfViewExitFullscreen() {
-    this.isFullscreen = false;
+  exitPresentationMode: function pdfViewExitPresentationMode() {
+    this.isPresentationMode = false;
     this.parseScale(this.previousScale);
     this.page = this.page;
     this.clearMouseScrollState();
     this.hidePresentationControls();
 
     // Ensure that the thumbnail of the current page is visible
-    // when exiting fullscreen mode.
+    // when exiting presentation mode.
     scrollIntoView(document.getElementById('thumbnailContainer' + this.page));
   },
 
   showPresentationControls: function pdfViewShowPresentationControls() {
     var DELAY_BEFORE_HIDING_CONTROLS = 3000;
     var wrapper = document.getElementById('viewerContainer');
     if (this.presentationControlsTimeout) {
       clearTimeout(this.presentationControlsTimeout);
@@ -1819,17 +1883,17 @@ var PDFView = {
 
     this.renderHighestPriority();
 
     var currentPage = this.pages[this.page - 1];
     if (!currentPage) {
       return;
     }
 
-    // Wait for fullscreen to take effect
+    // Wait for presentation mode to take effect
     setTimeout(function() {
       currentPage.scrollIntoView();
     }, 0);
   },
 
   /**
    * This function flips the page in presentation mode if the user scrolls up
    * or down with large enough motion and prevents page flipping too often.
@@ -1860,17 +1924,17 @@ var PDFView = {
     var PAGE_FLIP_THRESHOLD = 120;
     if (Math.abs(this.mouseScrollDelta) >= PAGE_FLIP_THRESHOLD) {
 
       var PageFlipDirection = {
         UP: -1,
         DOWN: 1
       };
 
-      // In fullscreen mode scroll one page at a time.
+      // In presentation mode scroll one page at a time.
       var pageFlipDirection = (this.mouseScrollDelta > 0) ?
                                 PageFlipDirection.UP :
                                 PageFlipDirection.DOWN;
       this.clearMouseScrollState();
       var currentPage = this.page;
 
       // In case we are already on the first or the last page there is no need
       // to do anything.
@@ -1977,111 +2041,64 @@ var PageView = function pageView(contain
 
   Object.defineProperty(this, 'height', {
     get: function PageView_getHeight() {
       return this.viewport.height;
     },
     enumerable: true
   });
 
-  function setupAnnotations(pdfPage, viewport) {
+  function setupAnnotations(annotationsDiv, pdfPage, viewport) {
+
     function bindLink(link, dest) {
       link.href = PDFView.getDestinationHash(dest);
       link.onclick = function pageViewSetupLinksOnclick() {
         if (dest)
           PDFView.navigateTo(dest);
         return false;
       };
       link.className = 'internalLink';
     }
-    function createElementWithStyle(tagName, item, rect) {
-      if (!rect) {
-        rect = viewport.convertToViewportRectangle(item.rect);
-        rect = PDFJS.Util.normalizeRect(rect);
-      }
-      var element = document.createElement(tagName);
-      element.style.left = Math.floor(rect[0]) + 'px';
-      element.style.top = Math.floor(rect[1]) + 'px';
-      element.style.width = Math.ceil(rect[2] - rect[0]) + 'px';
-      element.style.height = Math.ceil(rect[3] - rect[1]) + 'px';
-      return element;
-    }
-    function createTextAnnotation(item) {
-      var container = document.createElement('section');
-      container.className = 'annotText';
-
-      var rect = viewport.convertToViewportRectangle(item.rect);
-      rect = PDFJS.Util.normalizeRect(rect);
-      // sanity check because of OOo-generated PDFs
-      if ((rect[3] - rect[1]) < ANNOT_MIN_SIZE) {
-        rect[3] = rect[1] + ANNOT_MIN_SIZE;
-      }
-      if ((rect[2] - rect[0]) < ANNOT_MIN_SIZE) {
-        rect[2] = rect[0] + (rect[3] - rect[1]); // make it square
-      }
-      var image = createElementWithStyle('img', item, rect);
-      var iconName = item.name;
-      image.src = IMAGE_DIR + 'annotation-' +
-        iconName.toLowerCase() + '.svg';
-      image.alt = mozL10n.get('text_annotation_type', {type: iconName},
-        '[{{type}} Annotation]');
-      var content = document.createElement('div');
-      content.setAttribute('hidden', true);
-      var title = document.createElement('h1');
-      var text = document.createElement('p');
-      content.style.left = Math.floor(rect[2]) + 'px';
-      content.style.top = Math.floor(rect[1]) + 'px';
-      title.textContent = item.title;
-
-      if (!item.content && !item.title) {
-        content.setAttribute('hidden', true);
-      } else {
-        var e = document.createElement('span');
-        var lines = item.content.split(/(?:\r\n?|\n)/);
-        for (var i = 0, ii = lines.length; i < ii; ++i) {
-          var line = lines[i];
-          e.appendChild(document.createTextNode(line));
-          if (i < (ii - 1))
-            e.appendChild(document.createElement('br'));
+
+    pdfPage.getAnnotations().then(function(annotationsData) {
+      viewport = viewport.clone({ dontFlip: true });
+      for (var i = 0; i < annotationsData.length; i++) {
+        var data = annotationsData[i];
+        var annotation = PDFJS.Annotation.fromData(data);
+        if (!annotation || !annotation.hasHtml()) {
+          continue;
         }
-        text.appendChild(e);
-        image.addEventListener('mouseover', function annotationImageOver() {
-           content.removeAttribute('hidden');
-        }, false);
-
-        image.addEventListener('mouseout', function annotationImageOut() {
-           content.setAttribute('hidden', true);
-        }, false);
-      }
-
-      content.appendChild(title);
-      content.appendChild(text);
-      container.appendChild(image);
-      container.appendChild(content);
-
-      return container;
-    }
-
-    pdfPage.getAnnotations().then(function(items) {
-      for (var i = 0; i < items.length; i++) {
-        var item = items[i];
-        switch (item.type) {
-          case 'Link':
-            var link = createElementWithStyle('a', item);
-            link.href = item.url || '';
-            if (!item.url)
-              bindLink(link, ('dest' in item) ? item.dest : null);
-            div.appendChild(link);
-            break;
-          case 'Text':
-            var textAnnotation = createTextAnnotation(item);
-            if (textAnnotation)
-              div.appendChild(textAnnotation);
-            break;
+
+        var element = annotation.getHtmlElement(pdfPage.commonObjs);
+        mozL10n.translate(element);
+
+        data = annotation.getData();
+        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';
+
+        var transform = viewport.transform;
+        var transformStr = 'matrix(' + transform.join(',') + ')';
+        CustomStyle.setProp('transform', element, transformStr);
+        var transformOriginStr = -rect[0] + 'px ' + -rect[1] + 'px';
+        CustomStyle.setProp('transformOrigin', element, transformOriginStr);
+
+        if (data.subtype === 'Link' && !data.url) {
+          bindLink(element, ('dest' in data) ? data.dest : null);
         }
+
+        annotationsDiv.appendChild(element);
       }
     });
   }
 
   this.getPagePoint = function pageViewGetPagePoint(x, y) {
     return this.viewport.convertToPdfPoint(x, y);
   };
 
@@ -2130,20 +2147,26 @@ var PageView = function pageView(contain
           heightScale = (this.container.clientHeight - SCROLLBAR_PADDING) /
             height / CSS_UNITS;
           scale = Math.min(widthScale, heightScale);
           break;
         default:
           return;
       }
 
-      if (scale && scale !== PDFView.currentScale)
+      if (scale && scale !== PDFView.currentScale) {
         PDFView.parseScale(scale, true, true);
-      else if (PDFView.currentScale === UNKNOWN_SCALE)
+      } else if (PDFView.currentScale === UNKNOWN_SCALE) {
         PDFView.parseScale(DEFAULT_SCALE, true, true);
+      }
+
+      if (scale === 'page-fit' && !dest[4]) {
+        scrollIntoView(div);
+        return;
+      }
 
       var boundingRect = [
         this.viewport.convertToViewportPoint(x, y),
         this.viewport.convertToViewportPoint(x + width, y + height)
       ];
       setTimeout(function pageViewScrollIntoViewRelayout() {
         // letting page to re-layout before scrolling
         var scale = PDFView.currentScale;
@@ -2243,18 +2266,20 @@ var PageView = function pageView(contain
           !PDFView.supportsDocumentFonts) {
         console.error(mozL10n.get('web_fonts_disabled', null,
           'Web fonts are disabled: unable to use embedded PDF fonts.'));
         PDFView.fallback();
       }
       if (self.textLayer && self.textLayer.textDivs &&
           self.textLayer.textDivs.length > 0 &&
           !PDFView.supportsDocumentColors) {
-        console.error(mozL10n.get('web_colors_disabled', null,
-          'Web colors are disabled.'));
+        console.error(mozL10n.get('document_colors_disabled', null,
+          'PDF documents are not allowed to use their own colors: ' +
+          '\'Allow pages to choose their own colors\' ' +
+          'is deactivated in the browser.'));
         PDFView.fallback();
       }
       if (error) {
         PDFView.error(mozL10n.get('rendering_error', null,
           'An error occurred while rendering the page.'), error);
       }
 
       self.stats = pdfPage.stats;
@@ -2307,17 +2332,17 @@ var PageView = function pageView(contain
     if (textLayer) {
       this.getTextContent().then(
         function textContentResolved(textContent) {
           textLayer.setTextContent(textContent);
         }
       );
     }
 
-    setupAnnotations(this.pdfPage, this.viewport);
+    setupAnnotations(div, pdfPage, this.viewport);
     div.setAttribute('data-loaded', true);
   };
 
   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
@@ -2985,18 +3010,27 @@ document.addEventListener('DOMContentLoa
   var file = window.location.toString()
 
   document.getElementById('openFile').setAttribute('hidden', 'true');
 
   // Special debugging flags in the hash section of the URL.
   var hash = document.location.hash.substring(1);
   var hashParams = PDFView.parseQueryString(hash);
 
-  if ('disableWorker' in hashParams)
+  if ('disableWorker' in hashParams) {
     PDFJS.disableWorker = (hashParams['disableWorker'] === 'true');
+  }
+
+  if ('disableRange' in hashParams) {
+    PDFJS.disableRange = (hashParams['disableRange'] === 'true');
+  }
+
+  if ('disableAutoFetch' in hashParams) {
+    PDFJS.disableAutoFetch = (hashParams['disableAutoFetch'] === 'true');
+  }
 
 
   if ('textLayer' in hashParams) {
     switch (hashParams['textLayer']) {
       case 'off':
         PDFJS.disableTextLayer = true;
         break;
       case 'visible':
@@ -3016,21 +3050,21 @@ document.addEventListener('DOMContentLoa
     PDFBug.init();
   }
 
   if (!PDFView.supportsPrinting) {
     document.getElementById('print').classList.add('hidden');
   }
 
   if (!PDFView.supportsFullscreen) {
-    document.getElementById('fullscreen').classList.add('hidden');
+    document.getElementById('presentationMode').classList.add('hidden');
   }
 
   if (PDFView.supportsIntegratedFind) {
-    document.querySelector('#viewFind').classList.add('hidden');
+    document.getElementById('viewFind').classList.add('hidden');
   }
 
   // Listen for warnings to trigger the fallback UI.  Errors should be caught
   // and call PDFView.error() so we don't need to listen for those.
   PDFJS.LogManager.addLogger({
     warn: function() {
       PDFView.fallback();
     }
@@ -3071,29 +3105,29 @@ document.addEventListener('DOMContentLoa
       PDFView.page--;
     });
 
   document.getElementById('next').addEventListener('click',
     function() {
       PDFView.page++;
     });
 
-  document.querySelector('.zoomIn').addEventListener('click',
+  document.getElementById('zoomIn').addEventListener('click',
     function() {
       PDFView.zoomIn();
     });
 
-  document.querySelector('.zoomOut').addEventListener('click',
+  document.getElementById('zoomOut').addEventListener('click',
     function() {
       PDFView.zoomOut();
     });
 
-  document.getElementById('fullscreen').addEventListener('click',
+  document.getElementById('presentationMode').addEventListener('click',
     function() {
-      PDFView.fullscreen();
+      PDFView.presentationMode();
     });
 
   document.getElementById('openFile').addEventListener('click',
     function() {
       document.getElementById('fileInput').click();
     });
 
   document.getElementById('print').addEventListener('click',
@@ -3121,41 +3155,39 @@ document.addEventListener('DOMContentLoa
       }
     });
 
   document.getElementById('scaleSelect').addEventListener('change',
     function() {
       PDFView.parseScale(this.value);
     });
 
-  document.getElementById('first_page').addEventListener('click',
+  document.getElementById('firstPage').addEventListener('click',
     function() {
       PDFView.page = 1;
     });
 
-  document.getElementById('last_page').addEventListener('click',
+  document.getElementById('lastPage').addEventListener('click',
     function() {
       PDFView.page = PDFView.pdfDocument.numPages;
     });
 
-  document.getElementById('page_rotate_ccw').addEventListener('click',
+  document.getElementById('pageRotateCcw').addEventListener('click',
     function() {
       PDFView.rotatePages(-90);
     });
 
-  document.getElementById('page_rotate_cw').addEventListener('click',
+  document.getElementById('pageRotateCw').addEventListener('click',
     function() {
       PDFView.rotatePages(90);
     });
 
-  if (FirefoxCom.requestSync('getLoadingType') == 'passive') {
-    PDFView.setTitleUsingUrl(file);
-    PDFView.initPassiveLoading();
-    return;
-  }
+  PDFView.setTitleUsingUrl(file);
+  PDFView.initPassiveLoading();
+  return;
 
   PDFView.open(file, 0);
 }, true);
 
 function updateViewarea() {
 
   if (!PDFView.initialized)
     return;
@@ -3182,17 +3214,17 @@ function updateViewarea() {
       break;
     }
   }
 
   if (!stillFullyVisible) {
     currentId = visiblePages[0].id;
   }
 
-  if (!PDFView.isFullscreen) {
+  if (!PDFView.isPresentationMode) {
     updateViewarea.inProgress = true; // used in "set page"
     PDFView.page = currentId;
     updateViewarea.inProgress = false;
   }
 
   var currentScale = PDFView.currentScale;
   var currentScaleValue = PDFView.currentScaleValue;
   var normalizedScaleValue = currentScaleValue == currentScale ?
@@ -3280,36 +3312,35 @@ window.addEventListener('localized', fun
       var width = select.clientWidth + 8;
       select.setAttribute('style', 'min-width: ' + (width + 20) + 'px;');
       container.setAttribute('style', 'min-width: ' + width + 'px; ' +
                                       'max-width: ' + width + 'px;');
   });
 }, true);
 
 window.addEventListener('scalechange', function scalechange(evt) {
+  document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
+  document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
+
   var customScaleOption = document.getElementById('customScaleOption');
   customScaleOption.selected = false;
 
   if (!evt.resetAutoSettings &&
        (document.getElementById('pageWidthOption').selected ||
         document.getElementById('pageFitOption').selected ||
         document.getElementById('pageAutoOption').selected)) {
       updateViewarea();
       return;
   }
 
   var predefinedValueFound = selectScaleOption('' + evt.scale);
   if (!predefinedValueFound) {
     customScaleOption.textContent = Math.round(evt.scale * 10000) / 100 + '%';
     customScaleOption.selected = true;
   }
-  
-  document.getElementById('zoom_out').disabled = (evt.scale === MIN_SCALE);
-  document.getElementById('zoom_in').disabled = (evt.scale === MAX_SCALE);
-
   updateViewarea();
 }, true);
 
 window.addEventListener('pagechange', function pagechange(evt) {
   var page = evt.pageNumber;
   if (PDFView.previousPageNumber !== page) {
     document.getElementById('pageNumber').value = page;
     var selected = document.querySelector('.thumbnail.selected');
@@ -3338,45 +3369,46 @@ window.addEventListener('pagechange', fu
 window.addEventListener('DOMMouseScroll', function(evt) {
   if (evt.ctrlKey) {
     evt.preventDefault();
 
     var ticks = evt.detail;
     var direction = (ticks > 0) ? 'zoomOut' : 'zoomIn';
     for (var i = 0, length = Math.abs(ticks); i < length; i++)
       PDFView[direction]();
-  } else if (PDFView.isFullscreen) {
+  } else if (PDFView.isPresentationMode) {
     var FIREFOX_DELTA_FACTOR = -40;
     PDFView.mouseScroll(evt.detail * FIREFOX_DELTA_FACTOR);
   }
 }, false);
 
 window.addEventListener('mousemove', function mousemove(evt) {
-  if (PDFView.isFullscreen) {
+  if (PDFView.isPresentationMode) {
     PDFView.showPresentationControls();
   }
 }, false);
 
 window.addEventListener('mousedown', function mousedown(evt) {
-  if (PDFView.isFullscreen && evt.button === 0) {
-    // Enable clicking of links in fullscreen mode.
+  if (PDFView.isPresentationMode && evt.button === 0) {
+    // Enable clicking of links in presentation mode.
     // Note: Only links that point to the currently loaded PDF document works.
     var targetHref = evt.target.href;
     var internalLink = targetHref && (targetHref.replace(/#.*$/, '') ===
                                       window.location.href.replace(/#.*$/, ''));
     if (!internalLink) {
-      // Unless an internal link was clicked, advance a page in fullscreen mode.
+      // Unless an internal link was clicked, advance a page in presentation
+      // mode.
       evt.preventDefault();
       PDFView.page++;
     }
   }
 }, false);
 
 window.addEventListener('click', function click(evt) {
-  if (PDFView.isFullscreen && evt.button === 0) {
+  if (PDFView.isPresentationMode && evt.button === 0) {
     // Necessary since preventDefault() in 'mousedown' won't stop
     // the event propagation in all circumstances.
     evt.preventDefault();
   }
 }, false);
 
 window.addEventListener('keydown', function keydown(evt) {
   var handled = false;
@@ -3438,30 +3470,31 @@ window.addEventListener('keydown', funct
   // is selected.
   var curElement = document.activeElement || document.querySelector(':focus');
   if (curElement && (curElement.tagName.toUpperCase() === 'INPUT' ||
                      curElement.tagName.toUpperCase() === 'SELECT')) {
     return;
   }
   var controlsElement = document.getElementById('toolbar');
   while (curElement) {
-    if (curElement === controlsElement && !PDFView.isFullscreen)
+    if (curElement === controlsElement && !PDFView.isPresentationMode)
       return; // ignoring if the 'toolbar' element is focused
     curElement = curElement.parentNode;
   }
 
   if (cmd === 0) { // no control key pressed at all.
     switch (evt.keyCode) {
       case 38: // up arrow
       case 33: // pg up
       case 8: // backspace
-        if (!PDFView.isFullscreen && PDFView.currentScaleValue !== 'page-fit') {
+        if (!PDFView.isPresentationMode &&
+            PDFView.currentScaleValue !== 'page-fit') {
           break;
         }
-        /* in fullscreen mode */
+        /* in presentation mode */
         /* falls through */
       case 37: // left arrow
         // horizontal scrolling using arrow keys
         if (PDFView.isHorizontalScrollbarEnabled) {
           break;
         }
         /* falls through */
       case 75: // 'k'
@@ -3473,40 +3506,41 @@ window.addEventListener('keydown', funct
         if (!PDFView.supportsIntegratedFind && PDFFindBar.opened) {
           PDFFindBar.close();
           handled = true;
         }
         break;
       case 40: // down arrow
       case 34: // pg down
       case 32: // spacebar
-        if (!PDFView.isFullscreen && PDFView.currentScaleValue !== 'page-fit') {
+        if (!PDFView.isPresentationMode &&
+            PDFView.currentScaleValue !== 'page-fit') {
           break;
         }
         /* falls through */
       case 39: // right arrow
         // horizontal scrolling using arrow keys
         if (PDFView.isHorizontalScrollbarEnabled) {
           break;
         }
         /* falls through */
       case 74: // 'j'
       case 78: // 'n'
         PDFView.page++;
         handled = true;
         break;
 
       case 36: // home
-        if (PDFView.isFullscreen) {
+        if (PDFView.isPresentationMode) {
           PDFView.page = 1;
           handled = true;
         }
         break;
       case 35: // end
-        if (PDFView.isFullscreen) {
+        if (PDFView.isPresentationMode) {
           PDFView.page = PDFView.pdfDocument.numPages;
           handled = true;
         }
         break;
 
       case 82: // 'r'
         PDFView.rotatePages(90);
         break;
@@ -3530,29 +3564,31 @@ window.addEventListener('keydown', funct
 window.addEventListener('beforeprint', function beforePrint(evt) {
   PDFView.beforePrint();
 });
 
 window.addEventListener('afterprint', function afterPrint(evt) {
   PDFView.afterPrint();
 });
 
-(function fullscreenClosure() {
-  function fullscreenChange(e) {
-    var isFullscreen = document.fullscreenElement || document.mozFullScreen ||
-        document.webkitIsFullScreen;
-
-    if (!isFullscreen) {
-      PDFView.exitFullscreen();
+(function presentationModeClosure() {
+  function presentationModeChange(e) {
+    var isPresentationMode = document.fullscreenElement ||
+                             document.mozFullScreen ||
+                             document.webkitIsFullScreen;
+
+    if (!isPresentationMode) {
+      PDFView.exitPresentationMode();
     }
   }
 
-  window.addEventListener('fullscreenchange', fullscreenChange, false);
-  window.addEventListener('mozfullscreenchange', fullscreenChange, false);
-  window.addEventListener('webkitfullscreenchange', fullscreenChange, false);
+  window.addEventListener('fullscreenchange', presentationModeChange, false);
+  window.addEventListener('mozfullscreenchange', presentationModeChange, false);
+  window.addEventListener('webkitfullscreenchange', presentationModeChange,
+                          false);
 })();
 
 (function animationStartedClosure() {
   // The offsetParent is not set until the pdf.js iframe or object is visible.
   // Waiting for first animation.
   var requestAnimationFrame = window.requestAnimationFrame ||
                               window.mozRequestAnimationFrame ||
                               window.webkitRequestAnimationFrame ||
--- a/browser/extensions/pdfjs/extension-files
+++ b/browser/extensions/pdfjs/extension-files
@@ -1,12 +1,13 @@
 chrome.manifest
 components/PdfRedirector.js
 components/PdfStreamConverter.js
 content/build/pdf.js
+content/network.js
 content/PdfJs.jsm
 content/web/debugger.js
 content/web/images/annotation-check.svg
 content/web/images/annotation-comment.svg
 content/web/images/annotation-help.svg
 content/web/images/annotation-insert.svg
 content/web/images/annotation-key.svg
 content/web/images/annotation-newparagraph.svg
@@ -17,23 +18,23 @@ content/web/images/findbarButton-next.pn
 content/web/images/findbarButton-previous-rtl.png
 content/web/images/findbarButton-previous.png
 content/web/images/loading-icon.gif
 content/web/images/loading-small.png
 content/web/images/shadow.png
 content/web/images/texture.png
 content/web/images/toolbarButton-bookmark.png
 content/web/images/toolbarButton-download.png
-content/web/images/toolbarButton-fullscreen.png
 content/web/images/toolbarButton-menuArrows.png
 content/web/images/toolbarButton-openFile.png
 content/web/images/toolbarButton-pageDown-rtl.png
 content/web/images/toolbarButton-pageDown.png
 content/web/images/toolbarButton-pageUp-rtl.png
 content/web/images/toolbarButton-pageUp.png
+content/web/images/toolbarButton-presentationMode.png
 content/web/images/toolbarButton-print.png
 content/web/images/toolbarButton-search.png
 content/web/images/toolbarButton-sidebarToggle.png
 content/web/images/toolbarButton-viewOutline.png
 content/web/images/toolbarButton-viewThumbnail.png
 content/web/images/toolbarButton-zoomIn.png
 content/web/images/toolbarButton-zoomOut.png
 content/web/l10n.js
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -105,19 +105,20 @@ page_scale_auto=Automatic Zoom
 page_scale_actual=Actual Size
 
 # Loading indicator messages
 loading_error_indicator=Error
 loading_error=An error occurred while loading the PDF.
 invalid_file_error=Invalid or corrupted PDF file.
 missing_file_error=Missing PDF file.
 
-# LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
+# LOCALIZATION NOTE (text_annotation_type.alt): This is used as a tooltip.
 # "{{type}}" will be replaced with an annotation type from a list defined in
 # the PDF spec (32000-1:2008 Table 169 – Annotation types).
 # Some common types are e.g.: "Check", "Text", "Comment", "Note"
-text_annotation_type=[{{type}} Annotation]
+text_annotation_type.alt=[{{type}} Annotation]
 request_password=PDF is protected by a password:
+invalid_password=Invalid Password.
 
 printing_not_supported=Warning: Printing is not fully supported by this browser.
 printing_not_ready=Warning: The PDF is not fully loaded for printing.
 web_fonts_disabled=Web fonts are disabled: unable to use embedded PDF fonts.
-web_colors_disabled=Web colors are disabled.
+document_colors_disabled=PDF documents are not allowed to use their own colors: \'Allow pages to choose their own colors\' is deactivated in the browser.
--- a/js/xpconnect/idl/nsIScriptError.idl
+++ b/js/xpconnect/idl/nsIScriptError.idl
@@ -6,17 +6,17 @@
 /*
  * nsIConsoleMessage subclass for representing JavaScript errors and warnings.
  */
 
 
 #include "nsISupports.idl"
 #include "nsIConsoleMessage.idl"
 
-[scriptable, uuid(cac9d8e8-0d53-4fa8-9903-bb367e4fa1fe)]
+[scriptable, uuid(ec640482-be5f-49a0-a9cb-c87eacce9291)]
 interface nsIScriptError : nsIConsoleMessage
 {
     /** pseudo-flag for default case */
     const unsigned long errorFlag = 0x0;
 
     /** message is warning */
     const unsigned long warningFlag = 0x1;
 
@@ -36,16 +36,43 @@ interface nsIScriptError : nsIConsoleMes
     readonly attribute AString errorMessage;
 
     readonly attribute AString sourceName;
     readonly attribute AString sourceLine;
     readonly attribute uint32_t lineNumber;
     readonly attribute uint32_t columnNumber;
     readonly attribute uint32_t flags;
 
+    /**
+     * Categories I know about -
+     * XUL javascript
+     * content javascript (both of these from nsDocShell, currently)
+     * component javascript (errors in JS components)
+     */
+    readonly attribute string category;
+
+    /*
+       The time (in milliseconds from the Epoch) that the script error instance
+       was initialised, and thus the time when the error occurred.
+       Currently used to display date and time of the message in Error console.
+       The timestamp is initialized as JS_now/1000 so that it can be
+       compared to Date.now in Javascript.
+     */
+    readonly attribute long long timeStamp;
+
+    /* Get the window id this was initialized with.  Zero will be
+       returned if init() was used instead of initWithWindowID(). */
+    readonly attribute unsigned long long outerWindowID;
+
+    /* Get the inner window id this was initialized with.  Zero will be
+       returned if init() was used instead of initWithWindowID(). */
+    readonly attribute unsigned long long innerWindowID;
+
+    readonly attribute boolean isFromPrivateWindow;
+
     void init(in AString message,
               in AString sourceName,
               in AString sourceLine,
               in uint32_t lineNumber,
               in uint32_t columnNumber,
               in uint32_t flags,
               in string category);
 
@@ -55,16 +82,18 @@ interface nsIScriptError : nsIConsoleMes
     void initWithWindowID(in AString message,
                           in AString sourceName,
                           in AString sourceLine,
                           in uint32_t lineNumber,
                           in uint32_t columnNumber,
                           in uint32_t flags,
                           in string category,
                           in unsigned long long innerWindowID);
+
+    AUTF8String toString();
 };
 
 %{ C++
 #define NS_SCRIPTERROR_CID \
-{ 0x1950539a, 0x90f0, 0x4d22, { 0xb5, 0xaf, 0x71, 0x32, 0x9c, 0x68, 0xfa, 0x35 }}
+{ 0xe38e53b9, 0x5bb0, 0x456a, { 0xb5, 0x53, 0x57, 0x93, 0x70, 0xcb, 0x15, 0x67 }}
 
 #define NS_SCRIPTERROR_CONTRACTID "@mozilla.org/scripterror;1"
 %}
--- a/js/xpconnect/src/nsScriptError.cpp
+++ b/js/xpconnect/src/nsScriptError.cpp
@@ -88,26 +88,16 @@ nsScriptError::GetFlags(uint32_t *result
 }
 
 NS_IMETHODIMP
 nsScriptError::GetCategory(char **result) {
     *result = ToNewCString(mCategory);
     return NS_OK;
 }
 
-// nsIConsoleMessage method
-NS_IMETHODIMP
-nsScriptError::InitMessage(const nsAString& message,
-                           const char *category,
-                           uint64_t innerWindowID)
-{
-    return InitWithWindowID(message, EmptyString(), EmptyString(), 0, 0, 0,
-                            category, innerWindowID);
-}
-
 NS_IMETHODIMP
 nsScriptError::Init(const nsAString& message,
                     const nsAString& sourceName,
                     const nsAString& sourceLine,
                     uint32_t lineNumber,
                     uint32_t columnNumber,
                     uint32_t flags,
                     const char *category)
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -169,17 +169,16 @@ const ThreadStateTypes = {
 /**
  * Set of protocol messages that are sent by the server without a prior request
  * by the client.
  */
 const UnsolicitedNotifications = {
   "consoleAPICall": "consoleAPICall",
   "eventNotification": "eventNotification",
   "fileActivity": "fileActivity",
-  "logMessage": "logMessage",
   "networkEvent": "networkEvent",
   "networkEventUpdate": "networkEventUpdate",
   "newGlobal": "newGlobal",
   "newScript": "newScript",
   "newSource": "newSource",
   "tabDetached": "tabDetached",
   "tabNavigated": "tabNavigated",
   "pageError": "pageError",
--- a/toolkit/devtools/webconsole/WebConsoleUtils.jsm
+++ b/toolkit/devtools/webconsole/WebConsoleUtils.jsm
@@ -34,17 +34,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/devtools/gDevTools.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "devtools",
                                   "resource://gre/modules/devtools/Loader.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
                                   "resource:///modules/devtools/VariablesView.jsm");
 
 this.EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider", "JSTermHelpers",
-                         "ConsoleServiceListener", "ConsoleAPIListener",
+                         "PageErrorListener", "ConsoleAPIListener",
                          "NetworkResponseListener", "NetworkMonitor",
                          "ConsoleProgressListener"];
 
 // Match the function name from the result of toString() or toSource().
 //
 // Examples:
 // (function foobar(a, b) { ...
 // function foobar2(a) { ...
@@ -868,104 +868,101 @@ function getMatchedProps(aObj, aOptions 
 return JSPropertyProvider;
 })(WebConsoleUtils);
 
 ///////////////////////////////////////////////////////////////////////////////
 // The page errors listener
 ///////////////////////////////////////////////////////////////////////////////
 
 /**
- * The nsIConsoleService listener. This is used to send all of the console
- * messages (JavaScript, CSS and more) to the remote Web Console instance.
+ * The nsIConsoleService listener. This is used to send all the page errors
+ * (JavaScript, CSS and more) to the remote Web Console instance.
  *
  * @constructor
  * @param nsIDOMWindow [aWindow]
  *        Optional - the window object for which we are created. This is used
  *        for filtering out messages that belong to other windows.
  * @param object aListener
- *        The listener object must have one method:
- *        - onConsoleServiceMessage(). This method is invoked with one argument, the
- *        nsIConsoleMessage, whenever a relevant message is received.
+ *        The listener object must have a method: onPageError. This method is
+ *        invoked with one argument, the nsIScriptError, whenever a relevant
+ *        page error is received.
  */
-this.ConsoleServiceListener = function ConsoleServiceListener(aWindow, aListener)
+this.PageErrorListener = function PageErrorListener(aWindow, aListener)
 {
   this.window = aWindow;
   this.listener = aListener;
 }
 
-ConsoleServiceListener.prototype =
+PageErrorListener.prototype =
 {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
 
   /**
    * The content window for which we listen to page errors.
    * @type nsIDOMWindow
    */
   window: null,
 
   /**
-   * The listener object which is notified of messages from the console service.
+   * The listener object which is notified of page errors. It must have
+   * a onPageError method which is invoked with one argument: the nsIScriptError.
    * @type object
    */
   listener: null,
 
   /**
    * Initialize the nsIConsoleService listener.
    */
-  init: function CSL_init()
+  init: function PEL_init()
   {
     Services.console.registerListener(this);
   },
 
   /**
    * The nsIConsoleService observer. This method takes all the script error
    * messages belonging to the current window and sends them to the remote Web
    * Console instance.
    *
-   * @param nsIConsoleMessage aMessage
-   *        The message object coming from the nsIConsoleService.
+   * @param nsIScriptError aScriptError
+   *        The script error object coming from the nsIConsoleService.
    */
-  observe: function CSL_observe(aMessage)
+  observe: function PEL_observe(aScriptError)
   {
-    if (!this.listener) {
+    if (!this.listener ||
+        !(aScriptError instanceof Ci.nsIScriptError)) {
       return;
     }
 
     if (this.window) {
-      if (!aMessage.outerWindowID ||
-          !this.isCategoryAllowed(aMessage.category)) {
+      if (!aScriptError.outerWindowID ||
+          !this.isCategoryAllowed(aScriptError.category)) {
         return;
       }
 
-      let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
+      let errorWindow =
+        Services.wm.getOuterWindowWithId(aScriptError.outerWindowID);
       if (!errorWindow || errorWindow.top != this.window) {
         return;
       }
     }
 
-    if (aMessage.message) {
-      this.listener.onConsoleServiceMessage(aMessage);
-    }
+    this.listener.onPageError(aScriptError);
   },
 
   /**
-   * Check if the given message category is allowed to be tracked or not.
+   * Check if the given script error category is allowed to be tracked or not.
    * We ignore chrome-originating errors as we only care about content.
    *
    * @param string aCategory
-   *        The message category you want to check.
+   *        The nsIScriptError category you want to check.
    * @return boolean
    *         True if the category is allowed to be logged, false otherwise.
    */
-  isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
+  isCategoryAllowed: function PEL_isCategoryAllowed(aCategory)
   {
-    if (!aCategory) {
-      return false;
-    }
-
     switch (aCategory) {
       case "XPConnect JavaScript":
       case "component javascript":
       case "chrome javascript":
       case "chrome registration":
       case "XBL":
       case "XBL Prototype Handler":
       case "XBL Content Sink":
@@ -975,36 +972,38 @@ ConsoleServiceListener.prototype =
 
     return true;
   },
 
   /**
    * Get the cached page errors for the current inner window.
    *
    * @return array
-   *         The array of cached messages. Each element is an nsIScriptError or
-   *         an nsIConsoleMessage.
+   *         The array of cached messages. Each element is an nsIScriptError
+   *         with an added _type property so the remote Web Console instance can
+   *         tell the difference between various types of cached messages.
    */
-  getCachedMessages: function CSL_getCachedMessages()
+  getCachedMessages: function PEL_getCachedMessages()
   {
     let innerWindowId = this.window ?
                         WebConsoleUtils.getInnerWindowId(this.window) : null;
     let errors = Services.console.getMessageArray() || [];
 
-    return errors.filter((aError) => {
-      return !innerWindowId ||
+    return errors.filter(function(aError) {
+      return aError instanceof Ci.nsIScriptError &&
+             (!innerWindowId ||
               (aError.innerWindowID == innerWindowId &&
-               this.isCategoryAllowed(aError.category));
-    });
+               this.isCategoryAllowed(aError.category)));
+    }, this);
   },
 
   /**
    * Remove the nsIConsoleService listener.
    */
-  destroy: function CSL_destroy()
+  destroy: function PEL_destroy()
   {
     Services.console.unregisterListener(this);
     this.listener = this.window = null;
   },
 };
 
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -1087,17 +1086,18 @@ ConsoleAPIListener.prototype =
 
     this.owner.onConsoleAPICall(apiMessage);
   },
 
   /**
    * Get the cached messages for the current inner window.
    *
    * @return array
-   *         The array of cached messages.
+   *         The array of cached messages. Each element is a Console API
+   *         prepared to be sent to the remote Web Console instance.
    */
   getCachedMessages: function CAL_getCachedMessages()
   {
     let innerWindowId = this.window ?
                         WebConsoleUtils.getInnerWindowId(this.window) : null;
     return ConsoleAPIStorage.getEvents(innerWindowId);
   },
 
--- a/toolkit/devtools/webconsole/dbg-webconsole-actors.js
+++ b/toolkit/devtools/webconsole/dbg-webconsole-actors.js
@@ -13,17 +13,17 @@ let Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
                                   "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener",
+XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
                                   "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIListener",
                                   "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ConsoleProgressListener",
                                   "resource://gre/modules/devtools/WebConsoleUtils.jsm");
 
@@ -160,20 +160,20 @@ WebConsoleActor.prototype =
    * The content window we work with.
    * @type nsIDOMWindow
    */
   get window() this._window,
 
   _window: null,
 
   /**
-   * The ConsoleServiceListener instance.
+   * The PageErrorListener instance.
    * @type object
    */
-  consoleServiceListener: null,
+  pageErrorListener: null,
 
   /**
    * The ConsoleAPIListener instance.
    */
   consoleAPIListener: null,
 
   /**
    * The NetworkMonitor instance.
@@ -206,19 +206,19 @@ WebConsoleActor.prototype =
   _findProtoChain: ThreadActor.prototype._findProtoChain,
   _removeFromProtoChain: ThreadActor.prototype._removeFromProtoChain,
 
   /**
    * Destroy the current WebConsoleActor instance.
    */
   disconnect: function WCA_disconnect()
   {
-    if (this.consoleServiceListener) {
-      this.consoleServiceListener.destroy();
-      this.consoleServiceListener = null;
+    if (this.pageErrorListener) {
+      this.pageErrorListener.destroy();
+      this.pageErrorListener = null;
     }
     if (this.consoleAPIListener) {
       this.consoleAPIListener.destroy();
       this.consoleAPIListener = null;
     }
     if (this.networkMonitor) {
       this.networkMonitor.destroy();
       this.networkMonitor = null;
@@ -358,20 +358,20 @@ WebConsoleActor.prototype =
   {
     let startedListeners = [];
     let window = !this._isGlobalActor ? this.window : null;
 
     while (aRequest.listeners.length > 0) {
       let listener = aRequest.listeners.shift();
       switch (listener) {
         case "PageError":
-          if (!this.consoleServiceListener) {
-            this.consoleServiceListener =
-              new ConsoleServiceListener(window, this);
-            this.consoleServiceListener.init();
+          if (!this.pageErrorListener) {
+            this.pageErrorListener =
+              new PageErrorListener(window, this);
+            this.pageErrorListener.init();
           }
           startedListeners.push(listener);
           break;
         case "ConsoleAPI":
           if (!this.consoleAPIListener) {
             this.consoleAPIListener =
               new ConsoleAPIListener(window, this);
             this.consoleAPIListener.init();
@@ -421,19 +421,19 @@ WebConsoleActor.prototype =
     let toDetach = aRequest.listeners ||
                    ["PageError", "ConsoleAPI", "NetworkActivity",
                     "FileActivity"];
 
     while (toDetach.length > 0) {
       let listener = toDetach.shift();
       switch (listener) {
         case "PageError":
-          if (this.consoleServiceListener) {
-            this.consoleServiceListener.destroy();
-            this.consoleServiceListener = null;
+          if (this.pageErrorListener) {
+            this.pageErrorListener.destroy();
+            this.pageErrorListener = null;
           }
           stoppedListeners.push(listener);
           break;
         case "ConsoleAPI":
           if (this.consoleAPIListener) {
             this.consoleAPIListener.destroy();
             this.consoleAPIListener = null;
           }
@@ -490,34 +490,23 @@ WebConsoleActor.prototype =
             cache.forEach(function(aMessage) {
               let message = this.prepareConsoleMessageForRemote(aMessage);
               message._type = type;
               messages.push(message);
             }, this);
           }
           break;
         case "PageError":
-          if (this.consoleServiceListener) {
-            let cache = this.consoleServiceListener.getCachedMessages();
-            cache.forEach((aMessage) => {
-              let message = null;
-              if (aMessage instanceof Ci.nsIScriptError) {
-                message = this.preparePageErrorForRemote(aMessage);
-                message._type = type;
-              }
-              else {
-                message = {
-                  _type: "LogMessage",
-                  message: aMessage.message,
-                  timeStamp: aMessage.timeStamp,
-                  category: aMessage.category,
-                };
-              }
+          if (this.pageErrorListener) {
+            let cache = this.pageErrorListener.getCachedMessages();
+            cache.forEach(function(aMessage) {
+              let message = this.preparePageErrorForRemote(aMessage);
+              message._type = type;
               messages.push(message);
-            });
+            }, this);
           }
           break;
       }
     }
 
     messages.sort(function(a, b) { return a.timeStamp - b.timeStamp; });
 
     return {
@@ -601,20 +590,16 @@ WebConsoleActor.prototype =
    * The "clearMessagesCache" request handler.
    */
   onClearMessagesCache: function WCA_onClearMessagesCache()
   {
     // TODO: Bug 717611 - Web Console clear button does not clear cached errors
     let windowId = !this._isGlobalActor ?
                    WebConsoleUtils.getInnerWindowId(this.window) : null;
     ConsoleAPIStorage.clearEvents(windowId);
-    if (this._isGlobalActor) {
-      Services.console.logStringMessage(null); // for the Error Console
-      Services.console.reset();
-    }
     return {};
   },
 
   /**
    * The "setPreferences" request handler.
    *
    * @param object aRequest
    *        The request message - which preferences need to be updated.
@@ -862,41 +847,29 @@ WebConsoleActor.prototype =
     };
   },
 
   //////////////////
   // Event handlers for various listeners.
   //////////////////
 
   /**
-   * Handler for messages received from the ConsoleServiceListener. This method
-   * sends the nsIConsoleMessage to the remote Web Console client.
+   * Handler for page errors received from the PageErrorListener. This method
+   * sends the nsIScriptError to the remote Web Console client.
    *
-   * @param nsIConsoleMessage aMessage
-   *        The message we need to send to the client.
+   * @param nsIScriptError aPageError
+   *        The page error we need to send to the client.
    */
-  onConsoleServiceMessage: function WCA_onConsoleServiceMessage(aMessage)
+  onPageError: function WCA_onPageError(aPageError)
   {
-    let packet;
-    if (aMessage instanceof Ci.nsIScriptError) {
-      packet = {
-        from: this.actorID,
-        type: "pageError",
-        pageError: this.preparePageErrorForRemote(aMessage),
-      };
-    }
-    else {
-      packet = {
-        from: this.actorID,
-        type: "logMessage",
-        message: aMessage.message,
-        timeStamp: aMessage.timeStamp,
-        category: aMessage.category,
-      };
-    }
+    let packet = {
+      from: this.actorID,
+      type: "pageError",
+      pageError: this.preparePageErrorForRemote(aPageError),
+    };
     this.conn.send(packet);
   },
 
   /**
    * Prepare an nsIScriptError to be sent to the client.
    *
    * @param nsIScriptError aPageError
    *        The page error we need to send to the client.
--- a/toolkit/devtools/webconsole/test/Makefile.in
+++ b/toolkit/devtools/webconsole/test/Makefile.in
@@ -19,16 +19,15 @@ MOCHITEST_CHROME_FILES = \
     test_object_actor.html \
     test_network_get.html \
     test_network_post.html \
     test_network_longstring.html \
     test_file_uri.html \
     test_bug819670_getter_throws.html \
     test_object_actor_native_getters.html \
     test_object_actor_native_getters_lenient_this.html \
-    test_nsiconsolemessage.html \
     network