diff --git a/Makefile b/Makefile
index 34a7930a3..c1062d49d 100644
--- a/Makefile
+++ b/Makefile
@@ -209,11 +209,11 @@ pages-repo: | $(BUILD_DIR)
 # copy of the pdf.js source.
 CONTENT_DIR := content
 BUILD_NUMBER := `git log --format=oneline $(EXTENSION_BASE_VERSION).. | wc -l | awk '{print $$1}'`
-PDF_WEB_FILES = \
+EXTENSION_WEB_FILES = \
 	web/images \
-	web/compatibility.js \
 	web/viewer.css \
 	web/viewer.js \
+	web/viewer.html \
 	web/viewer-production.html \
 	$(NULL)
 
@@ -251,8 +251,19 @@ extension: | production
 	@cd extensions/firefox; cp -r $(FIREFOX_EXTENSION_FILES_TO_COPY) ../../$(FIREFOX_BUILD_DIR)/
 	# Copy a standalone version of pdf.js inside the content directory
 	@cp $(BUILD_TARGET) $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/
-	@cp -r $(PDF_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/
-	@mv -f $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html $(FIREFOX_BUILD_CONTENT)/web/viewer.html
+	@cp -r $(EXTENSION_WEB_FILES) $(FIREFOX_BUILD_CONTENT)/web/
+	@rm $(FIREFOX_BUILD_CONTENT)/web/viewer-production.html
+	# Copy over the firefox extension snippet so we can inline pdf.js in it
+	@cp web/viewer-snippet-firefox-extension.html $(FIREFOX_BUILD_CONTENT)/web/
+	# Modify the viewer so it does all the extension only stuff.
+	@cd $(FIREFOX_BUILD_CONTENT)/web; \
+	sed -i.bak '/PDFJSSCRIPT_INCLUDE_BUNDLE/ r ../build/pdf.js' viewer-snippet-firefox-extension.html; \
+	sed -i.bak '/PDFJSSCRIPT_REMOVE/d' viewer.html; \
+	sed -i.bak '/PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION/d' viewer.html; \
+	sed -i.bak '/PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION/ r viewer-snippet-firefox-extension.html' viewer.html; \
+	rm -f *.bak;
+	# We don't need pdf.js anymore since its inlined
+	@rm -Rf $(FIREFOX_BUILD_CONTENT)/$(BUILD_DIR)/;
 	# Update the build version number
 	@sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/install.rdf
 	@sed -i.bak "s/PDFJSSCRIPT_BUILD/$(BUILD_NUMBER)/" $(FIREFOX_BUILD_DIR)/update.rdf
@@ -274,7 +285,7 @@ extension: | production
 	@cp -R $(CHROME_EXTENSION_FILES) $(CHROME_BUILD_DIR)/
 	# Copy a standalone version of pdf.js inside the content directory
 	@cp $(BUILD_TARGET) $(CHROME_BUILD_CONTENT)/$(BUILD_DIR)/
-	@cp -r $(PDF_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/
+	@cp -r $(EXTENSION_WEB_FILES) $(CHROME_BUILD_CONTENT)/web/
 	@mv -f $(CHROME_BUILD_CONTENT)/web/viewer-production.html $(CHROME_BUILD_CONTENT)/web/viewer.html
 
   # Create the crx
diff --git a/README.md b/README.md
index f12fce934..09cc95039 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ rendering PDFs, and eventually release a PDF reader extension powered by
 pdf.js. Integration with Firefox is a possibility if the experiment proves 
 successful.
 
-
+ 
 
 # Getting started
 
diff --git a/extensions/firefox/bootstrap.js b/extensions/firefox/bootstrap.js
index e51df28f8..f1a712c0c 100644
--- a/extensions/firefox/bootstrap.js
+++ b/extensions/firefox/bootstrap.js
@@ -3,6 +3,8 @@
 
 'use strict';
 
+const EXT_PREFIX = 'extensions.uriloader@pdf.js';
+const PDFJS_EVENT_ID = 'pdf.js.message';
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cm = Components.manager;
@@ -14,6 +16,7 @@ function log(str) {
   dump(str + '\n');
 }
 
+
 function startup(aData, aReason) {
   let manifestPath = 'chrome.manifest';
   let manifest = Cc['@mozilla.org/file/local;1']
@@ -34,13 +37,11 @@ function shutdown(aData, aReason) {
 }
 
 function install(aData, aReason) {
-  let url = 'chrome://pdf.js/content/web/viewer.html?file=%s';
-  Services.prefs.setCharPref('extensions.pdf.js.url', url);
   Services.prefs.setBoolPref('extensions.pdf.js.active', false);
 }
 
 function uninstall(aData, aReason) {
-  Services.prefs.clearUserPref('extensions.pdf.js.url');
   Services.prefs.clearUserPref('extensions.pdf.js.active');
+  application.prefs.setValue(EXT_PREFIX + '.database', '{}');
 }
 
diff --git a/extensions/firefox/chrome.manifest b/extensions/firefox/chrome.manifest
index d7db20b38..5351257e7 100644
--- a/extensions/firefox/chrome.manifest
+++ b/extensions/firefox/chrome.manifest
@@ -1,5 +1,5 @@
-content pdf.js content/
+resource pdf.js content/
 
-component {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a} components/pdfContentHandler.js
-contract @mozilla.org/uriloader/content-handler;1?type=application/pdf {2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}
+component {6457a96b-2d68-439a-bcfa-44465fbcdbb1} components/PdfStreamConverter.js
+contract @mozilla.org/streamconv;1?from=application/pdf&to=*/* {6457a96b-2d68-439a-bcfa-44465fbcdbb1}
 
diff --git a/extensions/firefox/components/PdfStreamConverter.js b/extensions/firefox/components/PdfStreamConverter.js
new file mode 100644
index 000000000..984915d23
--- /dev/null
+++ b/extensions/firefox/components/PdfStreamConverter.js
@@ -0,0 +1,159 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+const PDFJS_EVENT_ID = 'pdf.js.message';
+const PDF_CONTENT_TYPE = 'application/pdf';
+const NS_ERROR_NOT_IMPLEMENTED = 0x80004001;
+const EXT_PREFIX = 'extensions.uriloader@pdf.js';
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+function log(aMsg) {
+  let msg = 'PdfStreamConverter.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
+  Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService)
+                                     .logStringMessage(msg);
+  dump(msg + '\n');
+}
+let application = Cc['@mozilla.org/fuel/application;1']
+                    .getService(Ci.fuelIApplication);
+let privateBrowsing = Cc['@mozilla.org/privatebrowsing;1']
+                        .getService(Ci.nsIPrivateBrowsingService);
+let inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled;
+
+// All the priviledged actions.
+function ChromeActions() {
+  this.inPrivateBrowswing = privateBrowsing.privateBrowsingEnabled;
+}
+ChromeActions.prototype = {
+  download: function(data) {
+    Services.wm.getMostRecentWindow('navigator:browser').saveURL(data);
+  },
+  setDatabase: function(data) {
+    if (this.inPrivateBrowswing)
+      return;
+    application.prefs.setValue(EXT_PREFIX + '.database', data);
+  },
+  getDatabase: function() {
+    if (this.inPrivateBrowswing)
+      return '{}';
+    return application.prefs.getValue(EXT_PREFIX + '.database', '{}');
+  }
+};
+
+// Event listener to trigger chrome privedged code.
+function RequestListener(actions) {
+  this.actions = actions;
+}
+// Receive an event and synchronously responds.
+RequestListener.prototype.receive = function(event) {
+  var message = event.target;
+  var action = message.getUserData('action');
+  var data = message.getUserData('data');
+  var actions = this.actions;
+  if (!(action in actions)) {
+    log('Unknown action: ' + action);
+    return;
+  }
+  var response = actions[action].call(this.actions, data);
+  message.setUserData('response', response, null);
+};
+
+
+function PdfStreamConverter() {
+}
+
+PdfStreamConverter.prototype = {
+
+  // properties required for XPCOM registration:
+  classID: Components.ID('{6457a96b-2d68-439a-bcfa-44465fbcdbb1}'),
+  classDescription: 'pdf.js Component',
+  contractID: '@mozilla.org/streamconv;1?from=application/pdf&to=*/*',
+
+  QueryInterface: XPCOMUtils.generateQI([
+      Ci.nsISupports,
+      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.
+   */
+
+  // nsIStreamConverter::convert
+  convert: function(aFromStream, aFromType, aToType, aCtxt) {
+    return aFromStream;
+  },
+
+  // nsIStreamConverter::asyncConvertData
+  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
+    if (!Services.prefs.getBoolPref('extensions.pdf.js.active'))
+      throw NS_ERROR_NOT_IMPLEMENTED;
+    // Store the listener passed to us
+    this.listener = aListener;
+  },
+
+  // nsIStreamListener::onDataAvailable
+  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+    // Do nothing since all the data loading is handled by the viewer.
+    log('SANITY CHECK: onDataAvailable SHOULD NOT BE CALLED!');
+  },
+
+  // nsIRequestObserver::onStartRequest
+  onStartRequest: function(aRequest, aContext) {
+
+    // Setup the request so we can use it below.
+    aRequest.QueryInterface(Ci.nsIChannel);
+    // Cancel the request so the viewer can handle it.
+    aRequest.cancel(Cr.NS_BINDING_ABORTED);
+
+    // Create a new channel that is viewer loaded as a resource.
+    var ioService = Cc['@mozilla.org/network/io-service;1']
+                      .getService(Ci.nsIIOService);
+    var channel = ioService.newChannel(
+                    'resource://pdf.js/web/viewer.html', null, null);
+
+    // Keep the URL the same so the browser sees it as the same.
+    channel.originalURI = aRequest.originalURI;
+    channel.asyncOpen(this.listener, aContext);
+
+    // Setup a global listener waiting for the next DOM to be created and verfiy
+    // that its the one we want by its URL. When the correct DOM is found create
+    // an event listener on that window for the pdf.js events that require
+    // chrome priviledges.
+    var url = aRequest.originalURI.spec;
+    var gb = Services.wm.getMostRecentWindow('navigator:browser');
+    var domListener = function domListener(event) {
+      var doc = event.originalTarget;
+      var win = doc.defaultView;
+      if (doc.location.href === url) {
+        gb.removeEventListener('DOMContentLoaded', domListener);
+        var requestListener = new RequestListener(new ChromeActions());
+        win.addEventListener(PDFJS_EVENT_ID, function(event) {
+          requestListener.receive(event);
+        }, false, true);
+      }
+    };
+    gb.addEventListener('DOMContentLoaded', domListener, false);
+  },
+
+  // nsIRequestObserver::onStopRequest
+  onStopRequest: function(aRequest, aContext, aStatusCode) {
+    // Do nothing.
+  }
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([PdfStreamConverter]);
diff --git a/extensions/firefox/components/pdfContentHandler.js b/extensions/firefox/components/pdfContentHandler.js
deleted file mode 100644
index 67459b759..000000000
--- a/extensions/firefox/components/pdfContentHandler.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
-
-'use strict';
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-
-const PDF_CONTENT_TYPE = 'application/pdf';
-
-Cu.import('resource://gre/modules/XPCOMUtils.jsm');
-Cu.import('resource://gre/modules/Services.jsm');
-
-function log(aMsg) {
-  let msg = 'pdfContentHandler.js: ' + (aMsg.join ? aMsg.join('') : aMsg);
-  Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService)
-                                     .logStringMessage(msg);
-  dump(msg + '\n');
-}
-
-const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
-function pdfContentHandler() {
-}
-
-pdfContentHandler.prototype = {
-  handleContent: function handleContent(aMimetype, aContext, aRequest) {
-    if (aMimetype != PDF_CONTENT_TYPE)
-      throw NS_ERROR_WONT_HANDLE_CONTENT;
-
-    if (!(aRequest instanceof Ci.nsIChannel))
-      throw NS_ERROR_WONT_HANDLE_CONTENT;
-
-    if (!Services.prefs.getBoolPref('extensions.pdf.js.active'))
-      throw NS_ERROR_WONT_HANDLE_CONTENT;
-
-    let window = null;
-    let callbacks = aRequest.notificationCallbacks ||
-                    aRequest.loadGroup.notificationCallbacks;
-    if (!callbacks)
-      return;
-
-    window = callbacks.getInterface(Ci.nsIDOMWindow);
-
-    let url = null;
-    try {
-      url = Services.prefs.getCharPref('extensions.pdf.js.url');
-    } catch (e) {
-      log('Error retrieving the pdf.js base url - ' + e);
-      throw NS_ERROR_WONT_HANDLE_CONTENT;
-    }
-
-    let targetUrl = aRequest.URI.spec;
-    if (targetUrl.indexOf('#pdfjs.action=download') >= 0)
-      throw NS_ERROR_WONT_HANDLE_CONTENT;
-
-    aRequest.cancel(Cr.NS_BINDING_ABORTED);
-    window.location = url.replace('%s', encodeURIComponent(targetUrl));
-  },
-
-  classID: Components.ID('{2278dfd0-b75c-11e0-8257-1ba3d93c9f1a}'),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentHandler])
-};
-
-var NSGetFactory = XPCOMUtils.generateNSGetFactory([pdfContentHandler]);
-
diff --git a/src/canvas.js b/src/canvas.js
index 5ef900861..d0b0064f6 100644
--- a/src/canvas.js
+++ b/src/canvas.js
@@ -548,7 +548,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
       var fontObj = this.objs.get(fontRefName).fontObj;
 
       if (!fontObj) {
-        throw 'Can\'t find font for ' + fontRefName;
+        error('Can\'t find font for ' + fontRefName);
       }
 
       var name = fontObj.loadedName || 'sans-serif';
@@ -866,7 +866,7 @@ var CanvasGraphics = (function CanvasGraphicsClosure() {
       } else if (IR[0] == 'RadialAxial' || IR[0] == 'Dummy') {
         var pattern = Pattern.shadingFromIR(this.ctx, IR);
       } else {
-        throw 'Unkown IR type';
+        error('Unkown IR type ' + IR[0]);
       }
       return pattern;
     },
diff --git a/src/colorspace.js b/src/colorspace.js
index 827fd2e19..d67d928b1 100644
--- a/src/colorspace.js
+++ b/src/colorspace.js
@@ -369,55 +369,16 @@ var DeviceCmykCS = (function DeviceCmykCSClosure() {
   DeviceCmykCS.prototype = {
     getRgb: function cmykcs_getRgb(color) {
       var c = color[0], m = color[1], y = color[2], k = color[3];
-      var c1 = 1 - c, m1 = 1 - m, y1 = 1 - y, k1 = 1 - k;
-
-      var x, r, g, b;
-      // this is a matrix multiplication, unrolled for performance
-      // code is taken from the poppler implementation
-      x = c1 * m1 * y1 * k1; // 0 0 0 0
-      r = g = b = x;
-      x = c1 * m1 * y1 * k;  // 0 0 0 1
-      r += 0.1373 * x;
-      g += 0.1216 * x;
-      b += 0.1255 * x;
-      x = c1 * m1 * y * k1;  // 0 0 1 0
-      r += x;
-      g += 0.9490 * x;
-      x = c1 * m1 * y * k;   // 0 0 1 1
-      r += 0.1098 * x;
-      g += 0.1020 * x;
-      x = c1 * m * y1 * k1;  // 0 1 0 0
-      r += 0.9255 * x;
-      b += 0.5490 * x;
-      x = c1 * m * y1 * k;   // 0 1 0 1
-      r += 0.1412 * x;
-      x = c1 * m * y * k1;   // 0 1 1 0
-      r += 0.9294 * x;
-      g += 0.1098 * x;
-      b += 0.1412 * x;
-      x = c1 * m * y * k;    // 0 1 1 1
-      r += 0.1333 * x;
-      x = c * m1 * y1 * k1;  // 1 0 0 0
-      g += 0.6784 * x;
-      b += 0.9373 * x;
-      x = c * m1 * y1 * k;   // 1 0 0 1
-      g += 0.0588 * x;
-      b += 0.1412 * x;
-      x = c * m1 * y * k1;   // 1 0 1 0
-      g += 0.6510 * x;
-      b += 0.3137 * x;
-      x = c * m1 * y * k;    // 1 0 1 1
-      g += 0.0745 * x;
-      x = c * m * y1 * k1;   // 1 1 0 0
-      r += 0.1804 * x;
-      g += 0.1922 * x;
-      b += 0.5725 * x;
-      x = c * m * y1 * k;    // 1 1 0 1
-      b += 0.0078 * x;
-      x = c * m * y * k1;    // 1 1 1 0
-      r += 0.2118 * x;
-      g += 0.2119 * x;
-      b += 0.2235 * x;
+
+      // CMYK -> CMY: http://www.easyrgb.com/index.php?X=MATH&H=14#text14
+      c = (c * (1 - k) + k);
+      m = (m * (1 - k) + k);
+      y = (y * (1 - k) + k);
+
+      // CMY -> RGB: http://www.easyrgb.com/index.php?X=MATH&H=12#text12
+      var r = (1 - c);
+      var g = (1 - m);
+      var b = (1 - y);
 
       return [r, g, b];
     },
diff --git a/src/core.js b/src/core.js
index 7a9f3ee03..cb601398e 100644
--- a/src/core.js
+++ b/src/core.js
@@ -410,14 +410,14 @@ var Page = (function PageClosure() {
             if (callback)
               callback(e);
             else
-              throw e;
+              error(e);
           }
         }.bind(this),
         function pageDisplayReadPromiseError(reason) {
           if (callback)
             callback(reason);
           else
-            throw reason;
+            error(reason);
         }
       );
     }
@@ -620,13 +620,23 @@ var PDFDoc = (function PDFDocClosure() {
     if (!globalScope.PDFJS.disableWorker && typeof Worker !== 'undefined') {
       var workerSrc = PDFJS.workerSrc;
       if (typeof workerSrc === 'undefined') {
-        throw 'No PDFJS.workerSrc specified';
+        error('No PDFJS.workerSrc specified');
       }
 
       try {
-        // Some versions of FF can't create a worker on localhost, see:
-        // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
-        var worker = new Worker(workerSrc);
+        var worker;
+        if (PDFJS.isFirefoxExtension) {
+          // The firefox extension can't load the worker from the resource://
+          // url so we have to inline the script and then use the blob loader.
+          var bb = new MozBlobBuilder();
+          bb.append(document.querySelector('#PDFJS_SCRIPT_TAG').textContent);
+          var blobUrl = window.URL.createObjectURL(bb.getBlob());
+          worker = new Worker(blobUrl);
+        } else {
+          // Some versions of FF can't create a worker on localhost, see:
+          // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
+          worker = new Worker(workerSrc);
+        }
 
         var messageHandler = new MessageHandler('main', worker);
 
@@ -645,7 +655,9 @@ var PDFDoc = (function PDFDocClosure() {
         // serializing the typed array.
         messageHandler.send('test', testObj);
         return;
-      } catch (e) {}
+      } catch (e) {
+        warn('The worker has been disabled.');
+      }
     }
     // Either workers are disabled, not supported or have thrown an exception.
     // Thus, we fallback to a faked worker.
@@ -716,7 +728,7 @@ var PDFDoc = (function PDFDocClosure() {
             });
             break;
           default:
-            throw 'Got unkown object type ' + type;
+            error('Got unkown object type ' + type);
         }
       }, this);
 
@@ -737,7 +749,7 @@ var PDFDoc = (function PDFDocClosure() {
         if (page.displayReadyPromise)
           page.displayReadyPromise.reject(data.error);
         else
-          throw data.error;
+          error(data.error);
       }, this);
 
       messageHandler.on('jpeg_decode', function(data, promise) {
diff --git a/src/evaluator.js b/src/evaluator.js
index 21530f42f..c70013d25 100644
--- a/src/evaluator.js
+++ b/src/evaluator.js
@@ -481,8 +481,10 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
           properties.cidToGidMap = this.readCidToGidMap(cidToGidMap);
       }
 
+      var flags = properties.flags;
       var differences = [];
-      var baseEncoding = Encodings.StandardEncoding;
+      var baseEncoding = !!(flags & FontFlags.Symbolic) ?
+                         Encodings.symbolsEncoding : Encodings.StandardEncoding;
       var hasEncoding = dict.has('Encoding');
       if (hasEncoding) {
         var encoding = xref.fetchIfRef(dict.get('Encoding'));
@@ -761,8 +763,9 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
           // Simulating descriptor flags attribute
           var fontNameWoStyle = baseFontName.split('-')[0];
           var flags = (serifFonts[fontNameWoStyle] ||
-            (fontNameWoStyle.search(/serif/gi) != -1) ? 2 : 0) |
-            (symbolsFonts[fontNameWoStyle] ? 4 : 32);
+            (fontNameWoStyle.search(/serif/gi) != -1) ? FontFlags.Serif : 0) |
+            (symbolsFonts[fontNameWoStyle] ? FontFlags.Symbolic :
+            FontFlags.Nonsymbolic);
 
           var properties = {
             type: type.name,
diff --git a/src/fonts.js b/src/fonts.js
index f96c15458..3f618b82a 100644
--- a/src/fonts.js
+++ b/src/fonts.js
@@ -19,6 +19,18 @@ var kPDFGlyphSpaceUnits = 1000;
 // Until hinting is fully supported this constant can be used
 var kHintingEnabled = false;
 
+var FontFlags = {
+  FixedPitch: 1,
+  Serif: 2,
+  Symbolic: 4,
+  Script: 8,
+  Nonsymbolic: 32,
+  Italic: 64,
+  AllCap: 65536,
+  SmallCap: 131072,
+  ForceBold: 262144
+};
+
 var Encodings = {
   get ExpertEncoding() {
     return shadow(this, 'ExpertEncoding', ['', '', '', '', '', '', '', '', '',
@@ -160,19 +172,20 @@ var Encodings = {
       'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore',
       'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
       'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
-      'braceleft', 'bar', 'braceright', 'asciitilde', '', '', 'exclamdown',
-      'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency',
-      'quotesingle', 'quotedblleft', 'guillemotleft', 'guilsinglleft',
-      'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', 'daggerdbl',
-      'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase',
-      'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis',
-      'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex',
-      'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring',
-      'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', 'emdash', '', '', '',
-      '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '',
-      'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE', 'ordmasculine',
-      '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', 'lslash',
-      'oslash', 'oe', 'germandbls'
+      'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '',
+      '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+      '', '', '', '', '', '', '', '', '', '', 'exclamdown', 'cent', 'sterling',
+      'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle',
+      'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi',
+      'fl', '', 'endash', 'dagger', 'daggerdbl', 'periodcentered', '',
+      'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright',
+      'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '',
+      'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent',
+      'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron',
+      'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '',
+      'AE', '', 'ordfeminine', '', '', '', '', 'Lslash', 'Oslash', 'OE',
+      'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '',
+      'lslash', 'oslash', 'oe', 'germandbls'
     ]);
   },
   get WinAnsiEncoding() {
@@ -405,6 +418,19 @@ var symbolsFonts = {
   'Dingbats': true, 'Symbol': true, 'ZapfDingbats': true
 };
 
+// Some characters, e.g. copyrightserif, mapped to the private use area and
+// might not be displayed using standard fonts. Mapping/hacking well-known chars
+// to the similar equivalents in the normal characters range.
+function mapPrivateUseChars(code) {
+  switch (code) {
+    case 0xF8E9: // copyrightsans
+    case 0xF6D9: // copyrightserif
+      return 0x00A9; // copyright
+    default:
+      return code;
+  }
+}
+
 var FontLoader = {
   listeningForFontLoad: false,
 
@@ -761,8 +787,8 @@ var Font = (function FontClosure() {
     var names = name.split('+');
     names = names.length > 1 ? names[1] : names[0];
     names = names.split(/[-,_]/g)[0];
-    this.isSerifFont = !!(properties.flags & 2);
-    this.isSymbolicFont = !!(properties.flags & 4);
+    this.isSerifFont = !!(properties.flags & FontFlags.Serif);
+    this.isSymbolicFont = !!(properties.flags & FontFlags.Symbolic);
 
     var type = properties.type;
     this.type = type;
@@ -2186,7 +2212,7 @@ var Font = (function FontClosure() {
         case 'CIDFontType0':
           if (this.noUnicodeAdaptation) {
             width = this.widths[this.unicodeToCID[charcode] || charcode];
-            unicode = charcode;
+            unicode = mapPrivateUseChars(charcode);
             break;
           }
           unicode = this.toUnicode[charcode] || charcode;
@@ -2194,7 +2220,7 @@ var Font = (function FontClosure() {
         case 'CIDFontType2':
           if (this.noUnicodeAdaptation) {
             width = this.widths[this.unicodeToCID[charcode] || charcode];
-            unicode = charcode;
+            unicode = mapPrivateUseChars(charcode);
             break;
           }
           unicode = this.toUnicode[charcode] || charcode;
@@ -2204,7 +2230,7 @@ var Font = (function FontClosure() {
           if (!isNum(width))
             width = this.widths[glyphName];
           if (this.noUnicodeAdaptation) {
-            unicode = GlyphsUnicode[glyphName] || charcode;
+            unicode = mapPrivateUseChars(GlyphsUnicode[glyphName] || charcode);
             break;
           }
           unicode = this.glyphNameMap[glyphName] ||
@@ -2235,9 +2261,8 @@ var Font = (function FontClosure() {
           }
 
           // MacRoman encoding address by re-encoding the cmap table
-          unicode = glyphName in GlyphsUnicode ?
-            GlyphsUnicode[glyphName] :
-            this.glyphNameMap[glyphName];
+          unicode = glyphName in this.glyphNameMap ?
+            this.glyphNameMap[glyphName] : GlyphsUnicode[glyphName];
           break;
         default:
           warn('Unsupported font type: ' + this.type);
diff --git a/src/function.js b/src/function.js
index 26b8fe679..4f81158f0 100644
--- a/src/function.js
+++ b/src/function.js
@@ -125,109 +125,99 @@ var PDFFunction = (function PDFFunctionClosure() {
       else
         decode = toMultiArray(decode);
 
-      // Precalc the multipliers
-      var inputMul = new Float64Array(inputSize);
-      for (var i = 0; i < inputSize; ++i) {
-        inputMul[i] = (encode[i][1] - encode[i][0]) /
-                  (domain[i][1] - domain[i][0]);
-      }
-
-      var idxMul = new Int32Array(inputSize);
-      idxMul[0] = outputSize;
-      for (i = 1; i < inputSize; ++i) {
-        idxMul[i] = idxMul[i - 1] * size[i - 1];
-      }
-
-      var nSamples = outputSize;
-      for (i = 0; i < inputSize; ++i)
-          nSamples *= size[i];
-
       var samples = this.getSampleArray(size, outputSize, bps, str);
 
       return [
         CONSTRUCT_SAMPLED, inputSize, domain, encode, decode, samples, size,
-        outputSize, bps, range, inputMul, idxMul, nSamples
+        outputSize, Math.pow(2, bps) - 1, range
       ];
     },
 
     constructSampledFromIR: function pdfFunctionConstructSampledFromIR(IR) {
-      var inputSize = IR[1];
-      var domain = IR[2];
-      var encode = IR[3];
-      var decode = IR[4];
-      var samples = IR[5];
-      var size = IR[6];
-      var outputSize = IR[7];
-      var bps = IR[8];
-      var range = IR[9];
-      var inputMul = IR[10];
-      var idxMul = IR[11];
-      var nSamples = IR[12];
+      // See chapter 3, page 109 of the PDF reference
+      function interpolate(x, xmin, xmax, ymin, ymax) {
+        return ymin + ((x - xmin) * ((ymax - ymin) / (xmax - xmin)));
+      }
 
       return function constructSampledFromIRResult(args) {
-        if (inputSize != args.length)
+        // See chapter 3, page 110 of the PDF reference.
+        var m = IR[1];
+        var domain = IR[2];
+        var encode = IR[3];
+        var decode = IR[4];
+        var samples = IR[5];
+        var size = IR[6];
+        var n = IR[7];
+        var mask = IR[8];
+        var range = IR[9];
+
+        if (m != args.length)
           error('Incorrect number of arguments: ' + inputSize + ' != ' +
                 args.length);
-        // Most of the below is a port of Poppler's implementation.
-        // TODO: There's a few other ways to do multilinear interpolation such
-        // as piecewise, which is much faster but an approximation.
-        var out = new Float64Array(outputSize);
-        var x;
-        var e = new Array(inputSize);
-        var efrac0 = new Float64Array(inputSize);
-        var efrac1 = new Float64Array(inputSize);
-        var sBuf = new Float64Array(1 << inputSize);
-        var i, j, k, idx, t;
-
-        // map input values into sample array
-        for (i = 0; i < inputSize; ++i) {
-          x = (args[i] - domain[i][0]) * inputMul[i] + encode[i][0];
-          if (x < 0) {
-            x = 0;
-          } else if (x > size[i] - 1) {
-            x = size[i] - 1;
-          }
-          e[i] = [Math.floor(x), 0];
-          if ((e[i][1] = e[i][0] + 1) >= size[i]) {
-            // this happens if in[i] = domain[i][1]
-            e[i][1] = e[i][0];
-          }
-          efrac1[i] = x - e[i][0];
-          efrac0[i] = 1 - efrac1[i];
-        }
 
-        // for each output, do m-linear interpolation
-        for (i = 0; i < outputSize; ++i) {
-
-          // pull 2^m values out of the sample array
-          for (j = 0; j < (1 << inputSize); ++j) {
-            idx = i;
-            for (k = 0, t = j; k < inputSize; ++k, t >>= 1) {
-              idx += idxMul[k] * (e[k][t & 1]);
-            }
-            if (idx >= 0 && idx < nSamples) {
-              sBuf[j] = samples[idx];
+        var x = args;
+
+        // Building the cube vertices: its part and sample index
+        // http://rjwagner49.com/Mathematics/Interpolation.pdf
+        var cubeVertices = 1 << m;
+        var cubeN = new Float64Array(cubeVertices);
+        var cubeVertex = new Uint32Array(cubeVertices);
+        for (var j = 0; j < cubeVertices; j++)
+          cubeN[j] = 1;
+
+        var k = n, pos = 1;
+        // Map x_i to y_j for 0 <= i < m using the sampled function.
+        for (var i = 0; i < m; ++i) {
+          // x_i' = min(max(x_i, Domain_2i), Domain_2i+1)
+          var domain_2i = domain[i][0];
+          var domain_2i_1 = domain[i][1];
+          var xi = Math.min(Math.max(x[i], domain_2i), domain_2i_1);
+
+          // e_i = Interpolate(x_i', Domain_2i, Domain_2i+1,
+          //                   Encode_2i, Encode_2i+1)
+          var e = interpolate(xi, domain_2i, domain_2i_1,
+                              encode[i][0], encode[i][1]);
+
+          // e_i' = min(max(e_i, 0), Size_i - 1)
+          var size_i = size[i];
+          e = Math.min(Math.max(e, 0), size_i - 1);
+
+          // Adjusting the cube: N and vertex sample index
+          var e0 = e < size_i - 1 ? Math.floor(e) : e - 1; // e1 = e0 + 1;
+          var n0 = e0 + 1 - e; // (e1 - e) / (e1 - e0);
+          var n1 = e - e0; // (e - e0) / (e1 - e0);
+          var offset0 = e0 * k;
+          var offset1 = offset0 + k; // e1 * k
+          for (var j = 0; j < cubeVertices; j++) {
+            if (j & pos) {
+              cubeN[j] *= n1;
+              cubeVertex[j] += offset1;
             } else {
-              sBuf[j] = 0; // TODO Investigate if this is what Adobe does
+              cubeN[j] *= n0;
+              cubeVertex[j] += offset0;
             }
           }
 
-          // do m sets of interpolations
-          for (j = 0, t = (1 << inputSize); j < inputSize; ++j, t >>= 1) {
-            for (k = 0; k < t; k += 2) {
-              sBuf[k >> 1] = efrac0[j] * sBuf[k] + efrac1[j] * sBuf[k + 1];
-            }
-          }
+          k *= size_i;
+          pos <<= 1;
+        }
 
-          // map output value to range
-          out[i] = (sBuf[0] * (decode[i][1] - decode[i][0]) + decode[i][0]);
-          if (out[i] < range[i][0]) {
-            out[i] = range[i][0];
-          } else if (out[i] > range[i][1]) {
-            out[i] = range[i][1];
-          }
+        var y = new Float64Array(n);
+        for (var j = 0; j < n; ++j) {
+          // Sum all cube vertices' samples portions
+          var rj = 0;
+          for (var i = 0; i < cubeVertices; i++)
+            rj += samples[cubeVertex[i] + j] * cubeN[i];
+
+          // r_j' = Interpolate(r_j, 0, 2^BitsPerSample - 1,
+          //                    Decode_2j, Decode_2j+1)
+          rj = interpolate(rj, 0, 1, decode[j][0], decode[j][1]);
+
+          // y_j = min(max(r_j, range_2j), range_2j+1)
+          y[j] = Math.min(Math.max(rj, range[j][0]), range[j][1]);
         }
-        return out;
+
+        return y;
       }
     },
 
diff --git a/src/jpx.js b/src/jpx.js
index 61a8f4487..a15c3db54 100644
--- a/src/jpx.js
+++ b/src/jpx.js
@@ -1052,7 +1052,7 @@ var JpxImage = (function JpxImageClosure() {
         }
         r = 0;
       }
-      throw 'Out of packets';
+      error('JPX error: Out of packets');
     };
   }
   function ResolutionLayerComponentPositionIterator(context) {
@@ -1091,7 +1091,7 @@ var JpxImage = (function JpxImageClosure() {
         }
         l = 0;
       }
-      throw 'Out of packets';
+      error('JPX error: Out of packets');
     };
   }
   function buildPackets(context) {
@@ -1187,7 +1187,7 @@ var JpxImage = (function JpxImageClosure() {
           new ResolutionLayerComponentPositionIterator(context);
         break;
       default:
-        throw 'Unsupported progression order';
+        error('JPX error: Unsupported progression order ' + progressionOrder);
     }
   }
   function parseTilePackets(context, data, offset, dataLength) {
@@ -1589,7 +1589,7 @@ var JpxImage = (function JpxImageClosure() {
         if (lbox == 0)
           lbox = length - position + headerSize;
         if (lbox < headerSize)
-          throw 'Invalid box field size';
+          error('JPX error: Invalid box field size');
         var dataLength = lbox - headerSize;
         var jumpDataLength = true;
         switch (tbox) {
@@ -1675,7 +1675,7 @@ var JpxImage = (function JpxImageClosure() {
                 scalarExpounded = true;
                 break;
               default:
-                throw 'Invalid SQcd value';
+                error('JPX error: Invalid SQcd value ' + sqcd);
             }
             qcd.noQuantization = spqcdSize == 8;
             qcd.scalarExpounded = scalarExpounded;
@@ -1728,7 +1728,7 @@ var JpxImage = (function JpxImageClosure() {
                 scalarExpounded = true;
                 break;
               default:
-                throw 'Invalid SQcd value';
+                error('JPX error: Invalid SQcd value ' + sqcd);
             }
             qcc.noQuantization = spqcdSize == 8;
             qcc.scalarExpounded = scalarExpounded;
@@ -1795,7 +1795,7 @@ var JpxImage = (function JpxImageClosure() {
                 cod.terminationOnEachCodingPass ||
                 cod.verticalyStripe || cod.predictableTermination ||
                 cod.segmentationSymbolUsed)
-              throw 'Unsupported COD options: ' + uneval(cod);
+              error('JPX error: Unsupported COD options: ' + uneval(cod));
 
             if (context.mainHeader)
               context.COD = cod;
@@ -1840,7 +1840,7 @@ var JpxImage = (function JpxImageClosure() {
             // skipping content
             break;
           default:
-            throw 'Unknown codestream code: ' + code.toString(16);
+            error('JPX error: Unknown codestream code: ' + code.toString(16));
         }
         position += length;
       }
diff --git a/src/obj.js b/src/obj.js
index ef7932546..3cdee8778 100644
--- a/src/obj.js
+++ b/src/obj.js
@@ -287,75 +287,70 @@ var XRef = (function XRefClosure() {
 
   XRef.prototype = {
     readXRefTable: function 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 (true) {
-        if (isCmd(obj = parser.getObj(), 'trailer'))
-          break;
-        if (!isInt(obj))
-          error('Invalid XRef table');
-        var first = obj;
-        if (!isInt(obj = parser.getObj()))
-          error('Invalid XRef table');
-        var n = obj;
-        if (first < 0 || n < 0 || (first + n) != ((first + n) | 0))
-          error('Invalid XRef table: ' + first + ', ' + n);
-        for (var i = first; i < first + n; ++i) {
+      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 = {};
-          if (!isInt(obj = parser.getObj()))
-            error('Invalid XRef table: ' + first + ', ' + n);
-          entry.offset = obj;
-          if (!isInt(obj = parser.getObj()))
-            error('Invalid XRef table: ' + first + ', ' + n);
-          entry.gen = obj;
-          obj = parser.getObj();
-          if (isCmd(obj, 'n')) {
-            entry.uncompressed = true;
-          } else if (isCmd(obj, 'f')) {
+          entry.offset = parser.getObj();
+          entry.gen = parser.getObj();
+          var type = parser.getObj();
+
+          if (isCmd(type, 'f'))
             entry.free = true;
-          } else {
-            error('Invalid XRef table: ' + first + ', ' + n);
-          }
-          if (!this.entries[i]) {
-            // In some buggy PDF files the xref table claims to start at 1
-            // instead of 0.
-            if (i == 1 && first == 1 &&
-                entry.offset == 0 && entry.gen == 65535 && entry.free) {
-              i = first = 0;
-            }
-            this.entries[i] = entry;
-          }
-        }
-      }
+          else if (isCmd(type, 'n'))
+            entry.uncompressed = true;
 
-      // read the trailer dictionary
-      var dict;
-      if (!isDict(dict = parser.getObj()))
-        error('Invalid XRef table');
-
-      // get the 'Prev' pointer
-      var prev;
-      obj = dict.get('Prev');
-      if (isInt(obj)) {
-        prev = obj;
-      } else if (isRef(obj)) {
-        // certain buggy PDF generators generate "/Prev NNN 0 R" instead
-        // of "/Prev NNN"
-        prev = obj.num;
-      }
-      if (prev) {
-        this.readXRef(prev);
-      }
+          // Validate entry obj
+          if (!isInt(entry.offset) || !isInt(entry.gen) ||
+              !(entry.free || entry.uncompressed)) {
+            error('Invalid entry in XRef subsection: ' + first + ', ' + count);
+          }
 
-      // check for 'XRefStm' key
-      if (isInt(obj = dict.get('XRefStm'))) {
-        var pos = obj;
-        // ignore previously loaded xref streams (possible infinite recursion)
-        if (!(pos in this.xrefstms)) {
-          this.xrefstms[pos] = 1;
-          this.readXRef(pos);
+          if (!this.entries[i + first])
+            this.entries[i + first] = entry;
         }
       }
 
+      // Sanity check: as per spec, first object must have these properties
+      if (this.entries[0] &&
+          !(this.entries[0].gen === 65535 && this.entries[0].free))
+        error('Invalid XRef table: unexpected first object');
+
+      // Sanity check
+      if (!isCmd(obj, 'trailer'))
+        error('Invalid XRef table: could not find trailer dictionary');
+
+      // Read trailer dictionary, e.g.
+      // trailer
+      //    << /Size 22
+      //      /Root 20R
+      //      /Info 10R
+      //      /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');
+
       return dict;
     },
     readXRefStream: function readXRefStream(stream) {
@@ -407,9 +402,6 @@ var XRef = (function XRefClosure() {
         }
         range.splice(0, 2);
       }
-      var prev = streamParameters.get('Prev');
-      if (isInt(prev))
-        this.readXRef(prev);
       return streamParameters;
     },
     indexObjects: function indexObjects() {
@@ -529,22 +521,47 @@ var XRef = (function XRefClosure() {
       try {
         var parser = new Parser(new Lexer(stream), true);
         var obj = parser.getObj();
-
-        // parse an old-style xref table
-        if (isCmd(obj, 'xref'))
-          return this.readXRefTable(parser);
-
-        // parse an xref stream
-        if (isInt(obj)) {
+        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');
+          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');
           }
-          return this.readXRefStream(obj);
+          dict = this.readXRefStream(obj);
+        }
+
+        // Recursively get previous dictionary, if any
+        obj = dict.get('Prev');
+        if (isInt(obj))
+          this.readXRef(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.readXRef(obj.num);
         }
+
+        return dict;
       } catch (e) {
-        log('Reading of the xref table/stream failed: ' + e);
+        log('(while reading XRef): ' + e);
       }
 
       warn('Indexing all PDF objects');
@@ -574,7 +591,7 @@ var XRef = (function XRefClosure() {
       var stream, parser;
       if (e.uncompressed) {
         if (e.gen != gen)
-          throw ('inconsistent generation in XRef');
+          error('inconsistent generation in XRef');
         stream = this.stream.makeSubStream(e.offset);
         parser = new Parser(new Lexer(stream), true, this);
         var obj1 = parser.getObj();
@@ -703,7 +720,7 @@ var PDFObjects = (function PDFObjectsClosure() {
       // If there isn't an object yet or the object isn't resolved, then the
       // data isn't ready yet!
       if (!obj || !obj.isResolved) {
-        throw 'Requesting object that isn\'t resolved yet ' + objId;
+        error('Requesting object that isn\'t resolved yet ' + objId);
         return null;
       } else {
         return obj.data;
diff --git a/src/stream.js b/src/stream.js
index 610a54d38..fc163171f 100644
--- a/src/stream.js
+++ b/src/stream.js
@@ -821,15 +821,19 @@ var JpegStream = (function JpegStreamClosure() {
   JpegStream.prototype.ensureBuffer = function jpegStreamEnsureBuffer(req) {
     if (this.bufferLength)
       return;
-    var jpegImage = new JpegImage();
-    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;
+    try {
+      var jpegImage = new JpegImage();
+      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;
+    } catch (e) {
+      error('JPEG error: ' + e);
+    }
   };
   JpegStream.prototype.getIR = function jpegStreamGetIR() {
     return bytesToString(this.bytes);
diff --git a/src/util.js b/src/util.js
index 99b422296..759908e9e 100644
--- a/src/util.js
+++ b/src/util.js
@@ -255,8 +255,8 @@ var Promise = (function PromiseClosure() {
         return;
       }
       if (this._data !== EMPTY_PROMISE) {
-        throw 'Promise ' + this.name +
-                                ': Cannot set the data of a promise twice';
+        error('Promise ' + this.name +
+              ': Cannot set the data of a promise twice');
       }
       this._data = value;
       this.hasData = true;
@@ -268,7 +268,7 @@ var Promise = (function PromiseClosure() {
 
     get data() {
       if (this._data === EMPTY_PROMISE) {
-        throw 'Promise ' + this.name + ': Cannot get data that isn\'t set';
+        error('Promise ' + this.name + ': Cannot get data that isn\'t set');
       }
       return this._data;
     },
@@ -283,10 +283,10 @@ var Promise = (function PromiseClosure() {
 
     resolve: function promiseResolve(data) {
       if (this.isResolved) {
-        throw 'A Promise can be resolved only once ' + this.name;
+        error('A Promise can be resolved only once ' + this.name);
       }
       if (this.isRejected) {
-        throw 'The Promise was already rejected ' + this.name;
+        error('The Promise was already rejected ' + this.name);
       }
 
       this.isResolved = true;
@@ -300,10 +300,10 @@ var Promise = (function PromiseClosure() {
 
     reject: function proimseReject(reason) {
       if (this.isRejected) {
-        throw 'A Promise can be rejected only once ' + this.name;
+        error('A Promise can be rejected only once ' + this.name);
       }
       if (this.isResolved) {
-        throw 'The Promise was already resolved ' + this.name;
+        error('The Promise was already resolved ' + this.name);
       }
 
       this.isRejected = true;
@@ -317,7 +317,7 @@ var Promise = (function PromiseClosure() {
 
     then: function promiseThen(callback, errback) {
       if (!callback) {
-        throw 'Requiring callback' + this.name;
+        error('Requiring callback' + this.name);
       }
 
       // If the promise is already resolved, call the callback directly.
diff --git a/src/worker.js b/src/worker.js
index 4d9dd1bb6..b81ff0540 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -26,7 +26,7 @@ function MessageHandler(name, comObj) {
         delete callbacks[callbackId];
         callback(data.data);
       } else {
-        throw 'Cannot resolve callback ' + callbackId;
+        error('Cannot resolve callback ' + callbackId);
       }
     } else if (data.action in ah) {
       var action = ah[data.action];
@@ -44,7 +44,7 @@ function MessageHandler(name, comObj) {
         action[0].call(action[1], data.data);
       }
     } else {
-      throw 'Unkown action from worker: ' + data.action;
+      error('Unkown action from worker: ' + data.action);
     }
   };
 }
@@ -53,7 +53,7 @@ MessageHandler.prototype = {
   on: function messageHandlerOn(actionName, handler, scope) {
     var ah = this.actionHandler;
     if (ah[actionName]) {
-      throw 'There is already an actionName called "' + actionName + '"';
+      error('There is already an actionName called "' + actionName + '"');
     }
     ah[actionName] = [handler, scope];
   },
@@ -208,6 +208,7 @@ var workerConsole = {
       action: 'console_error',
       data: args
     });
+    throw 'pdf.js execution error';
   },
 
   time: function time(name) {
@@ -217,7 +218,7 @@ var workerConsole = {
   timeEnd: function timeEnd(name) {
     var time = consoleTimer[name];
     if (time == null) {
-      throw 'Unkown timer name ' + name;
+      error('Unkown timer name ' + name);
     }
     this.log('Timer:', name, Date.now() - time);
   }
diff --git a/test/pdfs/issue1096.pdf.link b/test/pdfs/issue1096.pdf.link
new file mode 100644
index 000000000..aa07f14dd
--- /dev/null
+++ b/test/pdfs/issue1096.pdf.link
@@ -0,0 +1 @@
+http://www.faithaliveresources.org/Content/Site135/FilesSamples/105315400440pdf_00000009843.pdf
diff --git a/test/pdfs/issue1127.pdf.link b/test/pdfs/issue1127.pdf.link
new file mode 100644
index 000000000..2df2304ba
--- /dev/null
+++ b/test/pdfs/issue1127.pdf.link
@@ -0,0 +1 @@
+https://vmp.ethz.ch/pdfs/diplome/vordiplome/Block%201/Algorithmen_%26_Komplexitaet/AlgoKo_f08_Aufg.pdf
diff --git a/test/test_manifest.json b/test/test_manifest.json
index 5bc344abf..c6fed0a35 100644
--- a/test/test_manifest.json
+++ b/test/test_manifest.json
@@ -402,6 +402,21 @@
       "link": true,
       "type": "eq"
     },
+    {  "id": "issue1096",
+      "file": "pdfs/issue1096.pdf",
+      "md5": "7f75d2b4b93c78d401ff39e8c1b00612",
+      "rounds": 1,
+      "pageLimit": 10,
+      "link": true,
+      "type": "eq"
+    },
+    {  "id": "issue1127",
+      "file": "pdfs/issue1127.pdf",
+      "md5": "4fb2be5ffefeafda4ba977de2a1bb4d8",
+      "rounds": 1,
+      "link": true,
+      "type": "eq"
+    },
     {  "id": "liveprogramming",
       "file": "pdfs/liveprogramming.pdf",
       "md5": "7bd4dad1188232ef597d36fd72c33e52",
diff --git a/web/compatibility.js b/web/compatibility.js
index 26405ad8f..b22153516 100644
--- a/web/compatibility.js
+++ b/web/compatibility.js
@@ -224,3 +224,10 @@
     }
   });
 })();
+
+// Check console compatability
+(function checkConsoleCompatibility() {
+  if (typeof console == 'undefined') {
+    console = {log: function() {}};
+  }
+})();
diff --git a/web/viewer-snippet-firefox-extension.html b/web/viewer-snippet-firefox-extension.html
new file mode 100644
index 000000000..a3d3502a8
--- /dev/null
+++ b/web/viewer-snippet-firefox-extension.html
@@ -0,0 +1,14 @@
+<!-- This snippet is used in firefox extension, see Makefile -->
+<base href="resource://pdf.js/web/" />
+<script type="text/javascript" id="PDFJS_SCRIPT_TAG">
+<!--
+// pdf.js is inlined here because resource:// urls won't work
+// for loading workers.
+/* PDFJSSCRIPT_INCLUDE_BUNDLE */
+-->
+</script>
+<script type="text/javascript">
+  // This specifies the location of the pdf.js file.
+  PDFJS.isFirefoxExtension = true;
+  PDFJS.workerSrc = 'none';
+</script>
diff --git a/web/viewer.css b/web/viewer.css
index e355f7fc2..b9fd3e9e4 100644
--- a/web/viewer.css
+++ b/web/viewer.css
@@ -9,7 +9,7 @@ body {
 }
 
 [hidden] {
-  display: none;
+  display: none !important;
 }
 
 /* === Toolbar === */
diff --git a/web/viewer.html b/web/viewer.html
index 221ef7abf..7c55ec735 100644
--- a/web/viewer.html
+++ b/web/viewer.html
@@ -2,9 +2,11 @@
 <html>
     <head>
         <title>Simple pdf.js page viewer</title>
+        <!-- PDFJSSCRIPT_INCLUDE_FIREFOX_EXTENSION -->
+
         <link rel="stylesheet" href="viewer.css"/>
 
-        <script type="text/javascript" src="compatibility.js"></script>
+        <script type="text/javascript" src="compatibility.js"></script> <!-- PDFJSSCRIPT_REMOVE_FIREFOX_EXTENSION -->
 
         <!-- PDFJSSCRIPT_INCLUDE_BUILD -->
         <script type="text/javascript" src="../src/core.js"></script> <!-- PDFJSSCRIPT_REMOVE -->
@@ -91,7 +93,7 @@
 
       <input id="fileInput" type="file" oncontextmenu="return false;"/>
 
-      <div class="separator"></div>
+      <div id="fileInputSeperator" class="separator"></div>
 
       <a href="#" id="viewBookmark" title="Bookmark (or copy) current location">
         <img src="images/bookmark.svg" alt="Bookmark" align="top" height="16"/>
diff --git a/web/viewer.js b/web/viewer.js
index 11c0769a3..cc279cf67 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -6,6 +6,7 @@
 var kDefaultURL = 'compressed.tracemonkey-pldi-09.pdf';
 var kDefaultScale = 'auto';
 var kDefaultScaleDelta = 1.1;
+var kUnknownScale = 0;
 var kCacheSize = 20;
 var kCssUnits = 96.0 / 72.0;
 var kScrollbarPadding = 40;
@@ -61,6 +62,31 @@ var RenderingQueue = (function RenderingQueueClosure() {
   return RenderingQueue;
 })();
 
+var FirefoxCom = (function FirefoxComClosure() {
+  return {
+    /**
+     * Creates an event that hopefully the extension is listening for and will
+     * synchronously respond to.
+     * @param {String} action The action to trigger.
+     * @param {String} data Optional data to send.
+     * @return {*} The response.
+     */
+    request: function(action, data) {
+      var request = document.createTextNode('');
+      request.setUserData('action', action, null);
+      request.setUserData('data', data, null);
+      document.documentElement.appendChild(request);
+
+      var sender = document.createEvent('Events');
+      sender.initEvent('pdf.js.message', true, false);
+      request.dispatchEvent(sender);
+      var response = request.getUserData('response');
+      document.documentElement.removeChild(request);
+      return response;
+    }
+  };
+})();
+
 // Settings Manager - This is a utility for saving settings
 // First we see if localStorage is available
 // If not, we use FUEL in FF
@@ -76,22 +102,14 @@ var Settings = (function SettingsClosure() {
       return false;
     }
   })();
-  var extPrefix = 'extensions.uriloader@pdf.js';
-  var isExtension = location.protocol == 'chrome:' && !isLocalStorageEnabled;
-  var inPrivateBrowsing = false;
-  if (isExtension) {
-    var pbs = Components.classes['@mozilla.org/privatebrowsing;1']
-              .getService(Components.interfaces.nsIPrivateBrowsingService);
-    inPrivateBrowsing = pbs.privateBrowsingEnabled;
-  }
+
+  var isFirefoxExtension = PDFJS.isFirefoxExtension;
 
   function Settings(fingerprint) {
     var database = null;
     var index;
-    if (inPrivateBrowsing)
-      return false;
-    else if (isExtension)
-      database = Application.prefs.getValue(extPrefix + '.database', '{}');
+    if (isFirefoxExtension)
+      database = FirefoxCom.request('getDatabase', null);
     else if (isLocalStorageEnabled)
       database = localStorage.getItem('database') || '{}';
     else
@@ -113,29 +131,26 @@ var Settings = (function SettingsClosure() {
       index = database.files.push({fingerprint: fingerprint}) - 1;
     this.file = database.files[index];
     this.database = database;
-    if (isExtension)
-      Application.prefs.setValue(extPrefix + '.database',
-          JSON.stringify(database));
-    else if (isLocalStorageEnabled)
+    if (isLocalStorageEnabled)
       localStorage.setItem('database', JSON.stringify(database));
   }
 
   Settings.prototype = {
     set: function settingsSet(name, val) {
-      if (inPrivateBrowsing || !('file' in this))
+      if (!('file' in this))
         return false;
 
       var file = this.file;
       file[name] = val;
-      if (isExtension)
-        Application.prefs.setValue(extPrefix + '.database',
-            JSON.stringify(this.database));
+      var database = JSON.stringify(this.database);
+      if (isFirefoxExtension)
+        FirefoxCom.request('setDatabase', database);
       else if (isLocalStorageEnabled)
-        localStorage.setItem('database', JSON.stringify(this.database));
+        localStorage.setItem('database', database);
     },
 
     get: function settingsGet(name, defaultValue) {
-      if (inPrivateBrowsing || !('file' in this))
+      if (!('file' in this))
         return defaultValue;
 
       return this.file[name] || defaultValue;
@@ -152,7 +167,7 @@ var currentPageNumber = 1;
 var PDFView = {
   pages: [],
   thumbnails: [],
-  currentScale: 0,
+  currentScale: kUnknownScale,
   currentScaleValue: null,
   initialBookmark: document.location.hash.substring(1),
 
@@ -207,12 +222,12 @@ var PDFView = {
 
   zoomIn: function pdfViewZoomIn() {
     var newScale = Math.min(kMaxScale, this.currentScale * kDefaultScaleDelta);
-    this.setScale(newScale, true);
+    this.parseScale(newScale, true);
   },
 
   zoomOut: function pdfViewZoomOut() {
     var newScale = Math.max(kMinScale, this.currentScale / kDefaultScaleDelta);
-    this.setScale(newScale, true);
+    this.parseScale(newScale, true);
   },
 
   set page(val) {
@@ -261,7 +276,7 @@ var PDFView = {
         },
         error: function getPdfError(e) {
           var loadingIndicator = document.getElementById('loading');
-          loadingIndicator.innerHTML = 'Error';
+          loadingIndicator.textContent = 'Error';
           var moreInfo = {
             message: 'Unexpected server response of ' + e.target.status + '.'
           };
@@ -276,7 +291,13 @@ var PDFView = {
   },
 
   download: function pdfViewDownload() {
-    window.open(this.url + '#pdfjs.action=download', '_parent');
+    var url = this.url.split('#')[0];
+    if (PDFJS.isFirefoxExtension) {
+      FirefoxCom.request('download', url);
+    } else {
+      url += '#pdfjs.action=download', '_parent';
+      window.open(url, '_parent');
+    }
   },
 
   navigateTo: function pdfViewNavigateTo(dest) {
@@ -297,14 +318,14 @@ var PDFView = {
 
   getDestinationHash: function pdfViewGetDestinationHash(dest) {
     if (typeof dest === 'string')
-      return '#' + escape(dest);
+      return PDFView.getAnchorUrl('#' + escape(dest));
     if (dest instanceof Array) {
       var destRef = dest[0]; // see navigateTo method for dest format
       var pageNumber = destRef instanceof Object ?
         this.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] :
         (destRef + 1);
       if (pageNumber) {
-        var pdfOpenParams = '#page=' + pageNumber;
+        var pdfOpenParams = PDFView.getAnchorUrl('#page=' + pageNumber);
         var destKind = dest[1];
         if ('name' in destKind && destKind.name == 'XYZ') {
           var scale = (dest[4] || this.currentScale);
@@ -319,6 +340,17 @@ var PDFView = {
     return '';
   },
 
+  /**
+   * For the firefox extension we prefix the full url on anchor links so they
+   * don't come up as resource:// urls and so open in new tab/window works.
+   * @param {String} anchor The anchor hash include the #.
+   */
+  getAnchorUrl: function getAnchorUrl(anchor) {
+    if (PDFJS.isFirefoxExtension)
+      return this.url.split('#')[0] + anchor;
+    return anchor;
+  },
+
   /**
    * Show the error box.
    * @param {String} message A message that is human readable.
@@ -331,7 +363,7 @@ var PDFView = {
     errorWrapper.removeAttribute('hidden');
 
     var errorMessage = document.getElementById('errorMessage');
-    errorMessage.innerHTML = message;
+    errorMessage.textContent = message;
 
     var closeButton = document.getElementById('errorClose');
     closeButton.onclick = function() {
@@ -366,7 +398,7 @@ var PDFView = {
   progress: function pdfViewProgress(level) {
     var percent = Math.round(level * 100);
     var loadingIndicator = document.getElementById('loading');
-    loadingIndicator.innerHTML = 'Loading... ' + percent + '%';
+    loadingIndicator.textContent = 'Loading... ' + percent + '%';
   },
 
   load: function pdfViewLoad(data, scale) {
@@ -406,7 +438,7 @@ var PDFView = {
     var pagesCount = pdf.numPages;
     var id = pdf.fingerprint;
     var storedHash = null;
-    document.getElementById('numPages').innerHTML = pagesCount;
+    document.getElementById('numPages').textContent = pagesCount;
     document.getElementById('pageNumber').max = pagesCount;
     PDFView.documentFingerprint = id;
     var store = PDFView.store = new Settings(id);
@@ -456,10 +488,16 @@ var PDFView = {
     }
     else if (storedHash)
       this.setHash(storedHash);
-    else {
-      this.parseScale(scale || kDefaultScale, true);
+    else if (scale) {
+      this.parseScale(scale, true);
       this.page = 1;
     }
+
+    if (PDFView.currentScale === kUnknownScale) {
+      // Scale was not initialized: invalid bookmark or scale was not specified.
+      // Setting the default one.
+      this.parseScale(kDefaultScale, true);
+    }
   },
 
   setHash: function pdfViewSetHash(hash) {
@@ -652,7 +690,15 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
       if (!item.content) {
         content.setAttribute('hidden', true);
       } else {
-        text.innerHTML = item.content.replace('\n', '<br />');
+        var e = document.createElement('span');
+        var lines = item.content.split('\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() {
            this.nextSibling.removeAttribute('hidden');
         }, false);
@@ -746,6 +792,8 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
 
       if (scale && scale !== PDFView.currentScale)
         PDFView.parseScale(scale, true);
+      else if (PDFView.currentScale === kUnknownScale)
+        PDFView.parseScale(kDefaultScale, true);
 
       setTimeout(function pageViewScrollIntoViewRelayout() {
         // letting page to re-layout before scrolling
@@ -826,13 +874,13 @@ var PageView = function pageView(container, content, id, pageWidth, pageHeight,
     var t1 = stats.compile, t2 = stats.fonts, t3 = stats.render;
     var str = 'Time to compile/fonts/render: ' +
               (t1 - stats.begin) + '/' + (t2 - t1) + '/' + (t3 - t2) + ' ms';
-    document.getElementById('info').innerHTML = str;
+    document.getElementById('info').textContent = str;
   };
 };
 
 var ThumbnailView = function thumbnailView(container, page, id, pageRatio) {
   var anchor = document.createElement('a');
-  anchor.href = '#' + id;
+  anchor.href = PDFView.getAnchorUrl('#page=' + id);
   anchor.onclick = function stopNivigation() {
     PDFView.page = id;
     return false;
@@ -1040,12 +1088,18 @@ window.addEventListener('load', function webViewerLoad(evt) {
   }
 
   var scale = ('scale' in params) ? params.scale : 0;
-  PDFView.open(params.file || kDefaultURL, parseFloat(scale));
+  var file = PDFJS.isFirefoxExtension ?
+              window.location.toString() : params.file || kDefaultURL;
+  PDFView.open(file, parseFloat(scale));
 
-  if (!window.File || !window.FileReader || !window.FileList || !window.Blob)
+  if (PDFJS.isFirefoxExtension || !window.File || !window.FileReader ||
+      !window.FileList || !window.Blob) {
     document.getElementById('fileInput').setAttribute('hidden', 'true');
-  else
+    document.getElementById('fileInputSeperator')
+                              .setAttribute('hidden', 'true');
+  } else {
     document.getElementById('fileInput').value = null;
+  }
 
   if ('disableWorker' in params)
     PDFJS.disableWorker = (params['disableWorker'] === 'true');
@@ -1130,8 +1184,8 @@ function updateViewarea() {
   store.set('zoom', normalizedScaleValue);
   store.set('scrollLeft', Math.round(topLeft.x));
   store.set('scrollTop', Math.round(topLeft.y));
-
-  document.getElementById('viewBookmark').href = pdfOpenParams;
+  var href = PDFView.getAnchorUrl(pdfOpenParams);
+  document.getElementById('viewBookmark').href = href;
 }
 
 window.addEventListener('scroll', function webViewerScroll(evt) {
@@ -1271,7 +1325,7 @@ window.addEventListener('keydown', function keydown(evt) {
       handled = true;
       break;
     case 48: // '0'
-      PDFView.setScale(kDefaultScale, true);
+      PDFView.parseScale(kDefaultScale, true);
       handled = true;
       break;
     case 37: // left arrow