Merge the last PGO-green inbound changeset to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Sat, 14 Jul 2012 12:58:44 -0400
changeset 102004 9046ecf4db8fafb4ce1c2a28aaefff597f018b8b
parent 101954 011147826130675190cb2d7a2de5c3270e29eb6a (current diff)
parent 102003 2ff61044edbd8c8418cbb00dd12e15788fb3e544 (diff)
child 102023 8e2f9cc15bd32659d9564c66a3ab49fa691d2d4d
child 102031 5e6d4028b17602db1688efdb7b7e42e25b3b832f
push idunknown
push userunknown
push dateunknown
milestone16.0a1
Merge the last PGO-green inbound changeset to m-c.
browser/extensions/pdfjs/bootstrap.js
browser/extensions/pdfjs/install.rdf.in
content/xul/content/src/nsXULElement.h
dom/indexedDB/test/unit/head_idb.js
--- a/browser/base/content/sync/utils.js
+++ b/browser/base/content/sync/utils.js
@@ -192,18 +192,16 @@ let gSyncUtils = {
     if (!el2)
       valid = val1.length >= Weave.MIN_PASS_LENGTH;
     else if (val1 && val1 == Weave.Identity.username)
       error = "change.password.pwSameAsUsername";
     else if (val1 && val1 == Weave.Identity.account)
       error = "change.password.pwSameAsEmail";
     else if (val1 && val1 == Weave.Identity.basicPassword)
       error = "change.password.pwSameAsPassword";
-    else if (val1 && val1 == Weave.Identity.syncKey)
-      error = "change.password.pwSameAsRecoveryKey";
     else if (val1 && val2) {
       if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH)
         valid = true;
       else if (val1.length < Weave.MIN_PASS_LENGTH)
         error = "change.password.tooShort";
       else if (val1 != val2)
         error = "change.password.mismatch";
     }
--- a/browser/config/tooltool-manifests/linux32/clang.manifest
+++ b/browser/config/tooltool-manifests/linux32/clang.manifest
@@ -1,15 +1,17 @@
 [
-{"clang_version": "r159509"},
+{
+"clang_version": "r160176"
+},
 {
 "size": 47,
 "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
 "algorithm": "sha512",
 "filename": "setup.sh"
 },
 {
-"size": 74071397,
-"digest": "390e499161e8b5e91c179f3352ecbb07431e3d5a190298de7f338b48fe3807f3ddbeca72d8df39d11f89864fb1135f14500471faa741d3886b875b8e2b1d4416",
+"size": 74224137,
+"digest": "74963bd6531e7f39e65b50adacc2ef86ba710b6a0d68bbcba7579267bef156a0d9506e389d08483a003d0d05d5d078e7036092f928d37675b9c1f5bb518ae5ce",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2"
 }
 ]
--- a/browser/config/tooltool-manifests/linux64/clang.manifest
+++ b/browser/config/tooltool-manifests/linux64/clang.manifest
@@ -1,15 +1,17 @@
 [
-{"clang_version": "r159509"},
+{
+"clang_version": "r160176"
+},
 {
 "size": 47,
 "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
 "algorithm": "sha512",
 "filename": "setup.sh"
 },
 {
-"size": 72518043,
-"digest": "447dac319a8d7fa902cc065b758440bf6d4a7a98104362162fbdb0479a44d9b84c5878506f09be4398053a6ddc07935c7fb4f71e59b178073876fb5f90a45219",
+"size": 72747617,
+"digest": "4608fd00c4dbea8d640e16bcebaf0b8bc16506ffe50fe13726e2e565d3534a3452390c63a49044748863ca94eed22a0c4f44319f6d180badd5c36de411b8722e",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2"
 }
 ]
--- a/browser/config/tooltool-manifests/macosx64/clang.manifest
+++ b/browser/config/tooltool-manifests/macosx64/clang.manifest
@@ -1,15 +1,17 @@
 [
-{"clang_version": "r159509"},
+{
+"clang_version": "r160176"
+},
 {
 "size": 47,
 "digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
 "algorithm": "sha512",
 "filename": "setup.sh"
 },
 {
-"size": 63679229,
-"digest": "5257503e537b8d440b17e40aa06f0f70f1b124129c02f10e45b46ac642fc4170bfa77ae737a8bcac3ed7602ccd934a88cbe349986eb971d66a6fb553ae31f13c",
+"size": 63767767,
+"digest": "99e333756750a3bcdf9ef3cfd722e7165558e53396dabf3af2aab3f2e60a2c5b0023fb45f8b161d0f21d4281e02749f202a3f364d78de900b6d79c60aa350ba3",
 "algorithm": "sha512",
 "filename": "clang.tar.bz2"
 }
 ]
--- 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.3.345
+Current extension version is: 0.3.452
 
deleted file mode 100644
--- a/browser/extensions/pdfjs/bootstrap.js
+++ /dev/null
@@ -1,111 +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 RESOURCE_NAME = 'pdf.js';
-const EXT_PREFIX = 'extensions.uriloader@pdf.js';
-
-let Cc = Components.classes;
-let Ci = Components.interfaces;
-let Cm = Components.manager;
-let Cu = Components.utils;
-
-Cu.import('resource://gre/modules/Services.jsm');
-
-function getBoolPref(pref, def) {
-  try {
-    return Services.prefs.getBoolPref(pref);
-  } catch (ex) {
-    return def;
-  }
-}
-
-function setStringPref(pref, value) {
-  let str = Cc['@mozilla.org/supports-string;1']
-              .createInstance(Ci.nsISupportsString);
-  str.data = value;
-  Services.prefs.setComplexValue(pref, Ci.nsISupportsString, str);
-}
-
-function log(str) {
-  if (!getBoolPref(EXT_PREFIX + '.pdfBugEnabled', false))
-    return;
-  dump(str + '\n');
-}
-
-// Register/unregister a class as a component.
-let Factory = {
-  registrar: null,
-  aClass: null,
-  register: function(aClass) {
-    if (this.aClass) {
-      log('Cannot register more than one class');
-      return;
-    }
-    this.registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
-    this.aClass = aClass;
-    var proto = aClass.prototype;
-    this.registrar.registerFactory(proto.classID, proto.classDescription,
-      proto.contractID, this);
-  },
-  unregister: function() {
-    if (!this.aClass) {
-      log('Class was never registered.');
-      return;
-    }
-    var proto = this.aClass.prototype;
-    this.registrar.unregisterFactory(proto.classID, this);
-    this.aClass = null;
-  },
-  // nsIFactory::createInstance
-  createInstance: function(outer, iid) {
-    if (outer !== null)
-      throw Cr.NS_ERROR_NO_AGGREGATION;
-    return (new (this.aClass)).QueryInterface(iid);
-  }
-};
-
-let pdfStreamConverterUrl = null;
-
-// As of Firefox 13 bootstrapped add-ons don't support automatic registering and
-// unregistering of resource urls and components/contracts. Until then we do
-// it programatically. See ManifestDirective ManifestParser.cpp for support.
-
-function startup(aData, aReason) {
-  // Setup the resource url.
-  var ioService = Services.io;
-  var resProt = ioService.getProtocolHandler('resource')
-                  .QueryInterface(Ci.nsIResProtocolHandler);
-  var aliasURI = ioService.newURI('content/', 'UTF-8', aData.resourceURI);
-  resProt.setSubstitution(RESOURCE_NAME, aliasURI);
-
-  // Load the component and register it.
-  pdfStreamConverterUrl = aData.resourceURI.spec +
-                          'components/PdfStreamConverter.js';
-  Cu.import(pdfStreamConverterUrl);
-  Factory.register(PdfStreamConverter);
-}
-
-function shutdown(aData, aReason) {
-  if (aReason == APP_SHUTDOWN)
-    return;
-  var ioService = Services.io;
-  var resProt = ioService.getProtocolHandler('resource')
-                  .QueryInterface(Ci.nsIResProtocolHandler);
-  // Remove the resource url.
-  resProt.setSubstitution(RESOURCE_NAME, null);
-  // Remove the contract/component.
-  Factory.unregister();
-  // Unload the converter
-  Cu.unload(pdfStreamConverterUrl);
-  pdfStreamConverterUrl = null;
-}
-
-function install(aData, aReason) {
-}
-
-function uninstall(aData, aReason) {
-  setStringPref(EXT_PREFIX + '.database', '{}');
-}
-
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -123,47 +123,65 @@ function getLocalizedString(strings, id,
 }
 
 // All the priviledged actions.
 function ChromeActions(domWindow) {
   this.domWindow = domWindow;
 }
 
 ChromeActions.prototype = {
-  download: function(data) {
-    var handlerInfo = Svc.mime
-                         .getFromTypeAndExtension('application/pdf', 'pdf');
-    var uri = NetUtil.newURI(data);
-
+  download: function(data, sendResponse) {
+    var originalUrl = data.originalUrl;
+    // The data may not be downloaded so we need just retry getting the pdf with
+    // the original url.
+    var originalUri = NetUtil.newURI(data.originalUrl);
+    var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
     var extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
-            getService(Ci.nsIExternalHelperAppService);
+             getService(Ci.nsIExternalHelperAppService);
     var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
-                        getService(Ci.nsIWindowWatcher).activeWindow;
-    var ioService = Services.io;
-    var channel = ioService.newChannel(data, null, null);
-    var listener = {
-      extListener: null,
-      onStartRequest: function(aRequest, aContext) {
-        this.extListener = extHelperAppSvc.doContent('application/pdf',
-                              aRequest, frontWindow, false);
-        this.extListener.onStartRequest(aRequest, aContext);
-      },
-      onStopRequest: function(aRequest, aContext, aStatusCode) {
-        if (this.extListener)
-          this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
-      },
-      onDataAvailable: function(aRequest, aContext, aInputStream, aOffset,
-                                aCount) {
-        this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
-                                         aOffset, aCount);
+                         getService(Ci.nsIWindowWatcher).activeWindow;
+
+    NetUtil.asyncFetch(blobUri, function(aInputStream, aResult) {
+      if (!Components.isSuccessCode(aResult)) {
+        if (sendResponse)
+          sendResponse(true);
+        return;
       }
-    };
+      // Create a nsIInputStreamChannel so we can set the url on the channel
+      // so the filename will be correct.
+      let channel = Cc['@mozilla.org/network/input-stream-channel;1'].
+                       createInstance(Ci.nsIInputStreamChannel);
+      channel.setURI(originalUri);
+      channel.contentStream = aInputStream;
+      channel.QueryInterface(Ci.nsIChannel);
 
-    channel.asyncOpen(listener, null);
+      var listener = {
+        extListener: null,
+        onStartRequest: function(aRequest, aContext) {
+          this.extListener = extHelperAppSvc.doContent('application/pdf',
+                                aRequest, frontWindow, false);
+          this.extListener.onStartRequest(aRequest, aContext);
+        },
+        onStopRequest: function(aRequest, aContext, aStatusCode) {
+          if (this.extListener)
+            this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+          // Notify the content code we're done downloading.
+          if (sendResponse)
+            sendResponse(false);
+        },
+        onDataAvailable: function(aRequest, aContext, aInputStream, aOffset,
+                                  aCount) {
+          this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
+                                           aOffset, aCount);
+        }
+      };
+
+      channel.asyncOpen(listener, null);
+    });
   },
   setDatabase: function(data) {
     if (inPrivateBrowsing)
       return;
     // Protect against something sending tons of data to setDatabase.
     if (data.length > MAX_DATABASE_LENGTH)
       return;
     setStringPref(PREF_PREFIX + '.database', data);
@@ -190,58 +208,91 @@ ChromeActions.prototype = {
     }
   },
   pdfBugEnabled: function() {
     return getBoolPref(PREF_PREFIX + '.pdfBugEnabled', false);
   },
   searchEnabled: function() {
     return getBoolPref(PREF_PREFIX + '.searchEnabled', false);
   },
-  fallback: function(url) {
+  fallback: function(url, sendResponse) {
     var self = this;
     var domWindow = this.domWindow;
     var strings = getLocalizedStrings('chrome.properties');
     var message = getLocalizedString(strings, 'unsupported_feature');
 
     var win = Services.wm.getMostRecentWindow('navigator:browser');
     var browser = win.gBrowser.getBrowserForDocument(domWindow.top.document);
     var notificationBox = win.gBrowser.getNotificationBox(browser);
+    // Flag so we don't call the response callback twice, since if the user
+    // clicks open with different viewer both the button callback and
+    // eventCallback will be called.
+    var sentResponse = false;
     var buttons = [{
       label: getLocalizedString(strings, 'open_with_different_viewer'),
       accessKey: getLocalizedString(strings, 'open_with_different_viewer',
                                     'accessKey'),
       callback: function() {
-        self.download(url);
+        sentResponse = true;
+        sendResponse(true);
       }
     }];
     notificationBox.appendNotification(message, 'pdfjs-fallback', null,
                                        notificationBox.PRIORITY_WARNING_LOW,
-                                       buttons);
+                                       buttons,
+                                       function eventsCallback(eventType) {
+      // Currently there is only one event "removed" but if there are any other
+      // added in the future we still only care about removed at the moment.
+      if (eventType !== 'removed')
+        return;
+      // Don't send a response again if we already responded when the button was
+      // clicked.
+      if (!sentResponse)
+        sendResponse(false);
+    });
   }
 };
 
 // Event listener to trigger chrome privedged code.
 function RequestListener(actions) {
   this.actions = actions;
 }
-// Receive an event and synchronously responds.
+// Receive an event and synchronously or asynchronously responds.
 RequestListener.prototype.receive = function(event) {
   var message = event.target;
+  var doc = message.ownerDocument;
   var action = message.getUserData('action');
   var data = message.getUserData('data');
+  var sync = message.getUserData('sync');
   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);
+  if (sync) {
+    var response = actions[action].call(this.actions, data);
+    message.setUserData('response', response, null);
+  } else {
+    var response;
+    if (!message.getUserData('callback')) {
+      doc.documentElement.removeChild(message);
+      response = null;
+    } else {
+      response = function sendResponse(response) {
+        message.setUserData('response', response, null);
+
+        var listener = doc.createEvent('HTMLEvents');
+        listener.initEvent('pdf.js.response', true, false);
+        return message.dispatchEvent(listener);
+      }
+    }
+    actions[action].call(this.actions, data, response);
+  }
 };
 
-
 function PdfStreamConverter() {
 }
 
 PdfStreamConverter.prototype = {
 
   // properties required for XPCOM registration:
   classID: Components.ID('{d0c5195d-e798-49d4-b1d3-9324328b2291}'),
   classDescription: 'pdf.js Component',
index 12ce45f876b12eef3e5030b492d58d5fbb47ebbf..fc7023f2aafcdbb3b2412934c017225637c8bb9e
GIT binary patch
literal 417
zc$@*D0bc%zP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0004KNkl<Zc-p1X
zJxjwt7zgl^F4m=@tI(yT7>Y`u@r0Pg5(gItKS4o}g1A}g%jhT};$T7OAPy;H>flra
z2St?bnbSZL$Y+TEgF85m2n7#*aQD1j5<>Dk{}-_qVGT5BavG17$sDU1MbRrJ{zyK9
zAb6?OYC6B5c=6<(=Xqa_<9rLQ>rQ>&pH3!|N91>*sXm<;Tn)o;j2qwn@D3lU*=#mu
zy+CvXM(`|4({yNA*0pWh{VdCdY92<c7YN1SNfyWPfKqyj+y-o#rrAxBWT4iuULZR-
zJP7!h_qwhhBVLCxS8ugiCoCt|vR*OZPB0AP60tU{!UDthZ9LVLTq_T_l{nzCTgA`9
z_bxbctvujHg}5ONFp6sA0T(L7P3aZPq8c$^8;5gMqtWOfx5a3h)+wrCy+GFSx1Ax-
zRrH|G_0&A;VZA_BfWjdhF?@djHth4gny0*%lwb)euso*<?<xHPHO1-}!pO`$00000
LNkvXXu0mjf?Gd_1
--- a/browser/extensions/pdfjs/content/web/l10n.js
+++ b/browser/extensions/pdfjs/content/web/l10n.js
@@ -4,17 +4,17 @@
 'use strict';
 
 // Small subset of the webL10n API by Fabien Cazenave for pdf.js extension.
 (function(window) {
   var gLanguage = '';
 
   // fetch an l10n objects
   function getL10nData(key) {
-    var response = FirefoxCom.request('getStrings', key);
+    var response = FirefoxCom.requestSync('getStrings', key);
     var data = JSON.parse(response);
     if (!data)
       console.warn('[l10n] #' + key + ' missing for [' + gLanguage + ']');
     return data;
   }
 
   // replace {{arguments}} with their values
   function substArguments(text, args) {
@@ -73,17 +73,17 @@
       translateElement(children[i]);
 
     // translate element itself if necessary
     if (element.dataset.l10nId)
       translateElement(element);
   }
 
   window.addEventListener('DOMContentLoaded', function() {
-    gLanguage = FirefoxCom.request('getLocale', null);
+    gLanguage = FirefoxCom.requestSync('getLocale', null);
 
     translateFragment();
 
     // fire a 'localized' DOM event
     var evtObject = document.createEvent('Event');
     evtObject.initEvent('localized', false, false);
     evtObject.language = gLanguage;
     window.dispatchEvent(evtObject);
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -6,17 +6,23 @@
 html {
   height: 100%;
 }
 
 body {
   height: 100%;
   background-color: #404040;
   background-image: url(images/texture.png);
-  font-family: Segoe UI, Verdana, sans-serif;
+}
+
+body,
+input,
+button,
+select {
+  font: message-box;
 }
 
 .hidden {
   display: none;
 }
 [hidden] {
   display: none !important;
 }
@@ -49,29 +55,41 @@ html[dir='rtl'] .innerCenter {
 }
 
 #sidebarContainer {
   position: absolute;
   top: 0;
   bottom: 0;
   width: 200px;
   visibility: hidden;
-  -moz-transition-duration: 200ms;
-  -moz-transition-timing-function: ease;
   -webkit-transition-duration: 200ms;
   -webkit-transition-timing-function: ease;
+  -moz-transition-duration: 200ms;
+  -moz-transition-timing-function: ease;
+  -ms-transition-duration: 200ms;
+  -ms-transition-timing-function: ease;
+  -o-transition-duration: 200ms;
+  -o-transition-timing-function: ease;
+  transition-duration: 200ms;
+  transition-timing-function: ease;
+  
 }
 html[dir='ltr'] #sidebarContainer {
+  -webkit-transition-property: left;
   -moz-transition-property: left;
-  -webkit-transition-property: left;
+  -ms-transition-property: left;
+  -o-transition-property: left;
+  transition-property: left;
   left: -200px;
 }
 html[dir='rtl'] #sidebarContainer {
-  -moz-transition-property: right;
   -webkit-transition-property: right;
+  -ms-transition-property: right;
+  -o-transition-property: right;
+  transition-property: right;
   right: -200px;
 }
 
 #outerContainer.sidebarMoving > #sidebarContainer,
 #outerContainer.sidebarOpen > #sidebarContainer {
   visibility: visible;
 }
 html[dir='ltr'] #outerContainer.sidebarOpen > #sidebarContainer {
@@ -82,29 +100,41 @@ html[dir='rtl'] #outerContainer.sidebarO
 }
 
 #mainContainer {
   position: absolute;
   top: 0;
   right: 0;
   bottom: 0;
   left: 0;
-  -moz-transition-duration: 200ms;
-  -moz-transition-timing-function: ease;
   -webkit-transition-duration: 200ms;
   -webkit-transition-timing-function: ease;
+  -moz-transition-duration: 200ms;
+  -moz-transition-timing-function: ease;
+  -ms-transition-duration: 200ms;
+  -ms-transition-timing-function: ease;
+  -o-transition-duration: 200ms;
+  -o-transition-timing-function: ease;
+  transition-duration: 200ms;
+  transition-timing-function: ease;
 }
 html[dir='ltr'] #outerContainer.sidebarOpen > #mainContainer {
+  -webkit-transition-property: left;
   -moz-transition-property: left;
-  -webkit-transition-property: left;
+  -ms-transition-property: left;
+  -o-transition-property: left;
+  transition-property: left;
   left: 200px;
 }
 html[dir='rtl'] #outerContainer.sidebarOpen > #mainContainer {
+  -webkit-transition-property: right;
   -moz-transition-property: right;
-  -webkit-transition-property: right;
+  -ms-transition-property: right;
+  -o-transition-property: right;
+  transition-property: right;
   right: 200px;
 }
 
 #sidebarContent {
   top: 32px;
   bottom: 0;
   overflow: auto;
   position: absolute;
@@ -141,35 +171,47 @@ html[dir='rtl'] #sidebarContent {
 }
 
 #toolbarContainer {
   width: 100%;
 }
 
 #toolbarSidebar {
   width: 200px;
-  height: 32px;
+  height: 29px;
+  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),
-                    -webkit-linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
+                    -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-image: url(images/texture.png),
+                    linear-gradient(hsla(0,0%,30%,.99), hsla(0,0%,25%,.95));
   box-shadow: inset -2px 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 {
   position: relative;
   height: 32px;
   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),
-                    -webkit-linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
+                    -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-image: url(images/texture.png),
+                    linear-gradient(hsla(0,0%,32%,.99), hsla(0,0%,27%,.95));
   border-left: 1px solid hsla(0,0%,0%,.5);
   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);
 }
 html[dir='ltr'] #toolbarViewerLeft {
@@ -237,38 +279,58 @@ html[dir='rtl'] .splitToolbarButton > .t
 .toolbarButton[disabled] {
   opacity: .5;
 }
 
 .toolbarButton.group {
   margin-right:0;
 }
 
+.splitToolbarButton.toggled .toolbarButton {
+  margin: 0;
+}
+
 .splitToolbarButton:hover > .toolbarButton,
 .splitToolbarButton:focus > .toolbarButton,
-.splitToolbarButton.toggled > .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: -webkit-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;
+  -webkit-transition-duration: 150ms;
+  -webkit-transition-timing-function: ease;
   -moz-transition-property: background-color, border-color, box-shadow;
   -moz-transition-duration: 150ms;
   -moz-transition-timing-function: ease;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
+  -ms-transition-property: background-color, border-color, box-shadow;
+  -ms-transition-duration: 150ms;
+  -ms-transition-timing-function: ease;
+  -o-transition-property: background-color, border-color, box-shadow;
+  -o-transition-duration: 150ms;
+  -o-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
+
 }
 .splitToolbarButton > .toolbarButton:hover,
 .splitToolbarButton > .toolbarButton:focus,
-.dropdownToolbarButton:hover {
+.dropdownToolbarButton:hover,
+.toolbarButton.textButton:hover,
+.toolbarButton.textButton:focus {
   background-color: hsla(0,0%,0%,.2);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.05) inset,
               0 0 1px hsla(0,0%,100%,.15) inset,
               0 0 1px hsla(0,0%,0%,.05);
   z-index: 199;
 }
 html[dir='ltr'] .splitToolbarButton > .toolbarButton:first-child,
 html[dir='rtl'] .splitToolbarButton > .toolbarButton:last-child {
@@ -303,99 +365,146 @@ html[dir='ltr'] .splitToolbarButtonSepar
 html[dir='rtl'] .splitToolbarButtonSeparator {
   float:right;
 }
 .splitToolbarButton:hover > .splitToolbarButtonSeparator,
 .splitToolbarButton.toggled > .splitToolbarButtonSeparator {
   padding: 12px 0;
   margin: 0;
   box-shadow: 0 0 0 1px hsla(0,0%,100%,.03);
+  -webkit-transition-property: padding;
+  -webkit-transition-duration: 10ms;
+  -webkit-transition-timing-function: ease;
   -moz-transition-property: padding;
   -moz-transition-duration: 10ms;
   -moz-transition-timing-function: ease;
-  -webkit-transition-property: padding;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: ease;
+  -ms-transition-property: padding;
+  -ms-transition-duration: 10ms;
+  -ms-transition-timing-function: ease;
+  -o-transition-property: padding;
+  -o-transition-duration: 10ms;
+  -o-transition-timing-function: ease;
+  transition-property: padding;
+  transition-duration: 10ms;
+  transition-timing-function: ease;
 }
 
 .toolbarButton,
 .dropdownToolbarButton {
   min-width: 16px;
   padding: 2px 6px 0;
   border: 1px solid transparent;
   border-radius: 2px;
   color: hsl(0,0%,95%);
   font-size: 12px;
   line-height: 14px;
+  -webkit-user-select:none;
   -moz-user-select:none;
-  -webkit-user-select:none;
+  -ms-user-select:none;
+  /* Opera does not support user-select, use <... unselectable="on"> instead */
   cursor: default;
+  -webkit-transition-property: background-color, border-color, box-shadow;
+  -webkit-transition-duration: 150ms;
+  -webkit-transition-timing-function: ease;
   -moz-transition-property: background-color, border-color, box-shadow;
   -moz-transition-duration: 150ms;
   -moz-transition-timing-function: ease;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 150ms;
-  -webkit-transition-timing-function: ease;
+  -ms-transition-property: background-color, border-color, box-shadow;
+  -ms-transition-duration: 150ms;
+  -ms-transition-timing-function: ease;
+  -o-transition-property: background-color, border-color, box-shadow;
+  -o-transition-duration: 150ms;
+  -o-transition-timing-function: ease;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
 }
 
 html[dir='ltr'] .toolbarButton,
 html[dir='ltr'] .dropdownToolbarButton {
   margin: 3px 2px 4px 0;
 }
 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: -webkit-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: -webkit-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;
   -moz-transition-property: background-color, border-color, box-shadow;
   -moz-transition-duration: 10ms;
   -moz-transition-timing-function: linear;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
+  -ms-transition-property: background-color, border-color, box-shadow;
+  -ms-transition-duration: 10ms;
+  -ms-transition-timing-function: linear;
+  -o-transition-property: background-color, border-color, box-shadow;
+  -o-transition-duration: 10ms;
+  -o-transition-timing-function: linear;
+  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: -webkit-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;
   -moz-transition-property: background-color, border-color, box-shadow;
   -moz-transition-duration: 10ms;
   -moz-transition-timing-function: linear;
-  -webkit-transition-property: background-color, border-color, box-shadow;
-  -webkit-transition-duration: 10ms;
-  -webkit-transition-timing-function: linear;
+  -ms-transition-property: background-color, border-color, box-shadow;
+  -ms-transition-duration: 10ms;
+  -ms-transition-timing-function: linear;
+  -o-transition-property: background-color, border-color, box-shadow;
+  -o-transition-duration: 10ms;
+  -o-transition-timing-function: linear;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 10ms;
+  transition-timing-function: linear;
 }
 
 .toolbarButton.toggled:hover:active,
 .splitToolbarButton.toggled > .toolbarButton.toggled:hover:active {
   background-color: hsla(0,0%,0%,.4);
   border-color: hsla(0,0%,0%,.4) hsla(0,0%,0%,.5) hsla(0,0%,0%,.55);
   box-shadow: 0 1px 1px hsla(0,0%,0%,.2) inset,
               0 0 1px hsla(0,0%,0%,.3) inset,
@@ -412,18 +521,18 @@ html[dir='rtl'] .dropdownToolbarButton {
 html[dir='ltr'] .dropdownToolbarButton {
   background-position: 95%;
 }
 html[dir='rtl'] .dropdownToolbarButton {
   background-position: 5%;
 }
 
 .dropdownToolbarButton > select {
+  -webkit-appearance: none;
   -moz-appearance: none; /* in the future this might matter, see bugzilla bug #649849 */
-  -webkit-appearance: none;
   min-width: 140px;
   font-size: 12px;
   color: hsl(0,0%,95%);
   margin:0;
   padding:0;
   border:none;
   background: transparent;
 }
@@ -455,18 +564,18 @@ html[dir='rtl'] .toolbarButton:first-chi
 
 .toolbarButtonSpacer {
   width: 30px;
   display: inline-block;
   height: 1px;
 }
 
 .toolbarButtonFlexibleSpacer {
+  -webkit-box-flex: 1;
   -moz-box-flex: 1;
-  -webkit-box-flex: 1;
   min-width: 30px;
 }
 
 .toolbarButton#sidebarToggle::before {
   display: inline-block;
   content: url(images/toolbarButton-sidebarToggle.png);
 }
     
@@ -511,18 +620,18 @@ html[dir='rtl'] .toolbarButton.pageDown:
 }
 
 .toolbarButton.download::before {
   display: inline-block;
   content: url(images/toolbarButton-download.png);
 }
 
 .toolbarButton.bookmark {
+  -webkit-box-sizing: border-box;
   -moz-box-sizing: border-box;
-  -webkit-box-sizing: border-box;
   box-sizing: border-box;
   margin-top: 3px;
   padding-top: 4px;
 }
 
 .toolbarButton.bookmark::before {
   content: url(images/toolbarButton-bookmark.png);
 }
@@ -539,40 +648,39 @@ html[dir='rtl'] .toolbarButton.pageDown:
 
 #viewSearch.toolbarButton::before {
   display: inline-block;
   content: url(images/toolbarButton-search.png);
 }
 
 
 .toolbarField {
-  min-width: 16px;
-  width: 32px;
   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-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;
-  text-align: right;
   outline-style: none;
   -moz-transition-property: background-color, border-color, box-shadow;
   -moz-transition-duration: 150ms;
   -moz-transition-timing-function: ease;
 }
 
 .toolbarField.pageNumber {
+  min-width: 16px;
+  text-align: right;
   width: 40px;
 }
 
 .toolbarField.pageNumber::-webkit-inner-spin-button,
 .toolbarField.pageNumber::-webkit-outer-spin-button {
     -webkit-appearance: none;
     margin: 0;
 }
@@ -592,18 +700,18 @@ html[dir='rtl'] .toolbarButton.pageDown:
   padding: 3px 6px 3px 2px;
   margin: 4px 2px 4px 0;
   border: 1px solid transparent;
   border-radius: 2px;
   color: hsl(0,0%,85%);
   font-size: 12px;
   line-height: 14px;
   text-align: left;
+  -webkit-user-select:none;
   -moz-user-select:none;
-  -webkit-user-select:none;
   cursor: default;
 }
 
 #thumbnailView {
   position: fixed;
   width: 120px;
   top: 33px;
   bottom: 0;
@@ -669,197 +777,208 @@ a:focus > .thumbnail > .thumbnailSelecti
 
 #outlineView {
   position: fixed;
   width: 192px;
   top: 33px;
   bottom: 0;
   padding: 4px 4px 0;
   overflow: auto;
+  -webkit-user-select:none;
   -moz-user-select:none;
-  -webkit-user-select:none;
 }
 
 .outlineItem > .outlineItems {
   margin-left: 20px;
 }
 
-.outlineItem > a {
+.outlineItem > a,
+#searchResults > a {
   text-decoration: none;
-  display: block;
+  display: inline-block;
+  min-width: 95%;
   height: 20px;
   padding: 2px 0 0 10px;
   margin-bottom: 1px;
   border-radius: 2px;
   color: hsla(0,0%,100%,.8);
   font-size: 13px;
   line-height: 15px;
   -moz-user-select:none;
   cursor: default;
   white-space: nowrap;
 }
 
-.outlineItem > a:hover {
+.outlineItem > a:hover,
+#searchResults > 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-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-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);
+}
+
+.noOutline,
+.noResults {
+  font-size: 12px;
+  color: hsla(0,0%,100%,.8);
+  font-style: italic;
+}
+
 #searchScrollView {
   position: absolute;
   top: 10px;
   bottom: 10px;
   left: 10px;
   width: 280px;
 }
 
 #searchToolbar {
   padding-left: 0px;
   right: 0px;
   padding-top: 0px;
   padding-bottom: 5px;
 }
 
 #searchToolbar > input {
-  margin-left: 8px;
-  width: 130px;
+  margin-left: 4px;
+  width: 124px;
+}
+
+#searchToolbar button {
+  width: auto;
+  margin: 0;
+  padding: 0 6px;
+  height: 22px;
 }
 
 #searchResults {
   overflow: auto;
-  background-color: #fff;
   position: absolute;
   top: 30px;
   bottom: 0px;
   left: 0px;
   right: 0;
+  padding: 4px 4px 0;
   font-size: smaller;
-  opacity: 0.7;
-}
-
-#searchResults a {
-  display: block;
-  white-space: pre;
-  text-decoration: none;
-  color: black;
 }
 
 #sidebarControls {
   position:absolute;
   width: 180px;
   height: 32px;
   left: 15px;
   bottom: 35px;
 }
 
-.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-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);
-}
-
-.noOutline {
-  font-size: 12px;
-  color: hsla(0,0%,100%,.8);
-  font-style: italic;
-}
-
-
-
 canvas {
   margin: auto;
   display: block;
 }
 
 .page {
   direction: ltr;
   width: 816px;
   height: 1056px;
   margin: 10px auto;
   position: relative;
   overflow: hidden;
-  box-shadow: 0px 4px 10px #000;
+  -webkit-box-shadow: 0px 4px 10px #000;
   -moz-box-shadow: 0px 4px 10px #000;
-  -webkit-box-shadow: 0px 4px 10px #000;
+  box-shadow: 0px 4px 10px #000;
   background-color: white;
 }
 
 .page > a {
   display: block;
   position: absolute;
 }
 
 .page > a:hover {
   opacity: 0.2;
   background: #ff0;
-  box-shadow: 0px 2px 10px #ff0;
+  -webkit-box-shadow: 0px 2px 10px #ff0;
   -moz-box-shadow: 0px 2px 10px #ff0;
-  -webkit-box-shadow: 0px 2px 10px #ff0;
+  box-shadow: 0px 2px 10px #ff0;
 }
 
 .loadingIcon {
   position: absolute;
   display: block;
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
   background: url('images/loading-icon.gif') center no-repeat;
 }
 
 #loadingBox {
-  margin: 100px 0;
+  position: absolute;
+  top: 50%;
+  margin-top: -25px;
+  left: 0;
+  right: 0;
   text-align: center;
   color: #ddd;
   font-size: 14px;
 }
 
 #loadingBar {
-  background-color: #333;
   display: inline-block;
-  border: 1px solid black;
   clear: both;
   margin: 0px;
   margin-top: 5px;
   line-height: 0;
-  border-radius: 4px;
+  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: -moz-linear-gradient(top, #999 0%, #666 50%, #999 100%);
-  background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#999), color-stop(50%,#666), color-stop(100%,#999));
-  background: -webkit-linear-gradient(top, #999 0%,#666 50%,#999 100%);
-  background: -o-linear-gradient(top, #999 0%,#666 50%,#999 100%);
-  background: -ms-linear-gradient(top, #999 0%,#666 50%,#999 100%);
-  background: linear-gradient(top, #999 0%,#666 50%,#999 100%);    
+  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: 3px;
-  border-bottom-left-radius: 3px;
+  border-top-left-radius: 2px;
+  border-bottom-left-radius: 2px;
 
   width: 0%;
   height: 100%;
 }
 
 #loadingBar .progress.full {
-  border-top-right-radius: 3px;
-  border-bottom-right-radius: 3px;
+  border-top-right-radius: 2px;
+  border-bottom-right-radius: 2px;
 }
 
 .textLayer {
   position: absolute;
   left: 0;
   top: 0;
   right: 0;
   bottom: 0;
@@ -891,19 +1010,19 @@ canvas {
   cursor: pointer;
   opacity: 0.7;
 }
 
 .annotComment > div {
   padding: 0.2em;
   max-width: 20em;
   background-color: #F1E47B;
-  box-shadow: 0px 2px 10px #333;
+  -webkit-box-shadow: 0px 2px 10px #333;
   -moz-box-shadow: 0px 2px 10px #333;
-  -webkit-box-shadow: 0px 2px 10px #333;
+  box-shadow: 0px 2px 10px #333;
 }
 
 .annotComment > div > h1 {
   font-weight: normal;
   font-size: 1.2em;
   border-bottom: 1px solid #000000;
   margin: 0px;
 }
@@ -989,38 +1108,60 @@ canvas {
 }
 #PDFBug .stats .title {
     font-weight: bold;
 }
 #PDFBug table {
   font-size: 10px;
 }
 
+@page {
+  margin: 0;
+} 
+
+#printContainer {
+  display: none;
+}
+
 @media print {
+  /* Rules for browsers that don't support mozPrintCallback. */
   #sidebarContainer, .toolbar, #loadingBox, #errorWrapper, .textLayer {
     display: none;
   }
 
   #mainContainer, #viewerContainer, .page, .page canvas {
     position: static;
     padding: 0;
     margin: 0;
   }
 
   .page {
     float: left;
     display: none;
-    box-shadow: none;
+    -webkit-box-shadow: none;
     -moz-box-shadow: none;
-    -webkit-box-shadow: none;
+    box-shadow: none;
   }
 
   .page[data-loaded] {
     display: block;
   }
+
+  /* Rules for browsers that support mozPrintCallback */
+  body[data-mozPrintCallback] #outerContainer {
+    display: none;
+  }
+  body[data-mozPrintCallback] #printContainer {
+    display: block;
+  }
+  #printContainer canvas {
+    position: relative;
+    top: 0;
+    left: 0;
+  }
 }
 
 @media all and (max-width: 950px) {
   html[dir='ltr'] #outerContainer.sidebarMoving .outerCenter,
   html[dir='ltr'] #outerContainer.sidebarOpen .outerCenter {
     float: left;
     left: 180px;
   }
@@ -1061,8 +1202,15 @@ canvas {
   }
 }
 
 @media all and (max-width: 600px) {
   #toolbarViewerRight {
     display: none;
   }
 }
+
+@media all and (max-width: 500px) {
+  #scaleSelectContainer, #pageNumberLabel {
+    display: none;
+  }
+}
+
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -1,12 +1,13 @@
 <!DOCTYPE html>
 <html dir="ltr">
   <head>
     <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
     <title>PDF.js viewer</title>
 <!-- This snippet is used in firefox extension, see Makefile -->
 <base href="resource://pdf.js/web/" />
 <script type="application/l10n">
 <!-- PDFJSSCRIPT_LOCALE_DATA -->
 </script>
 <script type="text/javascript" src="l10n.js"></script>
 <script type="text/javascript" id="PDFJS_SCRIPT_TAG">
@@ -17,17 +18,17 @@
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 var PDFJS = {};
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
-  PDFJS.build = '121040a';
+  PDFJS.build = 'c757eed';
 
   // Files are inserted below - see Makefile
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 'use strict';
 
 var globalScope = (typeof window === 'undefined') ? this : window;
@@ -1182,17 +1183,19 @@ var StatTimer = (function StatTimerClosu
  *  - url   - The URL of the PDF.
  *  - data  - A typed array with PDF data.
  *  - httpHeaders - Basic authentication headers.
  *  - password - For decrypting password-protected PDFs.
  *
  * @return {Promise} A promise that is resolved with {PDFDocumentProxy} object.
  */
 PDFJS.getDocument = function getDocument(source) {
-  var url, data, headers, password, parameters = {};
+  var url, data, headers, password, parameters = {}, workerInitializedPromise,
+    workerReadyPromise, transport;
+
   if (typeof source === 'string') {
     url = source;
   } else if (isArrayBuffer(source)) {
     data = source;
   } else if (typeof source === 'object') {
     url = source.url;
     data = source.data;
     headers = source.httpHeaders;
@@ -1201,45 +1204,53 @@ PDFJS.getDocument = function getDocument
 
     if (!url && !data)
       error('Invalid parameter array, need either .data or .url');
   } else {
     error('Invalid parameter in getDocument, need either Uint8Array, ' +
           'string or a parameter object');
   }
 
-  var promise = new PDFJS.Promise();
-  var transport = new WorkerTransport(promise);
+  workerInitializedPromise = new PDFJS.Promise();
+  workerReadyPromise = new PDFJS.Promise();
+  transport = new WorkerTransport(workerInitializedPromise, workerReadyPromise);
   if (data) {
     // assuming the data is array, instantiating directly from it
     transport.sendData(data, parameters);
   } else if (url) {
     // fetch url
     PDFJS.getPdf(
       {
         url: url,
         progress: function getPDFProgress(evt) {
-          if (evt.lengthComputable)
-            promise.progress({
+          if (evt.lengthComputable) {
+            workerReadyPromise.progress({
               loaded: evt.loaded,
               total: evt.total
             });
+          }
         },
         error: function getPDFError(e) {
-          promise.reject('Unexpected server response of ' +
+          workerReadyPromise.reject('Unexpected server response of ' +
             e.target.status + '.');
         },
         headers: headers
       },
       function getPDFLoad(data) {
-        transport.sendData(data, parameters);
+        // sometimes the pdf has finished downloading before the web worker-test
+        // has finished. In that case the rendering of the final pdf would cause
+        // errors. We have to wait for the WorkerTransport to finalize worker-
+        // support detection
+        workerInitializedPromise.then(function workerInitialized() {
+          transport.sendData(data, parameters);
+        });
       });
   }
 
-  return promise;
+  return workerReadyPromise;
 };
 
 /**
  * Proxy to a PDFDocument in the worker thread. Also, contains commonly used
  * properties that can be read synchronously.
  */
 var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
   function PDFDocumentProxy(pdfInfo, transport) {
@@ -1315,16 +1326,25 @@ var PDFDocumentProxy = (function PDFDocu
       });
       return promise;
     },
     isEncrypted: function PDFDocumentProxy_isEncrypted() {
       var promise = new PDFJS.Promise();
       promise.resolve(this.pdfInfo.encrypted);
       return promise;
     },
+    /**
+     * @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;
+    },
     destroy: function PDFDocumentProxy_destroy() {
       this.transport.destroy();
     }
   };
   return PDFDocumentProxy;
 })();
 
 var PDFPageProxy = (function PDFPageProxyClosure() {
@@ -1389,17 +1409,21 @@ var PDFPageProxy = (function PDFPageProx
       return promise;
     },
     /**
      * Begins the process of rendering a page to the desired context.
      * @param {object} params A parameter object that supports:
      * {
      *   canvasContext(required): A 2D context of a DOM Canvas object.,
      *   textLayer(optional): An object that has beginLayout, endLayout, and
-     *                        appendText functions.
+     *                        appendText functions.,
+     *   continueCallback(optional): A function that will be called each time
+     *                               the rendering is paused.  To continue
+     *                               rendering call the function that is the
+     *                               first argument to the callback.
      * }.
      * @return {Promise} A promise that is resolved when the page finishes
      * rendering.
      */
     render: function PDFPageProxy_render(params) {
       this.renderInProgress = true;
 
       var promise = new Promise();
@@ -1425,29 +1449,30 @@ var PDFPageProxy = (function PDFPageProx
           delete self.displayReadyPromise;
         }
 
         if (error)
           promise.reject(error);
         else
           promise.resolve();
       };
+      var continueCallback = params.continueCallback;
 
       // Once the operatorList and fonts are loaded, do the actual rendering.
       this.displayReadyPromise.then(
         function pageDisplayReadyPromise() {
           if (self.destroyed) {
             complete();
             return;
           }
 
           var gfx = new CanvasGraphics(params.canvasContext,
             this.objs, params.textLayer);
           try {
-            this.display(gfx, params.viewport, complete);
+            this.display(gfx, params.viewport, complete, continueCallback);
           } catch (e) {
             complete(e);
           }
         }.bind(this),
         function pageDisplayReadPromiseError(reason) {
           complete(reason);
         }
       );
@@ -1495,44 +1520,51 @@ var PDFPageProxy = (function PDFPageProx
 
           callback.call(this);
         }.bind(this)
       );
     },
     /**
      * For internal use only.
      */
-    display: function PDFPageProxy_display(gfx, viewport, callback) {
+    display: function PDFPageProxy_display(gfx, viewport, callback,
+                                           continueCallback) {
       var stats = this.stats;
       stats.time('Rendering');
 
       gfx.beginDrawing(viewport);
 
       var startIdx = 0;
       var length = this.operatorList.fnArray.length;
       var operatorList = this.operatorList;
       var stepper = null;
       if (PDFJS.pdfBug && StepperManager.enabled) {
         stepper = StepperManager.create(this.pageNumber - 1);
         stepper.init(operatorList);
         stepper.nextBreakPoint = stepper.getNextBreakPoint();
       }
 
+      var continueWrapper;
+      if (continueCallback)
+        continueWrapper = function() { continueCallback(next); }
+      else
+        continueWrapper = next;
+
       var self = this;
       function next() {
-        startIdx =
-          gfx.executeOperatorList(operatorList, startIdx, next, stepper);
+        startIdx = gfx.executeOperatorList(operatorList, startIdx,
+                                           continueWrapper, stepper);
         if (startIdx == length) {
           gfx.endDrawing();
           stats.timeEnd('Rendering');
           stats.timeEnd('Overall');
           if (callback) callback();
         }
       }
-      next();
+      continueWrapper();
     },
     /**
      * @return {Promise} That is resolved with the a {string} that is the text
      * content from the page.
      */
     getTextContent: function PDFPageProxy_getTextContent() {
       var promise = new PDFJS.Promise();
       this.transport.messageHandler.send('GetTextContent', {
@@ -1569,18 +1601,18 @@ var PDFPageProxy = (function PDFPageProx
     }
   };
   return PDFPageProxy;
 })();
 /**
  * For internal use only.
  */
 var WorkerTransport = (function WorkerTransportClosure() {
-  function WorkerTransport(promise) {
-    this.workerReadyPromise = promise;
+  function WorkerTransport(workerInitializedPromise, workerReadyPromise) {
+    this.workerReadyPromise = workerReadyPromise;
     this.objs = new PDFObjects();
 
     this.pageCache = [];
     this.pagePromises = [];
     this.fontsLoading = {};
 
     // 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
@@ -1614,31 +1646,33 @@ var WorkerTransport = (function WorkerTr
         messageHandler.on('test', function transportTest(supportTypedArray) {
           if (supportTypedArray) {
             this.worker = worker;
             this.setupMessageHandler(messageHandler);
           } else {
             globalScope.PDFJS.disableWorker = true;
             this.setupFakeWorker();
           }
+          workerInitializedPromise.resolve();
         }.bind(this));
 
         var testObj = new Uint8Array(1);
         // Some versions of Opera throw a DATA_CLONE_ERR on
         // serializing the typed array.
         messageHandler.send('test', testObj);
         return;
       } catch (e) {
         info('The worker has been disabled.');
       }
     }
     // 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 = [];
@@ -1780,16 +1814,22 @@ var WorkerTransport = (function WorkerTr
         img.src = src;
       });
     },
 
     sendData: function WorkerTransport_sendData(data, params) {
       this.messageHandler.send('GetDocRequest', {data: data, params: params});
     },
 
+    getData: function WorkerTransport_sendData(promise) {
+      this.messageHandler.send('GetData', null, function(data) {
+        promise.resolve(data);
+      });
+    },
+
     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;
@@ -2898,16 +2938,50 @@ var CanvasGraphics = (function CanvasGra
             if (!(buf & mask) == inverseDecode) {
               buffer[bufferPos] = 0;
             }
             bufferPos += 4;
             mask >>= 1;
           }
         }
       }
+      function rescaleImage(pixels, widthScale, heightScale) {
+        var scaledWidth = Math.ceil(width / widthScale);
+        var scaledHeight = Math.ceil(height / heightScale);
+
+        var itemsSum = new Uint32Array(scaledWidth * scaledHeight * 4);
+        var itemsCount = new Uint32Array(scaledWidth * scaledHeight);
+        for (var i = 0, position = 0; i < height; i++) {
+          var lineOffset = (0 | (i / heightScale)) * scaledWidth;
+          for (var j = 0; j < width; j++) {
+            var countOffset = lineOffset + (0 | (j / widthScale));
+            var sumOffset = countOffset << 2;
+            itemsSum[sumOffset] += pixels[position];
+            itemsSum[sumOffset + 1] += pixels[position + 1];
+            itemsSum[sumOffset + 2] += pixels[position + 2];
+            itemsSum[sumOffset + 3] += pixels[position + 3];
+            itemsCount[countOffset]++;
+            position += 4;
+          }
+        }
+        var tmpCanvas = createScratchCanvas(scaledWidth, scaledHeight);
+        var tmpCtx = tmpCanvas.getContext('2d');
+        var imgData = tmpCtx.getImageData(0, 0, scaledWidth, scaledHeight);
+        pixels = imgData.data;
+        for (var i = 0, j = 0, ii = scaledWidth * scaledHeight; i < ii; i++) {
+          var count = itemsCount[i];
+          pixels[j] = itemsSum[j] / count;
+          pixels[j + 1] = itemsSum[j + 1] / count;
+          pixels[j + 2] = itemsSum[j + 2] / count;
+          pixels[j + 3] = itemsSum[j + 3] / count;
+          j += 4;
+        }
+        tmpCtx.putImageData(imgData, 0, 0);
+        return tmpCanvas;
+      }
 
       this.save();
 
       var ctx = this.ctx;
       var w = width, h = height;
       // scale the image to the unit square
       ctx.scale(1 / w, -1 / h);
 
@@ -2920,18 +2994,29 @@ var CanvasGraphics = (function CanvasGra
                           fillColor.getPattern(tmpCtx) : fillColor;
       tmpCtx.fillRect(0, 0, w, h);
 
       var imgData = tmpCtx.getImageData(0, 0, w, h);
       var pixels = imgData.data;
 
       applyStencilMask(pixels, inverseDecode);
 
-      tmpCtx.putImageData(imgData, 0, 0);
-      ctx.drawImage(tmpCanvas, 0, -h);
+      var currentTransform = ctx.mozCurrentTransformInverse;
+      var widthScale = Math.max(Math.abs(currentTransform[0]), 1);
+      var heightScale = Math.max(Math.abs(currentTransform[3]), 1);
+      if (widthScale >= 2 || heightScale >= 2) {
+        // canvas does not resize well large images to small -- using simple
+        // algorithm to perform pre-scaling
+        tmpCanvas = rescaleImage(imgData.data, widthScale, heightScale);
+        ctx.scale(widthScale, heightScale);
+        ctx.drawImage(tmpCanvas, 0, -h / heightScale);
+      } else {
+        tmpCtx.putImageData(imgData, 0, 0);
+        ctx.drawImage(tmpCanvas, 0, -h);
+      }
       this.restore();
     },
 
     paintImageXObject: function CanvasGraphics_paintImageXObject(objId) {
       var imgData = this.objs.get(objId);
       if (!imgData)
         error('Dependent image isn\'t ready yet');
 
@@ -13211,18 +13296,20 @@ var PartialEvaluator = (function Partial
         }
 
         // 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];
 
-        var softMask = dict.get('SMask', 'IM') || false;
-        if (!softMask && image instanceof JpegStream &&
+        var softMask = dict.get('SMask', 'SM') || false;
+        var mask = dict.get('Mask') || false;
+
+        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, 'JpegStream', image.getIR()]);
           return;
         }
 
         fn = 'paintImageXObject';
@@ -14504,17 +14591,17 @@ var FontLoader = {
             document.documentElement.dispatchEvent(evt);
           },
           false);
         this.listeningForFontLoad = true;
       }
 
       // XXX we should have a time-out here too, and maybe fire
       // pdfjsFontLoadFailed?
-      var src = '<!DOCTYPE HTML><html><head>';
+      var src = '<!DOCTYPE HTML><html><head><meta charset="utf-8">';
       src += '<style type="text/css">';
       for (var i = 0, ii = rules.length; i < ii; ++i) {
         src += rules[i];
       }
       src += '</style>';
       src += '<script type="application/javascript">';
       var fontNamesArray = '';
       for (var i = 0, ii = names.length; i < ii; ++i) {
@@ -16745,24 +16832,26 @@ var Font = (function FontClosure() {
 
               newGlyphUnicodes[i] = unicode;
               if (changeCode)
                 toFontChar[code] = unicode;
               delete reverseMap[code];
             }
           }
           for (var index in newGlyphUnicodes) {
-            var unicode = newGlyphUnicodes[index];
-            if (reverseMap[unicode]) {
-              // avoiding assigning to the same unicode
-              glyphs[index].unicode = unusedUnicode++;
-              continue;
-            }
-            glyphs[index].unicode = unicode;
-            reverseMap[unicode] = index;
+            if (newGlyphUnicodes.hasOwnProperty(index)) {
+              var unicode = newGlyphUnicodes[index];
+              if (reverseMap[unicode]) {
+                // avoiding assigning to the same unicode
+                glyphs[index].unicode = unusedUnicode++;
+                continue;
+              }
+              glyphs[index].unicode = unicode;
+              reverseMap[unicode] = index;
+            }
           }
           this.useToFontChar = true;
         }
 
         // Moving all symbolic font glyphs into 0xF000 - 0xF0FF range.
         if (this.isSymbolicFont) {
           for (var i = 0, ii = glyphs.length; i < ii; i++) {
             var code = glyphs[i].unicode & 0xFF;
@@ -17076,19 +17165,23 @@ var Font = (function FontClosure() {
     bindDOM: function Font_bindDOM(data) {
       var fontName = this.loadedName;
 
       // Add the font-face rule to the document
       var url = ('url(data:' + this.mimetype + ';base64,' +
                  window.btoa(data) + ');');
       var rule = "@font-face { font-family:'" + fontName + "';src:" + url + '}';
 
-      var styleElement = document.createElement('style');
-      document.documentElement.getElementsByTagName('head')[0].appendChild(
-        styleElement);
+      var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
+      if (!styleElement) {
+          styleElement = document.createElement('style');
+          styleElement.id = 'PDFJS_FONT_STYLE_TAG';
+          document.documentElement.getElementsByTagName('head')[0].appendChild(
+            styleElement);
+      }
 
       var styleSheet = styleElement.sheet;
       styleSheet.insertRule(rule, styleSheet.cssRules.length);
 
       if (PDFJS.pdfBug && FontInspector.enabled)
         FontInspector.fontAdded(this, url);
 
       return rule;
@@ -18331,18 +18424,20 @@ var CFFParser = (function CFFParserClosu
       cff.charset = charset;
       cff.encoding = encoding;
 
       if (!cff.isCIDFont || !normalizeCIDData)
         return cff;
 
       // DirectWrite does not like CID fonts data. Trying to convert/flatten
       // the font data and remove CID properties.
-      if (cff.fdArray.length !== 1)
-        error('Unable to normalize CID font in CFF data');
+      if (cff.fdArray.length !== 1) {
+        warn('Unable to normalize CID font in CFF data -- using font as is');
+        return cff;
+      }
 
       var fontDict = cff.fdArray[0];
       fontDict.setByKey(17, topDict.getByName('CharStrings'));
       cff.topDict = fontDict;
       cff.isCIDFont = false;
       delete cff.fdArray;
       delete cff.fdSelect;
 
@@ -23648,17 +23743,17 @@ var PDFImage = (function PDFImageClosure
    * Decode and clamp a value. The formula is different from the spec because we
    * don't decode to float range [0,1], we decode it in the [0,max] range.
    */
   function decodeAndClamp(value, addend, coefficient, max) {
     value = addend + value * coefficient;
     // Clamp the value to the range
     return value < 0 ? 0 : value > max ? max : value;
   }
-  function PDFImage(xref, res, image, inline, smask) {
+  function PDFImage(xref, res, image, inline, smask, mask) {
     this.image = image;
     if (image.getParams) {
       // JPX/JPEG2000 streams directly contain bits per component
       // and color space mode information.
       TODO('get params from actual stream');
       // var bits = ...
       // var colorspace = ...
     }
@@ -23709,47 +23804,60 @@ var PDFImage = (function PDFImageClosure
       for (var i = 0, j = 0; i < this.decode.length; i += 2, ++j) {
         var dmin = this.decode[i];
         var dmax = this.decode[i + 1];
         this.decodeCoefficients[j] = dmax - dmin;
         this.decodeAddends[j] = max * dmin;
       }
     }
 
-    var mask = dict.get('Mask');
-
-    if (mask) {
-      TODO('masked images');
-    } else if (smask) {
+    if (smask) {
       this.smask = new PDFImage(xref, res, smask, false);
+    } else if (mask) {
+      this.mask = new PDFImage(xref, res, mask, false);
     }
   }
   /**
    * Handles processing of image data and calls the callback with an argument
    * of a PDFImage when the image is ready to be used.
    */
   PDFImage.buildImage = function PDFImage_buildImage(callback, handler, xref,
                                                      res, image, inline) {
     var imageDataPromise = new Promise();
     var smaskPromise = new Promise();
+    var maskPromise = new Promise();
     // The image data and smask data may not be ready yet, wait till both are
     // resolved.
-    Promise.all([imageDataPromise, smaskPromise]).then(function(results) {
-      var imageData = results[0], smaskData = results[1];
-      var image = new PDFImage(xref, res, imageData, inline, smaskData);
+    Promise.all([imageDataPromise, smaskPromise, maskPromise]).then(
+        function(results) {
+      var imageData = results[0], smaskData = results[1], maskData = results[2];
+      var image = new PDFImage(xref, res, imageData, inline, smaskData,
+                               maskData);
       callback(image);
     });
 
     handleImageData(handler, xref, res, image, imageDataPromise);
 
     var smask = image.dict.get('SMask');
-    if (smask)
+    var mask = image.dict.get('Mask');
+
+    if (smask) {
       handleImageData(handler, xref, res, smask, smaskPromise);
-    else
+      maskPromise.resolve(null);
+    } else {
       smaskPromise.resolve(null);
+      if (mask && isStream(mask)) {
+        handleImageData(handler, xref, res, mask, maskPromise);
+      } else if (mask) {
+        TODO('handle color key masking');
+        maskPromise.resolve(null);
+      } else {
+        maskPromise.resolve(null);
+      }
+    }
   };
 
   /**
    * Resize an image using the nearest neighbor algorithm.  Currently only
    * supports one and three component images.
    * @param {TypedArray} pixels The original image with one component.
    * @param {Number} bpc Number of bits per component.
    * @param {Number} components Number of color components, 1 or 3 is supported.
@@ -23883,27 +23991,41 @@ var PDFImage = (function PDFImageClosure
           buf = buf & ((1 << remainingBits) - 1);
           bits = remainingBits;
         }
       }
       return output;
     },
     getOpacity: function PDFImage_getOpacity(width, height) {
       var smask = this.smask;
+      var mask = this.mask;
       var originalWidth = this.width;
       var originalHeight = this.height;
       var buf;
 
       if (smask) {
         var sw = smask.width;
         var sh = smask.height;
         buf = new Uint8Array(sw * sh);
         smask.fillGrayBuffer(buf);
         if (sw != width || sh != height)
-          buf = PDFImage.resize(buf, smask.bps, 1, sw, sh, width, height);
+          buf = PDFImage.resize(buf, smask.bpc, 1, sw, sh, width, height);
+      } else if (mask) {
+        var sw = mask.width;
+        var sh = mask.height;
+        buf = new Uint8Array(sw * sh);
+        mask.numComps = 1;
+        mask.fillGrayBuffer(buf);
+
+        // Need to invert values in buffer
+        for (var i = 0, ii = sw * sh; i < ii; ++i)
+          buf[i] = 255 - buf[i];
+
+        if (sw != width || sh != height)
+          buf = PDFImage.resize(buf, mask.bpc, 1, sw, sh, width, height);
       } else {
         buf = new Uint8Array(width * height);
         for (var i = 0, ii = width * height; i < ii; ++i)
           buf[i] = 255;
       }
       return buf;
     },
     applyStencilMask: function PDFImage_applyStencilMask(buffer,
@@ -27192,17 +27314,18 @@ var Parser = (function ParserClosure() {
       }
       if (name == 'CCITTFaxDecode' || name == 'CCF') {
         return new CCITTFaxStream(stream, params);
       }
       if (name == 'RunLengthDecode' || name == 'RL') {
         return new RunLengthStream(stream);
       }
       if (name == 'JBIG2Decode') {
-        error('JBIG2 image format is not currently supprted.');
+        var bytes = stream.getBytes(length);
+        return new Jbig2Stream(bytes, stream.dict);
       }
       warn('filter "' + name + '" not supported yet');
       return stream;
     }
   };
 
   return Parser;
 })();
@@ -28879,16 +29002,60 @@ var JpxStream = (function JpxStreamClosu
   };
   JpxStream.prototype.getChar = function JpxStream_getChar() {
     error('internal error: getChar is not valid on JpxStream');
   };
 
   return JpxStream;
 })();
 
+/**
+ * For JBIG2's we use a library to decode these images and
+ * the stream behaves like all the other DecodeStreams.
+ */
+var Jbig2Stream = (function Jbig2StreamClosure() {
+  function Jbig2Stream(bytes, dict) {
+    this.dict = dict;
+    this.bytes = bytes;
+
+    DecodeStream.call(this);
+  }
+
+  Jbig2Stream.prototype = Object.create(DecodeStream.prototype);
+
+  Jbig2Stream.prototype.ensureBuffer = function Jbig2Stream_ensureBuffer(req) {
+    if (this.bufferLength)
+      return;
+
+    var jbig2Image = new Jbig2Image();
+
+    var chunks = [], decodeParams = this.dict.get('DecodeParms');
+    if (decodeParams && decodeParams.has('JBIG2Globals')) {
+      var globalsStream = decodeParams.get('JBIG2Globals');
+      var globals = globalsStream.getBytes();
+      chunks.push({data: globals, start: 0, end: globals.length});
+    }
+    chunks.push({data: this.bytes, start: 0, end: this.bytes.length});
+    var data = jbig2Image.parseChunks(chunks);
+    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;
+  };
+  Jbig2Stream.prototype.getChar = function Jbig2Stream_getChar() {
+    error('internal error: getChar is not valid on Jbig2Stream');
+  };
+
+  return Jbig2Stream;
+})();
+
 var DecryptStream = (function DecryptStreamClosure() {
   function DecryptStream(str, decrypt) {
     this.str = str;
     this.dict = str.dict;
     this.decrypt = decrypt;
 
     DecodeStream.call(this);
   }
@@ -30331,16 +30498,20 @@ var WorkerMessageHandler = {
         pageIndex: data.pageIndex,
         rotate: pdfPage.rotate,
         ref: pdfPage.ref,
         view: pdfPage.view
       };
       handler.send('GetPage', {pageInfo: page});
     });
 
+    handler.on('GetData', function wphSetupGetData(data, promise) {
+      promise.resolve(pdfModel.stream.bytes);
+    });
+
     handler.on('GetAnnotationsRequest', function wphSetupGetAnnotations(data) {
       var pdfPage = pdfModel.getPage(data.pageIndex + 1);
       handler.send('GetAnnotations', {
         pageIndex: data.pageIndex,
         annotations: pdfPage.getAnnotations()
       });
     });
 
@@ -31646,18 +31817,17 @@ var JpxImage = (function JpxImageClosure
                 }
                 cod.precinctsSizes = precinctsSizes;
               }
 
               if (cod.sopMarkerUsed || cod.ephMarkerUsed ||
                   cod.selectiveArithmeticCodingBypass ||
                   cod.resetContextProbabilities ||
                   cod.terminationOnEachCodingPass ||
-                  cod.verticalyStripe || cod.predictableTermination ||
-                  cod.segmentationSymbolUsed)
+                  cod.verticalyStripe || cod.predictableTermination)
                 throw 'Unsupported COD options: ' + uneval(cod);
 
               if (context.mainHeader)
                 context.COD = cod;
               else {
                 context.currentTile.COD = cod;
                 context.currentTile.COC = [];
               }
@@ -32224,17 +32394,18 @@ var JpxImage = (function JpxImageClosure
           codingpasses: packetItem.codingpasses
         });
         position += packetItem.dataLength;
       }
     }
     return position;
   }
   function copyCoefficients(coefficients, x0, y0, width, height,
-                            delta, mb, codeblocks, transformation) {
+                            delta, mb, codeblocks, transformation,
+                            segmentationSymbolUsed) {
     var r = 0.5; // formula (E-6)
     for (var i = 0, ii = codeblocks.length; i < ii; ++i) {
       var codeblock = codeblocks[i];
       var blockWidth = codeblock.tbx1_ - codeblock.tbx0_;
       var blockHeight = codeblock.tby1_ - codeblock.tby0_;
       if (blockWidth == 0 || blockHeight == 0)
         continue;
       if (!('data' in codeblock))
@@ -32268,16 +32439,18 @@ var JpxImage = (function JpxImageClosure
           case 0:
             bitModel.runSignificancePropogationPass();
             break;
           case 1:
             bitModel.runMagnitudeRefinementPass();
             break;
           case 2:
             bitModel.runCleanupPass();
+            if (segmentationSymbolUsed)
+              bitModel.checkSegmentationSymbol();
             break;
         }
         currentCodingpassType = (currentCodingpassType + 1) % 3;
       }
 
       var offset = (codeblock.tbx0_ - x0) + (codeblock.tby0_ - y0) * width;
       var position = 0;
       for (var j = 0; j < blockHeight; j++) {
@@ -32304,16 +32477,17 @@ var JpxImage = (function JpxImageClosure
     var codingStyleParameters = component.codingStyleParameters;
     var quantizationParameters = component.quantizationParameters;
     var decompositionLevelsCount =
       codingStyleParameters.decompositionLevelsCount;
     var spqcds = quantizationParameters.SPqcds;
     var scalarExpounded = quantizationParameters.scalarExpounded;
     var guardBits = quantizationParameters.guardBits;
     var transformation = codingStyleParameters.transformation;
+    var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed;
     var precision = context.components[c].precision;
 
     var subbandCoefficients = [];
     var k = 0, b = 0;
     for (var i = 0; i <= decompositionLevelsCount; i++) {
       var resolution = component.resolutions[i];
 
       for (var j = 0, jj = resolution.subbands.length; j < jj; j++) {
@@ -32334,17 +32508,18 @@ var JpxImage = (function JpxImageClosure
 
         // calulate quantization coefficient (Section E.1.1.1)
         var delta = Math.pow(2, (precision + gainLog2) - epsilon) *
           (1 + mu / 2048);
         var mb = (guardBits + epsilon - 1);
 
         var coefficients = new Float32Array(width * height);
         copyCoefficients(coefficients, subband.tbx0, subband.tby0,
-          width, height, delta, mb, subband.codeblocks, transformation);
+          width, height, delta, mb, subband.codeblocks, transformation,
+          segmentationSymbolUsed);
 
         subbandCoefficients.push({
           width: width,
           height: height,
           items: coefficients
         });
 
         b++;
@@ -33037,16 +33212,24 @@ var JpxImage = (function JpxImageClosure
                 coefficentsMagnitude[index] = 1;
                 this.setNeighborsSignificance(i, j);
                 processingFlags[index] |= firstMagnitudeBitMask;
               }
               bitsDecoded[index]++;
             }
           }
         }
+      },
+      checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() {
+        var decoder = this.decoder;
+        var cx = this.uniformContext;
+        var symbol = (decoder.readBit(cx) << 3) | (decoder.readBit(cx) << 2) |
+                     (decoder.readBit(cx) << 1) | decoder.readBit(cx);
+        if (symbol != 0xA)
+          throw 'Invalid segmentation symbol';
       }
     };
 
     return BitModel;
   })();
 
   // Section F, Discrete wavelet transofrmation
   var Transform = (function TransformClosure() {
@@ -33258,16 +33441,1078 @@ var JpxImage = (function JpxImageClosure
 })();
 
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 'use strict';
 
+var Jbig2Image = (function Jbig2ImageClosure() {
+
+  // Annex E. Arithmetic Coding
+  var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
+    var QeTable = [
+      {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
+      {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
+      {qe: 0x1801, nmps: 3, nlps: 9, switchFlag: 0},
+      {qe: 0x0AC1, nmps: 4, nlps: 12, switchFlag: 0},
+      {qe: 0x0521, nmps: 5, nlps: 29, switchFlag: 0},
+      {qe: 0x0221, nmps: 38, nlps: 33, switchFlag: 0},
+      {qe: 0x5601, nmps: 7, nlps: 6, switchFlag: 1},
+      {qe: 0x5401, nmps: 8, nlps: 14, switchFlag: 0},
+      {qe: 0x4801, nmps: 9, nlps: 14, switchFlag: 0},
+      {qe: 0x3801, nmps: 10, nlps: 14, switchFlag: 0},
+      {qe: 0x3001, nmps: 11, nlps: 17, switchFlag: 0},
+      {qe: 0x2401, nmps: 12, nlps: 18, switchFlag: 0},
+      {qe: 0x1C01, nmps: 13, nlps: 20, switchFlag: 0},
+      {qe: 0x1601, nmps: 29, nlps: 21, switchFlag: 0},
+      {qe: 0x5601, nmps: 15, nlps: 14, switchFlag: 1},
+      {qe: 0x5401, nmps: 16, nlps: 14, switchFlag: 0},
+      {qe: 0x5101, nmps: 17, nlps: 15, switchFlag: 0},
+      {qe: 0x4801, nmps: 18, nlps: 16, switchFlag: 0},
+      {qe: 0x3801, nmps: 19, nlps: 17, switchFlag: 0},
+      {qe: 0x3401, nmps: 20, nlps: 18, switchFlag: 0},
+      {qe: 0x3001, nmps: 21, nlps: 19, switchFlag: 0},
+      {qe: 0x2801, nmps: 22, nlps: 19, switchFlag: 0},
+      {qe: 0x2401, nmps: 23, nlps: 20, switchFlag: 0},
+      {qe: 0x2201, nmps: 24, nlps: 21, switchFlag: 0},
+      {qe: 0x1C01, nmps: 25, nlps: 22, switchFlag: 0},
+      {qe: 0x1801, nmps: 26, nlps: 23, switchFlag: 0},
+      {qe: 0x1601, nmps: 27, nlps: 24, switchFlag: 0},
+      {qe: 0x1401, nmps: 28, nlps: 25, switchFlag: 0},
+      {qe: 0x1201, nmps: 29, nlps: 26, switchFlag: 0},
+      {qe: 0x1101, nmps: 30, nlps: 27, switchFlag: 0},
+      {qe: 0x0AC1, nmps: 31, nlps: 28, switchFlag: 0},
+      {qe: 0x09C1, nmps: 32, nlps: 29, switchFlag: 0},
+      {qe: 0x08A1, nmps: 33, nlps: 30, switchFlag: 0},
+      {qe: 0x0521, nmps: 34, nlps: 31, switchFlag: 0},
+      {qe: 0x0441, nmps: 35, nlps: 32, switchFlag: 0},
+      {qe: 0x02A1, nmps: 36, nlps: 33, switchFlag: 0},
+      {qe: 0x0221, nmps: 37, nlps: 34, switchFlag: 0},
+      {qe: 0x0141, nmps: 38, nlps: 35, switchFlag: 0},
+      {qe: 0x0111, nmps: 39, nlps: 36, switchFlag: 0},
+      {qe: 0x0085, nmps: 40, nlps: 37, switchFlag: 0},
+      {qe: 0x0049, nmps: 41, nlps: 38, switchFlag: 0},
+      {qe: 0x0025, nmps: 42, nlps: 39, switchFlag: 0},
+      {qe: 0x0015, nmps: 43, nlps: 40, switchFlag: 0},
+      {qe: 0x0009, nmps: 44, nlps: 41, switchFlag: 0},
+      {qe: 0x0005, nmps: 45, nlps: 42, switchFlag: 0},
+      {qe: 0x0001, nmps: 45, nlps: 43, switchFlag: 0},
+      {qe: 0x5601, nmps: 46, nlps: 46, switchFlag: 0}
+    ];
+
+    function ArithmeticDecoder(data, start, end) {
+      this.data = data;
+      this.bp = start;
+      this.dataEnd = end;
+
+      this.chigh = data[start];
+      this.clow = 0;
+
+      this.byteIn();
+
+      this.chigh = ((this.chigh << 7) & 0xFFFF) | ((this.clow >> 9) & 0x7F);
+      this.clow = (this.clow << 7) & 0xFFFF;
+      this.ct -= 7;
+      this.a = 0x8000;
+    }
+
+    ArithmeticDecoder.prototype = {
+      byteIn: function ArithmeticDecoder_byteIn() {
+        var data = this.data;
+        var bp = this.bp;
+        if (data[bp] == 0xFF) {
+          var b1 = data[bp + 1];
+          if (b1 > 0x8F) {
+            this.clow += 0xFF00;
+            this.ct = 8;
+          } else {
+            bp++;
+            this.clow += (data[bp] << 9);
+            this.ct = 7;
+            this.bp = bp;
+          }
+        } else {
+          bp++;
+          this.clow += bp < this.dataEnd ? (data[bp] << 8) : 0xFF00;
+          this.ct = 8;
+          this.bp = bp;
+        }
+        if (this.clow > 0xFFFF) {
+          this.chigh += (this.clow >> 16);
+          this.clow &= 0xFFFF;
+        }
+      },
+      readBit: function ArithmeticDecoder_readBit(cx) {
+        var qeIcx = QeTable[cx.index].qe;
+        this.a -= qeIcx;
+
+        if (this.chigh < qeIcx) {
+          var d = this.exchangeLps(cx);
+          this.renormD();
+          return d;
+        } else {
+          this.chigh -= qeIcx;
+          if ((this.a & 0x8000) == 0) {
+            var d = this.exchangeMps(cx);
+            this.renormD();
+            return d;
+          } else {
+            return cx.mps;
+          }
+        }
+      },
+      renormD: function ArithmeticDecoder_renormD() {
+        do {
+          if (this.ct == 0)
+            this.byteIn();
+
+          this.a <<= 1;
+          this.chigh = ((this.chigh << 1) & 0xFFFF) | ((this.clow >> 15) & 1);
+          this.clow = (this.clow << 1) & 0xFFFF;
+          this.ct--;
+        } while ((this.a & 0x8000) == 0);
+      },
+      exchangeMps: function ArithmeticDecoder_exchangeMps(cx) {
+        var d;
+        var qeTableIcx = QeTable[cx.index];
+        if (this.a < qeTableIcx.qe) {
+          d = 1 - cx.mps;
+
+          if (qeTableIcx.switchFlag == 1) {
+            cx.mps = 1 - cx.mps;
+          }
+          cx.index = qeTableIcx.nlps;
+        } else {
+          d = cx.mps;
+          cx.index = qeTableIcx.nmps;
+        }
+        return d;
+      },
+      exchangeLps: function ArithmeticDecoder_exchangeLps(cx) {
+        var d;
+        var qeTableIcx = QeTable[cx.index];
+        if (this.a < qeTableIcx.qe) {
+          this.a = qeTableIcx.qe;
+          d = cx.mps;
+          cx.index = qeTableIcx.nmps;
+        } else {
+          this.a = qeTableIcx.qe;
+          d = 1 - cx.mps;
+
+          if (qeTableIcx.switchFlag == 1) {
+            cx.mps = 1 - cx.mps;
+          }
+          cx.index = qeTableIcx.nlps;
+        }
+        return d;
+      }
+    };
+
+    return ArithmeticDecoder;
+  })();
+
+  // Utility data structures
+  function ContextCache() {}
+
+  ContextCache.prototype = {
+    getContexts: function(id) {
+      if (id in this)
+        return this[id];
+      return (this[id] = []);
+    }
+  };
+
+  function DecodingContext(data, start, end) {
+    this.data = data;
+    this.start = start;
+    this.end = end;
+  }
+
+  DecodingContext.prototype = {
+    get decoder() {
+      var decoder = new ArithmeticDecoder(this.data, this.start, this.end);
+      return shadow(this, 'decoder', decoder);
+    },
+    get contextCache() {
+      var cache = new ContextCache();
+      return shadow(this, 'contextCache', cache);
+    }
+  };
+
+  // Annex A. Arithmetic Integer Decoding Procedure
+  // A.2 Procedure for decoding values
+  function decodeInteger(contextCache, procedure, decoder) {
+    var contexts = contextCache.getContexts(procedure);
+
+    var prev = 1;
+    var state = 1, v = 0, s;
+    var toRead = 32, offset = 4436; // defaults for state 7
+    while (state) {
+      var cx = contexts[prev];
+      if (!cx)
+        contexts[prev] = cx = {index: 0, mps: 0};
+      var bit = decoder.readBit(cx);
+      prev = prev < 256 ? (prev << 1) | bit :
+        (((prev << 1) | bit) & 511) | 256;
+      switch (state) {
+        case 1:
+          s = !!bit;
+          break;
+        case 2:
+          if (bit) break;
+          state = 7;
+          toRead = 2;
+          offset = 0;
+          break;
+        case 3:
+          if (bit) break;
+          state = 7;
+          toRead = 4;
+          offset = 4;
+          break;
+        case 4:
+          if (bit) break;
+          state = 7;
+          toRead = 6;
+          offset = 20;
+          break;
+        case 5:
+          if (bit) break;
+          state = 7;
+          toRead = 8;
+          offset = 84;
+          break;
+        case 6:
+          if (bit) break;
+          state = 7;
+          toRead = 12;
+          offset = 340;
+          break;
+        default:
+          v = v * 2 + bit;
+          if (--toRead == 0)
+            state = 0;
+          continue;
+      }
+      state++;
+    }
+    v += offset;
+    return !s ? v : v > 0 ? -v : null;
+  }
+
+  // A.3 The IAID decoding procedure
+  function decodeIAID(contextCache, decoder, codeLength) {
+    var contexts = contextCache.getContexts('IAID');
+
+    var prev = 1;
+    for (var i = 0; i < codeLength; i++) {
+      var cx = contexts[prev];
+      if (!cx)
+        contexts[prev] = cx = {index: 0, mps: 0};
+      var bit = decoder.readBit(cx);
+      prev = (prev * 2) + bit;
+    }
+    if (codeLength < 31)
+      return prev & ((1 << codeLength) - 1);
+    else
+      return prev - Math.pow(2, codeLength);
+  }
+
+  // 7.3 Segment types
+  var SegmentTypes = [
+    'SymbolDictionary', null, null, null, 'IntermediateTextRegion', null,
+    'ImmediateTextRegion', 'ImmediateLosslessTextRegion', null, null, null,
+    null, null, null, null, null, 'patternDictionary', null, null, null,
+    'IntermediateHalftoneRegion', null, 'ImmediateHalftoneRegion',
+    'ImmediateLosslessHalftoneRegion', null, null, null, null, null, null, null,
+    null, null, null, null, null, 'IntermediateGenericRegion', null,
+    'ImmediateGenericRegion', 'ImmediateLosslessGenericRegion',
+    'IntermediateGenericRefinementRegion', null,
+    'ImmediateGenericRefinementRegion',
+    'ImmediateLosslessGenericRefinementRegion', null, null, null, null,
+    'PageInformation', 'EndOfPage', 'EndOfStripe', 'EndOfFile', 'Profiles',
+    'Tables', null, null, null, null, null, null, null, null,
+    'Extension'
+  ];
+
+  var CodingTemplates = [
+    [{x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}, {x: -2, y: -1},
+     {x: -1, y: -1}, {x: 0, y: -1}, {x: 1, y: -1}, {x: 2, y: -1},
+     {x: -4, y: 0}, {x: -3, y: 0}, {x: -2, y: 0}, {x: -1, y: 0}],
+    [{x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}, {x: -2, y: -1},
+     {x: -1, y: -1}, {x: 0, y: -1}, {x: 1, y: -1}, {x: 2, y: -1},
+     {x: -3, y: 0}, {x: -2, y: 0}, {x: -1, y: 0}],
+    [{x: -1, y: -2}, {x: 0, y: -2}, {x: 1, y: -2}, {x: -2, y: -1},
+     {x: -1, y: -1}, {x: 0, y: -1}, {x: 1, y: -1}, {x: -2, y: 0},
+     {x: -1, y: 0}],
+    [{x: -3, y: -1}, {x: -2, y: -1}, {x: -1, y: -1}, {x: 0, y: -1},
+     {x: 1, y: -1}, {x: -4, y: 0}, {x: -3, y: 0}, {x: -2, y: 0}, {x: -1, y: 0}]
+  ];
+
+  var RefinementTemplates = [
+    {
+      coding: [{x: 0, y: -1}, {x: 1, y: -1}, {x: -1, y: 0}],
+      reference: [{x: 0, y: -1}, {x: 1, y: -1}, {x: -1, y: 0}, {x: 0, y: 0},
+                  {x: 1, y: 0}, {x: -1, y: 1}, {x: 0, y: 1}, {x: 1, y: 1}]
+    },
+    {
+      coding: [{x: -1, y: -1}, {x: 0, y: -1}, {x: 1, y: -1}, {x: -1, y: 0}],
+      reference: [{x: 0, y: -1}, {x: -1, y: 0}, {x: 0, y: 0}, {x: 1, y: 0},
+                  {x: 0, y: 1}, {x: 1, y: 1}]
+    }
+  ];
+
+  var ReusedContexts = [
+    0x1CD3, // '00111001101' (template) + '0011' (at),
+    0x079A, // '001111001101' + '0',
+    0x00E3, // '001110001' + '1',
+    0x018B  // '011000101' + '1'
+  ];
+
+  var RefinementReusedContexts = [
+    0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference)
+    0x0008  // '0000' + '001000'
+  ];
+
+  function log2(x) {
+    var n = 1, i = 0;
+    while (x > n) {
+      n <<= 1;
+      i++;
+    }
+    return i;
+  }
+
+  function readInt32(data, start) {
+    return (data[start] << 24) | (data[start + 1] << 16) |
+           (data[start + 2] << 8) | data[start + 3];
+  }
+
+  function readUint32(data, start) {
+    var value = readInt32(data, start);
+    return value & 0x80000000 ? (value + 4294967296) : value;
+  }
+
+  function readUint16(data, start) {
+    return (data[start] << 8) | data[start + 1];
+  }
+
+  function readInt8(data, start) {
+    return (data[start] << 24) >> 24;
+  }
+
+  // 6.2 Generic Region Decoding Procedure
+  function decodeBitmap(mmr, width, height, templateIndex, prediction, skip, at,
+                        decodingContext) {
+    if (mmr)
+      error('JBIG2 error: MMR encoding is not supported');
+
+    var useskip = !!skip;
+    var template = CodingTemplates[templateIndex].concat(at);
+    var templateLength = template.length;
+    var templateX = new Int32Array(templateLength);
+    var templateY = new Int32Array(templateLength);
+    for (var k = 0; k < templateLength; k++) {
+      templateX[k] = template[k].x;
+      templateY[k] = template[k].y;
+    }
+
+    var pseudoPixelContext = ReusedContexts[templateIndex];
+    var bitmap = [];
+
+    var decoder = decodingContext.decoder;
+    var contexts = decodingContext.contextCache.getContexts('GB');
+
+    var ltp = 0;
+    for (var i = 0; i < height; i++) {
+      if (prediction) {
+        var cx = contexts[pseudoPixelContext];
+        if (!cx)
+          contexts[pseudoPixelContext] = cx = {index: 0, mps: 0};
+        var sltp = decoder.readBit(cx);
+        ltp ^= sltp;
+      }
+      if (ltp) {
+        bitmap.push(bitmap[bitmap.length - 1]); // duplicate previous row
+        continue;
+      }
+      var row = new Uint8Array(width);
+      bitmap.push(row);
+      for (var j = 0; j < width; j++) {
+        if (useskip && skip[i][j]) {
+          row[j] = 0;
+          continue;
+        }
+        var contextLabel = 0;
+        for (var k = 0; k < templateLength; k++) {
+          var i0 = i + templateY[k], j0 = j + templateX[k];
+          if (i0 < 0 || j0 < 0 || j0 >= width)
+            contextLabel <<= 1; // out of bound pixel
+          else
+            contextLabel = (contextLabel << 1) | bitmap[i0][j0];
+        }
+        var cx = contexts[contextLabel];
+        if (!cx)
+          contexts[contextLabel] = cx = {index: 0, mps: 0};
+        var pixel = decoder.readBit(cx);
+        row[j] = pixel;
+      }
+    }
+    return bitmap;
+  }
+
+  // 6.3.2 Generic Refinement Region Decoding Procedure
+  function decodeRefinement(width, height, templateIndex, referenceBitmap,
+                            offsetX, offsetY, prediction, at,
+                            decodingContext) {
+    var codingTemplate = RefinementTemplates[templateIndex].coding;
+    if (templateIndex == 0)
+      codingTemplate = codingTemplate.concat([at[0]]);
+    var codingTemplateLength = codingTemplate.length;
+    var codingTemplateX = new Int32Array(codingTemplateLength);
+    var codingTemplateY = new Int32Array(codingTemplateLength);
+    for (var k = 0; k < codingTemplateLength; k++) {
+      codingTemplateX[k] = codingTemplate[k].x;
+      codingTemplateY[k] = codingTemplate[k].y;
+    }
+    var referenceTemplate = RefinementTemplates[templateIndex].reference;
+    if (templateIndex == 0)
+      referenceTemplate = referenceTemplate.concat([at[1]]);
+    var referenceTemplateLength = referenceTemplate.length;
+    var referenceTemplateX = new Int32Array(referenceTemplateLength);
+    var referenceTemplateY = new Int32Array(referenceTemplateLength);
+    for (var k = 0; k < referenceTemplateLength; k++) {
+      referenceTemplateX[k] = referenceTemplate[k].x;
+      referenceTemplateY[k] = referenceTemplate[k].y;
+    }
+    var referenceWidth = referenceBitmap[0].length;
+    var referenceHeight = referenceBitmap.length;
+
+    var pseudoPixelContext = RefinementReusedContexts[templateIndex];
+    var bitmap = [];
+
+    var decoder = decodingContext.decoder;
+    var contexts = decodingContext.contextCache.getContexts('GR');
+
+    var ltp = 0;
+    for (var i = 0; i < height; i++) {
+      if (prediction) {
+        var cx = contexts[pseudoPixelContext];
+        if (!cx)
+          contexts[pseudoPixelContext] = cx = {index: 0, mps: 0};
+        var sltp = decoder.readBit(cx);
+        ltp ^= sltp;
+      }
+      var row = new Uint8Array(width);
+      bitmap.push(row);
+      for (var j = 0; j < width; j++) {
+        if (ltp)
+          error('JBIG2 error: prediction is not supported');
+
+        var contextLabel = 0;
+        for (var k = 0; k < codingTemplateLength; k++) {
+          var i0 = i + codingTemplateY[k], j0 = j + codingTemplateX[k];
+          if (i0 < 0 || j0 < 0 || j0 >= width)
+            contextLabel <<= 1; // out of bound pixel
+          else
+            contextLabel = (contextLabel << 1) | bitmap[i0][j0];
+        }
+        for (var k = 0; k < referenceTemplateLength; k++) {
+          var i0 = i + referenceTemplateY[k] + offsetY;
+          var j0 = j + referenceTemplateX[k] + offsetX;
+          if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth)
+            contextLabel <<= 1; // out of bound pixel
+          else
+            contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0];
+        }
+        var cx = contexts[contextLabel];
+        if (!cx)
+          contexts[contextLabel] = cx = {index: 0, mps: 0};
+        var pixel = decoder.readBit(cx);
+        row[j] = pixel;
+      }
+    }
+
+    return bitmap;
+  }
+
+  // 6.5.5 Decoding the symbol dictionary
+  function decodeSymbolDictionary(huffman, refinement, symbols,
+                                  numberOfNewSymbols, numberOfExportedSymbols,
+                                  huffmanTables, templateIndex, at,
+                                  refinementTemplateIndex, refinementAt,
+                                  decodingContext) {
+    if (huffman)
+      error('JBIG2 error: huffman is not supported');
+
+    var newSymbols = [];
+    var currentHeight = 0;
+    var symbolCodeLength = log2(symbols.length + numberOfNewSymbols);
+
+    var decoder = decodingContext.decoder;
+    var contextCache = decodingContext.contextCache;
+
+    while (newSymbols.length < numberOfNewSymbols) {
+      var deltaHeight = decodeInteger(contextCache, 'IADH', decoder); // 6.5.6
+      currentHeight += deltaHeight;
+      var currentWidth = 0;
+      var totalWidth = 0;
+      while (true) {
+        var deltaWidth = decodeInteger(contextCache, 'IADW', decoder); // 6.5.7
+        if (deltaWidth == null)
+          break; // OOB
+        currentWidth += deltaWidth;
+        totalWidth += currentWidth;
+        var bitmap;
+        if (refinement) {
+          // 6.5.8.2 Refinement/aggregate-coded symbol bitmap
+          var numberOfInstances = decodeInteger(contextCache, 'IAAI', decoder);
+          if (numberOfInstances > 1)
+            error('JBIG2 error: number of instances > 1 is not supported');
+          var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
+          var rdx = decodeInteger(contextCache, 'IARDX', decoder); // 6.4.11.3
+          var rdy = decodeInteger(contextCache, 'IARDY', decoder); // 6.4.11.4
+          var symbol = symbolId < symbols.length ? symbols[symbolId] :
+            newSymbols[symbolId - symbols.length];
+          bitmap = decodeRefinement(currentWidth, currentHeight,
+            refinementTemplateIndex, symbol, rdx, rdy, false, refinementAt,
+            decodingContext);
+        } else {
+          // 6.5.8.1 Direct-coded symbol bitmap
+          bitmap = decodeBitmap(false, currentWidth, currentHeight,
+            templateIndex, false, null, at, decodingContext);
+        }
+        newSymbols.push(bitmap);
+      }
+    }
+    // 6.5.10 Exported symbols
+    var exportedSymbols = [];
+    var flags = [], currentFlag = false;
+    var totalSymbolsLength = symbols.length + numberOfNewSymbols;
+    while (flags.length < totalSymbolsLength) {
+      var runLength = decodeInteger(contextCache, 'IAEX', decoder);
+      while (runLength--)
+        flags.push(currentFlag);
+      currentFlag = !currentFlag;
+    }
+    for (var i = 0, ii = symbols.length; i < ii; i++)
+      if (flags[i]) exportedSymbols.push(symbols[i]);
+    for (var j = 0; j < numberOfNewSymbols; i++, j++)
+      if (flags[i]) exportedSymbols.push(newSymbols[j]);
+    return exportedSymbols;
+  }
+
+  function decodeTextRegion(huffman, refinement, width, height,
+                            defaultPixelValue, numberOfSymbolInstances,
+                            stripSize, inputSymbols, symbolCodeLength,
+                            transposed, dsOffset, referenceCorner,
+                            combinationOperator, huffmanTables,
+                            refinementTemplateIndex, refinementAt,
+                            decodingContext) {
+    if (huffman)
+      error('JBIG2 error: huffman is not supported');
+
+    // Prepare bitmap
+    var bitmap = [];
+    for (var i = 0; i < height; i++) {
+      var row = new Uint8Array(width);
+      if (defaultPixelValue) {
+        for (var j = 0; j < width; j++)
+          row[j] = defaultPixelValue;
+      }
+      bitmap.push(row);
+    }
+
+    var decoder = decodingContext.decoder;
+    var contextCache = decodingContext.contextCache;
+
+    if (transposed)
+      error('JBIG2 error: transposed is not supported');
+
+    var stripT = -decodeInteger(contextCache, 'IADT', decoder); // 6.4.6
+    var firstS = 0;
+    var i = 0;
+    while (i < numberOfSymbolInstances) {
+      var deltaT = decodeInteger(contextCache, 'IADT', decoder); // 6.4.6
+      stripT += deltaT;
+
+      var deltaFirstS = decodeInteger(contextCache, 'IAFS', decoder); // 6.4.7
+      firstS += deltaFirstS;
+      var currentS = firstS;
+      do {
+        var currentT = stripSize == 1 ? 0 :
+          decodeInteger(contextCache, 'IAIT', decoder); // 6.4.9
+        var t = stripSize * stripT + currentT;
+        var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength);
+        var applyRefinement = refinement &&
+          decodeInteger(contextCache, 'IARI', decoder);
+        var symbolBitmap = inputSymbols[symbolId];
+        var symbolWidth = symbolBitmap[0].length;
+        var symbolHeight = symbolBitmap.length;
+        if (applyRefinement) {
+          var rdw = decodeInteger(contextCache, 'IARDW', decoder); // 6.4.11.1
+          var rdh = decodeInteger(contextCache, 'IARDH', decoder); // 6.4.11.2
+          var rdx = decodeInteger(contextCache, 'IARDX', decoder); // 6.4.11.3
+          var rdy = decodeInteger(contextCache, 'IARDY', decoder); // 6.4.11.4
+          symbolWidth += rdw;
+          symbolHeight += rdh;
+          symbolBitmap = decodeRefinement(symbolWidth, symbolHeight,
+            refinementTemplateIndex, symbolBitmap, (rdw >> 1) + rdx,
+            (rdh >> 1) + rdy, false, refinementAt,
+            decodingContext);
+        }
+        var offsetT = t - ((referenceCorner & 1) ? 0 : symbolHeight);
+        var offsetS = currentS - ((referenceCorner & 2) ? symbolWidth : 0);
+        for (var t2 = 0; t2 < symbolHeight; t2++) {
+          var row = bitmap[offsetT + t2];
+          if (!row) continue;
+          var symbolRow = symbolBitmap[t2];
+          switch (combinationOperator) {
+            case 0: // OR
+              for (var s2 = 0; s2 < symbolWidth; s2++)
+                row[offsetS + s2] |= symbolRow[s2];
+              break;
+            case 2: // XOR
+              for (var s2 = 0; s2 < symbolWidth; s2++)
+                row[offsetS + s2] ^= symbolRow[s2];
+              break;
+            default:
+              error('JBIG2 error: operator ' + combinationOperator +
+                    ' is not supported');
+          }
+        }
+
+        currentS += symbolWidth - 1;
+        i++;
+
+        var deltaS = decodeInteger(contextCache, 'IADS', decoder); // 6.4.8
+        if (deltaS == null)
+          break; // OOB
+        currentS += deltaS + dsOffset;
+      } while (true);
+    }
+    return bitmap;
+  }
+
+  function readSegmentHeader(data, start) {
+    var segmentHeader = {};
+    segmentHeader.number = readUint32(data, start);
+    var flags = data[start + 4];
+    var segmentType = flags & 0x3F;
+    if (!SegmentTypes[segmentType])
+      error('JBIG2 error: invalid segment type: ' + segmentType);
+    segmentHeader.type = segmentType;
+    segmentHeader.typeName = SegmentTypes[segmentType];
+    segmentHeader.deferredNonRetain = !!(flags & 0x80);
+    var pageAssociationFieldSize = !!(flags & 0x40);
+    var referredFlags = data[start + 5];
+    var referredToCount = (referredFlags >> 5) & 7;
+    var retainBits = [referredFlags & 31];
+    var position = start + 6;
+    if (referredFlags == 7) {
+      referredToCount = readInt32(data, position - 1) & 0x1FFFFFFF;
+      position += 3;
+      var bytes = (referredToCount + 7) >> 3;
+      retainBits[0] = data[position++];
+      while (--bytes > 0) {
+        retainBits.push(data[position++]);
+      }
+    } else if (referredFlags == 5 || referredFlags == 6)
+      error('JBIG2 error: invalid referred-to flags');
+    segmentHeader.retainBits = retainBits;
+    var referredToSegmentNumberSize = segmentHeader.number <= 256 ? 1 :
+      segmentHeader.number <= 65536 ? 2 : 4;
+    var referredTo = [];
+    for (var i = 0; i < referredToCount; i++) {
+      var number = referredToSegmentNumberSize == 1 ? data[position] :
+        referredToSegmentNumberSize == 2 ? readUint16(data, position) :
+        readUint32(data, position);
+      referredTo.push(number);
+      position += referredToSegmentNumberSize;
+    }
+    segmentHeader.referredTo = referredTo;
+    if (!pageAssociationFieldSize)
+      segmentHeader.pageAssociation = data[position++];
+    else {
+      segmentHeader.pageAssociation = readUint32(data, position);
+      position += 4;
+    }
+    segmentHeader.length = readUint32(data, position);
+    if (segmentHeader.length == 0xFFFFFFFF)
+      error('JBIG2 error: unknown segment length is not supported');
+    position += 4;
+    segmentHeader.headerEnd = position;
+    return segmentHeader;
+  }
+
+  function readSegments(header, data, start, end) {
+    var segments = [];
+    var position = start;
+    while (position < end) {
+      var segmentHeader = readSegmentHeader(data, position);
+      position = segmentHeader.headerEnd;
+      var segment = {
+        header: segmentHeader,
+        data: data
+      };
+      if (!header.randomAccess) {
+        segment.start = position;
+        position += segmentHeader.length;
+        segment.end = position;
+      }
+      segments.push(segment);
+      if (segmentHeader.type == 51)
+        break; // end of file is found
+    }
+    if (header.randomAccess) {
+      for (var i = 0, ii = segments.length; i < ii; i++) {
+        segments[i].start = position;
+        position += segments[i].header.length;
+        segments[i].end = position;
+      }
+    }
+    return segments;
+  }
+
+  // 7.4.1 Region segment information field
+  function readRegionSegmentInformation(data, start) {
+    return {
+      width: readUint32(data, start),
+      height: readUint32(data, start + 4),
+      x: readUint32(data, start + 8),
+      y: readUint32(data, start + 12),
+      combinationOperator: data[start + 16] & 7
+    };
+  }
+  var RegionSegmentInformationFieldLength = 17;
+
+  function processSegment(segment, visitor) {
+    var header = segment.header;
+
+    var data = segment.data, position = segment.start, end = segment.end;
+    var args;
+    switch (header.type) {
+      case 0: // SymbolDictionary
+        // 7.4.2 Symbol dictionary segment syntax
+        var dictionary = {};
+        var dictionaryFlags = readUint16(data, position); // 7.4.2.1.1
+        dictionary.huffman = !!(dictionaryFlags & 1);
+        dictionary.refinement = !!(dictionaryFlags & 2);
+        dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3;
+        dictionary.huffmanDWSelector = (dictionaryFlags >> 4) & 3;
+        dictionary.bitmapSizeSelector = (dictionaryFlags >> 6) & 1;
+        dictionary.aggregationInstancesSelector = (dictionaryFlags >> 7) & 1;
+        dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256);
+        dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512);
+        dictionary.template = (dictionaryFlags >> 10) & 3;
+        dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1;
+        position += 2;
+        if (!dictionary.huffman) {
+          var atLength = dictionary.template == 0 ? 4 : 1;
+          var at = [];
+          for (var i = 0; i < atLength; i++) {
+            at.push({
+              x: readInt8(data, position),
+              y: readInt8(data, position + 1)
+            });
+            position += 2;
+          }
+          dictionary.at = at;
+        }
+        if (dictionary.refinement && !dictionary.refinementTemplate) {
+          var at = [];
+          for (var i = 0; i < 2; i++) {
+            at.push({
+              x: readInt8(data, position),
+              y: readInt8(data, position + 1)
+            });
+            position += 2;
+          }
+          dictionary.refinementAt = at;
+        }
+        dictionary.numberOfExportedSymbols = readUint32(data, position);
+        position += 4;
+        dictionary.numberOfNewSymbols = readUint32(data, position);
+        position += 4;
+        args = [dictionary, header.number, header.referredTo,
+                data, position, end];
+        break;
+      case 6: // ImmediateTextRegion
+      case 7: // ImmediateLosslessTextRegion
+        var textRegion = {};
+        textRegion.info = readRegionSegmentInformation(data, position);
+        position += RegionSegmentInformationFieldLength;
+        var textRegionSegmentFlags = readUint16(data, position);
+        position += 2;
+        textRegion.huffman = !!(textRegionSegmentFlags & 1);
+        textRegion.refinement = !!(textRegionSegmentFlags & 2);
+        textRegion.stripSize = 1 << ((textRegionSegmentFlags >> 2) & 3);
+        textRegion.referenceCorner = (textRegionSegmentFlags >> 4) & 3;
+        textRegion.transposed = !!(textRegionSegmentFlags & 64);
+        textRegion.combinationOperator = (textRegionSegmentFlags >> 7) & 3;
+        textRegion.defaultPixelValue = (textRegionSegmentFlags >> 9) & 1;
+        textRegion.dsOffset = (textRegionSegmentFlags >> 10) & 31;
+        textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1;
+        if (textRegion.huffman) {
+          var textRegionHuffmanFlags = readUint16(data, position);
+          position += 2;
+          textRegion.huffmanFS = (textRegionHuffmanFlags) & 3;
+          textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3;
+          textRegion.huffmanDT = (textRegionHuffmanFlags >> 4) & 3;
+          textRegion.huffmanRefinementDW = (textRegionHuffmanFlags >> 6) & 3;
+          textRegion.huffmanRefinementDH = (textRegionHuffmanFlags >> 8) & 3;
+          textRegion.huffmanRefinementDX = (textRegionHuffmanFlags >> 10) & 3;
+          textRegion.huffmanRefinementDY = (textRegionHuffmanFlags >> 12) & 3;
+          textRegion.huffmanRefinementSizeSelector =
+            !!(textRegionHuffmanFlags & 14);
+        }
+        if (textRegion.refinement && !textRegion.refinementTemplate) {
+          var at = [];
+          for (var i = 0; i < 2; i++) {
+            at.push({
+              x: readInt8(data, position),
+              y: readInt8(data, position + 1)
+            });
+            position += 2;
+          }
+          textRegion.refinementAt = at;
+        }
+        textRegion.numberOfSymbolInstances = readUint32(data, position);
+        position += 4;
+        // TODO 7.4.3.1.7 Symbol ID Huffman table decoding
+        if (textRegion.huffman)
+          error('JBIG2 error: huffman is not supported');
+        args = [textRegion, header.referredTo, data, position, end];
+        break;
+      case 38: // ImmediateGenericRegion
+      case 39: // ImmediateLosslessGenericRegion
+        var genericRegion = {};
+        genericRegion.info = readRegionSegmentInformation(data, position);
+        position += RegionSegmentInformationFieldLength;
+        var genericRegionSegmentFlags = data[position++];
+        genericRegion.mmr = !!(genericRegionSegmentFlags & 1);
+        genericRegion.template = (genericRegionSegmentFlags >> 1) & 3;
+        genericRegion.prediction = !!(genericRegionSegmentFlags & 8);
+        if (!genericRegion.mmr) {
+          var atLength = genericRegion.template == 0 ? 4 : 1;
+          var at = [];
+          for (var i = 0; i < atLength; i++) {
+            at.push({
+              x: readInt8(data, position),
+              y: readInt8(data, position + 1)
+            });
+            position += 2;
+          }
+          genericRegion.at = at;
+        }
+        args = [genericRegion, data, position, end];
+        break;
+      case 48: // PageInformation
+        var pageInfo = {
+          width: readUint32(data, position),
+          height: readUint32(data, position + 4),
+          resolutionX: readUint32(data, position + 8),
+          resolutionY: readUint32(data, position + 12)
+        };
+        if (pageInfo.height == 0xFFFFFFFF)
+          delete pageInfo.height;
+        var pageSegmentFlags = data[position + 16];
+        var pageStripingInformatiom = readUint16(data, position + 17);
+        pageInfo.lossless = !!(pageSegmentFlags & 1);
+        pageInfo.refinement = !!(pageSegmentFlags & 2);
+        pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1;
+        pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3;
+        pageInfo.requiresBuffer = !!(pageSegmentFlags & 32);
+        pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64);
+        args = [pageInfo];
+        break;
+      case 50: // EndOfStripe
+        break;
+      case 51: // EndOfFile
+        break;
+      default:
+        error('JBIG2 error: segment type ' + header.typeName + '(' +
+              header.type + ') is not implemented');
+    }
+    var callbackName = 'on' + header.typeName;
+    if (callbackName in visitor)
+      visitor[callbackName].apply(visitor, args);
+  }
+
+  function processSegments(segments, visitor) {
+    for (var i = 0, ii = segments.length; i < ii; i++)
+      processSegment(segments[i], visitor);
+  }
+
+  function parseJbig2(data, start, end) {
+    var position = start;
+    if (data[position] != 0x97 || data[position + 1] != 0x4A ||
+        data[position + 2] != 0x42 || data[position + 3] != 0x32 ||
+        data[position + 4] != 0x0D || data[position + 5] != 0x0A ||
+        data[position + 6] != 0x1A || data[position + 7] != 0x0A)
+      error('JBIG2 error: invalid header');
+    var header = {};
+    position += 8;
+    var flags = data[position++];
+    header.randomAccess = !(flags & 1);
+    if (!(flags & 2)) {
+      header.numberOfPages = readUint32(data, position);
+      position += 4;
+    }
+    var segments = readSegments(header, data, position, end);
+    error('Not implemented');
+    // processSegments(segments, new SimpleSegmentVisitor());
+  }
+
+  function parseJbig2Chunks(chunks) {
+    var visitor = new SimpleSegmentVisitor();
+    for (var i = 0, ii = chunks.length; i < ii; i++) {
+      var chunk = chunks[i];
+      var segments = readSegments({}, chunk.data, chunk.start, chunk.end);
+      processSegments(segments, visitor);
+    }
+    return visitor.buffer;
+  }
+
+  function SimpleSegmentVisitor() {}
+
+  SimpleSegmentVisitor.prototype = {
+    onPageInformation: function SimpleSegmentVisitor_onPageInformation(info) {
+      this.currentPageInfo = info;
+      var rowSize = (info.width + 7) >> 3;
+      var buffer = new Uint8Array(rowSize * info.height);
+      var fill = info.defaultPixelValue ? 0xFF : 0;
+      for (var i = 0, ii = buffer.length; i < ii; i++)
+        buffer[i] = fill;
+      this.buffer = buffer;
+    },
+    drawBitmap: function SimpleSegmentVisitor_drawBitmap(regionInfo, bitmap) {
+      var pageInfo = this.currentPageInfo;
+      var width = regionInfo.width, height = regionInfo.height;
+      var rowSize = (pageInfo.width + 7) >> 3;
+      var combinationOperator = pageInfo.combinationOperatorOverride ?
+        regionInfo.combinationOperator : pageInfo.combinationOperator;
+      var buffer = this.buffer;
+      for (var i = 0; i < height; i++) {
+        var mask = 128 >> (regionInfo.x & 7);
+        var offset = (i + regionInfo.y) * rowSize + (regionInfo.x >> 3);
+        switch (combinationOperator) {
+          case 0: // OR
+            for (var j = 0; j < width; j++) {
+              buffer[offset] |= bitmap[i][j] ? mask : 0;
+              mask >>= 1;
+              if (!mask) {
+                mask = 128;
+                offset++;
+              }
+            }
+            break;
+          case 2: // XOR
+            for (var j = 0; j < width; j++) {
+              buffer[offset] ^= bitmap[i][j] ? mask : 0;
+              mask >>= 1;
+              if (!mask) {
+                mask = 128;
+                offset++;
+              }
+            }
+            break;
+          default:
+            error('JBIG2 error: operator ' + combinationOperator +
+                  ' is not supported');
+        }
+      }
+    },
+    onImmediateGenericRegion:
+      function SimpleSegmentVisitor_onImmediateGenericRegion(region, data,
+                                                             start, end) {
+      var regionInfo = region.info;
+      var decodingContext = new DecodingContext(data, start, end);
+      var bitmap = decodeBitmap(region.mmr, regionInfo.width, regionInfo.height,
+                                region.template, region.prediction, null,
+                                region.at, decodingContext);
+      this.drawBitmap(regionInfo, bitmap);
+    },
+    onImmediateLosslessGenericRegion:
+      function SimpleSegmentVisitor_onImmediateLosslessGenericRegion() {
+      this.onImmediateGenericRegion.apply(this, arguments);
+    },
+    onSymbolDictionary:
+      function SimpleSegmentVisitor_onSymbolDictionary(dictionary,
+                                                       currentSegment,
+                                                       referredSegments,
+                                                       data, start, end) {
+      var huffmanTables;
+      if (dictionary.huffman)
+        error('JBIG2 error: huffman is not supported');
+
+      // Combines exported symbols from all referred segments
+      var symbols = this.symbols;
+      if (!symbols)
+        this.symbols = symbols = {};
+
+      var inputSymbols = [];
+      for (var i = 0, ii = referredSegments.length; i < ii; i++)
+        inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
+
+      var decodingContext = new DecodingContext(data, start, end);
+      symbols[currentSegment] = decodeSymbolDictionary(dictionary.huffman,
+        dictionary.refinement, inputSymbols, dictionary.numberOfNewSymbols,
+        dictionary.numberOfExportedSymbols, huffmanTables,
+        dictionary.template, dictionary.at,
+        dictionary.refinementTemplate, dictionary.refinementAt,
+        decodingContext);
+    },
+    onImmediateTextRegion:
+      function SimpleSegmentVisitor_onImmediateTextRegion(region,
+                                                          referredSegments,
+                                                          data, start, end) {
+      var regionInfo = region.info;
+      var huffmanTables;
+
+      // Combines exported symbols from all referred segments
+      var symbols = this.symbols;
+      var inputSymbols = [];
+      for (var i = 0, ii = referredSegments.length; i < ii; i++)
+        inputSymbols = inputSymbols.concat(symbols[referredSegments[i]]);
+      var symbolCodeLength = log2(inputSymbols.length);
+
+      var decodingContext = new DecodingContext(data, start, end);
+      var bitmap = decodeTextRegion(region.huffman, region.refinement,
+        regionInfo.width, regionInfo.height, region.defaultPixelValue,
+        region.numberOfSymbolInstances, region.stripSize, inputSymbols,
+        symbolCodeLength, region.transposed, region.dsOffset,
+        region.referenceCorner, region.combinationOperator, huffmanTables,
+        region.refinementTemplate, region.refinementAt, decodingContext);
+      this.drawBitmap(regionInfo, bitmap);
+    },
+    onImmediateLosslessTextRegion:
+      function SimpleSegmentVisitor_onImmediateLosslessTextRegion() {
+        this.onImmediateTextRegion.apply(this, arguments);
+    }
+  };
+
+  function Jbig2Image() {}
+
+  Jbig2Image.prototype = {
+    parseChunks: function Jbig2Image_parseChunks(chunks) {
+      return parseJbig2Chunks(chunks);
+    }
+  };
+
+  return Jbig2Image;
+})();
+
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+
+'use strict';
+
 var bidi = PDFJS.bidi = (function bidiClosure() {
   // Character types for symbols from 0000 to 00FF.
   var baseTypes = [
     'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'S', 'B', 'S', 'WS',
     'B', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN', 'BN',
     'BN', 'BN', 'B', 'B', 'B', 'S', 'WS', 'ON', 'ON', 'ET', 'ET', 'ET', 'ON',
     'ON', 'ON', 'ON', 'ON', 'ON', 'CS', 'ON', 'CS', 'ON', 'EN', 'EN', 'EN',
     'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'EN', 'ON', 'ON', 'ON', 'ON', 'ON',
@@ -33794,36 +35039,36 @@ var Metadata = PDFJS.Metadata = (functio
     <script type="text/javascript" src="debugger.js"></script>
     <script type="text/javascript" src="viewer.js"></script>
   </head>
 
   <body>
     <div id="outerContainer">
 
       <div id="sidebarContainer">
-        <div id="toolbarSidebar">
+        <div id="toolbarSidebar" class="splitToolbarButton toggled">
           <button id="viewThumbnail" class="toolbarButton group toggled" title="Show Thumbnails" onclick="PDFView.switchSidebarView('thumbs')" tabindex="1" data-l10n-id="thumbs">
              <span data-l10n-id="thumbs_label">Thumbnails</span>
           </button>
           <button id="viewOutline" class="toolbarButton group" title="Show Document Outline" onclick="PDFView.switchSidebarView('outline')" tabindex="2" data-l10n-id="outline">
              <span data-l10n-id="outline_label">Document Outline</span>
           </button>
-          <button id="viewSearch" class="toolbarButton group hidden" title="Search Document" onclick="PDFView.switchSidebarView('search')" tabindex="3" data-l10n-id="search">
-             <span data-l10n-id="search_label">Search Document</span>
+          <button id="viewSearch" class="toolbarButton group hidden" title="Search Document" onclick="PDFView.switchSidebarView('search')" tabindex="3" data-l10n-id="search_panel">
+             <span data-l10n-id="search_panel_label">Search Document</span>
           </button>
         </div>
         <div id="sidebarContent">
           <div id="thumbnailView">
           </div>
           <div id="outlineView" class="hidden">
           </div>
           <div id="searchView" class="hidden">
             <div id="searchToolbar">
-              <input id="searchTermsInput" onkeydown='if (event.keyCode == 13) PDFView.search()'>
-              <button id="searchButton" onclick='PDFView.search()' data-l10n-id="search_button">Find</button>
+              <input id="searchTermsInput" class="toolbarField" onkeydown='if (event.keyCode == 13) PDFView.search()'>
+              <button id="searchButton" class="textButton toolbarButton" onclick='PDFView.search()' data-l10n-id="search">Find</button>
             </div>
             <div id="searchResults"></div>
           </div>
         </div>
       </div>  <!-- sidebarContainer -->
 
       <div id="mainContainer">
         <div class="toolbar">
@@ -33850,21 +35095,19 @@ var Metadata = PDFJS.Metadata = (functio
                 <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="openFile" class="toolbarButton openFile" title="Open File" tabindex="11" data-l10n-id="open_file" onclick="document.getElementById('fileInput').click()">
                    <span data-l10n-id="open_file_label">Open</span>
                 </button>
 
-                <!--
                 <button id="print" class="toolbarButton print" title="Print" tabindex="11" data-l10n-id="print" onclick="window.print()">
                   <span data-l10n-id="print_label">Print</span>
                 </button>
-                -->
 
                 <button id="download" class="toolbarButton download" title="Download" onclick="PDFView.download();" tabindex="12" data-l10n-id="download">
                   <span data-l10n-id="download_label">Download</span>
                 </button>
                 <!-- <div class="toolbarButtonSpacer"></div> -->
                 <a href="#" id="viewBookmark" class="toolbarButton bookmark" title="Current view (copy or open in new window)" tabindex="13" data-l10n-id="bookmark"><span data-l10n-id="bookmark_label">Current View</span></a>
               </div>
               <div class="outerCenter">
@@ -33899,18 +35142,18 @@ var Metadata = PDFJS.Metadata = (functio
           </div>
         </div>
 
         <div id="viewerContainer">
           <div id="viewer"></div>
         </div>
 
         <div id="loadingBox">
-            <div id="loading" data-l10n-id="loading" data-l10n-args='{"percent": 0}'>Loading... 0%</div>
-            <div id="loadingBar"><div class="progress"></div></div>
+          <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>
@@ -33924,10 +35167,11 @@ var Metadata = PDFJS.Metadata = (functio
             </button>
           </div>
           <div class="clearBoth"></div>
           <textarea id="errorMoreInfo" hidden='true' readonly="readonly"></textarea>
         </div>
       </div> <!-- mainContainer -->
 
     </div> <!-- outerContainer -->
+    <div id="printContainer"></div>
   </body>
 </html>
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -9,16 +9,23 @@ var kDefaultScaleDelta = 1.1;
 var kUnknownScale = 0;
 var kCacheSize = 20;
 var kCssUnits = 96.0 / 72.0;
 var kScrollbarPadding = 40;
 var kMinScale = 0.25;
 var kMaxScale = 4.0;
 var kImageDirectory = './images/';
 var kSettingsMemory = 20;
+var RenderingStates = {
+  INITIAL: 0,
+  RUNNING: 1,
+  PAUSED: 2,
+  FINISHED: 3
+};
+
 
 var mozL10n = document.mozL10n || document.webL10n;
 
 function getFileName(url) {
   var anchor = url.indexOf('#');
   var query = url.indexOf('?');
   var end = Math.min(
     anchor > 0 ? anchor : url.length,
@@ -78,67 +85,73 @@ var ProgressBar = (function ProgressBarC
       this._percent = clamp(val, 0, 100);
       this.updateBar();
     }
   };
 
   return ProgressBar;
 })();
 
-var RenderingQueue = (function RenderingQueueClosure() {
-  function RenderingQueue() {
-    this.items = [];
-  }
-
-  RenderingQueue.prototype = {
-    enqueueDraw: function RenderingQueueEnqueueDraw(item) {
-      if (!item.drawingRequired())
-        return; // as no redraw required, no need for queueing.
-
-      this.items.push(item);
-      if (this.items.length > 1)
-        return; // not first item
-
-      item.draw(this.continueExecution.bind(this));
-    },
-    continueExecution: function RenderingQueueContinueExecution() {
-      var item = this.items.shift();
-
-      if (this.items.length == 0)
-        return; // queue is empty
-
-      item = this.items[0];
-      item.draw(this.continueExecution.bind(this));
-    }
-  };
-
-  return RenderingQueue;
-})();
-
 var FirefoxCom = (function FirefoxComClosure() {
   return {
     /**
-     * Creates an event that hopefully the extension is listening for and will
+     * Creates an event that the extension is listening for and will
      * synchronously respond to.
+     * NOTE: It is reccomended to use request() instead since one day we may not
+     * be able to synchronously reply.
      * @param {String} action The action to trigger.
      * @param {String} data Optional data to send.
      * @return {*} The response.
      */
-    request: function(action, data) {
+    requestSync: function(action, data) {
       var request = document.createTextNode('');
       request.setUserData('action', action, null);
       request.setUserData('data', data, null);
+      request.setUserData('sync', true, 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;
+    },
+    /**
+     * Creates an event that the extension is listening for and will
+     * asynchronously respond by calling the callback.
+     * @param {String} action The action to trigger.
+     * @param {String} data Optional data to send.
+     * @param {Function} callback Optional response callback that will be called
+     * with one data argument.
+     */
+    request: function(action, data, callback) {
+      var request = document.createTextNode('');
+      request.setUserData('action', action, null);
+      request.setUserData('data', data, null);
+      request.setUserData('sync', false, null);
+      if (callback) {
+        request.setUserData('callback', callback, null);
+
+        document.addEventListener('pdf.js.response', function listener(event) {
+          var node = event.target,
+              callback = node.getUserData('callback'),
+              response = node.getUserData('response');
+
+          document.documentElement.removeChild(node);
+
+          document.removeEventListener('pdf.js.response', listener, false);
+          return callback(response);
+        }, false);
+      }
+      document.documentElement.appendChild(request);
+
+      var sender = document.createEvent('HTMLEvents');
+      sender.initEvent('pdf.js.message', true, false);
+      return request.dispatchEvent(sender);
     }
   };
 })();
 
 // Settings Manager - This is a utility for saving settings
 // First we see if localStorage is available
 // If not, we use FUEL in FF
 var Settings = (function SettingsClosure() {
@@ -155,17 +168,17 @@ var Settings = (function SettingsClosure
   })();
 
   var isFirefoxExtension = PDFJS.isFirefoxExtension;
 
   function Settings(fingerprint) {
     var database = null;
     var index;
     if (isFirefoxExtension)
-      database = FirefoxCom.request('getDatabase', null) || '{}';
+      database = FirefoxCom.requestSync('getDatabase', null) || '{}';
     else if (isLocalStorageEnabled)
       database = localStorage.getItem('database') || '{}';
     else
       return false;
 
     database = JSON.parse(database);
     if (!('files' in database))
       database.files = [];
@@ -188,107 +201,139 @@ var Settings = (function SettingsClosure
     set: function settingsSet(name, val) {
       if (!('file' in this))
         return false;
 
       var file = this.file;
       file[name] = val;
       var database = JSON.stringify(this.database);
       if (isFirefoxExtension)
-        FirefoxCom.request('setDatabase', database);
+        FirefoxCom.requestSync('setDatabase', database);
       else if (isLocalStorageEnabled)
         localStorage.setItem('database', database);
     },
 
     get: function settingsGet(name, defaultValue) {
       if (!('file' in this))
         return defaultValue;
 
       return this.file[name] || defaultValue;
     }
   };
 
   return Settings;
 })();
 
 var cache = new Cache(kCacheSize);
-var renderingQueue = new RenderingQueue();
 var currentPageNumber = 1;
 
 var PDFView = {
   pages: [],
   thumbnails: [],
   currentScale: kUnknownScale,
   currentScaleValue: null,
   initialBookmark: document.location.hash.substring(1),
   startedTextExtraction: false,
   pageText: [],
   container: null,
+  thumbnailContainer: null,
   initialized: false,
   fellback: false,
+  pdfDocument: null,
+  sidebarOpen: false,
+  pageViewScroll: null,
+  thumbnailViewScroll: null,
+
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
-    this.container = document.getElementById('viewerContainer');
+    var container = this.container = document.getElementById('viewerContainer');
+    this.pageViewScroll = {};
+    this.watchScroll(container, this.pageViewScroll, updateViewarea);
+
+    var thumbnailContainer = this.thumbnailContainer =
+                             document.getElementById('thumbnailView');
+    this.thumbnailViewScroll = {};
+    this.watchScroll(thumbnailContainer, this.thumbnailViewScroll,
+                     this.renderHighestPriority.bind(this));
+
     this.initialized = true;
   },
 
-  setScale: function pdfViewSetScale(val, resetAutoSettings) {
+  // Helper function to keep track whether a div was scrolled up or down and
+  // then call a callback.
+  watchScroll: function pdfViewWatchScroll(viewAreaElement, state, callback) {
+    state.down = true;
+    state.lastY = viewAreaElement.scrollTop;
+    viewAreaElement.addEventListener('scroll', function webViewerScroll(evt) {
+      var currentY = viewAreaElement.scrollTop;
+      var lastY = state.lastY;
+      if (currentY > lastY)
+        state.down = true;
+      else if (currentY < lastY)
+        state.down = false;
+      // else do nothing and use previous value
+      state.lastY = currentY;
+      callback();
+    }, true);
+  },
+
+  setScale: function pdfViewSetScale(val, resetAutoSettings, noScroll) {
     if (val == this.currentScale)
       return;
 
     var pages = this.pages;
     for (var i = 0; i < pages.length; i++)
       pages[i].update(val * kCssUnits);
 
-    if (this.currentScale != val)
+    if (!noScroll && this.currentScale != val)
       this.pages[this.page - 1].scrollIntoView();
     this.currentScale = val;
 
     var event = document.createEvent('UIEvents');
     event.initUIEvent('scalechange', false, false, window, 0);
     event.scale = val;
     event.resetAutoSettings = resetAutoSettings;
     window.dispatchEvent(event);
   },
 
-  parseScale: function pdfViewParseScale(value, resetAutoSettings) {
+  parseScale: function pdfViewParseScale(value, resetAutoSettings, noScroll) {
     if ('custom' == value)
       return;
 
     var scale = parseFloat(value);
     this.currentScaleValue = value;
     if (scale) {
-      this.setScale(scale, true);
+      this.setScale(scale, true, noScroll);
       return;
     }
 
     var container = this.container;
     var currentPage = this.pages[this.page - 1];
     var pageWidthScale = (container.clientWidth - kScrollbarPadding) /
                           currentPage.width * currentPage.scale / kCssUnits;
     var pageHeightScale = (container.clientHeight - kScrollbarPadding) /
                            currentPage.height * currentPage.scale / kCssUnits;
     switch (value) {
       case 'page-actual':
-        this.setScale(1, resetAutoSettings);
+        scale = 1;
         break;
       case 'page-width':
-        this.setScale(pageWidthScale, resetAutoSettings);
+        scale = pageWidthScale;
         break;
       case 'page-height':
-        this.setScale(pageHeightScale, resetAutoSettings);
+        scale = pageHeightScale;
         break;
       case 'page-fit':
-        this.setScale(
-            Math.min(pageWidthScale, pageHeightScale), resetAutoSettings);
+        scale = Math.min(pageWidthScale, pageHeightScale);
         break;
       case 'auto':
-        this.setScale(Math.min(1.0, pageWidthScale), resetAutoSettings);
+        scale = Math.min(1.0, pageWidthScale);
         break;
     }
+    this.setScale(scale, resetAutoSettings, noScroll);
 
     selectScaleOption(value);
   },
 
   zoomIn: function pdfViewZoomIn() {
     var newScale = (this.currentScale * kDefaultScaleDelta).toFixed(2);
     newScale = Math.min(kMaxScale, newScale);
     this.parseScale(newScale, true);
@@ -329,39 +374,51 @@ var PDFView = {
 
     pages[val - 1].scrollIntoView();
   },
 
   get page() {
     return currentPageNumber;
   },
 
+  get supportsPrinting() {
+    var canvas = document.createElement('canvas');
+    var value = 'mozPrintCallback' in canvas;
+    // shadow
+    Object.defineProperty(this, 'supportsPrinting', { value: value,
+                                                      enumerable: true,
+                                                      configurable: true,
+                                                      writable: false });
+    return value;
+  },
+
   open: function pdfViewOpen(url, scale, password) {
     var parameters = {password: password};
     if (typeof url === 'string') { // URL
       this.url = url;
       document.title = decodeURIComponent(getFileName(url)) || url;
       parameters.url = url;
     } else if (url && 'byteLength' in url) { // ArrayBuffer
       parameters.data = url;
     }
 
     if (!PDFView.loadingBar) {
       PDFView.loadingBar = new ProgressBar('#loadingBar', {});
     }
 
+    this.pdfDocument = null;
     var self = this;
     self.loading = true;
     PDFJS.getDocument(parameters).then(
       function getDocumentCallback(pdfDocument) {
         self.load(pdfDocument, scale);
         self.loading = false;
       },
       function getDocumentError(message, exception) {
-        if (exception.name === 'PasswordException') {
+        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);
             }
           }
@@ -379,35 +436,67 @@ var PDFView = {
       },
       function getDocumentProgress(progressData) {
         self.progress(progressData.loaded / progressData.total);
       }
     );
   },
 
   download: function pdfViewDownload() {
+    function noData() {
+      FirefoxCom.request('download', { originalUrl: url });
+    }
+
     var url = this.url.split('#')[0];
     if (PDFJS.isFirefoxExtension) {
-      FirefoxCom.request('download', url);
+      // Document isn't ready just try to download with the url.
+      if (!this.pdfDocument) {
+        noData();
+        return;
+      }
+      this.pdfDocument.getData().then(
+        function getDataSuccess(data) {
+          var bb = new MozBlobBuilder();
+          bb.append(data.buffer);
+          var blobUrl = window.URL.createObjectURL(
+                          bb.getBlob('application/pdf'));
+
+          FirefoxCom.request('download', { blobUrl: blobUrl, originalUrl: url },
+            function response(err) {
+              if (err) {
+                // This error won't really be helpful because it's likely the
+                // fallback won't work either (or is already open).
+                PDFView.error('PDF failed to download.');
+              }
+              window.URL.revokeObjectURL(blobUrl);
+            }
+          );
+        },
+        noData // Error ocurred try downloading with just the url.
+      );
     } else {
       url += '#pdfjs.action=download', '_parent';
       window.open(url, '_parent');
     }
   },
 
   fallback: function pdfViewFallback() {
     if (!PDFJS.isFirefoxExtension)
       return;
     // Only trigger the fallback once so we don't spam the user with messages
     // for one PDF.
     if (this.fellback)
       return;
     this.fellback = true;
     var url = this.url.split('#')[0];
-    FirefoxCom.request('fallback', url);
+    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..>
@@ -523,37 +612,36 @@ var PDFView = {
     lessInfoButton.setAttribute('hidden', 'true');
     errorMoreInfo.value = moreInfoText;
 
     errorMoreInfo.rows = moreInfoText.split('\n').length - 1;
   },
 
   progress: function pdfViewProgress(level) {
     var percent = Math.round(level * 100);
-    var loadingIndicator = document.getElementById('loading');
-    loadingIndicator.textContent = mozL10n.get('loading', {percent: percent},
-      'Loading... {{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);
-        preDraw();
       };
     }
 
+    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 = '';
 
     var thumbsView = document.getElementById('thumbnailView');
     thumbsView.parentNode.scrollTop = 0;
 
     while (thumbsView.hasChildNodes())
       thumbsView.removeChild(thumbsView.lastChild);
 
     if ('_loadingInterval' in thumbsView)
@@ -658,16 +746,98 @@ var PDFView = {
 
     if (PDFView.currentScale === kUnknownScale) {
       // Scale was not initialized: invalid bookmark or scale was not specified.
       // Setting the default one.
       this.parseScale(kDefaultScale, true);
     }
   },
 
+  renderHighestPriority: function pdfViewRenderHighestPriority() {
+    // Pages have a higher priority than thumbnails, so check them first.
+    var visiblePages = this.getVisiblePages();
+    var pageView = this.getHighestPriority(visiblePages, this.pages,
+                                           this.pageViewScroll.down);
+    if (pageView) {
+      this.renderView(pageView, 'page');
+      return;
+    }
+    // No pages needed rendering so check thumbnails.
+    if (this.sidebarOpen) {
+      var visibleThumbs = this.getVisibleThumbs();
+      var thumbView = this.getHighestPriority(visibleThumbs,
+                                              this.thumbnails,
+                                              this.thumbnailViewScroll.down);
+      if (thumbView)
+        this.renderView(thumbView, 'thumbnail');
+    }
+  },
+
+  getHighestPriority: function pdfViewGetHighestPriority(visibleViews, views,
+                                                         scrolledDown) {
+    // The state has changed figure out which page has the highest priority to
+    // render next (if any).
+    // Priority:
+    // 1 visible pages
+    // 2 if last scrolled down page after the visible pages
+    // 2 if last scrolled up page before the visible pages
+    var numVisible = visibleViews.length;
+    if (numVisible === 0) {
+      info('No visible views.');
+      return false;
+    }
+    for (var i = 0; i < numVisible; ++i) {
+      var view = visibleViews[i].view;
+      if (!this.isViewFinshed(view))
+        return view;
+    }
+
+    // All the visible views have rendered, try to render next/previous pages.
+    if (scrolledDown) {
+      var lastVisible = visibleViews[visibleViews.length - 1];
+      var nextPageIndex = lastVisible.id;
+      // ID's start at 1 so no need to add 1.
+      if (views[nextPageIndex] && !this.isViewFinshed(views[nextPageIndex]))
+        return views[nextPageIndex];
+    } else {
+      var previousPageIndex = visibleViews[0].id - 2;
+      if (views[previousPageIndex] &&
+          !this.isViewFinshed(views[previousPageIndex]))
+        return views[previousPageIndex];
+    }
+    // Everything that needs to be rendered has been.
+    return false;
+  },
+
+  isViewFinshed: function pdfViewNeedsRendering(view) {
+    return view.renderingState === RenderingStates.FINISHED;
+  },
+
+  // Render a page or thumbnail view. This calls the appropriate function based
+  // on the views state. If the view is already rendered it will return false.
+  renderView: function pdfViewRender(view, type) {
+    var state = view.renderingState;
+    switch (state) {
+      case RenderingStates.FINISHED:
+        return false;
+      case RenderingStates.PAUSED:
+        PDFView.highestPriorityPage = type + view.id;
+        view.resume();
+        break;
+      case RenderingStates.RUNNING:
+        PDFView.highestPriorityPage = type + view.id;
+        break;
+      case RenderingStates.INITIAL:
+        PDFView.highestPriorityPage = type + view.id;
+        view.draw(this.renderHighestPriority.bind(this));
+        break;
+    }
+    return true;
+  },
+
   search: function pdfViewStartSearch() {
     // Limit this function to run every <SEARCH_TIMEOUT>ms.
     var SEARCH_TIMEOUT = 250;
     var lastSeach = this.lastSearch;
     var now = Date.now();
     if (lastSeach && (now - lastSeach) < SEARCH_TIMEOUT) {
       if (!this.searchTimer) {
         this.searchTimer = setTimeout(function resumeSearch() {
@@ -715,17 +885,22 @@ var PDFView = {
       var link = document.createElement('a');
       bindLink(link, pageNumber);
       link.textContent = 'Page ' + pageNumber + ': ' + textSample;
       searchResults.appendChild(link);
 
       pageFound = true;
     }
     if (!pageFound) {
-      searchResults.textContent = '(Not found)';
+      searchResults.textContent = '';
+      var noResults = document.createElement('div');
+      noResults.classList.add('noResults');
+      noResults.textContent = mozL10n.get('search_terms_not_found', null,
+                                              '(Not found)');
+      searchResults.appendChild(noResults);
     }
   },
 
   setHash: function pdfViewSetHash(hash) {
     if (!hash)
       return;
 
     if (hash.indexOf('=') >= 0) {
@@ -776,17 +951,17 @@ var PDFView = {
       case 'thumbs':
         thumbsButton.classList.add('toggled');
         outlineButton.classList.remove('toggled');
         searchButton.classList.remove('toggled');
         thumbsView.classList.remove('hidden');
         outlineView.classList.add('hidden');
         searchView.classList.add('hidden');
 
-        updateThumbViewArea();
+        PDFView.renderHighestPriority();
         break;
 
       case 'outline':
         thumbsButton.classList.remove('toggled');
         outlineButton.classList.add('toggled');
         searchButton.classList.remove('toggled');
         thumbsView.classList.add('hidden');
         outlineView.classList.remove('hidden');
@@ -826,113 +1001,115 @@ var PDFView = {
             extractPageText(pageIndex + 1);
         }
       );
     };
     extractPageText(0);
   },
 
   getVisiblePages: function pdfViewGetVisiblePages() {
-    var pages = this.pages;
-    var kBottomMargin = 10;
-    var kTopPadding = 30;
-    var visiblePages = [];
-
-    var currentHeight = kTopPadding + kBottomMargin;
-    var container = this.container;
-    // Add 1px to the scrolltop to give a little wiggle room if the math is off,
-    // this won't be needed if we calc current page number based off the middle
-    // of the screen instead of the top.
-    var containerTop = container.scrollTop + 1;
-    for (var i = 1; i <= pages.length; ++i) {
-      var page = pages[i - 1];
-      var pageHeight = page.height + kBottomMargin;
-      if (currentHeight + pageHeight > containerTop)
-        break;
-
-      currentHeight += pageHeight;
-    }
-
-    var containerBottom = containerTop + container.clientHeight;
-    for (; i <= pages.length && currentHeight < containerBottom; ++i) {
-      var singlePage = pages[i - 1];
-      visiblePages.push({ id: singlePage.id, y: currentHeight,
-                          view: singlePage });
-      currentHeight += page.height + kBottomMargin;
-    }
-    return visiblePages;
+    return this.getVisibleElements(this.container,
+                                   this.pages);
   },
 
   getVisibleThumbs: function pdfViewGetVisibleThumbs() {
-    var thumbs = this.thumbnails;
-    var kBottomMargin = 15;
-    var visibleThumbs = [];
-
-    var view = document.getElementById('thumbnailView');
-    var currentHeight = kBottomMargin;
+    return this.getVisibleElements(this.thumbnailContainer,
+                                   this.thumbnails);
+  },
 
-    var top = view.scrollTop;
-    for (var i = 1; i <= thumbs.length; ++i) {
-      var thumb = thumbs[i - 1];
-      var thumbHeight = thumb.height * thumb.scaleY + kBottomMargin;
-      if (currentHeight + thumbHeight > top)
+  // Generic helper to find out what elements are visible within a scroll pane.
+  getVisibleElements: function pdfViewGetVisibleElements(scrollEl, views) {
+    var currentHeight = 0, view;
+    var top = scrollEl.scrollTop;
+
+    for (var i = 1; i <= views.length; ++i) {
+      view = views[i - 1];
+      currentHeight = view.el.offsetTop;
+      if (currentHeight + view.el.clientHeight > top)
         break;
-
-      currentHeight += thumbHeight;
+      currentHeight += view.el.clientHeight;
     }
 
-    var bottom = top + view.clientHeight;
-    for (; i <= thumbs.length && currentHeight < bottom; ++i) {
-      var singleThumb = thumbs[i - 1];
-      visibleThumbs.push({ id: singleThumb.id, y: currentHeight,
-                          view: singleThumb });
-      currentHeight += singleThumb.height * singleThumb.scaleY + kBottomMargin;
+    var visible = [];
+    var bottom = top + scrollEl.clientHeight;
+    for (; i <= views.length && currentHeight < bottom; ++i) {
+      view = views[i - 1];
+      currentHeight = view.el.offsetTop;
+      visible.push({ id: view.id, y: currentHeight,
+                     view: view });
+      currentHeight += view.el.clientHeight;
     }
 
-    return visibleThumbs;
+    return visible;
   },
 
   // 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);
     }
     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.');
+      this.error(printMessage);
+      return;
+    }
+    var body = document.querySelector('body');
+    body.setAttribute('data-mozPrintCallback', true);
+    for (var i = 0, ii = this.pages.length; i < ii; ++i) {
+      this.pages[i].beforePrint();
+    }
+  },
+
+  afterPrint: function pdfViewSetupAfterPrint() {
+    var div = document.getElementById('printContainer');
+    while (div.hasChildNodes())
+      div.removeChild(div.lastChild);
   }
 };
 
 var PageView = function pageView(container, pdfPage, id, scale,
                                  stats, navigateTo) {
   this.id = id;
   this.pdfPage = pdfPage;
 
   this.scale = scale || 1.0;
   this.viewport = this.pdfPage.getViewport(this.scale);
 
+  this.renderingState = RenderingStates.INITIAL;
+  this.resume = null;
+
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
 
-  var div = document.createElement('div');
+  var div = this.el = document.createElement('div');
   div.id = 'pageContainer' + this.id;
   div.className = 'page';
 
   container.appendChild(anchor);
   container.appendChild(div);
 
   this.destroy = function pageViewDestroy() {
     this.update();
     this.pdfPage.destroy();
   };
 
   this.update = function pageViewUpdate(scale) {
+    this.renderingState = RenderingStates.INITIAL;
+    this.resume = null;
+
     this.scale = scale || this.scale;
     var viewport = this.pdfPage.getViewport(this.scale);
 
     this.viewport = viewport;
     div.style.width = viewport.width + 'px';
     div.style.height = viewport.height + 'px';
 
     while (div.hasChildNodes())
@@ -1096,19 +1273,19 @@ var PageView = function pageView(contain
             height / kCssUnits;
           scale = Math.min(widthScale, heightScale);
           break;
         default:
           return;
       }
 
       if (scale && scale !== PDFView.currentScale)
-        PDFView.parseScale(scale, true);
+        PDFView.parseScale(scale, true, true);
       else if (PDFView.currentScale === kUnknownScale)
-        PDFView.parseScale(kDefaultScale, true);
+        PDFView.parseScale(kDefaultScale, true, true);
 
       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;
@@ -1125,26 +1302,21 @@ var PageView = function pageView(contain
         tempDiv.style.width = Math.ceil(width) + 'px';
         tempDiv.style.height = Math.ceil(height) + 'px';
         div.appendChild(tempDiv);
         tempDiv.scrollIntoView(true);
         div.removeChild(tempDiv);
       }, 0);
   };
 
-  this.drawingRequired = function() {
-    return !div.querySelector('canvas');
-  };
+  this.draw = function pageviewDraw(callback) {
+    if (this.renderingState !== RenderingStates.INITIAL)
+      error('Must be in new state before drawing');
 
-  this.draw = function pageviewDraw(callback) {
-    if (!this.drawingRequired()) {
-      this.updateStats();
-      callback();
-      return;
-    }
+    this.renderingState = RenderingStates.RUNNING;
 
     var canvas = document.createElement('canvas');
     canvas.id = 'page' + this.id;
     canvas.mozOpaque = true;
     div.appendChild(canvas);
     this.canvas = canvas;
 
     var textLayerDiv = null;
@@ -1164,16 +1336,18 @@ var PageView = function pageView(contain
     ctx.fillStyle = 'rgb(255, 255, 255)';
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     ctx.restore();
 
     // Rendering area
 
     var self = this;
     function pageViewDrawCallback(error) {
+      self.renderingState = RenderingStates.FINISHED;
+
       if (self.loadingIconDiv) {
         div.removeChild(self.loadingIconDiv);
         delete self.loadingIconDiv;
       }
 
       if (error) {
         PDFView.error(mozL10n.get('rendering_error', null,
           'An error occurred while rendering the page.'), error);
@@ -1186,31 +1360,80 @@ var PageView = function pageView(contain
 
       cache.push(self);
       callback();
     }
 
     var renderContext = {
       canvasContext: ctx,
       viewport: this.viewport,
-      textLayer: textLayer
+      textLayer: textLayer,
+      continueCallback: function pdfViewcContinueCallback(cont) {
+        if (PDFView.highestPriorityPage !== 'page' + self.id) {
+          self.renderingState = RenderingStates.PAUSED;
+          self.resume = function resumeCallback() {
+            self.renderingState = RenderingStates.RUNNING;
+            cont();
+          };
+          return;
+        }
+        cont();
+      }
     };
     this.pdfPage.render(renderContext).then(
       function pdfPageRenderCallback() {
         pageViewDrawCallback(null);
       },
       function pdfPageRenderError(error) {
         pageViewDrawCallback(error);
       }
     );
 
     setupAnnotations(this.pdfPage, this.viewport);
     div.setAttribute('data-loaded', true);
   };
 
+  this.beforePrint = function pageViewBeforePrint() {
+    var pdfPage = this.pdfPage;
+    var viewport = pdfPage.getViewport(1);
+
+    var canvas = this.canvas = document.createElement('canvas');
+    canvas.width = viewport.width;
+    canvas.height = viewport.height;
+    canvas.style.width = viewport.width + 'pt';
+    canvas.style.height = viewport.height + 'pt';
+
+    var printContainer = document.getElementById('printContainer');
+    printContainer.appendChild(canvas);
+
+    var self = this;
+    canvas.mozPrintCallback = function(obj) {
+      var ctx = obj.context;
+      var renderContext = {
+        canvasContext: ctx,
+        viewport: viewport
+      };
+
+      pdfPage.render(renderContext).then(function() {
+        // Tell the printEngine that rendering this canvas/page has finished.
+        obj.done();
+        self.pdfPage.destroy();
+      }, function(error) {
+        console.error(error);
+        // Tell the printEngine that rendering this canvas/page has failed.
+        // This will make the print proces stop.
+        if ('abort' in object)
+          obj.abort();
+        else
+          obj.done();
+        self.pdfPage.destroy();
+      });
+    };
+  };
+
   this.updateStats = function pageViewUpdateStats() {
     if (PDFJS.pdfBug && Stats.enabled) {
       var stats = this.stats;
       Stats.add(this.id, stats);
     }
   };
 };
 
@@ -1229,24 +1452,25 @@ var ThumbnailView = function thumbnailVi
   var pageRatio = pageWidth / pageHeight;
   this.id = id;
 
   var canvasWidth = 98;
   var canvasHeight = canvasWidth / this.width * this.height;
   var scaleX = this.scaleX = (canvasWidth / pageWidth);
   var scaleY = this.scaleY = (canvasHeight / pageHeight);
 
-  var div = document.createElement('div');
+  var div = this.el = document.createElement('div');
   div.id = 'thumbnailContainer' + id;
   div.className = 'thumbnail';
 
   anchor.appendChild(div);
   container.appendChild(anchor);
 
   this.hasImage = false;
+  this.renderingState = RenderingStates.INITIAL;
 
   function getPageDrawContext() {
     var canvas = document.createElement('canvas');
     canvas.id = 'thumbnail' + id;
     canvas.mozOpaque = true;
 
     canvas.width = canvasWidth;
     canvas.height = canvasHeight;
@@ -1269,42 +1493,60 @@ var ThumbnailView = function thumbnailVi
     return ctx;
   }
 
   this.drawingRequired = function thumbnailViewDrawingRequired() {
     return !this.hasImage;
   };
 
   this.draw = function thumbnailViewDraw(callback) {
+    if (this.renderingState !== RenderingStates.INITIAL)
+      error('Must be in new state before drawing');
+
+    this.renderingState = RenderingStates.RUNNING;
     if (this.hasImage) {
       callback();
       return;
     }
 
+    var self = this;
     var ctx = getPageDrawContext();
     var drawViewport = pdfPage.getViewport(scaleX);
     var renderContext = {
       canvasContext: ctx,
-      viewport: drawViewport
+      viewport: drawViewport,
+      continueCallback: function(cont) {
+        if (PDFView.highestPriorityPage !== 'thumbnail' + self.id) {
+          self.renderingState = RenderingStates.PAUSED;
+          self.resume = function() {
+            self.renderingState = RenderingStates.RUNNING;
+            cont();
+          };
+          return;
+        }
+        cont();
+      }
     };
     pdfPage.render(renderContext).then(
       function pdfPageRenderCallback() {
+        self.renderingState = RenderingStates.FINISHED;
         callback();
       },
       function pdfPageRenderError(error) {
+        self.renderingState = RenderingStates.FINISHED;
         callback();
       }
     );
     this.hasImage = true;
   };
 
   this.setImage = function thumbnailViewSetImage(img) {
     if (this.hasImage || !img)
       return;
-
+    this.renderingState = RenderingStates.FINISHED;
     var ctx = getPageDrawContext();
     ctx.drawImage(img, 0, 0, img.width, img.height,
                   0, 0, ctx.canvas.width, ctx.canvas.height);
 
     this.hasImage = true;
   };
 };
 
@@ -1515,106 +1757,70 @@ window.addEventListener('load', function
       locale = hashParams['locale'];
     mozL10n.language.code = locale;
   }
 
   if ('disableTextLayer' in hashParams)
     PDFJS.disableTextLayer = (hashParams['disableTextLayer'] === 'true');
 
   if ('pdfBug' in hashParams &&
-      (!PDFJS.isFirefoxExtension || FirefoxCom.request('pdfBugEnabled'))) {
+      (!PDFJS.isFirefoxExtension || FirefoxCom.requestSync('pdfBugEnabled'))) {
     PDFJS.pdfBug = true;
     var pdfBug = hashParams['pdfBug'];
     var enabled = pdfBug.split(',');
     PDFBug.enable(enabled);
     PDFBug.init();
   }
 
   if (!PDFJS.isFirefoxExtension ||
-    (PDFJS.isFirefoxExtension && FirefoxCom.request('searchEnabled'))) {
+    (PDFJS.isFirefoxExtension && FirefoxCom.requestSync('searchEnabled'))) {
     document.querySelector('#viewSearch').classList.remove('hidden');
   }
 
+  if (!PDFView.supportsPrinting) {
+    document.getElementById('print').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();
     }
   });
 
-  var thumbsView = document.getElementById('thumbnailView');
-  thumbsView.addEventListener('scroll', updateThumbViewArea, true);
-
   var mainContainer = document.getElementById('mainContainer');
   var outerContainer = document.getElementById('outerContainer');
   mainContainer.addEventListener('transitionend', function(e) {
     if (e.target == mainContainer) {
       var event = document.createEvent('UIEvents');
       event.initUIEvent('resize', false, false, window, 0);
       window.dispatchEvent(event);
       outerContainer.classList.remove('sidebarMoving');
     }
   }, true);
 
   document.getElementById('sidebarToggle').addEventListener('click',
     function() {
       this.classList.toggle('toggled');
       outerContainer.classList.add('sidebarMoving');
       outerContainer.classList.toggle('sidebarOpen');
-      updateThumbViewArea();
+      PDFView.sidebarOpen = outerContainer.classList.contains('sidebarOpen');
+      PDFView.renderHighestPriority();
     });
 
   PDFView.open(file, 0);
 }, true);
 
-/**
- * Render the next not yet visible page already such that it is
- * hopefully ready once the user scrolls to it.
- */
-function preDraw() {
-  var pages = PDFView.pages;
-  var visible = PDFView.getVisiblePages();
-  var last = visible[visible.length - 1];
-  // PageView.id is the actual page number, which is + 1 compared
-  // to the index in `pages`. That means, pages[last.id] is the next
-  // PageView instance.
-  if (pages[last.id] && pages[last.id].drawingRequired()) {
-    renderingQueue.enqueueDraw(pages[last.id]);
-    return;
-  }
-  // If there is nothing to draw on the next page, maybe the user
-  // is scrolling up, so, let's try to render the next page *before*
-  // the first visible page
-  if (pages[visible[0].id - 2]) {
-    renderingQueue.enqueueDraw(pages[visible[0].id - 2]);
-  }
-}
-
 function updateViewarea() {
   if (!PDFView.initialized)
     return;
   var visiblePages = PDFView.getVisiblePages();
-  var pageToDraw;
-  for (var i = 0; i < visiblePages.length; i++) {
-    var page = visiblePages[i];
-    var pageObj = PDFView.pages[page.id - 1];
 
-    pageToDraw |= pageObj.drawingRequired();
-    renderingQueue.enqueueDraw(pageObj);
-  }
-
-  if (!visiblePages.length)
-    return;
-
-  // If there is no need to draw a page that is currenlty visible, preDraw the
-  // next page the user might scroll to.
-  if (!pageToDraw) {
-    preDraw();
-  }
+  PDFView.renderHighestPriority();
 
   updateViewarea.inProgress = true; // used in "set page"
   var currentId = PDFView.page;
   var firstPage = visiblePages[0];
   PDFView.page = firstPage.id;
   updateViewarea.inProgress = false;
 
   var currentScale = PDFView.currentScale;
@@ -1635,39 +1841,16 @@ function updateViewarea() {
   store.set('page', pageNumber);
   store.set('zoom', normalizedScaleValue);
   store.set('scrollLeft', Math.round(topLeft[0]));
   store.set('scrollTop', Math.round(topLeft[1]));
   var href = PDFView.getAnchorUrl(pdfOpenParams);
   document.getElementById('viewBookmark').href = href;
 }
 
-window.addEventListener('scroll', function webViewerScroll(evt) {
-  updateViewarea();
-}, true);
-
-var thumbnailTimer;
-
-function updateThumbViewArea() {
-  // Only render thumbs after pausing scrolling for this amount of time
-  // (makes UI more responsive)
-  var delay = 50; // in ms
-
-  if (thumbnailTimer)
-    clearTimeout(thumbnailTimer);
-
-  thumbnailTimer = setTimeout(function() {
-    var visibleThumbs = PDFView.getVisibleThumbs();
-    for (var i = 0; i < visibleThumbs.length; i++) {
-      var thumb = visibleThumbs[i];
-      renderingQueue.enqueueDraw(PDFView.thumbnails[thumb.id - 1]);
-    }
-  }, delay);
-}
-
 window.addEventListener('resize', function webViewerResize(evt) {
   if (PDFView.initialized &&
       (document.getElementById('pageWidthOption').selected ||
       document.getElementById('pageFitOption').selected ||
       document.getElementById('pageAutoOption').selected))
       PDFView.parseScale(document.getElementById('scaleSelect').value);
   updateViewarea();
 });
@@ -1766,16 +1949,28 @@ window.addEventListener('pagechange', fu
         thumbnail.scrollIntoView();
     }
 
   }
   document.getElementById('previous').disabled = (page <= 1);
   document.getElementById('next').disabled = (page >= PDFView.pages.length);
 }, true);
 
+// Firefox specific event, so that we can prevent browser from zooming
+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]();
+  }
+}, false);
+
 window.addEventListener('keydown', function keydown(evt) {
   var handled = false;
   var cmd = (evt.ctrlKey ? 1 : 0) |
             (evt.altKey ? 2 : 0) |
             (evt.shiftKey ? 4 : 0) |
             (evt.metaKey ? 8 : 0);
 
   // First, handle the key bindings that are independent whether an input
@@ -1833,8 +2028,16 @@ window.addEventListener('keydown', funct
         break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
   }
 });
+
+window.addEventListener('beforeprint', function beforePrint(evt) {
+  PDFView.beforePrint();
+});
+
+window.addEventListener('afterprint', function afterPrint(evt) {
+  PDFView.afterPrint();
+});
deleted file mode 100644
--- a/browser/extensions/pdfjs/install.rdf.in
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0"?>
-
-#filter substitution
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>uriloader@pdf.js</em:id>
-    <!-- PDFJS_LOCALIZED_METADATA -->
-    <em:name>PDF Viewer</em:name>
-    <em:version>0.3.266</em:version>
-    <em:targetApplication>
-      <Description>
-       <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-       <em:minVersion>@FIREFOX_VERSION@</em:minVersion>
-       <em:maxVersion>@FIREFOX_VERSION@</em:maxVersion>
-     </Description>
-    </em:targetApplication>
-    <em:strictCompatibility>true</em:strictCompatibility>
-    <em:bootstrap>true</em:bootstrap>
-    <em:creator>Mozilla</em:creator>
-    <em:description>Uses HTML5 to display PDF files directly in Firefox.</em:description>
-    <em:homepageURL>https://support.mozilla.org/kb/Opening%20PDF%20files%20within%20Firefox</em:homepageURL>
-    <em:type>2</em:type>
-  </Description>
-</RDF>
--- a/browser/locales/en-US/pdfviewer/chrome.properties
+++ b/browser/locales/en-US/pdfviewer/chrome.properties
@@ -1,3 +1,4 @@
 # Chrome notification bar messages and buttons
 unsupported_feature=This PDF document might not be displayed correctly.
 open_with_different_viewer=Open With Different Viewer
+open_with_different_viewer.accessKey=o
--- a/browser/locales/en-US/pdfviewer/viewer.properties
+++ b/browser/locales/en-US/pdfviewer/viewer.properties
@@ -73,19 +73,19 @@ rendering_error=An error occurred while 
 
 # Predefined zoom values
 page_scale_width=Page Width
 page_scale_fit=Page Fit
 page_scale_auto=Automatic Zoom
 page_scale_actual=Actual Size
 
 # Loading indicator messages
-# LOCALIZATION NOTE (error_line): "{{[percent}}" will be replaced with a percentage
-loading=Loading… {{percent}}%
 loading_error_indicator=Error
 loading_error=An error occurred while loading the PDF.
 
 # LOCALIZATION NOTE (text_annotation_type): This is used as a tooltip.
-# "{{[type}}" will be replaced with an annotation type from a list defined in
+# "{{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]
 request_password=PDF is protected by a password:
+
+printing_not_supported=Warning: Printing is not fully supported by this browser.
--- a/build/unix/build-clang/build-clang.py
+++ b/build/unix/build-clang/build-clang.py
@@ -1,14 +1,14 @@
 #!/usr/bin/python
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-llvm_revision = "160105"
+llvm_revision = "160176"
 moz_version = "moz0"
 
 ##############################################
 
 import os
 import os.path
 import shutil
 import tarfile
--- a/content/base/src/nsCSPService.cpp
+++ b/content/base/src/nsCSPService.cpp
@@ -264,17 +264,17 @@ CSPService::AsyncOnChannelRedirect(nsICh
     if (NS_SUCCEEDED(rv)) {
       return NS_OK;
     }
   }
 
   // The redirecting channel isn't a writable property bag, we won't be able
   // to enforce the load policy if it redirects again, so we stop it now.
   nsCAutoString newUriSpec;
-  newUri->GetSpec(newUriSpec);
+  rv = newUri->GetSpec(newUriSpec);
   const PRUnichar *formatParams[] = { NS_ConvertUTF8toUTF16(newUriSpec).get() };
   if (NS_SUCCEEDED(rv)) {
     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                     "Redirect Error", nsnull,
                                     nsContentUtils::eDOM_PROPERTIES,
                                     "InvalidRedirectChannelWarning",
                                     formatParams, 1);
   }
--- a/content/smil/nsSMILAnimationController.cpp
+++ b/content/smil/nsSMILAnimationController.cpp
@@ -765,17 +765,18 @@ nsSMILAnimationController::GetTargetIden
     if (attributeNamespaceID == kNameSpaceID_None) {
       // width/height are special as they may be attributes or for
       // outer-<svg> elements, mapped into style.
       if (attributeName == nsGkAtoms::width ||
           attributeName == nsGkAtoms::height) {
         isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
       } else {
         nsCSSProperty prop =
-          nsCSSProps::LookupProperty(nsDependentAtomString(attributeName));
+          nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
+                                     nsCSSProps::eEnabled);
         isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
       }
     }
   } else {
     isCSS = (attributeType == eSMILTargetAttrType_CSS);
   }
 
   // Construct the key
--- a/content/smil/nsSMILCompositor.cpp
+++ b/content/smil/nsSMILCompositor.cpp
@@ -119,17 +119,18 @@ nsSMILCompositor::ClearAnimationEffects(
 
 // Protected Helper Functions
 // --------------------------
 nsISMILAttr*
 nsSMILCompositor::CreateSMILAttr()
 {
   if (mKey.mIsCSS) {
     nsCSSProperty propId =
-      nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName));
+      nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
+                                 nsCSSProps::eEnabled);
     if (nsSMILCSSProperty::IsPropertyAnimatable(propId)) {
       return new nsSMILCSSProperty(propId, mKey.mElement.get());
     }
   } else {
     return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID,
                                           mKey.mAttributeName);
   }
   return nsnull;
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -1126,17 +1126,18 @@ MappedAttrParser::ParseMappedAttrValue(n
 {
   if (!mDecl) {
     mDecl = new css::Declaration();
     mDecl->InitializeEmpty();
   }
 
   // Get the nsCSSProperty ID for our mapped attribute.
   nsCSSProperty propertyID =
-    nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName));
+    nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName),
+                               nsCSSProps::eEnabled);
   if (propertyID != eCSSProperty_UNKNOWN) {
     bool changed; // outparam for ParseProperty. (ignored)
     mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
                           mNodePrincipal, mDecl, &changed, false);
     return;
   }
   NS_ABORT_IF_FALSE(aMappedAttrName == nsGkAtoms::lang,
                     "Only 'lang' should be unrecognized!");
@@ -2448,17 +2449,18 @@ nsSVGElement::GetAnimatedAttr(PRInt32 aN
     // We check mapped-into-style attributes first so that animations
     // targeting width/height on outer-<svg> don't appear to be ignored
     // because we returned a nsISMILAttr for the corresponding
     // SVGAnimatedLength.
 
     // Mapped attributes:
     if (IsAttributeMapped(aName)) {
       nsCSSProperty prop =
-        nsCSSProps::LookupProperty(nsDependentAtomString(aName));
+        nsCSSProps::LookupProperty(nsDependentAtomString(aName),
+                                   nsCSSProps::eEnabled);
       // Check IsPropertyAnimatable to avoid attributes that...
       //  - map to explicitly unanimatable properties (e.g. 'direction')
       //  - map to unsupported attributes (e.g. 'glyph-orientation-horizontal')
       if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) {
         return new nsSMILMappedAttribute(prop, this);
       }
     }
 
--- a/dom/alarm/AlarmService.jsm
+++ b/dom/alarm/AlarmService.jsm
@@ -76,26 +76,20 @@ let AlarmService = {
     debug("receiveMessage(): " + aMessage.name);
 
     let json = aMessage.json;
     switch (aMessage.name) {
       case "AlarmsManager:GetAll":
         this._db.getAll(
           function getAllSuccessCb(aAlarms) {
             debug("Callback after getting alarms from database: " + JSON.stringify(aAlarms));
-            ppmm.sendAsyncMessage(
-              "AlarmsManager:GetAll:Return:OK", 
-              { requestID: json.requestID, alarms: aAlarms }
-            ); 
+            this._sendAsyncMessage("GetAll:Return:OK", json.requestId, aAlarms);
           }.bind(this),
           function getAllErrorCb(aErrorMsg) {
-            ppmm.sendAsyncMessage(
-              "AlarmsManager:GetAll:Return:KO", 
-              { requestID: json.requestID, errorMsg: aErrorMsg }
-            );
+            this._sendAsyncMessage("GetAll:Return:KO", json.requestId, aErrorMsg);
           }.bind(this)
         );
         break;
 
       case "AlarmsManager:Add":
         // prepare a record for the new alarm to be added
         let newAlarm = {
           date: json.date, 
@@ -111,52 +105,48 @@ let AlarmService = {
             debug("Callback after adding alarm in database.");
 
             newAlarm['id'] = aNewId;
             let newAlarmTime = this._getAlarmTime(newAlarm);
 
             if (newAlarmTime <= Date.now()) {
               debug("Adding a alarm that has past time. Don't set it in system.");
               this._debugCurrentAlarm();
+              this._sendAsyncMessage("Add:Return:OK", json.requestId, aNewId);
               return;
             }
 
             // if there is no alarm being set in system, set the new alarm
             if (this._currentAlarm == null) {
               this._currentAlarm = newAlarm;
               this._debugCurrentAlarm();
+              this._sendAsyncMessage("Add:Return:OK", json.requestId, aNewId);
               return;
             }
 
             // if the new alarm is earlier than the current alarm
             // swap them and push the previous alarm back to queue
             let alarmQueue = this._alarmQueue;
             let currentAlarmTime = this._getAlarmTime(this._currentAlarm);
             if (newAlarmTime < currentAlarmTime) {
               alarmQueue.unshift(this._currentAlarm);
               this._currentAlarm = newAlarm;
               this._debugCurrentAlarm();
+              this._sendAsyncMessage("Add:Return:OK", json.requestId, aNewId);
               return;
             }
 
             //push the new alarm in the queue
             alarmQueue.push(newAlarm);
             alarmQueue.sort(this._sortAlarmByTimeStamps.bind(this));
             this._debugCurrentAlarm();
-
-            ppmm.sendAsyncMessage(
-              "AlarmsManager:Add:Return:OK", 
-              { requestID: json.requestID, id: aNewId }
-            );
+            this._sendAsyncMessage("Add:Return:OK", json.requestId, aNewId);
           }.bind(this),
           function addErrorCb(aErrorMsg) {
-            ppmm.sendAsyncMessage(
-              "AlarmsManager:Add:Return:KO", 
-              { requestID: json.requestID, errorMsg: aErrorMsg }
-            );
+            this._sendAsyncMessage("Add:Return:KO", json.requestId, aErrorMsg);
           }.bind(this)
         );
         break;
 
       case "AlarmsManager:Remove":
         this._db.remove(
           json.id,
           function removeSuccessCb() {
@@ -200,16 +190,44 @@ let AlarmService = {
         break;
 
       default:
         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
         break;
     }
   },
 
+  _sendAsyncMessage: function _sendAsyncMessage(aMessageName, aRequestId, aData) {
+    debug("_sendAsyncMessage()");
+
+    let json = null;
+    switch (aMessageName)
+    {
+      case "Add:Return:OK":
+        json = { requestId: aRequestId, id: aData };
+        break;
+
+      case "GetAll:Return:OK":
+        json = { requestId: aRequestId, alarms: aData };
+        break;
+
+      case "Add:Return:KO":
+      case "GetAll:Return:KO":
+        json = { requestId: aRequestId, errorMsg: aData };
+        break;
+
+      default:
+        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+        break;
+    }
+
+    if (aMessageName && json)
+      ppmm.sendAsyncMessage("AlarmsManager:" + aMessageName, json);
+  },
+
   _onAlarmFired: function _onAlarmFired() {
     debug("_onAlarmFired()");
 
     if (this._currentAlarm) {
       debug("Fire system intent: " + JSON.stringify(this._currentAlarm));
       if (this._currentAlarm.manifestURL)
         messenger.sendMessage("alarm", this._currentAlarm, this._currentAlarm.manifestURL);
       this._currentAlarm = null;
--- a/dom/alarm/AlarmsManager.js
+++ b/dom/alarm/AlarmsManager.js
@@ -58,17 +58,17 @@ AlarmsManager.prototype = {
       default:
         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
         break;
     }
 
     let request = this.createRequest();
     this._cpmm.sendAsyncMessage(
       "AlarmsManager:Add", 
-      { requestID: this.getRequestId(request), date: aDate, ignoreTimezone: isIgnoreTimezone, data: aData, manifestURL: this._manifestURL }
+      { requestId: this.getRequestId(request), date: aDate, ignoreTimezone: isIgnoreTimezone, data: aData, manifestURL: this._manifestURL }
     );
     return request;
   },
 
   remove: function remove(aId) {
     debug("remove()");
 
     return this._cpmm.sendSyncMessage(
@@ -78,29 +78,29 @@ AlarmsManager.prototype = {
   },
 
   getAll: function getAll() {
     debug("getAll()");
 
     let request = this.createRequest();
     this._cpmm.sendAsyncMessage(
       "AlarmsManager:GetAll", 
-      { requestID: this.getRequestId(request) }
+      { requestId: this.getRequestId(request) }
     );
     return request;
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     debug("receiveMessage(): " + aMessage.name);
 
     let json = aMessage.json;
-    let request = this.getRequest(json.requestID);
+    let request = this.getRequest(json.requestId);
 
     if (!request) {
-      debug("No request stored! " + json.requestID);
+      debug("No request stored! " + json.requestId);
       return;
     }
 
     switch (aMessage.name) {
       case "AlarmsManager:Add:Return:OK":
         Services.DOMRequest.fireSuccess(request, json.id);
         break;
 
@@ -115,17 +115,17 @@ AlarmsManager.prototype = {
       case "AlarmsManager:GetAll:Return:KO":
         Services.DOMRequest.fireError(request, json.errorMsg);
         break;
 
       default:
         debug("Wrong message: " + aMessage.name);
         break;
     }
-    this.removeRequest(json.requestID);
+    this.removeRequest(json.requestId);
    },
 
   // nsIDOMGlobalPropertyInitializer implementation
   init: function init(aWindow) {
     debug("init()");
 
     // Set navigator.mozAlarms to null.
     if (!Services.prefs.getBoolPref("dom.mozAlarms.enabled"))
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2021,17 +2021,18 @@ nsDOMWindowUtils::ComputeAnimationDistan
   }
 
   nsresult rv;
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Convert direction-dependent properties as appropriate, e.g.,
   // border-left to border-left-value.
-  nsCSSProperty property = nsCSSProps::LookupProperty(aProperty);
+  nsCSSProperty property = nsCSSProps::LookupProperty(aProperty,
+                                                      nsCSSProps::eAny);
   if (property != eCSSProperty_UNKNOWN && nsCSSProps::IsShorthand(property)) {
     nsCSSProperty subprop0 = *nsCSSProps::SubpropertyEntryFor(property);
     if (nsCSSProps::PropHasFlags(subprop0, CSS_PROPERTY_REPORT_OTHER_NAME) &&
         nsCSSProps::OtherNameFor(subprop0) == property) {
       property = subprop0;
     } else {
       property = eCSSProperty_UNKNOWN;
     }
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -39,19 +39,16 @@
 #                   cannot be prefable, and must ensure that they disallow
 #                   XPConnect wrapping.  Always true for worker descriptors.
 #                   Defaults to true.
 #
 #   The following fields are either a string, an array (defaults to an empty
 #   array) or a dictionary with three possible keys (all, getterOnly and
 #   setterOnly) each having such an array as the value
 #
-#   * infallible - attributes and methods specified in the .webidl file that
-#                  cannot fail and therefore do not require the final nsresult&
-#                  argument
 #   * implicitJSContext - attributes and methods specified in the .webidl file
 #                         that require a JSContext as the first argument
 #   * resultNotAddRefed - attributes and methods specified in the .webidl file
 #                         that do not AddRef the return value
 
 DOMInterfaces = {
 
 'Blob': [
@@ -69,33 +66,16 @@ DOMInterfaces = {
 }],
 
 'CanvasRenderingContext2D': [
 {
     'nativeType': 'nsCanvasRenderingContext2DAzure',
     # Making this non-prefable requires that we ensure that nothing takes this
     # type as an argument or that the non-Azure variant is removed.
     'prefable': True,
-    'infallible': {
-        'all': [
-            'canvas', 'save', 'restore', 'globalAlpha', 'shadowOffsetX',
-            'shadowOffsetY', 'shadowBlur', 'shadowColor', 'clearRect',
-            'fillRect', 'strokeRect', 'beginPath', 'fill', 'stroke', 'clip',
-            'isPointInPath', 'lineWidth', 'lineCap', 'miterLimit', 'textAlign',
-            'textBaseline', 'closePath', 'moveTo', 'lineTo', 'quadraticCurveTo',
-            'bezierCurveTo', 'rect', 'mozFillRule', 'mozDashOffset',
-            'mozImageSmoothingEnabled'
-        ],
-        'setterOnly': [
-            'strokeStyle', 'fillStyle', 'lineJoin'
-        ],
-        'getterOnly': [
-            'font', 'mozTextStyle'
-        ]
-    },
     'implicitJSContext': [
         'createImageData', 'getImageData', 'putImageData', 'strokeStyle',
         'fillStyle', 'mozDash'
     ],
     'resultNotAddRefed': [ 'canvas' ],
     'binaryNames': {
         'mozImageSmoothingEnabled': 'imageSmoothingEnabled',
         'mozFillRule': 'fillRule'
@@ -210,109 +190,43 @@ DOMInterfaces = {
 
 'WebGLRenderingContext': {
   'nativeType': 'mozilla::WebGLContext',
   'headerFile': 'WebGLContext.h',
   'prefable': True,
   'resultNotAddRefed': [ 'canvas', 'getContextAttributes', 'getExtension',
                          'getAttachedShaders' ],
   'implicitJSContext': [ 'texImage2D', 'texSubImage2D' ],
-  'infallible': [ 'canvas', 'drawingBufferWidth', 'drawingBufferHeight',
-                  'isContextLost', 'getSupportedExtensions',
-                  'getExtension', 'activeTexture',
-                  'attachShader', 'bindAttribLocation', 'bindBuffer',
-                  'bindFramebuffer', 'bindRenderbuffer', 'bindTexture',
-                  'blendColor', 'blendEquation', 'blendEquationSeparate',
-                  'blendFunc', 'blendFuncSeparate', 'bufferData',
-                  'bufferSubData', 'checkFramebufferStatus',
-                  'clear', 'clearColor', 'clearDepth', 'clearStencil',
-                  'colorMask', 'compileShader', 'compressedTexImage2D',
-                  'compressedTexSubImage2D', 'copyTexImage2D',
-                  'copyTexSubImage2D', 'createBuffer', 'createFramebuffer',
-                  'createProgram', 'createRenderbuffer', 'createShader',
-                  'createTexture', 'cullFace', 'deleteBuffer',
-                  'deleteFramebuffer', 'deleteProgram', 'deleteRenderbuffer',
-                  'deleteShader', 'deleteTexture', 'depthFunc', 'depthMask',
-                  'depthRange', 'detachShader', 'disable',
-                  'disableVertexAttribArray', 'drawArrays', 'drawElements',
-                  'enable', 'enableVertexAttribArray', 'finish', 'flush',
-                  'framebufferRenderbuffer', 'framebufferTexture2D',
-                  'frontFace', 'generateMipmap', 'getActiveAttrib',
-                  'getActiveUniform', 'getAttachedShaders', 'getAttribLocation',
-                  'getBufferParameter', 'getError', 'getProgramParameter',
-                  'getRenderbufferParameter', 'getShaderParameter',
-                  'getShaderPrecisionFormat', 'getShaderSource',
-                  'getTexParameter', 'getUniformLocation',
-                  'getVertexAttribOffset', 'hint', 'isBuffer',
-                  'isEnabled', 'isFramebuffer', 'isProgram', 'isRenderbuffer',
-                  'isShader', 'isTexture', 'lineWidth', 'pixelStorei',
-                  'polygonOffset', 'renderbufferStorage', 'sampleCoverage',
-                  'scissor', 'shaderSource', 'stencilFunc',
-                  'stencilFuncSeparate', 'stencilMask', 'stencilMaskSeparate',
-                  'stencilOp', 'stencilOpSeparate', 'texParameterf',
-                  'texParameteri', 'uniform1f', 'uniform1fv', 'uniform1i',
-                  'uniform1iv', 'uniform2f', 'uniform2fv', 'uniform2i',
-                  'uniform2iv', 'uniform3f', 'uniform3fv', 'uniform3i',
-                  'uniform3iv', 'uniform4f', 'uniform4fv', 'uniform4i',
-                  'uniform4iv', 'uniformMatrix2fv', 'uniformMatrix3fv',
-                  'uniformMatrix4fv', 'useProgram', 'validateProgram',
-                  'vertexAttrib1f', 'vertexAttrib1fv', 'vertexAttrib2f',
-                  'vertexAttrib2fv', 'vertexAttrib3f', 'vertexAttrib3fv',
-                  'vertexAttrib4f', 'vertexAttrib4fv', 'vertexAttribPointer',
-                  'viewport'
-                  ]
 },
 
 'WebGLUniformLocation': {
    'nativeType': 'mozilla::WebGLUniformLocation',
    'headerFile': 'WebGLContext.h',
    'wrapperCache': False
 },
 
 'XMLHttpRequest': [
 {
     'nativeType': 'nsXMLHttpRequest',
     'prefable': True,
-    'infallible': {
-        'all': [
-            'readyState', 'withCredentials', 'abort', 'statusText',
-            'getAllResponseHeaders', 'overrideMimeType', 'mozBackgroundRequest',
-            'multipart', 'channel', 'upload', 'status', 'mozAnon', 'mozSystem'
-        ],
-        'getterOnly': [
-            'responseType', 'timeout', 'onreadystatechange', 'onuploadprogress'
-        ]
-    },
     'implicitJSContext': [ 'constructor', ],
     'resultNotAddRefed': [ 'upload', 'responseXML' ]
 },
 {
     'workers': True,
     'nativeType': 'mozilla::dom::workers::XMLHttpRequest',
     'headerFile': 'mozilla/dom/workers/bindings/XMLHttpRequest.h',
-    'infallible': {
-        'all': ['readyState', 'statusText', 'mozAnon', 'mozSystem' ],
-        'getterOnly': [ 'timeout', 'withCredentials', 'mozBackgroundRequest',
-                        'responseType', 'responseXML', 'channel', 'multipart' ]
-    }
-
 }],
 
 'XMLHttpRequestEventTarget': [
 {
     'nativeType': 'nsXHREventTarget',
     'headerFile': 'nsXMLHttpRequest.h',
     'concrete': False,
     'prefable': True,
-    'infallible': {
-        'getterOnly': [
-            'onabort', 'onerror', 'onload', 'onloadstart', 'onprogress',
-            'ontimeout', 'onloadend'
-        ]
-    },
 },
 {
     'workers': True,
     'concrete': False,
     'nativeType': 'mozilla::dom::workers::XMLHttpRequestEventTarget',
     'headerFile': 'mozilla/dom/workers/bindings/XMLHttpRequestEventTarget.h'
 }],
 
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -203,16 +203,43 @@ class Descriptor(DescriptorProvider):
         # interface prototype object as they're going to use QI (on main thread)
         # or be passed as a JSObject (on worker threads).
         if self.interface.isExternal():
             return False
 
         return self.interface.hasInterfaceObject() or self.interface.hasInterfacePrototypeObject()
 
     def getExtendedAttributes(self, member, getter=False, setter=False):
+        def ensureValidInfallibleExtendedAttribute(attr):
+            assert(attr is None or attr is True or len(attr) == 1)
+            if (attr is not None and attr is not True and
+                'Workers' not in attr and 'MainThread' not in attr):
+                raise TypeError("Unknown value for 'infallible': " + attr[0])
+
         name = member.identifier.name
         if member.isMethod():
-            return self.extendedAttributes['all'].get(name, [])
+            attrs = self.extendedAttributes['all'].get(name, [])
+            infallible = member.getExtendedAttribute("Infallible")
+            ensureValidInfallibleExtendedAttribute(infallible)
+            if (infallible is not None and
+                (infallible is True or
+                 ('Workers' in infallible and self.workers) or
+                 ('MainThread' in infallible and not self.workers))):
+                attrs.append("infallible")
+            return attrs
 
         assert member.isAttr()
         assert bool(getter) != bool(setter)
         key = 'getterOnly' if getter else 'setterOnly'
-        return self.extendedAttributes['all'].get(name, []) + self.extendedAttributes[key].get(name, [])
+        attrs = self.extendedAttributes['all'].get(name, []) + self.extendedAttributes[key].get(name, [])
+        infallible = member.getExtendedAttribute("Infallible")
+        if infallible is None:
+            infallibleAttr = "GetterInfallible" if getter else "SetterInfallible"
+            infallible = member.getExtendedAttribute(infallibleAttr)
+
+        ensureValidInfallibleExtendedAttribute(infallible)
+        if (infallible is not None and
+            (infallible is True or
+             ('Workers' in infallible and self.workers) or
+             ('MainThread' in infallible and not self.workers))):
+            attrs.append("infallible")
+
+        return attrs
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -1741,28 +1741,28 @@ class IDLInterfaceMember(IDLObjectWithId
         'Const',
         'Attr',
         'Method'
     )
 
     def __init__(self, location, identifier, tag):
         IDLObjectWithIdentifier.__init__(self, location, None, identifier)
         self.tag = tag
+        self._extendedAttrDict = {}
 
     def isMethod(self):
         return self.tag == IDLInterfaceMember.Tags.Method
 
     def isAttr(self):
         return self.tag == IDLInterfaceMember.Tags.Attr
 
     def isConst(self):
         return self.tag == IDLInterfaceMember.Tags.Const
 
     def addExtendedAttributes(self, attrs):
-        self._extendedAttrDict = {}
         for attr in attrs:
             attrlist = list(attr)
             identifier = attrlist.pop(0)
             self.handleExtendedAttribute(identifier, attrlist)
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
 
     def handleExtendedAttribute(self, name, list):
         pass
@@ -2106,26 +2106,31 @@ class IDLMethod(IDLInterfaceMember, IDLS
         IDLScope.__init__(self, self.location, parentScope, self.identifier)
         for (returnType, arguments) in self.signatures():
             for argument in arguments:
                 argument.resolve(self)
 
     def addOverload(self, method):
         assert len(method._overloads) == 1
 
+        if self._extendedAttrDict != method ._extendedAttrDict:
+            raise WebIDLError("Extended attributes differ on different "
+                              "overloads of %s" % method.identifier,
+                              [self.location, method.location])
+
         self._overloads.extend(method._overloads)
 
         self._hasOverloads = True
 
         if self.isStatic() != method.isStatic():
-            raise WebIDLError("Overloaded identifier %s appears with different values of the 'static' attribute" % method1.identifier,
+            raise WebIDLError("Overloaded identifier %s appears with different values of the 'static' attribute" % method.identifier,
                               [method.location])
 
         if self.isLegacycaller() != method.isLegacycaller():
-            raise WebIDLError("Overloaded identifier %s appears with different values of the 'legacycaller' attribute" % method1.identifier,
+            raise WebIDLError("Overloaded identifier %s appears with different values of the 'legacycaller' attribute" % method.identifier,
                               [method.location])
 
         # Can't overload special things!
         assert not self.isGetter()
         assert not method.isGetter()
         assert not self.isSetter()
         assert not method.isSetter()
         assert not self.isCreator()
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/DeviceStorageRequestChild.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#include "DeviceStorageRequestChild.h"
+#include "nsDeviceStorage.h"
+#include "nsDOMFile.h"
+
+namespace mozilla {
+namespace dom {
+namespace devicestorage {
+
+DeviceStorageRequestChild::DeviceStorageRequestChild()
+{
+  MOZ_COUNT_CTOR(DeviceStorageRequestChild);
+}
+
+DeviceStorageRequestChild::DeviceStorageRequestChild(DOMRequest* aRequest,
+                                                     DeviceStorageFile* aFile)
+  : mRequest(aRequest)
+  , mFile(aFile)
+{
+  MOZ_COUNT_CTOR(DeviceStorageRequestChild);
+}
+
+DeviceStorageRequestChild::~DeviceStorageRequestChild() {
+  MOZ_COUNT_DTOR(DeviceStorageRequestChild);
+}
+bool
+DeviceStorageRequestChild::Recv__delete__(const DeviceStorageResponseValue& aValue)
+{
+  switch (aValue.type()) {
+
+    case DeviceStorageResponseValue::TErrorResponse:
+    {
+      ErrorResponse r = aValue;
+      mRequest->FireError(r.error());
+      break;
+    }
+
+    case DeviceStorageResponseValue::TSuccessResponse:
+    {
+      jsval result = StringToJsval(mRequest->GetOwner(), mFile->mPath);
+      mRequest->FireSuccess(result);
+      break;
+    }
+
+    case DeviceStorageResponseValue::TBlobResponse:
+    {
+      BlobResponse r = aValue;
+
+      // I am going to hell for this.  bent says he'll save me.
+      const InfallibleTArray<PRUint8> bits = r.bits();
+      void* buffer = PR_Malloc(bits.Length());
+      memcpy(buffer, (void*) bits.Elements(), bits.Length());
+
+      nsString mimeType;
+      mimeType.AssignWithConversion(r.contentType());
+
+      nsCOMPtr<nsIDOMBlob> blob = new nsDOMMemoryFile(buffer,
+                                                      bits.Length(),
+                                                      mFile->mPath,
+                                                      mimeType);
+
+      jsval result = BlobToJsval(mRequest->GetOwner(), blob);
+      mRequest->FireSuccess(result);
+      break;
+    }
+
+    case DeviceStorageResponseValue::TEnumerationResponse:
+    {
+      EnumerationResponse r = aValue;
+      nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
+
+      PRUint32 count = r.paths().Length();
+      for (PRUint32 i = 0; i < count; i++) {
+        nsCOMPtr<nsIFile> f;
+        NS_NewLocalFile(r.paths()[i].fullpath(), false, getter_AddRefs(f));
+
+        nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+        dsf->SetPath(r.paths()[i].path());
+        cursor->mFiles.AppendElement(dsf);
+      }
+
+      nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(cursor);
+      NS_DispatchToMainThread(event);
+      break;
+    }
+
+    default:
+    {
+      NS_RUNTIMEABORT("not reached");
+      break;
+    }
+  }
+  return true;
+}
+
+
+} // namespace devicestorage
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/DeviceStorageRequestChild.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_devicestorage_DeviceStorageRequestChild_h
+#define mozilla_dom_devicestorage_DeviceStorageRequestChild_h
+
+#include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
+#include "DOMRequest.h"
+#include "nsDeviceStorage.h"
+namespace mozilla {
+namespace dom {
+namespace devicestorage {
+
+class DeviceStorageRequestChild : public PDeviceStorageRequestChild
+{
+public:
+  DeviceStorageRequestChild();
+  DeviceStorageRequestChild(DOMRequest* aRequest, DeviceStorageFile* aFile);
+  ~DeviceStorageRequestChild();
+
+  virtual bool Recv__delete__(const DeviceStorageResponseValue& value);
+
+private:
+  nsRefPtr<DOMRequest> mRequest;
+  nsRefPtr<DeviceStorageFile> mFile;
+};
+
+} // namespace devicestorage
+} // namespace dom
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -0,0 +1,360 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DeviceStorageRequestParent.h"
+#include "nsDOMFile.h"
+#include "nsIMIMEService.h"
+#include "nsCExternalHandlerService.h"
+#include "mozilla/unused.h"
+
+namespace mozilla {
+namespace dom {
+namespace devicestorage {
+
+DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams& aParams)
+{
+  MOZ_COUNT_CTOR(DeviceStorageRequestParent);
+
+  switch (aParams.type()) {
+    case DeviceStorageParams::TDeviceStorageAddParams:
+    {
+      DeviceStorageAddParams p = aParams;
+
+      nsCOMPtr<nsIFile> f;
+      NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      nsRefPtr<WriteFileEvent> r = new WriteFileEvent(this, dsf, p.bits());
+
+      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      NS_ASSERTION(target, "Must have stream transport service");
+      target->Dispatch(r, NS_DISPATCH_NORMAL);
+      break;
+    }
+
+    case DeviceStorageParams::TDeviceStorageGetParams:
+    {
+      DeviceStorageGetParams p = aParams;
+
+      nsCOMPtr<nsIFile> f;
+      NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      nsRefPtr<ReadFileEvent> r = new ReadFileEvent(this, dsf);
+
+      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      NS_ASSERTION(target, "Must have stream transport service");
+      target->Dispatch(r, NS_DISPATCH_NORMAL);
+      break;
+    }
+
+    case DeviceStorageParams::TDeviceStorageDeleteParams:
+    {
+      DeviceStorageDeleteParams p = aParams;
+
+      nsCOMPtr<nsIFile> f;
+      NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      nsRefPtr<DeleteFileEvent> r = new DeleteFileEvent(this, dsf);
+
+      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      NS_ASSERTION(target, "Must have stream transport service");
+      target->Dispatch(r, NS_DISPATCH_NORMAL);
+      break;
+    }
+
+    case DeviceStorageParams::TDeviceStorageEnumerationParams:
+    {
+      DeviceStorageEnumerationParams p = aParams;
+
+      nsCOMPtr<nsIFile> f;
+      NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      nsRefPtr<EnumerateFileEvent> r = new EnumerateFileEvent(this, dsf, p.since());
+
+      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      NS_ASSERTION(target, "Must have stream transport service");
+      target->Dispatch(r, NS_DISPATCH_NORMAL);
+      break;
+    }
+    default:
+    {
+      NS_RUNTIMEABORT("not reached");
+      break;
+    }
+  }
+}
+
+DeviceStorageRequestParent::~DeviceStorageRequestParent()
+{
+  MOZ_COUNT_DTOR(DeviceStorageRequestParent);
+}
+
+DeviceStorageRequestParent::PostErrorEvent::PostErrorEvent(DeviceStorageRequestParent* aParent,
+                                                           const char* aError)
+  : mParent(aParent)
+{
+  mError.AssignWithConversion(aError);
+}
+
+DeviceStorageRequestParent::PostErrorEvent::~PostErrorEvent() {}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::PostErrorEvent::Run() {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  ErrorResponse response(mError);
+  unused << mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
+
+DeviceStorageRequestParent::PostSuccessEvent::PostSuccessEvent(DeviceStorageRequestParent* aParent)
+  : mParent(aParent)
+{
+}
+
+DeviceStorageRequestParent::PostSuccessEvent::~PostSuccessEvent() {}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::PostSuccessEvent::Run() {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  SuccessResponse response;
+  unused <<  mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
+DeviceStorageRequestParent::PostBlobSuccessEvent::PostBlobSuccessEvent(DeviceStorageRequestParent* aParent,
+                                                                       void* aBuffer,
+                                                                       PRUint32 aLength,
+                                                                       nsACString& aMimeType)
+  : mParent(aParent)
+  , mMimeType(aMimeType)
+{
+  mBits.SetCapacity(aLength);
+  void* bits = mBits.Elements();
+  memcpy(bits, aBuffer, aLength);
+}
+
+DeviceStorageRequestParent::PostBlobSuccessEvent::~PostBlobSuccessEvent() {}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::PostBlobSuccessEvent::Run() {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  BlobResponse response(mBits, mMimeType);
+  unused <<  mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
+DeviceStorageRequestParent::PostEnumerationSuccessEvent::PostEnumerationSuccessEvent(DeviceStorageRequestParent* aParent,
+                                                                                     InfallibleTArray<DeviceStorageFileValue>& aPaths)
+  : mParent(aParent)
+  , mPaths(aPaths)
+{
+}
+
+DeviceStorageRequestParent::PostEnumerationSuccessEvent::~PostEnumerationSuccessEvent() {}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::PostEnumerationSuccessEvent::Run() {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  EnumerationResponse response(mPaths);
+  unused <<  mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
+DeviceStorageRequestParent::WriteFileEvent::WriteFileEvent(DeviceStorageRequestParent* aParent,
+                                                           DeviceStorageFile* aFile,
+                                                           InfallibleTArray<PRUint8>& aBits)
+  : mParent(aParent)
+  , mFile(aFile)
+  , mBits(aBits)
+{
+}
+
+DeviceStorageRequestParent::WriteFileEvent::~WriteFileEvent()
+{
+}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::WriteFileEvent::Run()
+{
+  nsRefPtr<nsRunnable> r;
+  nsresult rv = mFile->Write(mBits);
+
+  if (NS_FAILED(rv)) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+  }
+  else {
+    r = new PostPathResultEvent(mParent, mFile->mPath);
+  }
+
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+
+DeviceStorageRequestParent::DeleteFileEvent::DeleteFileEvent(DeviceStorageRequestParent* aParent,
+                                                             DeviceStorageFile* aFile)
+  : mParent(aParent)
+  , mFile(aFile)
+{
+}
+
+DeviceStorageRequestParent::DeleteFileEvent::~DeleteFileEvent()
+{
+}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::DeleteFileEvent::Run()
+{
+  mFile->mFile->Remove(true);
+
+  nsRefPtr<nsRunnable> r;
+
+  bool check = false;
+  mFile->mFile->Exists(&check);
+  if (check) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+  }
+  else {
+    r = new PostPathResultEvent(mParent, mFile->mPath);
+  }
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+DeviceStorageRequestParent::ReadFileEvent::ReadFileEvent(DeviceStorageRequestParent* aParent,
+                                                         DeviceStorageFile* aFile)
+  : mParent(aParent)
+  , mFile(aFile)
+{
+  nsCOMPtr<nsIMIMEService> mimeService = do_GetService(NS_MIMESERVICE_CONTRACTID);
+  if (mimeService) {
+    nsresult rv = mimeService->GetTypeFromFile(mFile->mFile, mMimeType);
+    if (NS_FAILED(rv)) {
+      mMimeType.Truncate();
+    }
+  }
+}
+
+DeviceStorageRequestParent::ReadFileEvent::~ReadFileEvent()
+{
+}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::ReadFileEvent::Run()
+{
+  nsCOMPtr<nsIRunnable> r;
+  bool check = false;
+  mFile->mFile->Exists(&check);
+  if (!check) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+  PRInt64 fileSize;
+  nsresult rv = mFile->mFile->GetFileSize(&fileSize);
+  if (NS_FAILED(rv)) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+  
+  PRFileDesc *fileHandle;
+  rv = mFile->mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fileHandle);
+
+  // i am going to hell.  this is temp until bent provides seralizaiton of blobs.
+  void* buf = (void*) malloc(fileSize);
+  PRInt32 read = PR_Read(fileHandle, buf, fileSize); 
+  if (read != fileSize) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+  r = new PostBlobSuccessEvent(mParent, buf, fileSize, mMimeType);
+
+  PR_Free(buf);
+  PR_Close(fileHandle);
+
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+DeviceStorageRequestParent::EnumerateFileEvent::EnumerateFileEvent(DeviceStorageRequestParent* aParent,
+                                                                   DeviceStorageFile* aFile,
+                                                                   PRUint32 aSince)
+  : mParent(aParent)
+  , mFile(aFile)
+  , mSince(aSince)
+{
+}
+
+DeviceStorageRequestParent::EnumerateFileEvent::~EnumerateFileEvent()
+{
+}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::EnumerateFileEvent::Run()
+{
+  nsCOMPtr<nsIRunnable> r;
+  bool check = false;
+  mFile->mFile->Exists(&check);
+  if (!check) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+  nsTArray<nsRefPtr<DeviceStorageFile> > files;
+  mFile->CollectFiles(files, mSince);
+
+  InfallibleTArray<DeviceStorageFileValue> values;
+
+  PRUint32 count = files.Length();
+  for (PRUint32 i = 0; i < count; i++) {
+    nsString fullpath;
+    files[i]->mFile->GetPath(fullpath);
+    DeviceStorageFileValue dsvf(fullpath, files[i]->mPath);
+    values.AppendElement(dsvf);
+  }
+
+  r = new PostEnumerationSuccessEvent(mParent, values);
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+
+DeviceStorageRequestParent::PostPathResultEvent::PostPathResultEvent(DeviceStorageRequestParent* aParent,
+                                                             const nsAString& aPath)
+  : mParent(aParent)
+  , mPath(aPath)
+{
+}
+
+DeviceStorageRequestParent::PostPathResultEvent::~PostPathResultEvent()
+{
+}
+
+NS_IMETHODIMP
+DeviceStorageRequestParent::PostPathResultEvent::Run()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  SuccessResponse response;
+  unused <<  mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
+
+} // namespace devicestorage
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/DeviceStorageRequestParent.h
@@ -0,0 +1,136 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_devicestorage_DeviceStorageRequestParent_h
+#define mozilla_dom_devicestorage_DeviceStorageRequestParent_h
+
+#include "mozilla/dom/devicestorage/PDeviceStorageRequestParent.h"
+#include "mozilla/dom/ContentChild.h"
+
+#include "nsThreadUtils.h"
+#include "nsDeviceStorage.h"
+
+namespace mozilla {
+namespace dom {
+namespace devicestorage {
+
+class DeviceStorageRequestParent : public PDeviceStorageRequestParent
+{
+public:
+  DeviceStorageRequestParent(const DeviceStorageParams& aParams);
+  ~DeviceStorageRequestParent();
+
+private:
+  class PostErrorEvent : public nsRunnable
+  {
+    public:
+      PostErrorEvent(DeviceStorageRequestParent* aParent, const char* aError);
+      ~PostErrorEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      nsString mError;
+  };
+
+  class PostSuccessEvent : public nsRunnable
+  {
+    public:
+      PostSuccessEvent(DeviceStorageRequestParent* aParent);
+      ~PostSuccessEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+  };
+
+  class PostBlobSuccessEvent : public nsRunnable
+  {
+    public:
+      PostBlobSuccessEvent(DeviceStorageRequestParent* aParent, void* aBuffer, PRUint32 aLength, nsACString& aMimeType);
+      ~PostBlobSuccessEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      InfallibleTArray<PRUint8> mBits;
+      nsCString mMimeType;
+  };
+
+  class PostEnumerationSuccessEvent : public nsRunnable
+  {
+    public:
+      PostEnumerationSuccessEvent(DeviceStorageRequestParent* aParent, InfallibleTArray<DeviceStorageFileValue>& aPaths);
+      ~PostEnumerationSuccessEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      InfallibleTArray<DeviceStorageFileValue> mPaths;
+  };
+
+  class WriteFileEvent : public nsRunnable
+  {
+    public:
+      WriteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, InfallibleTArray<PRUint8>& aBits);
+      ~WriteFileEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      nsRefPtr<DeviceStorageFile> mFile;
+    InfallibleTArray<PRUint8> mBits; // another copy?
+  };
+
+  class DeleteFileEvent : public nsRunnable
+  {
+    public:
+      DeleteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      ~DeleteFileEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      nsRefPtr<DeviceStorageFile> mFile;
+  };
+
+  class ReadFileEvent : public nsRunnable
+  {
+    public:
+      ReadFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      ~ReadFileEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      nsRefPtr<DeviceStorageFile> mFile;
+      nsCString mMimeType;
+  };
+
+  class EnumerateFileEvent : public nsRunnable
+  {
+    public:
+      EnumerateFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, PRUint32 aSince);
+      ~EnumerateFileEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      nsRefPtr<DeviceStorageFile> mFile;
+      PRUint32 mSince;
+  };
+
+  class PostPathResultEvent : public nsRunnable
+  {
+    public:
+      PostPathResultEvent(DeviceStorageRequestParent* aParent, const nsAString& aPath);
+      ~PostPathResultEvent();
+      NS_IMETHOD Run();
+    private:
+      DeviceStorageRequestParent* mParent;
+      nsRefPtr<DeviceStorageFile> mFile;
+      InfallibleTArray<PRUint8> mBits;
+      nsString mPath;
+  };
+
+};
+
+} // namespace devicestorage
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/devicestorage/Makefile.in
+++ b/dom/devicestorage/Makefile.in
@@ -12,31 +12,40 @@ include $(DEPTH)/config/autoconf.mk
 MODULE           = dom
 LIBRARY_NAME     = domdevicestorage_s
 XPIDL_MODULE     = dom_devicestorage
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
+EXPORTS_NAMESPACES = mozilla/dom/devicestorage
+
+EXPORTS_mozilla/dom/devicestorage = \
+  DeviceStorageRequestChild.h \
+  DeviceStorageRequestParent.h \
+  $(NULL)
+
 CPPSRCS		= \
 		nsDeviceStorage.cpp \
+		DeviceStorageRequestParent.cpp \
+		DeviceStorageRequestChild.cpp \
 		$(NULL)
 
 EXPORTS         = \
 		nsDeviceStorage.h \
 		$(NULL)
 
 LOCAL_INCLUDES = \
 		-I$(topsrcdir)/dom/base \
 		-I$(topsrcdir)/dom/ipc \
 		-I$(topsrcdir)/content/base/src \
 		-I$(topsrcdir)/content/events/src \
 		$(NULL)
 
-TEST_DIRS += test
+TEST_DIRS += test ipc
 
 include $(topsrcdir)/config/config.mk
 include $(topsrcdir)/ipc/chromium/chromium-config.mk
 include $(topsrcdir)/config/rules.mk
 
 DEFINES += -D_IMPL_NS_LAYOUT
 
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/PDeviceStorageRequest.ipdl
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace dom {
+namespace devicestorage {
+
+
+struct ErrorResponse
+{
+  nsString error;
+};
+
+struct SuccessResponse
+{
+};
+
+struct BlobResponse
+{
+  // todo going away
+  PRUint8[] bits;
+  nsCString contentType;
+};
+
+struct DeviceStorageFileValue
+{
+  // todo going away
+  nsString fullpath;
+  nsString path;
+};
+
+struct EnumerationResponse
+{
+  DeviceStorageFileValue[] paths;
+  // todo bent PBlob
+};
+
+union DeviceStorageResponseValue
+{
+  ErrorResponse;
+  SuccessResponse;
+  BlobResponse;
+  EnumerationResponse;
+};
+
+sync protocol PDeviceStorageRequest {
+    manager PContent;
+child:
+    __delete__(DeviceStorageResponseValue response);
+};
+
+} // namespace devicestorage
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/ipc/Makefile.in
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = ../../..
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = dom/devicestorage/ipc
+
+include $(DEPTH)/config/autoconf.mk
+
+
+TEST_FILES = \
+  test_ipc.html \
+  ../test/devicestorage_common.js \
+  $(NULL)
+
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
+include $(topsrcdir)/config/rules.mk
+
+libs:: $(TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") \
+	$(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)\
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/ipc/test_ipc.html
@@ -0,0 +1,160 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for OOP DeviceStorage</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+  <body>
+
+  <script type="application/javascript;version=1.7">
+    "use strict";
+
+    SimpleTest.waitForExplicitFinish();
+
+    function iframeScriptFirst() {
+      content.wrappedJSObject.RunSet.reloadAndRunAll({
+        preventDefault: function() { }
+      });
+    }
+
+    function iframeScriptSecond() {
+      let TestRunner = content.wrappedJSObject.TestRunner;
+
+      let oldComplete = TestRunner.onComplete;
+
+      TestRunner.onComplete = function() {
+        TestRunner.onComplete = oldComplete;
+
+        sendAsyncMessage("test:DeviceStorage:ipcTestComplete", {
+          result: JSON.stringify(TestRunner._failedTests)
+        });
+
+        if (oldComplete) {
+          oldComplete();
+        }
+      };
+
+      let oldLog = TestRunner.log;
+      TestRunner.log = function(msg) {
+        sendAsyncMessage("test:DeviceStorage:ipcTestMessage", { msg: msg });
+      }
+    }
+
+    let regex = /^(TEST-PASS|TEST-UNEXPECTED-PASS|TEST-KNOWN-FAIL|TEST-UNEXPECTED-FAIL|TEST-DEBUG-INFO) \| ([^\|]+) \|(.*)/;
+
+    function onTestMessage(data) {
+      let message = data.json.msg;
+      let match = regex.exec(message);
+      if (match) {
+        let state = match[1];
+        let details = match[2] + " | " + match[3];
+
+        switch (state) {
+          case "TEST-PASS":
+          case "TEST-KNOWN-FAIL":
+            ok(true, details);
+            break;
+
+          case "TEST-UNEXPECTED-FAIL":
+          case "TEST-UNEXPECTED-PASS":
+            ok(false, details);
+            break;
+
+          case "TEST-DEBUG-INFO":
+          default:
+            info(details);
+        }
+      }
+    }
+
+    function onTestComplete() {
+      let comp = SpecialPowers.wrap(Components);
+      SimpleTest.executeSoon(function () { SimpleTest.finish(); });
+    }
+
+    function runTests() {
+      let iframe = document.createElement("iframe");
+      iframe.mozbrowser = true;
+      iframe.id = "iframe";
+      iframe.style.width = "100%";
+      iframe.style.height = "1000px";
+
+      function iframeLoadSecond() {
+        ok(true, "Got second iframe load event.");
+        iframe.removeEventListener("mozbrowserloadend", iframeLoadSecond);
+        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+        mm.loadFrameScript("data:,(" + iframeScriptSecond.toString() + ")();",
+                           false);
+      }
+
+      function iframeLoadFirst() {
+        ok(true, "Got first iframe load event.");
+        iframe.removeEventListener("mozbrowserloadend", iframeLoadFirst);
+        iframe.addEventListener("mozbrowserloadend", iframeLoadSecond);
+
+        let mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+
+        let comp = SpecialPowers.wrap(Components);
+
+        let spObserver =
+          comp.classes["@mozilla.org/special-powers-observer;1"]
+              .getService(comp.interfaces.nsIFrameMessageListener);
+
+        mm.addMessageListener("SPPrefService", spObserver);
+        mm.addMessageListener("SPProcessCrashService", spObserver);
+        mm.addMessageListener("SPPingService", spObserver);
+        mm.addMessageListener("SpecialPowers.Quit", spObserver);
+        mm.addMessageListener("SPPermissionManager", spObserver);
+
+        mm.addMessageListener("test:DeviceStorage:ipcTestMessage", onTestMessage);
+        mm.addMessageListener("test:DeviceStorage:ipcTestComplete", onTestComplete);
+
+        let specialPowersBase = "chrome://specialpowers/content/";
+        mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
+        mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
+        mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
+
+        mm.loadFrameScript("data:,(" + iframeScriptFirst.toString() + ")();", false);
+      }
+
+      iframe.addEventListener("mozbrowserloadend", iframeLoadFirst);
+
+      // Strip this filename and one directory level and then add "/test".
+      let href =  window.location.href;
+      href = href.substring(0, href.lastIndexOf('/'));
+      href = href.substring(0, href.lastIndexOf('/'));
+      iframe.src = href + "/test?consoleLevel=INFO";
+
+      document.body.appendChild(iframe);
+    }
+
+    addEventListener("load", function() {
+      let whitelist;
+      try {
+        whitelist =
+          SpecialPowers.getCharPref("dom.mozBrowserFramesWhitelist") + ", ";
+      } catch (e) {
+        whitelist = "";
+      }
+
+      whitelist += window.location.protocol + "//" + window.location.host;
+
+      SpecialPowers.pushPrefEnv({
+        "set": [
+
+          ["device.storage.enabled", true],
+          ["device.storage.testing", true],
+          ["device.storage.prompt.testing", true],
+
+          ["dom.ipc.browser_frames.oop_by_default", true],
+          ["dom.mozBrowserFramesEnabled", true],
+          ["browser.pageThumbs.enabled", false],
+          ["dom.mozBrowserFramesWhitelist", whitelist]
+        ]
+      }, runTests);
+    });
+
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/ipdl.mk
@@ -0,0 +1,7 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+IPDLSRCS = \
+  PDeviceStorageRequest.ipdl \
+  $(NULL)
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -1,142 +1,263 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsDeviceStorage.h"
-#include "DOMRequest.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIFile.h"
 #include "nsIDirectoryEnumerator.h"
 #include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
 #include "nsIDOMFile.h"
 #include "nsDOMBlobBuilder.h"
 #include "nsNetUtil.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIContentPermissionPrompt.h"
 #include "nsIPrincipal.h"
 #include "mozilla/Preferences.h"
 #include "nsJSUtils.h"
 #include "DictionaryHelpers.h"
 #include "mozilla/Attributes.h"
 #include "nsContentUtils.h"
+#include "nsXULAppAPI.h"
+#include "TabChild.h"
+#include "DeviceStorageRequestChild.h"
+
+// Microsoft's API Name hackery sucks
+#undef CreateEvent
 
 using namespace mozilla::dom;
+using namespace mozilla::dom::devicestorage;
 
 #include "nsDirectoryServiceDefs.h"
 
-class DeviceStorageFile MOZ_FINAL : public nsISupports {
-public:
-
-  nsCOMPtr<nsIFile> mFile;
-  nsString mPath;
-  bool mEditable;
-
-  DeviceStorageFile(nsIFile* aFile, const nsAString& aPath)
+DeviceStorageFile::DeviceStorageFile(nsIFile* aFile, const nsAString& aPath)
   : mPath(aPath)
   , mEditable(false)
-  {
-    NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
-    // always take a clone
-    nsCOMPtr<nsIFile> file;
-    aFile->Clone(getter_AddRefs(mFile));
+{
+  NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
+  // always take a clone
+  nsCOMPtr<nsIFile> file;
+  aFile->Clone(getter_AddRefs(mFile));
 
-    AppendRelativePath();
+  AppendRelativePath();
+  NormalizeFilePath();
+}
 
-    NormalizeFilePath();
-  }
-
-  DeviceStorageFile(nsIFile* aFile)
+DeviceStorageFile::DeviceStorageFile(nsIFile* aFile)
   : mEditable(false)
-  {
-    NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
-    // always take a clone
-    nsCOMPtr<nsIFile> file;
-    aFile->Clone(getter_AddRefs(mFile));
+{
+  NS_ASSERTION(aFile, "Must not create a DeviceStorageFile with a null nsIFile");
+  // always take a clone
+  nsCOMPtr<nsIFile> file;
+  aFile->Clone(getter_AddRefs(mFile));
+}
+
+void
+DeviceStorageFile::SetPath(const nsAString& aPath) {
+  mPath.Assign(aPath);
+  NormalizeFilePath();
+}
+
+void
+DeviceStorageFile::SetEditable(bool aEditable) {
+  mEditable = aEditable;
+}
+
+// we want to make sure that the names of file can't reach
+// outside of the type of storage the user asked for.
+bool
+DeviceStorageFile::IsSafePath()
+{
+  nsAString::const_iterator start, end;
+  mPath.BeginReading(start);
+  mPath.EndReading(end);
+
+  // if the path has a ~ or \ in it, return false.
+  NS_NAMED_LITERAL_STRING(tilde, "~");
+  NS_NAMED_LITERAL_STRING(bslash, "\\");
+  if (FindInReadable(tilde, start, end) ||
+      FindInReadable(bslash, start, end)) {
+    return false;
+   }
+  // split on /.  if any token is "", ., or .., return false.
+  NS_ConvertUTF16toUTF8 cname(mPath);
+  char* buffer = cname.BeginWriting();
+  const char* token;
+
+  while ((token = nsCRT::strtok(buffer, "/", &buffer))) {
+    if (PL_strcmp(token, "") == 0 ||
+	PL_strcmp(token, ".") == 0 ||
+	PL_strcmp(token, "..") == 0 ) {
+      return false;
+    }
   }
+  return true;
+}
 
-  void
-  setPath(const nsAString& aPath) {
-    mPath.Assign(aPath);
-    NormalizeFilePath();
+void
+DeviceStorageFile::NormalizeFilePath() {
+#if defined(XP_WIN)
+  PRUnichar* cur = mPath.BeginWriting();
+  PRUnichar* end = mPath.EndWriting();
+  for (; cur < end; ++cur) {
+    if (PRUnichar('\\') == *cur)
+      *cur = PRUnichar('/');
   }
+#endif
+}
 
-  void
-  setEditable(bool aEditable) {
-    mEditable = aEditable;
+void
+DeviceStorageFile::AppendRelativePath() {
+#if defined(XP_WIN)
+  // replace forward slashes with backslashes,
+  // since nsLocalFileWin chokes on them
+  nsString temp;
+  temp.Assign(mPath);
+
+  PRUnichar* cur = temp.BeginWriting();
+  PRUnichar* end = temp.EndWriting();
+
+  for (; cur < end; ++cur) {
+    if (PRUnichar('/') == *cur)
+      *cur = PRUnichar('\\');
+  }
+  mFile->AppendRelativePath(temp);
+#else
+  mFile->AppendRelativePath(mPath);
+#endif
+}
+
+nsresult
+DeviceStorageFile::Write(nsIDOMBlob* aBlob)
+{
+  nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  nsCOMPtr<nsIInputStream> stream;
+  aBlob->GetInternalStream(getter_AddRefs(stream));
+
+  PRUint32 bufSize;
+  stream->Available(&bufSize);
+
+  nsCOMPtr<nsIOutputStream> outputStream;
+  NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);
+
+  if (!outputStream) {
+    return NS_ERROR_FAILURE;
   }
 
-  NS_DECL_ISUPPORTS
+  nsCOMPtr<nsIOutputStream> bufferedOutputStream;
+  NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
+			     outputStream,
+			     4096*4);
 
-  // we want to make sure that the names of file can't reach
-  // outside of the type of storage the user asked for.
-  bool
-  isSafePath()
-  {
-    nsAString::const_iterator start, end;
-    mPath.BeginReading(start);
-    mPath.EndReading(end);
+  if (!bufferedOutputStream) {
+    return NS_ERROR_FAILURE;
+  }
 
-    // if the path has a ~ or \ in it, return false.
-    NS_NAMED_LITERAL_STRING(tilde, "~");
-    NS_NAMED_LITERAL_STRING(bslash, "\\");
-    if (FindInReadable(tilde, start, end) ||
-        FindInReadable(bslash, start, end)) {
-      return false;
-    }
+  PRUint32 wrote;
+  bufferedOutputStream->WriteFrom(stream, bufSize, &wrote);
+  bufferedOutputStream->Close();
+  outputStream->Close();
+  if (bufSize != wrote) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
 
-    // split on /.  if any token is "", ., or .., return false.
-    NS_ConvertUTF16toUTF8 cname(mPath);
-    char* buffer = cname.BeginWriting();
-    const char* token;
-  
-    while ((token = nsCRT::strtok(buffer, "/", &buffer))) {
-      if (PL_strcmp(token, "") == 0 ||
-          PL_strcmp(token, ".") == 0 ||
-          PL_strcmp(token, "..") == 0 ) {
-            return false;
-      }
-    }
-    return true;
+nsresult
+DeviceStorageFile::Write(InfallibleTArray<PRUint8>& aBits) {
+
+  nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  nsCOMPtr<nsIOutputStream> outputStream;
+  NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);
+
+  if (!outputStream) {
+    return NS_ERROR_FAILURE;
   }
 
-private:
-  void NormalizeFilePath() {
-#if defined(XP_WIN)
-    PRUnichar* cur = mPath.BeginWriting();
-    PRUnichar* end = mPath.EndWriting();
-    for (; cur < end; ++cur) {
-      if (PRUnichar('\\') == *cur)
-	*cur = PRUnichar('/');
-    }
-#endif
+  PRUint32 wrote;
+  outputStream->Write((char*) aBits.Elements(), aBits.Length(), &wrote);
+  outputStream->Close();
+
+  if (aBits.Length() != wrote) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
+void
+DeviceStorageFile::CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles,
+				PRUint64 aSince)
+{
+  nsString rootPath;
+  mFile->GetPath(rootPath);
+
+  return collectFilesInternal(aFiles, aSince, rootPath);
+}
+
+void
+DeviceStorageFile::collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles,
+					PRUint64 aSince,
+					nsAString& aRootPath)
+{
+  nsCOMPtr<nsISimpleEnumerator> e;
+  mFile->GetDirectoryEntries(getter_AddRefs(e));
+
+  if (!e) {
+    return;
   }
 
-  void AppendRelativePath() {
-#if defined(XP_WIN)
-    // replace forward slashes with backslashes,
-    // since nsLocalFileWin chokes on them
-    nsString temp;
-    temp.Assign(mPath);
+  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
+  nsCOMPtr<nsIFile> f;
+
+  while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
+
+    PRInt64 msecs;
+    f->GetLastModifiedTime(&msecs);
 
-    PRUnichar* cur = temp.BeginWriting();
-    PRUnichar* end = temp.EndWriting();
+    if (msecs < aSince) {
+      continue;
+     }
+
+    bool isDir;
+    f->IsDirectory(&isDir);
+
+    bool isFile;
+    f->IsFile(&isFile);
 
-    for (; cur < end; ++cur) {
-      if (PRUnichar('/') == *cur)
-        *cur = PRUnichar('\\');
+    nsString fullpath;
+    f->GetPath(fullpath);
+
+    if (!StringBeginsWith(fullpath, aRootPath)) {
+      NS_ERROR("collectFiles returned a path that does not belong!");
+      continue;
     }
-    mFile->AppendRelativePath(temp);
-#else
-    mFile->AppendRelativePath(mPath);
-#endif
+
+    nsAString::size_type len = aRootPath.Length() + 1; // +1 for the trailing /
+    nsDependentSubstring newPath = Substring(fullpath, len);
+
+    if (isDir) {
+      DeviceStorageFile dsf(f);
+      dsf.SetPath(newPath);
+      dsf.collectFilesInternal(aFiles, aSince, aRootPath);
+    } else if (isFile) {
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      dsf->SetPath(newPath);
+      aFiles.AppendElement(dsf);
+    }
   }
-
-};
+}
 
 NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
 
 
 // TODO - eventually, we will want to factor this method
 // out into different system specific subclasses (or
 // something)
 PRInt32
@@ -209,24 +330,23 @@ nsDOMDeviceStorage::SetRootFileForType(c
       dirService->Get(NS_UNIX_XDG_MUSIC_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
     }
 #endif
   }
 
   // in testing, we have access to a few more directory locations
   if (mozilla::Preferences::GetBool("device.storage.testing", false)) {
 
-    // Temp directory
-    if (aType.Equals(NS_LITERAL_STRING("temp")) && aIndex == 0) {
+    // testing directory
+    if (aType.Equals(NS_LITERAL_STRING("testing")) && aIndex == 0) {
       dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
-    }
-
-    // Profile directory
-    else if (aType.Equals(NS_LITERAL_STRING("profile")) && aIndex == 0) {
-      dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
+      if (f) {
+	f->AppendRelativeNativePath(NS_LITERAL_CSTRING("device-storage-testing"));
+	f->Create(nsIFile::DIRECTORY_TYPE, 0777);
+      }
     }
   } 
 
   mFile = f;
   return typeResult;
 }
 
 static jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
@@ -239,56 +359,60 @@ static jsval nsIFileToJsval(nsPIDOMWindo
     return JSVAL_NULL;
   }
 
   if (aFile == nsnull) {
     return JSVAL_NULL;
   }
 
   nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath);
+  return BlobToJsval(aWindow, blob);
+}
 
+
+jsval BlobToJsval(nsPIDOMWindow* aWindow, nsIDOMBlob* aBlob)
+{
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
   }
-    
+
   nsIScriptContext *scriptContext = sgo->GetScriptContext();
   if (!scriptContext) {
     return JSVAL_NULL;
   }
 
   JSContext *cx = scriptContext->GetNativeContext();
   if (!cx) {
     return JSVAL_NULL;
   }
 
   jsval wrappedFile;
   nsresult rv = nsContentUtils::WrapNative(cx,
                                            JS_GetGlobalObject(cx),
-                                           blob,
+                                           aBlob,
                                            &NS_GET_IID(nsIDOMFile),
                                            &wrappedFile);
   if (NS_FAILED(rv)) {
     return JSVAL_NULL;
   }
 
   return wrappedFile;
 }
 
-
-static jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString)
+jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null Window");
 
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
   }
-    
+
   nsIScriptContext *scriptContext = sgo->GetScriptContext();
   if (!scriptContext) {
     return JSVAL_NULL;
   }
 
   JSContext *cx = scriptContext->GetNativeContext();
   if (!cx) {
     return JSVAL_NULL;
@@ -299,61 +423,47 @@ static jsval StringToJsval(nsPIDOMWindow
   jsval result = JSVAL_NULL;
   if (!xpc::StringToJsval(cx, aString, &result)) {
     return JSVAL_NULL;
   }
 
   return result;
 }
 
-
-class nsDOMDeviceStorageCursor
-  : public nsIDOMDeviceStorageCursor
-  , public DOMRequest
-  , public nsIContentPermissionRequest
-{
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSICONTENTPERMISSIONREQUEST
-  NS_DECL_NSIDOMDEVICESTORAGECURSOR
-
-  nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow,
-                           nsIURI* aURI,
-                           DeviceStorageFile* aFile,
-                           PRUint64 aSince);
-
-private:
-  ~nsDOMDeviceStorageCursor();
-
-protected:
-  nsTArray<nsRefPtr<DeviceStorageFile> > mFiles;
-
-  bool mOkToCallContinue;
-  nsRefPtr<DeviceStorageFile> mFile;
-  nsCOMPtr<nsIURI> mURI;
-  PRUint64 mSince;
-
-  // to access mFiles
-  friend class InitCursorEvent;
-  friend class ContinueCursorEvent;
-};
-
-class DeviceStorageCursorRequest MOZ_FINAL : public nsIContentPermissionRequest
+class DeviceStorageCursorRequest MOZ_FINAL
+  : public nsIContentPermissionRequest
+  , public PCOMContentPermissionRequestChild
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(DeviceStorageCursorRequest, nsIContentPermissionRequest)
 
   NS_FORWARD_NSICONTENTPERMISSIONREQUEST(mCursor->);
 
   DeviceStorageCursorRequest(nsDOMDeviceStorageCursor* aCursor)
     : mCursor(aCursor) { }
 
   ~DeviceStorageCursorRequest() {}
 
+  bool Recv__delete__(const bool& allow)
+  {
+    if (allow) {
+      Allow();
+    }
+    else {
+      Cancel();
+    }
+    return true;
+  }
+
+  void IPDLRelease()
+  {
+    Release();
+  }
+
 private:
   nsRefPtr<nsDOMDeviceStorageCursor> mCursor;
 };
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageCursorRequest)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
 NS_INTERFACE_MAP_END
@@ -366,23 +476,16 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(De
 NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mCursor)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(DeviceStorageCursorRequest)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mCursor, nsIDOMDeviceStorageCursor)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 
-#define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "File location doesn't exists"
-#define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "File location is not enumerable"
-#define POST_ERROR_EVENT_PERMISSION_DENIED           "Permission Denied"
-#define POST_ERROR_EVENT_ILLEGAL_FILE_NAME           "Illegal file name"
-#define POST_ERROR_EVENT_UNKNOWN                     "Unknown"
-#define POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED "Non-string type unsupported"
-
 class PostErrorEvent : public nsRunnable
 {
 public:
   PostErrorEvent(nsRefPtr<DOMRequest>& aRequest, const char* aMessage, DeviceStorageFile* aFile)
   {
     mRequest.swap(aRequest);
     BuildErrorString(aMessage, aFile);
   }
@@ -400,84 +503,79 @@ public:
     nsAutoString fullPath;
 
     if (aFile && aFile->mFile) {
       aFile->mFile->GetPath(fullPath);
     }
     else {
       fullPath.Assign(NS_LITERAL_STRING("null file"));
     }
-      
+
     mError = NS_ConvertASCIItoUTF16(aMessage);
     mError.Append(NS_LITERAL_STRING(" file path = "));
     mError.Append(fullPath.get());
     mError.Append(NS_LITERAL_STRING(" path = "));
 
     if (aFile) {
       mError.Append(aFile->mPath);
     }
     else {
       mError.Append(NS_LITERAL_STRING("null path"));
     }
   }
 
-  NS_IMETHOD Run() {
+  NS_IMETHOD Run()
+  {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
     mRequest->FireError(mError);
     mRequest = nsnull;
     return NS_OK;
   }
 
 private:
   nsRefPtr<DOMRequest> mRequest;
   nsString mError;
 };
 
-class ContinueCursorEvent : public nsRunnable
+ContinueCursorEvent::ContinueCursorEvent(nsRefPtr<DOMRequest>& aRequest)
 {
-public:
+  mRequest.swap(aRequest);
+}
+
+ContinueCursorEvent::ContinueCursorEvent(DOMRequest* aRequest)
+  : mRequest(aRequest)
+{
+}
+
+ContinueCursorEvent::~ContinueCursorEvent() {}
 
-  ContinueCursorEvent(nsRefPtr<DOMRequest>& aRequest)
-  {
-    mRequest.swap(aRequest);
+NS_IMETHODIMP
+ContinueCursorEvent::Run() {
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  jsval val;
+
+  nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
+  if (cursor->mFiles.Length() == 0) {
+    val = JSVAL_NULL;
   }
+  else {
+    nsRefPtr<DeviceStorageFile> file = cursor->mFiles[0];
+    cursor->mFiles.RemoveElementAt(0);
 
-  ContinueCursorEvent(DOMRequest* aRequest)
-    : mRequest(aRequest)
-  {
+    // todo, this blob needs to be opened in the parent.  This will be signifincally easier when bent lands
+    val = nsIFileToJsval(cursor->GetOwner(), file);
+    cursor->mOkToCallContinue = true;
   }
 
-  ~ContinueCursorEvent() {}
-
-  NS_IMETHOD Run() {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
-    jsval val;
-
-    nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-    if (cursor->mFiles.Length() == 0) {
-      val = JSVAL_NULL;
-    }
-    else {
-      nsRefPtr<DeviceStorageFile> file = cursor->mFiles[0];
-      cursor->mFiles.RemoveElementAt(0);
-      val = nsIFileToJsval(cursor->GetOwner(), file);
-      cursor->mOkToCallContinue = true;
-    }
-
-    mRequest->FireSuccess(val);
-    mRequest = nsnull;
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<DOMRequest> mRequest;
-};
-
+  mRequest->FireSuccess(val);
+  mRequest = nsnull;
+  return NS_OK;
+}
 
 class InitCursorEvent : public nsRunnable
 {
 public:
     InitCursorEvent(DOMRequest* aRequest, DeviceStorageFile* aFile)
     : mFile(aFile)
     , mRequest(aRequest)
   {
@@ -493,81 +591,25 @@ public:
     if (!check) {
       nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
                                                           POST_ERROR_EVENT_FILE_NOT_ENUMERABLE,
                                                           mFile);
       NS_DispatchToMainThread(event);
       return NS_OK;
     }
 
-    collectFiles(mFile);
+    nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
+    mFile->CollectFiles(cursor->mFiles, cursor->mSince);
 
     nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(mRequest);
     NS_DispatchToMainThread(event);
 
     return NS_OK;
   }
 
-  void collectFiles(DeviceStorageFile* aFile)
-  {
-      // TODO - we may want to do this incrementally.
-    if (!aFile) {
-      return;
-    }
-
-    nsCOMPtr<nsISimpleEnumerator> e;
-    aFile->mFile->GetDirectoryEntries(getter_AddRefs(e));
-
-    if (!e) {
-      return;
-    }
-
-    nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
-    nsCOMPtr<nsIFile> f;
-
-    while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
-      nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
-
-      PRInt64 msecs;
-      f->GetLastModifiedTime(&msecs);
-
-      if (msecs < (PRInt64) cursor->mSince) {
-        continue;
-      }
-
-      bool isDir;
-      f->IsDirectory(&isDir);
-
-      bool isFile;
-      f->IsFile(&isFile);
-
-      nsString fullpath;
-      f->GetPath(fullpath);
-
-      nsString rootPath;
-      mFile->mFile->GetPath(rootPath);
-
-      if (!StringBeginsWith(fullpath, rootPath)) {
-	NS_WARNING("collectFiles returned a path that does not belong!");
-	continue;
-      }
-
-      nsAString::size_type len = rootPath.Length() + 1; // +1 for the trailing /
-      nsDependentSubstring newPath = Substring(fullpath, len);
-      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
-      dsf->setPath(newPath);
-
-      if (isDir) {
-        collectFiles(dsf);
-      }
-      else if (isFile) {
-        cursor->mFiles.AppendElement(dsf);
-      }
-    }
-  }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
 DOMCI_DATA(DeviceStorageCursor, nsDOMDeviceStorageCursor)
 
@@ -583,19 +625,19 @@ NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStor
 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorageCursor, DOMRequest)
 
 nsDOMDeviceStorageCursor::nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow,
                                                    nsIURI* aURI,
                                                    DeviceStorageFile* aFile,
                                                    PRUint64 aSince)
   : DOMRequest(aWindow)
   , mOkToCallContinue(false)
+  , mSince(aSince)
   , mFile(aFile)
   , mURI(aURI)
-  , mSince(aSince)
 {
 }
 
 nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
 {
 }
 
 NS_IMETHODIMP
@@ -634,24 +676,35 @@ nsDOMDeviceStorageCursor::Cancel()
                                                       mFile);
   NS_DispatchToMainThread(event);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorageCursor::Allow()
 {
-  if (!mFile->isSafePath()) {
+  if (!mFile->IsSafePath()) {
     nsCOMPtr<nsIRunnable> r = new PostErrorEvent(this,
                                                  POST_ERROR_EVENT_ILLEGAL_FILE_NAME,
                                                  mFile);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
+  if (XRE_GetProcessType() != GeckoProcessType_Default) {
+
+    nsString fullpath;
+    mFile->mFile->GetPath(fullpath);
+
+    PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(this, mFile);
+    DeviceStorageEnumerationParams params(fullpath, mSince);
+    ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
   NS_ASSERTION(target, "Must have stream transport service");
 
   nsCOMPtr<InitCursorEvent> event = new InitCursorEvent(this, mFile);
   target->Dispatch(event, NS_DISPATCH_NORMAL);
   return NS_OK;
 }
 
@@ -673,16 +726,33 @@ nsDOMDeviceStorageCursor::Continue()
 
   nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(this);
   NS_DispatchToMainThread(event);
 
   mOkToCallContinue = false;
   return NS_OK;
 }
 
+bool
+nsDOMDeviceStorageCursor::Recv__delete__(const bool& allow)
+{
+  if (allow) {
+    Allow();
+  }
+  else {
+    Cancel();
+  }
+  return true;
+}
+
+void
+nsDOMDeviceStorageCursor::IPDLRelease()
+{
+  Release();
+}
 
 class PostResultEvent : public nsRunnable
 {
 public:
   PostResultEvent(nsRefPtr<DOMRequest>& aRequest, DeviceStorageFile* aFile)
     : mFile(aFile)
     {
       mRequest.swap(aRequest);
@@ -691,17 +761,17 @@ public:
   PostResultEvent(nsRefPtr<DOMRequest>& aRequest, const nsAString & aPath)
     : mPath(aPath)
     {
       mRequest.swap(aRequest);
     }
 
   ~PostResultEvent() {}
 
-  NS_IMETHOD Run() 
+  NS_IMETHOD Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
     jsval result = JSVAL_NULL;
     if (mFile) {
       result = nsIFileToJsval(mRequest->GetOwner(), mFile);
     } else {
       result = StringToJsval(mRequest->GetOwner(), mPath);
@@ -716,96 +786,45 @@ private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsString mPath;
   nsRefPtr<DOMRequest> mRequest;
 };
 
 class WriteFileEvent : public nsRunnable
 {
 public:
-  WriteFileEvent(nsIDOMBlob *aBlob,
+  WriteFileEvent(nsIDOMBlob* aBlob,
                  DeviceStorageFile *aFile,
                  nsRefPtr<DOMRequest>& aRequest)
   : mBlob(aBlob)
   , mFile(aFile)
     {
       mRequest.swap(aRequest);
     }
 
   ~WriteFileEvent() {}
 
-  void CleanupOnFail(const char* error)
-  {
-    if (mFile) {
-      mFile->mFile->Remove(false);
-    }
-
-    nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
-                                                        error,
-                                                        mFile);
-    NS_DispatchToMainThread(event);
-  }
-
-  NS_IMETHOD Run() 
+  NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-    //TODO - this might be faster if we check to see if
-    //these are backed by OS-files, and if so, then just do
-    //a copy()
+    nsresult rv = mFile->Write(mBlob);
 
-    nsCOMPtr<nsIFile> f = mFile->mFile;
-
-    // This also creates all ancestors
-    nsresult rv = f->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
     if (NS_FAILED(rv)) {
-      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 1 ");
-      return NS_OK;
-    }
-    
-    nsCOMPtr<nsIInputStream> stream;
-    mBlob->GetInternalStream(getter_AddRefs(stream));
+      mFile->mFile->Remove(false);
 
-    if (!stream) {
-      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 2 ");
+      nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
+							  POST_ERROR_EVENT_UNKNOWN,
+							  mFile);
+      NS_DispatchToMainThread(event);
       return NS_OK;
     }
 
-    PRUint32 bufSize;
-    stream->Available(&bufSize);
-
-    nsCOMPtr<nsIOutputStream> outputStream;
-    NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), f);
-
-    if (!outputStream) {
-      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 3 ");
-      return NS_OK;
-    }
-
-    nsCOMPtr<nsIOutputStream> bufferedOutputStream;
-    NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream),
-                               outputStream,
-                               4096*4);
-
-    if (!bufferedOutputStream) {
-      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 4" );
-      return NS_OK;
-    }
-
-    PRUint32 wrote;
-    bufferedOutputStream->WriteFrom(stream, bufSize, &wrote);
-    bufferedOutputStream->Close();
-    outputStream->Close();
-
-    if (bufSize != wrote) {
-      CleanupOnFail(POST_ERROR_EVENT_UNKNOWN " 5 " );
-      return NS_OK;
-    }
-
-    nsCOMPtr<PostResultEvent> event = new PostResultEvent(mRequest, mFile->mPath);
+    nsCOMPtr<PostResultEvent> event = new PostResultEvent(mRequest,
+							  mFile->mPath);
     NS_DispatchToMainThread(event);
 
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIDOMBlob> mBlob;
   nsRefPtr<DeviceStorageFile> mFile;
@@ -818,17 +837,17 @@ public:
                   nsRefPtr<DOMRequest>& aRequest)
   : mFile(aFile)
     {
       mRequest.swap(aRequest);
     }
 
   ~ReadFileEvent() {}
 
-  NS_IMETHOD Run() 
+  NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
     nsRefPtr<nsRunnable> r;
 
     if (!mFile->mEditable) {
       bool check = false;
       mFile->mFile->Exists(&check);
@@ -855,18 +874,18 @@ public:
   DeleteFileEvent(DeviceStorageFile* aFile,
                   nsRefPtr<DOMRequest>& aRequest)
   : mFile(aFile)
     {
       mRequest.swap(aRequest);
     }
 
   ~DeleteFileEvent() {}
-    
-  NS_IMETHOD Run() 
+
+  NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
     mFile->mFile->Remove(true);
 
     nsRefPtr<nsRunnable> r;
 
     bool check = false;
@@ -881,17 +900,20 @@ public:
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
-class DeviceStorageRequest MOZ_FINAL : public nsIContentPermissionRequest, public nsIRunnable
+class DeviceStorageRequest MOZ_FINAL
+  : public nsIContentPermissionRequest
+  , public nsIRunnable
+  , public PCOMContentPermissionRequestChild
 {
 public:
 
     enum {
         DEVICE_STORAGE_REQUEST_READ,
         DEVICE_STORAGE_REQUEST_WRITE,
         DEVICE_STORAGE_REQUEST_DELETE
     };
@@ -913,16 +935,35 @@ public:
 
   NS_IMETHOD Run() {
 
     if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
       Allow();
       return NS_OK;
     }
 
+    if (XRE_GetProcessType() == GeckoProcessType_Content) {
+
+      // because owner implements nsITabChild, we can assume that it is
+      // the one and only TabChild.
+      TabChild* child = GetTabChildFrom(mWindow->GetDocShell());
+      if (!child)
+	return false;
+
+      // Retain a reference so the object isn't deleted without IPDL's knowledge.
+      // Corresponding release occurs in DeallocPContentPermissionRequest.
+      AddRef();
+
+      nsCString type = NS_LITERAL_CSTRING("device-storage");
+      child->SendPContentPermissionRequestConstructor(this, type, IPC::URI(mURI));
+
+      Sendprompt();
+      return NS_OK;
+    }
+
     nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
     if (prompt) {
       prompt->Prompt(this);
     }
     return NS_OK;
   }
 
   NS_IMETHOD GetType(nsACString & aType)
@@ -961,46 +1002,98 @@ public:
   NS_IMETHOD Allow()
   {
     nsCOMPtr<nsIRunnable> r;
 
     if (!mRequest) {
       return NS_ERROR_FAILURE;
     }
 
+    nsString fullpath;
+    mFile->mFile->GetPath(fullpath);
+
     switch(mRequestType) {
       case DEVICE_STORAGE_REQUEST_WRITE:
       {
         if (!mBlob) {
           return NS_ERROR_FAILURE;
         }
 
-        r = new WriteFileEvent(mBlob, mFile, mRequest);
+	if (XRE_GetProcessType() != GeckoProcessType_Default) {
+	  PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
+
+	  nsCOMPtr<nsIInputStream> stream;
+	  mBlob->GetInternalStream(getter_AddRefs(stream));
+
+	  InfallibleTArray<PRUint8> bits;
+	  PRUint32 bufSize, numRead;
+
+	  stream->Available(&bufSize);
+	  bits.SetCapacity(bufSize);
+
+	  void* buffer = (void*) bits.Elements();
+
+	  stream->Read((char*)buffer, bufSize, &numRead);
+
+	  DeviceStorageAddParams params(fullpath, bits);
+	  ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
+	  return NS_OK;
+	}
+	r = new WriteFileEvent(mBlob, mFile, mRequest);
         break;
       }
       case DEVICE_STORAGE_REQUEST_READ:
       {
+	if (XRE_GetProcessType() != GeckoProcessType_Default) {
+	  PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
+	  DeviceStorageGetParams params(fullpath);
+	  ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
+	  return NS_OK;
+	}
+
         r = new ReadFileEvent(mFile, mRequest);
         break;
       }
       case DEVICE_STORAGE_REQUEST_DELETE:
       {
+	if (XRE_GetProcessType() != GeckoProcessType_Default) {
+	  PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
+	  DeviceStorageDeleteParams params(fullpath);
+	  ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
+	  return NS_OK;
+	}
         r = new DeleteFileEvent(mFile, mRequest);
         break;
       }
     }
-    
+
     if (r) {
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
     }
     return NS_OK;
   }
 
+  bool Recv__delete__(const bool& allow)
+  {
+    if (allow) {
+      Allow();
+    }
+    else {
+      Cancel();
+    }
+    return true;
+  }
+
+  void IPDLRelease()
+  {
+    Release();
+  }
+
 private:
   PRInt32 mRequestType;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIURI> mURI;
   nsRefPtr<DeviceStorageFile> mFile;
 
   nsRefPtr<DOMRequest> mRequest;
   nsCOMPtr<nsIDOMBlob> mBlob;
@@ -1062,17 +1155,16 @@ nsDOMDeviceStorage::Init(nsPIDOMWindow* 
   // Grab the uri of the document
   nsCOMPtr<nsIDOMDocument> domdoc;
   aWindow->GetDocument(getter_AddRefs(domdoc));
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
   doc->NodePrincipal()->GetURI(getter_AddRefs(mURI));
-
   return NS_OK;
 }
 
 nsDOMDeviceStorage::~nsDOMDeviceStorage()
 {
 }
 
 void
@@ -1120,16 +1212,17 @@ nsDOMDeviceStorage::GetType(nsAString & 
       break;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::Add(nsIDOMBlob *aBlob, nsIDOMDOMRequest * *_retval NS_OUTPARAM)
 {
+  // possible race here w/ unique filename
   char buffer[128];
   NS_MakeRandomString(buffer, 128);
 
   nsString path;
   path.AssignWithConversion(nsDependentCString(buffer));
 
   return AddNamed(aBlob, path, _retval);
 }
@@ -1142,25 +1235,25 @@ nsDOMDeviceStorage::AddNamed(nsIDOMBlob 
   // if the blob is null here, bail
   if (aBlob == nsnull)
     return NS_OK;
 
   nsCOMPtr<nsPIDOMWindow> win = do_QueryReferent(mOwner);
   if (!win) {
     return NS_ERROR_UNEXPECTED;
   }
-  
+
   nsRefPtr<DOMRequest> request = new DOMRequest(win);
   NS_ADDREF(*_retval = request);
 
   nsCOMPtr<nsIRunnable> r;
 
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, aPath);
 
-  if (!dsf->isSafePath()) {
+  if (!dsf->IsSafePath()) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
   }
   else {
     r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_WRITE,
                                  win, mURI, dsf, request, aBlob);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
@@ -1205,19 +1298,19 @@ nsDOMDeviceStorage::GetInternal(const JS
     r = new PostErrorEvent(request,
                            POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED,
                            dsf);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
-  dsf->setEditable(aEditable);
+  dsf->SetEditable(aEditable);
 
-  if (!dsf->isSafePath()) {
+  if (!dsf->IsSafePath()) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
   } else {
     r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_READ,
                                  win, mURI, dsf, request);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
@@ -1241,17 +1334,17 @@ nsDOMDeviceStorage::Delete(const JS::Val
     nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
     r = new PostErrorEvent(request, POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED, dsf);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
 
-  if (!dsf->isSafePath()) {
+  if (!dsf->IsSafePath()) {
     r = new PostErrorEvent(request, POST_ERROR_EVENT_ILLEGAL_FILE_NAME, dsf);
   }
   else {
     r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_DELETE,
                                  win, mURI, dsf, request);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
@@ -1319,35 +1412,54 @@ nsDOMDeviceStorage::EnumerateInternal(co
       jspath.init(aCx, jsstr);
       path.Assign(jspath);
     } else if (!JSVAL_IS_PRIMITIVE(aName)) {
       // it also might be an options object
       since = ExtractDateFromOptions(aCx, aName);
     } else {
       return NS_ERROR_FAILURE;
     }
-      
+
     if (aArgc == 2 && (JSVAL_IS_VOID(aOptions) || aOptions.isNull() || !aOptions.isObject())) {
       return NS_ERROR_FAILURE;
     }
     since = ExtractDateFromOptions(aCx, aOptions);
   }
-  
+
   nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile, path);
-  dsf->setEditable(aEditable);
+  dsf->SetEditable(aEditable);
 
   nsRefPtr<nsDOMDeviceStorageCursor> cursor = new nsDOMDeviceStorageCursor(win, mURI, dsf, since);
   nsRefPtr<DeviceStorageCursorRequest> r = new DeviceStorageCursorRequest(cursor);
 
   NS_ADDREF(*aRetval = cursor);
 
   if (mozilla::Preferences::GetBool("device.storage.prompt.testing", false)) {
     r->Allow();
     return NS_OK;
   }
 
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    // because owner implements nsITabChild, we can assume that it is
+    // the one and only TabChild.
+    TabChild* child = GetTabChildFrom(win->GetDocShell());
+    if (!child)
+      return false;
+
+    // Retain a reference so the object isn't deleted without IPDL's knowledge.
+    // Corresponding release occurs in DeallocPContentPermissionRequest.
+    r->AddRef();
+
+    nsCString type = NS_LITERAL_CSTRING("device-storage");
+    child->SendPContentPermissionRequestConstructor(r, type, IPC::URI(mURI));
+
+    r->Sendprompt();
+
+    return NS_OK;
+  }
+
   nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
   if (prompt) {
     prompt->Prompt(r);
   }
 
   return NS_OK;
 }
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -2,29 +2,74 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDeviceStorage_h
 #define nsDeviceStorage_h
 
 class nsPIDOMWindow;
 
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
+
+
+#include "DOMRequest.h"
+#include "PCOMContentPermissionRequestChild.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/PContentPermissionRequestChild.h"
+#include "nsAutoPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMClassInfoID.h"
 #include "nsIClassInfo.h"
+#include "nsIContentPermissionPrompt.h"
 #include "nsIDOMDeviceStorage.h"
 #include "nsIDOMDeviceStorageCursor.h"
 #include "nsIDOMWindow.h"
 #include "nsIURI.h"
-
-#include "nsAutoPtr.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsDOMClassInfoID.h"
+#include "nsInterfaceHashtable.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
-#include "nsInterfaceHashtable.h"
-#include "mozilla/Attributes.h"
+
+
+#define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "File location doesn't exists"
+#define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "File location is not enumerable"
+#define POST_ERROR_EVENT_PERMISSION_DENIED           "Permission Denied"
+#define POST_ERROR_EVENT_ILLEGAL_FILE_NAME           "Illegal file name"
+#define POST_ERROR_EVENT_UNKNOWN                     "Unknown"
+#define POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED "Non-string type unsupported"
+
+using namespace mozilla::dom;
+
+class DeviceStorageFile MOZ_FINAL : public nsISupports {
+public:
+  nsCOMPtr<nsIFile> mFile;
+  nsString mPath;
+  bool mEditable;
+
+  DeviceStorageFile(nsIFile* aFile, const nsAString& aPath);
+  DeviceStorageFile(nsIFile* aFile);
+  void SetPath(const nsAString& aPath);
+  void SetEditable(bool aEditable);
+
+  NS_DECL_ISUPPORTS
+
+  // we want to make sure that the names of file can't reach
+  // outside of the type of storage the user asked for.
+  bool IsSafePath();
+  
+  nsresult Write(nsIDOMBlob* blob);
+  nsresult Write(InfallibleTArray<PRUint8>& bits);
+  void CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRUint64 aSince = 0);
+  void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRUint64 aSince, nsAString& aRootPath);
+
+private:
+  void NormalizeFilePath();
+  void AppendRelativePath();
+};
 
 class nsDOMDeviceStorage MOZ_FINAL : public nsIDOMDeviceStorage
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMDEVICESTORAGE
 
   nsDOMDeviceStorage();
@@ -33,17 +78,16 @@ public:
 
   PRInt32 SetRootFileForType(const nsAString& aType, const PRInt32 aIndex);
 
   static void CreateDeviceStoragesFor(nsPIDOMWindow* aWin, const nsAString &aType, nsIVariant** _retval);
 
 private:
   ~nsDOMDeviceStorage();
 
-
   nsresult GetInternal(const JS::Value & aName, JSContext* aCx, nsIDOMDOMRequest * *_retval NS_OUTPARAM, bool aEditable);
 
   nsresult EnumerateInternal(const JS::Value & aName, const JS::Value & aOptions, JSContext* aCx, PRUint8 aArgc, bool aEditable, nsIDOMDeviceStorageCursor** aRetval);
 
   PRInt32 mStorageType;
   nsCOMPtr<nsIFile> mFile;
 
   nsWeakPtr mOwner;
@@ -52,9 +96,57 @@ private:
   // nsIDOMDeviceStorage.type
   enum {
       DEVICE_STORAGE_TYPE_DEFAULT = 0,
       DEVICE_STORAGE_TYPE_SHARED,
       DEVICE_STORAGE_TYPE_EXTERNAL,
   };
 };
 
+class ContinueCursorEvent MOZ_FINAL: public nsRunnable
+{
+public:
+  ContinueCursorEvent(nsRefPtr<DOMRequest>& aRequest);
+  ContinueCursorEvent(DOMRequest* aRequest);
+  ~ContinueCursorEvent();
+  NS_IMETHOD Run();
+private:
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+class nsDOMDeviceStorageCursor MOZ_FINAL
+  : public nsIDOMDeviceStorageCursor
+  , public DOMRequest
+  , public nsIContentPermissionRequest
+  , public PCOMContentPermissionRequestChild
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
+  NS_DECL_NSIDOMDEVICESTORAGECURSOR
+
+  nsDOMDeviceStorageCursor(nsIDOMWindow* aWindow,
+                           nsIURI* aURI,
+                           DeviceStorageFile* aFile,
+                           PRUint64 aSince);
+
+
+  nsTArray<nsRefPtr<DeviceStorageFile> > mFiles;
+  bool mOkToCallContinue;
+  PRUint64 mSince;
+
+  virtual bool Recv__delete__(const bool& allow);
+  virtual void IPDLRelease();
+
+private:
+  ~nsDOMDeviceStorageCursor();
+
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsCOMPtr<nsIURI> mURI;
+};
+
+//helpers
+jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString);
+jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile, bool aEditable);
+jsval BlobToJsval(nsPIDOMWindow* aWindow, nsIDOMBlob* aBlob);
+
+
 #endif
--- a/dom/devicestorage/test/devicestorage_common.js
+++ b/dom/devicestorage/test/devicestorage_common.js
@@ -8,44 +8,59 @@ var oldVal = false;
 // Array Remove - By John Resig (MIT Licensed)
 Array.prototype.remove = function(from, to) {
   var rest = this.slice((to || from) + 1 || this.length);
   this.length = from < 0 ? this.length + from : from;
   return this.push.apply(this, rest);
 };
 
 function devicestorage_setup() {
-  SimpleTest.waitForExplicitFinish();
+
+  // ensure that the directory we are writing into is empty
   try {
-    oldVal = SpecialPowers.getBoolPref("device.storage.enabled");
+    const Cc = SpecialPowers.wrap(Components).classes;
+    const Ci = Components.interfaces;
+    var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+    var f = directoryService.get("TmpD", Ci.nsIFile);
+    f.appendRelativePath("device-storage-testing");
+    f.remove(true);
   } catch(e) {}
-  SpecialPowers.setBoolPref("device.storage.enabled", true);
-  SpecialPowers.setBoolPref("device.storage.testing", true);
-  SpecialPowers.setBoolPref("device.storage.prompt.testing", true);
+
+  SimpleTest.waitForExplicitFinish();
+  if (SpecialPowers.isMainProcess()) {
+    try {
+      oldVal = SpecialPowers.getBoolPref("device.storage.enabled");
+    } catch(e) {}
+    SpecialPowers.setBoolPref("device.storage.enabled", true);
+    SpecialPowers.setBoolPref("device.storage.testing", true);
+    SpecialPowers.setBoolPref("device.storage.prompt.testing", true);
+  }
 }
 
 function devicestorage_cleanup() {
-  SpecialPowers.setBoolPref("device.storage.enabled", oldVal);
-  SpecialPowers.setBoolPref("device.storage.testing", false);
-  SpecialPowers.setBoolPref("device.storage.prompt.testing", false);
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.setBoolPref("device.storage.enabled", oldVal);
+    SpecialPowers.setBoolPref("device.storage.testing", false);
+    SpecialPowers.setBoolPref("device.storage.prompt.testing", false);
+  }
   SimpleTest.finish();
 }
 
 function getRandomBuffer() {
   var size = 1024;
   var buffer = new ArrayBuffer(size);
   var view = new Uint8Array(buffer);
   for (var i = 0; i < size; i++) {
     view[i] = parseInt(Math.random() * 255);
   }
   return buffer;
 }
 
 function createRandomBlob() {
- return blob = new Blob([getRandomBuffer()], {type: 'binary/random'});
+  return blob = new Blob([getRandomBuffer()], {type: 'binary/random'});
 }
 
 function randomFilename(l) {
     var set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ";
     var result = "";
     for (var i=0; i<l; i++) {
 	var r = Math.floor(set.length * Math.random());
 	result += set.substring(r, r + 1);
--- a/dom/devicestorage/test/test_basic.html
+++ b/dom/devicestorage/test/test_basic.html
@@ -20,81 +20,102 @@ https://bugzilla.mozilla.org/show_bug.cg
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 devicestorage_setup();
 
 var gFileName = "devicestorage/hi";
+var gData = "My name is Doug Turner.  My IRC nick is DougT.  I like Maple cookies."
+var gDataBlob = new Blob([gData], {type: 'text/plain'});
 
 function getAfterDeleteSuccess(e) {
   ok(false, "file was deleted not successfully");
   devicestorage_cleanup();
 }
 
 function getAfterDeleteError(e) {
   ok(true, "file was deleted successfully");
   devicestorage_cleanup();
 }
 
 function deleteSuccess(e) {
 
   ok(e.target.result == gFileName, "File name should match");
+  dump(e.target.result + "\n")
 
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   request = storage[0].get(e.target.result);
   request.onsuccess = getAfterDeleteSuccess;
   request.onerror = getAfterDeleteError;
 
 }
 
 function deleteError(e) {
   ok(false, "deleteError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function getSuccess(e) {
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   ok(e.target.result.name == gFileName, "File name should match");
 
-  request = storage[0].delete(e.target.result.name)
-  request.onsuccess = deleteSuccess;
-  request.onerror = deleteError;
+  var name = e.target.result.name;
+
+  var reader = new FileReader();
+  reader.readAsArrayBuffer(gDataBlob);
+  reader.onload = function(e) {
+    readerCallback(e);
+
+    request = storage[0].delete(name)
+    request.onsuccess = deleteSuccess;
+    request.onerror = deleteError;
+  }
+}
+
+function readerCallback(e) {
+
+  ab = e.target.result;
+
+  is(ab.byteLength, gData.length, "wrong arraybuffer byteLength");
+  var u8v = new Uint8Array(ab);
+  is(String.fromCharCode.apply(String, u8v), gData, "wrong values");
 }
 
 function getError(e) {
   ok(false, "getError was called : " + e.target.error.name);
-  SpecialPowers.setBoolPref("device.storage.enabled", oldVal);
-  SimpleTest.finish();
+  devicestorage_cleanup();
 }
 
 function addSuccess(e) {
 
   ok(e.target.result == gFileName, "File name should match");
 
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   request = storage[0].get(gFileName);
   request.onsuccess = getSuccess;
   request.onerror = getError;
 
   ok(true, "addSuccess was called");
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
-var storage = navigator.getDeviceStorage("profile");
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
-request = storage[0].addNamed(createRandomBlob(), "devicestorage/hi");
+var storage = navigator.getDeviceStorage("testing");
+ok(storage, "Should have gotten a storage");
+
+request = storage[0].addNamed(gDataBlob, "devicestorage/hi");
 ok(request, "Should have a non-null request");
 
 request.onsuccess = addSuccess;
 request.onerror = addError;
 
 </script>
 </pre>
 </body>
--- a/dom/devicestorage/test/test_dotdot.html
+++ b/dom/devicestorage/test/test_dotdot.html
@@ -19,25 +19,25 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 devicestorage_setup();
 
-function profileStorage() {
-  return navigator.getDeviceStorage("profile")[0];
+function testingStorage() {
+  return navigator.getDeviceStorage("testing")[0];
 }
 
 var tests = [
-  function () { return profileStorage().addNamed(createRandomBlob(), gFileName); },
-  function () { return profileStorage().delete(gFileName); },
-  function () { return profileStorage().get(gFileName); },
-  function () { var r = profileStorage().enumerate("../"); return r; }
+  function () { return testingStorage().addNamed(createRandomBlob(), gFileName); },
+  function () { return testingStorage().delete(gFileName); },
+  function () { return testingStorage().get(gFileName); },
+  function () { var r = testingStorage().enumerate("../"); return r; }
 ];
 
 var gFileName = "../owned";
 
 function fail(e) {
   ok(false, "addSuccess was called");
   dump(request);
   devicestorage_cleanup();
--- a/dom/devicestorage/test/test_enumerate.html
+++ b/dom/devicestorage/test/test_enumerate.html
@@ -23,17 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script class="testbody" type="text/javascript">
 
 devicestorage_setup();
 
 function enumerateSuccess(e) {
 
   if (e.target.result == null) {
     ok(files.length == 0, "when the enumeration is done, we shouldn't have any files in this array")
-    dump("We still have length = " + files.length);
+    dump("We still have length = " + files.length + "\n");
     devicestorage_cleanup();
     return;
   }
   
   var filename = e.target.result.name;
 
   var index = files.indexOf(filename);
   files.remove(index);
@@ -62,17 +62,17 @@ function addSuccess(e) {
   }
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
-var storage = navigator.getDeviceStorage("profile");
+var storage = navigator.getDeviceStorage("testing");
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 var prefix = "devicestorage/" + randomFilename(12)
 
 var files = [ "a", "b", "c", "d/a", "d/b", "d/c", "d/d", "The/quick/brown/fox/jumps/over/the/lazy/dog"]
 var addedSoFar = 0;
 
 
 for (var i=0; i<files.length; i++) {
--- a/dom/devicestorage/test/test_enumerateMultipleContinue.html
+++ b/dom/devicestorage/test/test_enumerateMultipleContinue.html
@@ -25,17 +25,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 devicestorage_setup();
 
 function enumerateSuccess(e) {
 }
 
 function enumerateFailure(e) {
 }
 
-var cursor = navigator.getDeviceStorage("profile")[0].enumerate();
+var cursor = navigator.getDeviceStorage("testing")[0].enumerate();
 cursor.onsuccess = enumerateSuccess;
 cursor.onerror = enumerateFailure;
 
 try {
  cursor.continue();
 }
 catch (e) {
   ok(true, "Calling continue before enumerateSuccess fires should throw");
--- a/dom/devicestorage/test/test_enumerateNoParam.html
+++ b/dom/devicestorage/test/test_enumerateNoParam.html
@@ -66,17 +66,17 @@ function addSuccess(e) {
   }
 }
 
 function addError(e) {
   ok(false, "addError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
-var storage = navigator.getDeviceStorage("profile");
+var storage = navigator.getDeviceStorage("testing");
 ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 var prefix = "devicestorage/" + randomFilename(12)
 
 var files = [ "a", "b", "c" ]
 var addedSoFar = 0;
 
 
 for (var i=0; i<files.length; i++) {
--- a/dom/devicestorage/test/test_enumerateOptions.html
+++ b/dom/devicestorage/test/test_enumerateOptions.html
@@ -20,17 +20,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 devicestorage_setup()
 
-storage = navigator.getDeviceStorage("profile");
+storage = navigator.getDeviceStorage("testing");
 
 
 throws = false;
 try {
 var cursor = storage[0].enumerate();
 } catch(e) {throws = true}
 ok(!throws, "enumerate no parameter");
 
--- a/dom/devicestorage/test/test_lastModificationFilter.html
+++ b/dom/devicestorage/test/test_lastModificationFilter.html
@@ -39,30 +39,33 @@ function verifyAndDelete(prefix, files, 
   var index = files.indexOf(filename);
   ok(index > -1, "filename should be in the enumeration : " + e.target.result.name);
   if (index == -1)
     return;
 
   files.remove(index);
 
   // clean up
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   var cleanup = storage[0].delete(prefix + "/" + filename);
   cleanup.onsuccess = function(e) {}
 }
 
 function addFiles(prefix, files, date, callback) {
 
   const Cc = SpecialPowers.wrap(Components).classes;
   const Ci = Components.interfaces;
 
   var directoryService = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
 
   for (var i=0; i<files.length; i++) {
-    var f = directoryService.get("ProfD", Components.interfaces.nsIFile);
+
+    var f = directoryService.get("TmpD", Ci.nsIFile);
+    f.appendRelativePath("device-storage-testing");
+
     var path = prefix + '/' + files[i];
     path.split("/").forEach(function(p) {
       f.appendRelativePath(p);
     });
     f.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0644);
     f.lastModifiedTime = date;
   }
   callback();
@@ -76,17 +79,17 @@ var prefix = "devicestorage/" + randomFi
 var oldFiles = ["a", "b", "c"];
 var newFiles = ["d", "e", "f"];
 
 // 157795200 is a long long time ago.
 addFiles(prefix, oldFiles, 157795200, addNewFiles);
 
 function enumerateNew() {
 
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
 // 836031600 is a long time ago
   var cursor = storage[0].enumerate(prefix, {"since": new Date(836031600)});
   cursor.onsuccess = function(e) {
     verifyAndDelete(prefix, newFiles, e);
     if (e.target.result) {
       e.target.continue();
--- a/dom/devicestorage/test/test_overwrite.html
+++ b/dom/devicestorage/test/test_overwrite.html
@@ -34,49 +34,50 @@ function deleteSuccess(e) {
 
 function deleteError(e) {
   ok(false, "deleteError was called : " + e.target.error.name);
   devicestorage_cleanup();
 }
 
 function addOverwritingSuccess(e) {
   ok(false, "addOverwritingSuccess was called.");
+  devicestorage_cleanup();
 }
 
 function addOverwritingError(e) {
   ok(true, "Adding to the same location should fail");
 
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   request = storage[0].delete(filename)
   request.onsuccess = deleteSuccess;
   request.onerror = deleteError;
 }
 
 function addSuccess(e) {
   ok(true, "addSuccess was called");
 
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   request = storage[0].addNamed(createRandomBlob(), filename);
   ok(request, "Should have a non-null request");
 
   request.onsuccess = addOverwritingSuccess;
   request.onerror = addOverwritingError;
 }
 
 function addError(e) {
   // test file is already exists.  clean it up and try again..
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   request = storage[0].delete(filename)
   request.onsuccess = runtest;
 }
 
 function runtest() {
-  var storage = navigator.getDeviceStorage("profile");
+  var storage = navigator.getDeviceStorage("testing");
   ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
 
   request = storage[0].addNamed(createRandomBlob(), filename);
   ok(request, "Should have a non-null request");
 
   request.onsuccess = addSuccess;
   request.onerror = addError;
 }
--- a/dom/devicestorage/test/test_sanity.html
+++ b/dom/devicestorage/test/test_sanity.html
@@ -33,40 +33,21 @@ var throws = false;
 try {
  storage = navigator.getDeviceStorage();
 } catch(e) {throws = true}
 ok(throws, "getDeviceStorage takes one arg");
 
 storage = navigator.getDeviceStorage("kilimanjaro");
 ok(!storage, "kilimanjaro - Should not have this type of storage");
 
-storage = navigator.getDeviceStorage("temp");
-ok(storage, "temp - Should have getDeviceStorage");
-
-storage = navigator.getDeviceStorage("profile");
-ok(storage, "profile - Should have getDeviceStorage");
+storage = navigator.getDeviceStorage("testing");
+ok(storage, "testing - Should have getDeviceStorage");
 
 var cursor = storage[0].enumerate();
 ok(cursor, "Should have a non-null cursor");
 
-var i = 4;
-cursor.onsuccess = function(e) {
-  i = i - 1;
-  if (i > 0) {
-    ok(true, "onsuccess was called");
-    e.target.continue();
-  }
-  else {
-    ok(true, "onsuccess was called 4 times");
-    devicestorage_cleanup();
-  }
-}
-
-cursor.onerror = function(e) {
-  ok(false, "onerror was called : " + e.target.error.name);
-  devicestorage_cleanup();
-}
+devicestorage_cleanup();
 
 </script>
 </pre>
 </body>
 </html>
 
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -247,17 +247,16 @@ private:
   PRInt64 mId;
   nsString mName;
   KeyPath mKeyPath;
   JS::Value mCachedKeyPath;
   bool mRooted;
   bool mAutoIncrement;
   nsCOMPtr<nsIAtom> mDatabaseId;
   nsRefPtr<ObjectStoreInfo> mInfo;
-  PRUint32 mStructuredCloneVersion;
 
   nsTArray<nsRefPtr<IDBIndex> > mCreatedIndexes;
 
   IndexedDBObjectStoreChild* mActorChild;
   IndexedDBObjectStoreParent* mActorParent;
 };
 
 END_INDEXEDDB_NAMESPACE
--- a/dom/indexedDB/ipc/IndexedDBChild.cpp
+++ b/dom/indexedDB/ipc/IndexedDBChild.cpp
@@ -43,30 +43,27 @@ public:
   GetSuccessResult(JSContext* aCx, jsval* aVal) MOZ_OVERRIDE;
 
   virtual nsresult
   DoDatabaseWork(mozIStorageConnection* aConnection) MOZ_OVERRIDE;
 };
 
 class IPCSetVersionHelper : public AsyncConnectionHelper
 {
-  IndexedDBTransactionChild* mActor;
   nsRefPtr<IDBOpenDBRequest> mOpenRequest;
   uint64_t mOldVersion;
   uint64_t mRequestedVersion;
 
 public:
-  IPCSetVersionHelper(IndexedDBTransactionChild* aActor,
-                      IDBTransaction* aTransaction, IDBOpenDBRequest* aRequest,
+  IPCSetVersionHelper(IDBTransaction* aTransaction, IDBOpenDBRequest* aRequest,
                       uint64_t aOldVersion, uint64_t aRequestedVersion)
-  : AsyncConnectionHelper(aTransaction, aRequest),mActor(aActor),
+  : AsyncConnectionHelper(aTransaction, aRequest),
     mOpenRequest(aRequest), mOldVersion(aOldVersion),
     mRequestedVersion(aRequestedVersion)
   {
-    MOZ_ASSERT(aActor);
     MOZ_ASSERT(aTransaction);
     MOZ_ASSERT(aRequest);
   }
 
   virtual nsresult UnpackResponseFromParentProcess(
                                             const ResponseValue& aResponseValue)
                                             MOZ_OVERRIDE;
 
@@ -462,17 +459,17 @@ IndexedDBDatabaseChild::RecvPIndexedDBTr
 
   nsTArray<nsString> storesToOpen;
   nsRefPtr<IDBTransaction> transaction =
     IDBTransaction::CreateInternal(mDatabase, storesToOpen,
                                    IDBTransaction::VERSION_CHANGE, false, true);
   NS_ENSURE_TRUE(transaction, false);
 
   nsRefPtr<IPCSetVersionHelper> versionHelper =
-    new IPCSetVersionHelper(actor, transaction, mRequest, oldVersion, mVersion);
+    new IPCSetVersionHelper(transaction, mRequest, oldVersion, mVersion);
 
   mDatabase->EnterSetVersionTransaction();
   mDatabase->mPreviousDatabaseInfo->version = oldVersion;
 
   MainThreadEventTarget target;
   if (NS_FAILED(versionHelper->Dispatch(&target))) {
     NS_WARNING("Dispatch of IPCSetVersionHelper failed!");
     return false;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -74,22 +74,24 @@
 #define getpid _getpid
 #endif
 
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
 #endif
 
 #include "mozilla/dom/sms/SmsChild.h"
+#include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::net;
 using namespace mozilla::places;
 using namespace mozilla::docshell;
+using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::sms;
 
 namespace mozilla {
 namespace dom {
 
 class MemoryReportRequestChild : public PMemoryReportRequestChild
 {
 public:
@@ -466,16 +468,29 @@ ContentChild::DeallocPAudio(PAudioChild*
 {
 #if defined(MOZ_SYDNEYAUDIO)
     AudioChild *child = static_cast<AudioChild*>(doomed);
     NS_RELEASE(child);
 #endif
     return true;
 }
 
+PDeviceStorageRequestChild*
+ContentChild::AllocPDeviceStorageRequest(const DeviceStorageParams& aParams)
+{
+    return new DeviceStorageRequestChild();
+}
+
+bool
+ContentChild::DeallocPDeviceStorageRequest(PDeviceStorageRequestChild* aDeviceStorage)
+{
+    delete aDeviceStorage;
+    return true;
+}
+
 PNeckoChild* 
 ContentChild::AllocPNecko()
 {
     return new NeckoChild();
 }
 
 bool 
 ContentChild::DeallocPNecko(PNeckoChild* necko)
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -54,16 +54,19 @@ public:
 
     /* if you remove this, please talk to cjones or dougt */
     virtual bool RecvDummy(Shmem& foo) { return true; }
 
     virtual PBrowserChild* AllocPBrowser(const PRUint32& aChromeFlags,
                                          const bool& aIsBrowserFrame);
     virtual bool DeallocPBrowser(PBrowserChild*);
 
+    virtual PDeviceStorageRequestChild* AllocPDeviceStorageRequest(const DeviceStorageParams&);
+    virtual bool DeallocPDeviceStorageRequest(PDeviceStorageRequestChild*);
+
     virtual PCrashReporterChild*
     AllocPCrashReporter(const mozilla::dom::NativeThreadId& id,
                         const PRUint32& processType);
     virtual bool
     DeallocPCrashReporter(PCrashReporterChild*);
 
     NS_OVERRIDE virtual PHalChild* AllocPHal();
     NS_OVERRIDE virtual bool DeallocPHal(PHalChild*);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -71,28 +71,34 @@
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidBridge.h"
 #endif
 
 #include "nsIClipboard.h"
 #include "nsWidgetsCID.h"
 #include "nsISupportsPrimitives.h"
 #include "mozilla/dom/sms/SmsParent.h"
+#include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
 #include "nsDebugImpl.h"
 
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "mozilla/Preferences.h"
+
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 static const char* sClipboardTextFlavors[] = { kUnicodeMime };
 
 using mozilla::Preferences;
 using namespace mozilla::ipc;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::net;
 using namespace mozilla::places;
 using mozilla::unused; // heh
 using base::KillProcess;
+using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::sms;
 
 namespace mozilla {
 namespace dom {
 
 #define NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC "ipc:network:set-offline"
 
 class MemoryReportRequestParent : public PMemoryReportRequestParent
@@ -765,16 +771,29 @@ ContentParent::AllocPBrowser(const PRUin
 bool
 ContentParent::DeallocPBrowser(PBrowserParent* frame)
 {
   TabParent* parent = static_cast<TabParent*>(frame);
   NS_RELEASE(parent);
   return true;
 }
 
+PDeviceStorageRequestParent*
+ContentParent::AllocPDeviceStorageRequest(const DeviceStorageParams& aParams)
+{
+  return new DeviceStorageRequestParent(aParams);
+}
+
+bool
+ContentParent::DeallocPDeviceStorageRequest(PDeviceStorageRequestParent* doomed)
+{
+  delete doomed;
+  return true;
+}
+
 PCrashReporterParent*
 ContentParent::AllocPCrashReporter(const NativeThreadId& tid,
                                    const PRUint32& processType)
 {
 #ifdef MOZ_CRASHREPORTER
   return new CrashReporterParent();
 #else
   return nsnull;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -105,16 +105,19 @@ private:
     ContentParent(const nsAString& aAppManifestURL);
     virtual ~ContentParent();
 
     void Init();
 
     virtual PBrowserParent* AllocPBrowser(const PRUint32& aChromeFlags, const bool& aIsBrowserFrame);
     virtual bool DeallocPBrowser(PBrowserParent* frame);
 
+    virtual PDeviceStorageRequestParent* AllocPDeviceStorageRequest(const DeviceStorageParams&);
+    virtual bool DeallocPDeviceStorageRequest(PDeviceStorageRequestParent*);
+
     virtual PCrashReporterParent* AllocPCrashReporter(const NativeThreadId& tid,
                                                       const PRUint32& processType);
     virtual bool DeallocPCrashReporter(PCrashReporterParent* crashreporter);
     virtual bool RecvPCrashReporterConstructor(PCrashReporterParent* actor,
                                                const NativeThreadId& tid,
                                                const PRUint32& processType);
 
     NS_OVERRIDE virtual PHalParent* AllocPHal();
--- a/dom/ipc/Makefile.in
+++ b/dom/ipc/Makefile.in
@@ -72,16 +72,17 @@ LOCAL_INCLUDES += \
 	-I$(srcdir)/../../xpcom/base \
 	-I$(topsrcdir)/dom/indexedDB \
 	-I$(topsrcdir)/dom/indexedDB/ipc \
 	-I$(topsrcdir)/extensions/cookie \
 	-I$(topsrcdir)/dom/base \
 	-I$(topsrcdir)/toolkit/xre \
 	-I$(topsrcdir)/hal/sandbox \
 	-I$(topsrcdir)/dom/sms/src/ipc \
+	-I$(topsrcdir)/dom/devicestorage \
 	$(NULL)
 
 DEFINES += -DBIN_SUFFIX='"$(BIN_SUFFIX)"'
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),$(findstring $(MOZ_WIDGET_TOOLKIT),android gtk2 gonk qt))
 DEFINES += -DMOZ_ENABLE_FREETYPE
 endif
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PAudio;
 include protocol PBrowser;
 include protocol PCrashReporter;
 include protocol PExternalHelperApp;
+include protocol PDeviceStorageRequest;
 include protocol PHal;
 include protocol PMemoryReportRequest;
 include protocol PNecko;
 include protocol PSms;
 include protocol PStorage;
 include protocol PTestShell;
 
 include "mozilla/chrome/RegistryMessageUtils.h";
@@ -59,21 +60,51 @@ struct FontListEntry {
     nsString  faceName;
     nsCString filepath;
     PRUint16  weight;
     PRInt16   stretch;
     PRUint8   italic;
     PRUint8   index;
 };
 
+struct DeviceStorageAddParams
+{
+  nsString fullpath;
+  PRUint8[] bits;
+};
+
+struct DeviceStorageGetParams
+{
+  nsString fullpath;
+};
+
+struct DeviceStorageDeleteParams
+{
+  nsString fullpath;
+};
+
+struct DeviceStorageEnumerationParams
+{
+  nsString fullpath;
+  PRUint32 since;
+};
+
+union DeviceStorageParams
+{
+  DeviceStorageAddParams;
+  DeviceStorageGetParams;
+  DeviceStorageDeleteParams;
+  DeviceStorageEnumerationParams;
+};
 rpc protocol PContent
 {
     manages PAudio;
     manages PBrowser;
     manages PCrashReporter;
+    manages PDeviceStorageRequest;
     manages PExternalHelperApp;
     manages PHal;
     manages PMemoryReportRequest;
     manages PNecko;
     manages PSms;
     manages PStorage;
     manages PTestShell;
 
@@ -125,16 +156,18 @@ child:
     SetID(PRUint64 id);
 
     // Notify child that last-pb-context-exited notification was observed
     LastPrivateDocShellDestroyed();
 
 parent:
     PAudio(PRInt32 aNumChannels, PRInt32 aRate, PRInt32 aFormat);
 
+    PDeviceStorageRequest(DeviceStorageParams params);
+
     sync PCrashReporter(NativeThreadId tid, PRUint32 processType);
 
     PHal();
 
     PNecko();
 
     PSms();
     
--- a/dom/tests/mochitest/ajax/offline/Makefile.in
+++ b/dom/tests/mochitest/ajax/offline/Makefile.in
@@ -27,16 +27,19 @@ MOCHITEST_FILES	= \
 	test_offlineIFrame.html \
 	test_bug445544.html \
 	test_bug460353.html \
 	test_bug474696.html \
 	test_bug544462.html \
 	test_bug744719.html \
 	744719.cacheManifest \
 	744719.cacheManifest^headers^ \
+	test_bug765203.html \
+	unknownSection.cacheManifest \
+	unknownSection.cacheManifest^headers^ \
 	test_bug744719-cancel.html \
 	744719-cancel.cacheManifest \
 	744719-cancel.cacheManifest^headers^ \
 	subresource744719.html \
 	test_foreign.html \
 	test_fallback.html \
 	test_overlap.html \
 	test_redirectManifest.html \
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/test_bug765203.html
@@ -0,0 +1,74 @@
+<html xmlns="http://www.w3.org/1999/xhtml" manifest="http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/unknownSection.cacheManifest">
+<head>
+<title>unknown section</title>
+
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="/tests/dom/tests/mochitest/ajax/offline/offlineTests.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+<script type="text/javascript">
+
+var gGotChecking = false;
+var gGotDownloading = false;
+
+function manifestUpdated()
+{
+  OfflineTest.ok(gGotChecking, "Should get a checking event");
+  OfflineTest.ok(gGotDownloading, "Should get a downloading event");
+
+  OfflineTest.is(applicationCache.status, 1, "Cache status should be 1 (CACHED)");
+
+  var entries = [
+    // The manifest itself should be in the cache
+    ["http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/unknownSection.cacheManifest", true],
+
+    // The document that requested the manifest should be in the cache
+    [window.location.href, true],
+
+    // The entries from the manifest should be in the cache
+    ["http://mochi.test:8888/tests/SimpleTest/SimpleTest.js", true],
+    ["http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js", true],
+
+    // The bad entries from the manifest should not be in the cache
+    ["http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/jupiter.jpg", false]
+  ];
+  OfflineTest.checkCacheEntries(
+    entries,
+    function() {
+       OfflineTest.teardown();
+       OfflineTest.finish();
+    });
+}
+
+if (OfflineTest.setup()) {
+  OfflineTest.ok(applicationCache instanceof EventTarget,
+                 "applicationCache should be an event target");
+
+  applicationCache.onerror = OfflineTest.failEvent;
+
+  applicationCache.addEventListener("checking", function() {
+    // We should get the "checking" event during an initial update,
+    // but until we are associated it will not affect applicationCache.status
+    OfflineTest.is(applicationCache.status, applicationCache.UNCACHED,
+                   "CHECKING state during initial update");
+    gGotChecking = true;
+    }, true);
+  applicationCache.ondownloading = function() {
+    // We should get the "downloading" event during an initial update,
+    // but until we are associated it will not affect applicationCache.status
+    OfflineTest.is(applicationCache.status, applicationCache.UNCACHED,
+                   "DOWNLOADING state during initial update")
+    gGotDownloading = true; };
+  applicationCache.oncached = OfflineTest.priv(manifestUpdated);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</head>
+
+<body>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/unknownSection.cacheManifest
@@ -0,0 +1,10 @@
+CACHE MANIFEST
+http://mochi.test:8888/tests/SimpleTest/SimpleTest.js
+
+UNKNOWN-SECTION:
+http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/jupiter.jpg
+here can be anything the current implementaion
+is not able to parse at all and is just silently ignored
+
+CACHE:
+http://mochi.test:8888/tests/dom/tests/mochitest/ajax/offline/offlineTests.js
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/unknownSection.cacheManifest^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/cache-manifest
+
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -20,68 +20,86 @@ interface HTMLVideoElement;
 interface ImageData;
 interface TextMetrics;
 interface Window;
 interface XULElement;
 
 interface CanvasRenderingContext2D {
 
   // back-reference to the canvas
+  [Infallible]
   readonly attribute HTMLCanvasElement canvas;
 
   // state
+  [Infallible]
   void save(); // push state on state stack
+  [Infallible]
   void restore(); // pop state stack and restore state
 
   // transformations (default transform is the identity matrix)
 // NOT IMPLEMENTED           attribute SVGMatrix currentTransform;
   void scale(double x, double y);
   void rotate(double angle);
   void translate(double x, double y);
   void transform(double a, double b, double c, double d, double e, double f);
   void setTransform(double a, double b, double c, double d, double e, double f);
 // NOT IMPLEMENTED  void resetTransform();
 
   // compositing
+           [Infallible]
            attribute double globalAlpha; // (default 1.0)
            attribute DOMString globalCompositeOperation; // (default source-over)
 
   // colors and styles (see also the CanvasDrawingStyles interface)
+           [SetterInfallible]
            attribute any strokeStyle; // (default black)
+           [SetterInfallible]
            attribute any fillStyle; // (default black)
   CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
   CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
   CanvasPattern createPattern((HTMLImageElement or HTMLCanvasElement or HTMLVideoElement) image, DOMString repetition);
 
   // shadows
+           [Infallible]
            attribute double shadowOffsetX; // (default 0)
+           [Infallible]
            attribute double shadowOffsetY; // (default 0)
+           [Infallible]
            attribute double shadowBlur; // (default 0)
+           [Infallible]
            attribute DOMString shadowColor; // (default transparent black)
 
   // rects
+  [Infallible]
   void clearRect(double x, double y, double w, double h);
+  [Infallible]
   void fillRect(double x, double y, double w, double h);
+  [Infallible]
   void strokeRect(double x, double y, double w, double h);
 
   // path API (see also CanvasPathMethods)
+  [Infallible]
   void beginPath();
+  [Infallible]
   void fill();
 // NOT IMPLEMENTED  void fill(Path path);
+  [Infallible]
   void stroke();
 // NOT IMPLEMENTED  void stroke(Path path);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Element element);
 // NOT IMPLEMENTED  void drawSystemFocusRing(Path path, Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Element element);
 // NOT IMPLEMENTED  boolean drawCustomFocusRing(Path path, Element element);
 // NOT IMPLEMENTED  void scrollPathIntoView();
 // NOT IMPLEMENTED  void scrollPathIntoView(Path path);
+  [Infallible]
   void clip();
 // NOT IMPLEMENTED  void clip(Path path);
 // NOT IMPLEMENTED  void resetClip();
+  [Infallible]
   boolean isPointInPath(double x, double y);
 // NOT IMPLEMENTED  boolean isPointInPath(Path path, double x, double y);
 
   // text (see also the CanvasDrawingStyles interface)
   void fillText(DOMString text, double x, double y, optional double maxWidth);
   void strokeText(DOMString text, double x, double y, optional double maxWidth);
   TextMetrics measureText(DOMString text);
 
@@ -100,23 +118,31 @@ interface CanvasRenderingContext2D {
   [Creator] ImageData getImageData(double sx, double sy, double sw, double sh);
   void putImageData(ImageData imagedata, double dx, double dy);
   void putImageData(ImageData imagedata, double dx, double dy, double dirtyX, double dirtyY, double dirtyWidth, double dirtyHeight);
 
   // Mozilla-specific stuff
   // FIXME Bug 768048 mozCurrentTransform/mozCurrentTransformInverse should return a WebIDL array.
   attribute object mozCurrentTransform; // [ m11, m12, m21, m22, dx, dy ], i.e. row major
   attribute object mozCurrentTransformInverse;
+
+  [Infallible]
   attribute DOMString mozFillRule; /* "evenodd", "nonzero" (default) */
+
   attribute any mozDash; /* default |null| */
+
+  [Infallible]
   attribute double mozDashOffset; /* default 0.0 */
+
+  [GetterInfallible]
   attribute DOMString mozTextStyle;
 
   // image smoothing mode -- if disabled, images won't be smoothed
   // if scaled.
+  [Infallible]
   attribute boolean mozImageSmoothingEnabled;
 
   // Show the caret if appropriate when drawing
   const unsigned long DRAWWINDOW_DRAW_CARET   = 0x01;
   // Don't flush pending layout notifications that could otherwise
   // be batched up
   const unsigned long DRAWWINDOW_DO_NOT_FLUSH = 0x02;
   // Draw scrollbars and scroll the viewport if they are present
@@ -173,38 +199,58 @@ interface CanvasRenderingContext2D {
                            optional unsigned long flags = 0);
 };
 CanvasRenderingContext2D implements CanvasDrawingStyles;
 CanvasRenderingContext2D implements CanvasPathMethods;
 
 [NoInterfaceObject]
 interface CanvasDrawingStyles {
   // line caps/joins
+           [Infallible]
            attribute double lineWidth; // (default 1)
+           [Infallible]
            attribute DOMString lineCap; // "butt", "round", "square" (default "butt")
+           [SetterInfallible]
            attribute DOMString lineJoin; // "round", "bevel", "miter" (default "miter")
+           [Infallible]
            attribute double miterLimit; // (default 10)
 
   // dashed lines
 // NOT IMPLEMENTED    void setLineDash(sequence<double> segments); // default empty
 // NOT IMPLEMENTED    sequence<double> getLineDash();
 // NOT IMPLEMENTED             attribute double lineDashOffset;
 
   // text
+           [GetterInfallible]
            attribute DOMString font; // (default 10px sans-serif)
+           [Infallible]
            attribute DOMString textAlign; // "start", "end", "left", "right", "center" (default: "start")
+           [Infallible]
            attribute DOMString textBaseline; // "top", "hanging", "middle", "alphabetic", "ideographic", "bottom" (default: "alphabetic")
 };
 
 [NoInterfaceObject]
 interface CanvasPathMethods {
   // shared path API methods
+  [Infallible]
   void closePath();
+
+  [Infallible]
   void moveTo(double x, double y);
+
+  [Infallible]
   void lineTo(double x, double y);
+
+  [Infallible]
   void quadraticCurveTo(double cpx, double cpy, double x, double y);
+
+  [Infallible]
   void bezierCurveTo(double cp1x, double cp1y, double cp2x, double cp2y, double x, double y);
+
   void arcTo(double x1, double y1, double x2, double y2, double radius); 
 // NOT IMPLEMENTED  void arcTo(double x1, double y1, double x2, double y2, double radiusX, double radiusY, double rotation);
+
+  [Infallible]
   void rect(double x, double y, double w, double h);
+
   void arc(double x, double y, double radius, double startAngle, double endAngle, optional boolean anticlockwise = false); 
 // NOT IMPLEMENTED  void ellipse(double x, double y, double radiusX, double radiusY, double rotation, double startAngle, double endAngle, boolean anticlockwise);
 };
--- a/dom/webidl/WebGLRenderingContext.webidl
+++ b/dom/webidl/WebGLRenderingContext.webidl
@@ -1,9 +1,9 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * https://www.khronos.org/registry/webgl/specs/latest/webgl.idl
  *
  * Copyright © 2012 Khronos Group
@@ -467,254 +467,445 @@ interface WebGLRenderingContext {
     
     /* WebGL-specific enums */
     const unsigned long UNPACK_FLIP_Y_WEBGL            = 0x9240;
     const unsigned long UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241;
     const unsigned long CONTEXT_LOST_WEBGL             = 0x9242;
     const unsigned long UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243;
     const unsigned long BROWSER_DEFAULT_WEBGL          = 0x9244;
 
+    [Infallible]
     readonly attribute HTMLCanvasElement canvas;
+
+    [Infallible]
     readonly attribute long drawingBufferWidth;
+
+    [Infallible]
     readonly attribute long drawingBufferHeight;
 
     [WebGLHandlesContextLoss] WebGLContextAttributes getContextAttributes();
-    [WebGLHandlesContextLoss] boolean isContextLost();
-    
+    [WebGLHandlesContextLoss, Infallible] boolean isContextLost();
+
+    [Infallible]
     sequence<DOMString>? getSupportedExtensions();
+
     // XXXbz In the spec, this is "object?"; I'm making it
     // WebGLExtension? just for ease of implementation.
+    [Infallible]
     WebGLExtension? getExtension(DOMString name);
 
+    [Infallible]
     void activeTexture(unsigned long texture);
+
+    [Infallible]
     void attachShader(WebGLProgram? program, WebGLShader? shader);
+
+    [Infallible]
     void bindAttribLocation(WebGLProgram? program, unsigned long index, DOMString name);
+
+    [Infallible]
     void bindBuffer(unsigned long target, WebGLBuffer? buffer);
+
+    [Infallible]
     void bindFramebuffer(unsigned long target, WebGLFramebuffer? framebuffer);
+
+    [Infallible]
     void bindRenderbuffer(unsigned long target, WebGLRenderbuffer? renderbuffer);
+
+    [Infallible]
     void bindTexture(unsigned long target, WebGLTexture? texture);
+
+    [Infallible]
     void blendColor(float red, float green, float blue, float alpha);
+
+    [Infallible]
     void blendEquation(unsigned long mode);
+
+    [Infallible]
     void blendEquationSeparate(unsigned long modeRGB, unsigned long modeAlpha);
+
+    [Infallible]
     void blendFunc(unsigned long sfactor, unsigned long dfactor);
+
+    [Infallible]
     void blendFuncSeparate(unsigned long srcRGB, unsigned long dstRGB, 
                            unsigned long srcAlpha, unsigned long dstAlpha);
 
+    [Infallible]
     void bufferData(unsigned long target, long long size, unsigned long usage);
+    [Infallible]
     void bufferData(unsigned long target, ArrayBufferView data, unsigned long usage);
+    [Infallible]
     void bufferData(unsigned long target, ArrayBuffer? data, unsigned long usage);
+    [Infallible]
     void bufferSubData(unsigned long target, long long offset, ArrayBufferView data);
+    [Infallible]
     void bufferSubData(unsigned long target, long long offset, ArrayBuffer? data);
 
-    [WebGLHandlesContextLoss] unsigned long checkFramebufferStatus(unsigned long target);
+    [WebGLHandlesContextLoss, Infallible]
+    unsigned long checkFramebufferStatus(unsigned long target);
+
+    [Infallible]
     void clear(unsigned long mask);
+
+    [Infallible]
     void clearColor(float red, float green, float blue, float alpha);
+
+    [Infallible]
     void clearDepth(float depth);
+
+    [Infallible]
     void clearStencil(long s);
+
+    [Infallible]
     void colorMask(boolean red, boolean green, boolean blue, boolean alpha);
+
+    [Infallible]
     void compileShader(WebGLShader? shader);
 
+    [Infallible]
     void compressedTexImage2D(unsigned long target, long level, unsigned long internalformat,
                               long width, long height, long border,
                               ArrayBufferView data);
+    [Infallible]
     void compressedTexSubImage2D(unsigned long target, long level,
                                  long xoffset, long yoffset,
                                  long width, long height, unsigned long format,
                                  ArrayBufferView data);
 
+    [Infallible]
     void copyTexImage2D(unsigned long target, long level, unsigned long internalformat, 
                         long x, long y, long width, long height, 
                         long border);
+    [Infallible]
     void copyTexSubImage2D(unsigned long target, long level, long xoffset, long yoffset, 
                            long x, long y, long width, long height);
 
+    [Infallible]
     WebGLBuffer? createBuffer();
+
+    [Infallible]
     WebGLFramebuffer? createFramebuffer();
+
+    [Infallible]
     WebGLProgram? createProgram();
+
+    [Infallible]
     WebGLRenderbuffer? createRenderbuffer();
+
+    [Infallible]
     WebGLShader? createShader(unsigned long type);
+
+    [Infallible]
     WebGLTexture? createTexture();
 
+    [Infallible]
     void cullFace(unsigned long mode);
 
+    [Infallible]
     void deleteBuffer(WebGLBuffer? buffer);
+    [Infallible]
     void deleteFramebuffer(WebGLFramebuffer? framebuffer);
+    [Infallible]
     void deleteProgram(WebGLProgram? program);
+    [Infallible]
     void deleteRenderbuffer(WebGLRenderbuffer? renderbuffer);
+    [Infallible]
     void deleteShader(WebGLShader? shader);
+    [Infallible]
     void deleteTexture(WebGLTexture? texture);
 
+    [Infallible]
     void depthFunc(unsigned long func);
+    [Infallible]
     void depthMask(boolean flag);
+    [Infallible]
     void depthRange(float zNear, float zFar);
+    [Infallible]
     void detachShader(WebGLProgram? program, WebGLShader? shader);
+    [Infallible]
     void disable(unsigned long cap);
+    [Infallible]
     void disableVertexAttribArray(unsigned long index);
+    [Infallible]
     void drawArrays(unsigned long mode, long first, long count);
+    [Infallible]
     void drawElements(unsigned long mode, long count, unsigned long type, long long offset);
 
+    [Infallible]
     void enable(unsigned long cap);
+    [Infallible]
     void enableVertexAttribArray(unsigned long index);
+    [Infallible]
     void finish();
+    [Infallible]
     void flush();
+
+    [Infallible]
     void framebufferRenderbuffer(unsigned long target, unsigned long attachment, 
                                  unsigned long renderbuffertarget, 
                                  WebGLRenderbuffer? renderbuffer);
+    [Infallible]
     void framebufferTexture2D(unsigned long target, unsigned long attachment, unsigned long textarget, 
                               WebGLTexture? texture, long level);
+    [Infallible]
     void frontFace(unsigned long mode);
 
+    [Infallible]
     void generateMipmap(unsigned long target);
 
+    [Infallible]
     WebGLActiveInfo? getActiveAttrib(WebGLProgram? program, unsigned long index);
+    [Infallible]
     WebGLActiveInfo? getActiveUniform(WebGLProgram? program, unsigned long index);
+    [Infallible]
     sequence<WebGLShader>? getAttachedShaders(WebGLProgram? program);
 
-    [WebGLHandlesContextLoss] long getAttribLocation(WebGLProgram? program, DOMString name);
+    [WebGLHandlesContextLoss, Infallible]
+    long getAttribLocation(WebGLProgram? program, DOMString name);
 
+    [Infallible]
     any getBufferParameter(unsigned long target, unsigned long pname);
     any getParameter(unsigned long pname);
 
-    [WebGLHandlesContextLoss] unsigned long getError();
+    [WebGLHandlesContextLoss, Infallible] unsigned long getError();
 
     any getFramebufferAttachmentParameter(unsigned long target, unsigned long attachment, 
                                           unsigned long pname);
+    [Infallible]
     any getProgramParameter(WebGLProgram? program, unsigned long pname);
+    [Infallible]
     DOMString? getProgramInfoLog(WebGLProgram? program);
+    [Infallible]
     any getRenderbufferParameter(unsigned long target, unsigned long pname);
+    [Infallible]
     any getShaderParameter(WebGLShader? shader, unsigned long pname);
+    [Infallible]
     WebGLShaderPrecisionFormat? getShaderPrecisionFormat(unsigned long shadertype, unsigned long precisiontype);
+    [Infallible]
     DOMString? getShaderInfoLog(WebGLShader? shader);
 
+    [Infallible]
     DOMString? getShaderSource(WebGLShader? shader);
 
+    [Infallible]
     any getTexParameter(unsigned long target, unsigned long pname);
 
     any getUniform(WebGLProgram? program, WebGLUniformLocation? location);
 
-    [Creator]
+    [Creator, Infallible]
     WebGLUniformLocation? getUniformLocation(WebGLProgram? program, DOMString name);
 
     any getVertexAttrib(unsigned long index, unsigned long pname);
 
-    [WebGLHandlesContextLoss] long long getVertexAttribOffset(unsigned long index, unsigned long pname);
+    [WebGLHandlesContextLoss, Infallible]
+    long long getVertexAttribOffset(unsigned long index, unsigned long pname);
 
+    [Infallible]
     void hint(unsigned long target, unsigned long mode);
-    [WebGLHandlesContextLoss] boolean isBuffer(WebGLBuffer? buffer);
-    [WebGLHandlesContextLoss] boolean isEnabled(unsigned long cap);
-    [WebGLHandlesContextLoss] boolean isFramebuffer(WebGLFramebuffer? framebuffer);
-    [WebGLHandlesContextLoss] boolean isProgram(WebGLProgram? program);
-    [WebGLHandlesContextLoss] boolean isRenderbuffer(WebGLRenderbuffer? renderbuffer);
-    [WebGLHandlesContextLoss] boolean isShader(WebGLShader? shader);
-    [WebGLHandlesContextLoss] boolean isTexture(WebGLTexture? texture);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isBuffer(WebGLBuffer? buffer);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isEnabled(unsigned long cap);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isFramebuffer(WebGLFramebuffer? framebuffer);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isProgram(WebGLProgram? program);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isRenderbuffer(WebGLRenderbuffer? renderbuffer);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isShader(WebGLShader? shader);
+
+    [WebGLHandlesContextLoss, Infallible]
+    boolean isTexture(WebGLTexture? texture);
+
+    [Infallible]
     void lineWidth(float width);
+
+    [Infallible]
     void linkProgram(WebGLProgram? program);
+
+    [Infallible]
     void pixelStorei(unsigned long pname, long param);
+
+    [Infallible]
     void polygonOffset(float factor, float units);
 
     void readPixels(long x, long y, long width, long height, 
                     unsigned long format, unsigned long type, ArrayBufferView? pixels);
 
+    [Infallible]
     void renderbufferStorage(unsigned long target, unsigned long internalformat, 
                              long width, long height);
+
+    [Infallible]
     void sampleCoverage(float value, boolean invert);
+
+    [Infallible]
     void scissor(long x, long y, long width, long height);
 
+    [Infallible]
     void shaderSource(WebGLShader? shader, DOMString source);
 
+    [Infallible]
     void stencilFunc(unsigned long func, long ref, unsigned long mask);
+
+    [Infallible]
     void stencilFuncSeparate(unsigned long face, unsigned long func, long ref, unsigned long mask);
+
+    [Infallible]
     void stencilMask(unsigned long mask);
+
+    [Infallible]
     void stencilMaskSeparate(unsigned long face, unsigned long mask);
+
+    [Infallible]
     void stencilOp(unsigned long fail, unsigned long zfail, unsigned long zpass);
+
+    [Infallible]
     void stencilOpSeparate(unsigned long face, unsigned long fail, unsigned long zfail, unsigned long zpass);
 
+
     void texImage2D(unsigned long target, long level, unsigned long internalformat, 
                     long width, long height, long border, unsigned long format, 
                     unsigned long type, ArrayBufferView? pixels);
     void texImage2D(unsigned long target, long level, unsigned long internalformat,
                     unsigned long format, unsigned long type, ImageData? pixels);
     void texImage2D(unsigned long target, long level, unsigned long internalformat,
                     unsigned long format, unsigned long type, HTMLImageElement image); // May throw DOMException
     void texImage2D(unsigned long target, long level, unsigned long internalformat,
                     unsigned long format, unsigned long type, HTMLCanvasElement canvas); // May throw DOMException
     void texImage2D(unsigned long target, long level, unsigned long internalformat,
                     unsigned long format, unsigned long type, HTMLVideoElement video); // May throw DOMException
 
+    [Infallible]
     void texParameterf(unsigned long target, unsigned long pname, float param);
+    [Infallible]
     void texParameteri(unsigned long target, unsigned long pname, long param);
 
     void texSubImage2D(unsigned long target, long level, long xoffset, long yoffset, 
                        long width, long height, 
                        unsigned long format, unsigned long type, ArrayBufferView? pixels);
     void texSubImage2D(unsigned long target, long level, long xoffset, long yoffset, 
                        unsigned long format, unsigned long type, ImageData? pixels);
     void texSubImage2D(unsigned long target, long level, long xoffset, long yoffset, 
                        unsigned long format, unsigned long type, HTMLImageElement image); // May throw DOMException
     void texSubImage2D(unsigned long target, long level, long xoffset, long yoffset, 
                        unsigned long format, unsigned long type, HTMLCanvasElement canvas); // May throw DOMException
     void texSubImage2D(unsigned long target, long level, long xoffset, long yoffset, 
                        unsigned long format, unsigned long type, HTMLVideoElement video); // May throw DOMException
 
+    [Infallible]
     void uniform1f(WebGLUniformLocation? location, float x);
+    [Infallible]
     void uniform1fv(WebGLUniformLocation? location, Float32Array v);
+    [Infallible]
     void uniform1fv(WebGLUniformLocation? location, sequence<float> v);
+    [Infallible]
     void uniform1i(WebGLUniformLocation? location, long x);
+    [Infallible]
     void uniform1iv(WebGLUniformLocation? location, Int32Array v);
+    [Infallible]
     void uniform1iv(WebGLUniformLocation? location, sequence<long> v);
+    [Infallible]
     void uniform2f(WebGLUniformLocation? location, float x, float y);
+    [Infallible]
     void uniform2fv(WebGLUniformLocation? location, Float32Array v);
+    [Infallible]
     void uniform2fv(WebGLUniformLocation? location, sequence<float> v);
+    [Infallible]
     void uniform2i(WebGLUniformLocation? location, long x, long y);
+    [Infallible]
     void uniform2iv(WebGLUniformLocation? location, Int32Array v);
+    [Infallible]
     void uniform2iv(WebGLUniformLocation? location, sequence<long> v);
+    [Infallible]
     void uniform3f(WebGLUniformLocation? location, float x, float y, float z);
+    [Infallible]
     void uniform3fv(WebGLUniformLocation? location, Float32Array v);
+    [Infallible]
     void uniform3fv(WebGLUniformLocation? location, sequence<float> v);
+    [Infallible]
     void uniform3i(WebGLUniformLocation? location, long x, long y, long z);
+    [Infallible]
     void uniform3iv(WebGLUniformLocation? location, Int32Array v);
+    [Infallible]
     void uniform3iv(WebGLUniformLocation? location, sequence<long> v);
+    [Infallible]
     void uniform4f(WebGLUniformLocation? location, float x, float y, float z, float w);
+    [Infallible]
     void uniform4fv(WebGLUniformLocation? location, Float32Array v);
+    [Infallible]
     void uniform4fv(WebGLUniformLocation? location, sequence<float> v);
+    [Infallible]
     void uniform4i(WebGLUniformLocation? location, long x, long y, long z, long w);
+    [Infallible]
     void uniform4iv(WebGLUniformLocation? location, Int32Array v);
+    [Infallible]
     void uniform4iv(WebGLUniformLocation? location, sequence<long> v);
 
+    [Infallible]
     void uniformMatrix2fv(WebGLUniformLocation? location, boolean transpose, 
                           Float32Array value);
+    [Infallible]
     void uniformMatrix2fv(WebGLUniformLocation? location, boolean transpose, 
                           sequence<float> value);
+    [Infallible]
     void uniformMatrix3fv(WebGLUniformLocation? location, boolean transpose, 
                           Float32Array value);
+    [Infallible]
     void uniformMatrix3fv(WebGLUniformLocation? location, boolean transpose, 
                           sequence<float> value);
+    [Infallible]
     void uniformMatrix4fv(WebGLUniformLocation? location, boolean transpose, 
                           Float32Array value);
+    [Infallible]
     void uniformMatrix4fv(WebGLUniformLocation? location, boolean transpose, 
                           sequence<float> value);
 
+    [Infallible]
     void useProgram(WebGLProgram? program);
+    [Infallible]
     void validateProgram(WebGLProgram? program);
 
+    [Infallible]
     void vertexAttrib1f(unsigned long indx, float x);
+    [Infallible]
     void vertexAttrib1fv(unsigned long indx, Float32Array values);
+    [Infallible]
     void vertexAttrib1fv(unsigned long indx, sequence<float> values);
+    [Infallible]
     void vertexAttrib2f(unsigned long indx, float x, float y);
+    [Infallible]
     void vertexAttrib2fv(unsigned long indx, Float32Array values);
+    [Infallible]
     void vertexAttrib2fv(unsigned long indx, sequence<float> values);
+    [Infallible]
     void vertexAttrib3f(unsigned long indx, float x, float y, float z);
+    [Infallible]
     void vertexAttrib3fv(unsigned long indx, Float32Array values);
+    [Infallible]
     void vertexAttrib3fv(unsigned long indx, sequence<float> values);
+    [Infallible]
     void vertexAttrib4f(unsigned long indx, float x, float y, float z, float w);
+    [Infallible]
     void vertexAttrib4fv(unsigned long indx, Float32Array values);
+    [Infallible]
     void vertexAttrib4fv(unsigned long indx, sequence<float> values);
+    [Infallible]
     void vertexAttribPointer(unsigned long indx, long size, unsigned long type, 
                              boolean normalized, long stride, long long offset);
 
+    [Infallible]
     void viewport(long x, long y, long width, long height);
 };
 
 /*[Constructor(DOMString type, optional WebGLContextEventInit eventInit)]
 interface WebGLContextEvent : Event {
     readonly attribute DOMString statusMessage;
     };*/
 
--- a/dom/webidl/XMLHttpRequest.webidl
+++ b/dom/webidl/XMLHttpRequest.webidl
@@ -49,57 +49,90 @@ dictionary MozXMLHttpRequestParameters
    * If true, the same origin policy will not be enforced on the request.
    */
   boolean mozSystem = false;
 };
 
 [Constructor(optional MozXMLHttpRequestParameters? params = null)]
 interface XMLHttpRequest : XMLHttpRequestEventTarget {
   // event handler
-  [TreatNonCallableAsNull] attribute Function? onreadystatechange;
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onreadystatechange;
 
   // states
   const unsigned short UNSENT = 0;
   const unsigned short OPENED = 1;
   const unsigned short HEADERS_RECEIVED = 2;
   const unsigned short LOADING = 3;
   const unsigned short DONE = 4;
+
+  [Infallible]
   readonly attribute unsigned short readyState;
 
   // request
   void open(DOMString method, DOMString url, optional boolean async = true,
             optional DOMString? user, optional DOMString? password);
   void setRequestHeader(DOMString header, DOMString value);
+
+  [GetterInfallible]
   attribute unsigned long timeout;
+
+  [GetterInfallible, SetterInfallible=MainThread]
   attribute boolean withCredentials;
+
+  [Infallible=MainThread]
   readonly attribute XMLHttpRequestUpload upload;
 
   void send();
   void send(ArrayBuffer data);
   void send(Blob data);
   void send(Document data);
   void send(DOMString? data);
   void send(FormData data);
   void send(InputStream data);
 
+  [Infallible=MainThread]
   void abort();
 
   // response
+  [Infallible=MainThread]
   readonly attribute unsigned short status;
+
+  [Infallible]
   readonly attribute DOMString statusText;
   DOMString? getResponseHeader(DOMString header);
+
+  [Infallible=MainThread]
   DOMString getAllResponseHeaders();
+
+  [Infallible=MainThread]
   void overrideMimeType(DOMString mime);
+
+  [GetterInfallible]
   attribute XMLHttpRequestResponseType responseType;
   readonly attribute any response;
   readonly attribute DOMString? responseText;
+
+  [GetterInfallible=Workers]
   readonly attribute Document? responseXML;
 
   // Mozilla-specific stuff
+  [GetterInfallible, SetterInfallible=MainThread]
   attribute boolean multipart;
+
+  [GetterInfallible, SetterInfallible=MainThread]
   attribute boolean mozBackgroundRequest;
-  [ChromeOnly] readonly attribute MozChannel channel;
+
+  [ChromeOnly, GetterInfallible, SetterInfallible=MainThread]
+  readonly attribute MozChannel channel;
+
   void sendAsBinary(DOMString body);
   any getInterface(IID iid);
-  [TreatNonCallableAsNull] attribute Function? onuploadprogress;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onuploadprogress;
+
+  [Infallible]
   readonly attribute boolean mozAnon;
+
+  [Infallible]
   readonly attribute boolean mozSystem;
 };
--- a/dom/webidl/XMLHttpRequestEventTarget.webidl
+++ b/dom/webidl/XMLHttpRequestEventTarget.webidl
@@ -8,16 +8,29 @@
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 [NoInterfaceObject]
 interface XMLHttpRequestEventTarget : EventTarget {
   // event handlers
-  [TreatNonCallableAsNull] attribute Function? onloadstart;
-  [TreatNonCallableAsNull] attribute Function? onprogress;
-  [TreatNonCallableAsNull] attribute Function? onabort;
-  [TreatNonCallableAsNull] attribute Function? onerror;
-  [TreatNonCallableAsNull] attribute Function? onload;
-  [TreatNonCallableAsNull] attribute Function? ontimeout;
-  [TreatNonCallableAsNull] attribute Function? onloadend;
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onloadstart;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onprogress;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onabort;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onerror;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onload;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? ontimeout;
+
+  [TreatNonCallableAsNull, GetterInfallible=MainThread]
+  attribute Function? onloadend;
 };
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -18,46 +18,81 @@
 #include <android/log.h>
 #endif
 
 using base::Thread;
 
 namespace mozilla {
 namespace layers {
 
-CompositorParent::CompositorParent(nsIWidget* aWidget, MessageLoop* aMsgLoop,
-                                   PlatformThreadId aThreadID, bool aRenderToEGLSurface,
+static Thread* sCompositorThread = nsnull;
+
+void CompositorParent::StartUp()
+{
+  CreateThread();
+}
+
+void CompositorParent::ShutDown()
+{
+  DestroyThread();
+}
+
+bool CompositorParent::CreateThread()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
+  if (sCompositorThread) {
+    return true;
+  }
+  sCompositorThread = new Thread("Compositor");
+  if (!sCompositorThread->Start()) {
+    delete sCompositorThread;
+    sCompositorThread = nsnull;
+    return false;
+  }
+  return true;
+}
+
+void CompositorParent::DestroyThread()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
+  if (sCompositorThread) {
+    delete sCompositorThread;
+    sCompositorThread = nsnull;
+  }
+}
+
+MessageLoop* CompositorParent::CompositorLoop()
+{
+  return sCompositorThread ? sCompositorThread->message_loop() : nsnull;
+}
+
+CompositorParent::CompositorParent(nsIWidget* aWidget,
+                                   bool aRenderToEGLSurface,
                                    int aSurfaceWidth, int aSurfaceHeight)
   : mWidget(aWidget)
   , mCurrentCompositeTask(NULL)
   , mPaused(false)
   , mXScale(1.0)
   , mYScale(1.0)
   , mIsFirstPaint(false)
   , mLayersUpdated(false)
-  , mCompositorLoop(aMsgLoop)
-  , mThreadID(aThreadID)
   , mRenderToEGLSurface(aRenderToEGLSurface)
   , mEGLSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
 {
+  NS_ABORT_IF_FALSE(sCompositorThread != nsnull, 
+                    "The compositor thread must be Initialized before instanciating a COmpositorParent.");
   MOZ_COUNT_CTOR(CompositorParent);
 }
 
-MessageLoop*
-CompositorParent::CompositorLoop()
-{
-  return mCompositorLoop;
-}
-
 PlatformThreadId
 CompositorParent::CompositorThreadID()
 {
-  return mThreadID;
+  return sCompositorThread->thread_id();
 }
 
 CompositorParent::~CompositorParent()
 {
   MOZ_COUNT_DTOR(CompositorParent);
 }
 
 void
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -18,16 +18,20 @@
 #include "mozilla/layers/PCompositorParent.h"
 #include "mozilla/layers/PLayersParent.h"
 #include "base/thread.h"
 #include "mozilla/Monitor.h"
 #include "ShadowLayersManager.h"
 
 class nsIWidget;
 
+namespace base {
+class Thread;
+}
+
 namespace mozilla {
 namespace layers {
 
 class LayerManager;
 
 // Represents (affine) transforms that are calculated from a content view.
 struct ViewTransform {
   ViewTransform(nsIntPoint aTranslation = nsIntPoint(0, 0), float aXScale = 1, float aYScale = 1)
@@ -48,18 +52,18 @@ struct ViewTransform {
   float mYScale;
 };
 
 class CompositorParent : public PCompositorParent,
                          public ShadowLayersManager
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorParent)
 public:
-  CompositorParent(nsIWidget* aWidget, MessageLoop* aMsgLoop,
-                   PlatformThreadId aThreadID, bool aRenderToEGLSurface = false,
+  CompositorParent(nsIWidget* aWidget,
+                   bool aRenderToEGLSurface = false,
                    int aSurfaceWidth = -1, int aSurfaceHeight = -1);
 
   virtual ~CompositorParent();
 
   virtual bool RecvWillStop() MOZ_OVERRIDE;
   virtual bool RecvStop() MOZ_OVERRIDE;
   virtual bool RecvPause() MOZ_OVERRIDE;
   virtual bool RecvResume() MOZ_OVERRIDE;
@@ -72,16 +76,33 @@ public:
   void SetTransformation(float aScale, nsIntPoint aScrollOffset);
   void AsyncRender();
 
   // Can be called from any thread
   void ScheduleRenderOnCompositorThread();
   void SchedulePauseOnCompositorThread();
   void ScheduleResumeOnCompositorThread(int width, int height);
 
+  /**
+   * Returns the compositor thread's message loop.
+   *
+   * This message loop is used by CompositorParent and ImageBridgeParent.
+   */
+  static MessageLoop* CompositorLoop();
+
+  /**
+   * Creates the compositor thread and the global compositor map.
+   */
+  static void StartUp();
+
+  /**
+   * Destroys the compositor thread and the global compositor map.
+   */
+  static void ShutDown();
+
 protected:
   virtual PLayersParent* AllocPLayers(const LayersBackend& aBackendType, int* aMaxTextureSize);
   virtual bool DeallocPLayers(PLayersParent* aLayers);
   virtual void ScheduleTask(CancelableTask*, int);
   virtual void Composite();
   virtual void ScheduleComposition();
   virtual void SetFirstPaintViewport(const nsIntPoint& aOffset, float aZoom, const nsIntRect& aPageRect, const gfx::Rect& aCssPageRect);
   virtual void SetPageRect(const gfx::Rect& aCssPageRect);
@@ -91,19 +112,38 @@ protected:
 
 private:
   void PauseComposition();
   void ResumeComposition();
   void ResumeCompositionAndResize(int width, int height);
 
   void TransformShadowTree();
 
-  inline MessageLoop* CompositorLoop();
   inline PlatformThreadId CompositorThreadID();
 
+  /**
+   * Creates the compositor thread.
+   *
+   * All compositors live on the same thread.
+   * The thread is not lazily created on first access to avoid dealing with 
+   * thread safety. Therefore it's best to create and destroy the thread when
+   * we know we areb't using it (So creating/destroying along with gfxPlatform 
+   * looks like a good place).
+   */
+  static bool CreateThread();
+
+  /**
+   * Destroys the compositor thread.
+   *
+   * It is safe to call this fucntion more than once, although the second call
+   * will have no effect.
+   * This function is not thread-safe.
+   */
+  static void DestroyThread();
+
   // Platform specific functions
   /**
    * Does a breadth-first search to find the first layer in the tree with a
    * displayport set.
    */
   Layer* GetPrimaryScrollableLayer();
 
   /**
@@ -138,18 +178,16 @@ private:
   // front-end (e.g. Java on Android) about this so that it take the new page
   // size and zoom into account when providing us with the next view transform.
   bool mIsFirstPaint;
 
   // This flag is set during a layers update, so that the first composition
   // after a layers update has it set. It is cleared after that first composition.
   bool mLayersUpdated;
 
-  MessageLoop* mCompositorLoop;
-  PlatformThreadId mThreadID;
   bool mRenderToEGLSurface;
   nsIntSize mEGLSurfaceSize;
 
   mozilla::Monitor mPauseCompositionMonitor;
   mozilla::Monitor mResumeCompositionMonitor;
 
   DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
 };
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1,20 +1,26 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifdef MOZ_LOGGING
 #define FORCE_PR_LOG /* Allow logging in the release build */
 #endif
+
+#include "mozilla/layers/CompositorParent.h"
+
 #include "prlog.h"
+#include "prenv.h"
 
 #include "gfxPlatform.h"
 
+#include "nsXULAppAPI.h"
+
 #if defined(XP_WIN)
 #include "gfxWindowsPlatform.h"
 #include "gfxD2DSurface.h"
 #elif defined(XP_MACOSX)
 #include "gfxPlatformMac.h"
 #elif defined(MOZ_WIDGET_GTK)
 #include "gfxPlatformGtk.h"
 #elif defined(MOZ_WIDGET_QT)
@@ -55,16 +61,17 @@
 #include "mozilla/FunctionTimer.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 
 #include "nsIGfxInfo.h"
 
 using namespace mozilla;
+using namespace mozilla::layers;
 
 gfxPlatform *gPlatform = nsnull;
 static bool gEverInitialized = false;
 
 // These two may point to the same profile
 static qcms_profile *gCMSOutputProfile = nsnull;
 static qcms_profile *gCMSsRGBProfile = nsnull;
 
@@ -238,16 +245,40 @@ gfxPlatform::Init()
 #ifdef PR_LOGGING
     sFontlistLog = PR_NewLogModule("fontlist");;
     sFontInitLog = PR_NewLogModule("fontinit");;
     sTextrunLog = PR_NewLogModule("textrun");;
     sTextrunuiLog = PR_NewLogModule("textrunui");;
     sCmapDataLog = PR_NewLogModule("cmapdata");;
 #endif
 
+    bool useOffMainThreadCompositing = false;
+#ifdef MOZ_X11
+    // On X11 platforms only use OMTC if firefox was initalized with thread-safe 
+    // X11 (else it would crash).
+    useOffMainThreadCompositing = (PR_GetEnv("MOZ_USE_OMTC") != NULL);
+#else
+    useOffMainThreadCompositing = Preferences::GetBool(
+          "layers.offmainthreadcomposition.enabled", 
+          false);
+    // Until https://bugzilla.mozilla.org/show_bug.cgi?id=745148 lands,
+    // we use either omtc or content processes, but not both.  Prefer
+    // OOP content to omtc.  (Currently, this only affects b2g.)
+    //
+    // See https://bugzilla.mozilla.org/show_bug.cgi?id=761962 .
+    if (!Preferences::GetBool("dom.ipc.tabs.disabled", true)) {
+        // Disable omtc if OOP content isn't force-disabled.
+        useOffMainThreadCompositing = false;
+    }
+#endif
+
+    if (useOffMainThreadCompositing && (XRE_GetProcessType() == 
+                                        GeckoProcessType_Default)) {
+        CompositorParent::StartUp();
+    }
 
     /* Initialize the GfxInfo service.
      * Note: we can't call functions on GfxInfo that depend
      * on gPlatform until after it has been initialized
      * below. GfxInfo initialization annotates our
      * crash reports so we want to do it before
      * we try to load any drivers and do device detection
      * incase that code crashes. See bug #591561. */
@@ -355,16 +386,18 @@ gfxPlatform::Shutdown()
     // most platforms.  Windows is a "special snowflake", though, and has three
     // context providers available, so we have to shut all of them down.
     // We should only support the default GL provider on Windows; then, this
     // could go away. Unfortunately, we currently support WGL (the default) for
     // WebGL on Optimus.
     mozilla::gl::GLContextProviderEGL::Shutdown();
 #endif
 
+    CompositorParent::ShutDown();
+
     delete gPlatform;
     gPlatform = nsnull;
 }
 
 gfxPlatform::~gfxPlatform()
 {
     mScreenReferenceSurface = nsnull;
 
--- a/ipc/ipdl/Makefile.in
+++ b/ipc/ipdl/Makefile.in
@@ -18,16 +18,17 @@ FORCE_STATIC_LIB = 1
 LIBXUL_LIBRARY = 1
 EXPORT_LIBRARY = 1
 
 ##-----------------------------------------------------------------------------
 ## When you add IPDL files to a source directory, list the directory here.
 ##
 IPDLDIRS =  \
   uriloader/exthandler \
+  dom/devicestorage \
   dom/indexedDB/ipc \
   dom/plugins/ipc  \
   dom/ipc  \
   dom/sms/src/ipc \
   dom/src/storage \
   gfx/layers/ipc \
   hal/sandbox \
   ipc/testshell  \
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -6404,16 +6404,24 @@ JS_NewDateObjectMsec(JSContext *cx, doub
 JS_PUBLIC_API(JSBool)
 JS_ObjectIsDate(JSContext *cx, JSObject *obj)
 {
     AssertHeapIsIdle(cx);
     JS_ASSERT(obj);
     return obj->isDate();
 }
 
+JS_PUBLIC_API(void)
+JS_ClearDateCaches(JSContext *cx)
+{
+    AssertHeapIsIdle(cx);
+    CHECK_REQUEST(cx);
+    js_ClearDateCaches();
+}
+
 /************************************************************************/
 
 /*
  * Regular Expressions.
  */
 JS_PUBLIC_API(JSObject *)
 JS_NewRegExpObject(JSContext *cx, JSObject *obj_, char *bytes, size_t length, unsigned flags)
 {
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -915,22 +915,22 @@ class HandleBase<Value> : public ValueOp
 
 /*
  * Augment the generic MutableHandle<T> interface when T = Value with
  * type-querying, value-extracting, and mutating operations.
  */
 template <>
 class MutableHandleBase<Value> : public MutableValueOperations<MutableHandle<Value> >
 {
-    friend class ValueOperations<Handle<Value> >;
+    friend class ValueOperations<MutableHandle<Value> >;
     const Value * extract() const {
         return static_cast<const MutableHandle<Value>*>(this)->address();
     }
 
-    friend class MutableValueOperations<Handle<Value> >;
+    friend class MutableValueOperations<MutableHandle<Value> >;
     Value * extractMutable() {
         return static_cast<MutableHandle<Value>*>(this)->address();
     }
 };
 
 /*
  * Augment the generic Rooted<T> interface when T = Value with type-querying,
  * value-extracting, and mutating operations.
@@ -5723,16 +5723,23 @@ extern JS_PUBLIC_API(JSObject *)
 JS_NewDateObjectMsec(JSContext *cx, double msec);
 
 /*
  * Infallible predicate to test whether obj is a date object.
  */
 extern JS_PUBLIC_API(JSBool)
 JS_ObjectIsDate(JSContext *cx, JSObject *obj);
 
+/*
+ * Clears the cache of calculated local time from each Date object.
+ * Call to propagate a system timezone change.
+ */
+extern JS_PUBLIC_API(void)
+JS_ClearDateCaches(JSContext *cx);
+
 /************************************************************************/
 
 /*
  * Regular Expressions.
  */
 #define JSREG_FOLD      0x01    /* fold uppercase to lowercase */
 #define JSREG_GLOB      0x02    /* global exec, creates array of matches */
 #define JSREG_MULTILINE 0x04    /* treat ^ and $ as begin and end of line */
--- a/js/src/jsdate.cpp
+++ b/js/src/jsdate.cpp
@@ -123,16 +123,24 @@ using namespace js::types;
  *
  * - look at tweaking function return types to return double instead
  * of int; this seems to make things run slightly faster sometimes.
  * (though it could be architecture-dependent.)  It'd be good to see
  * how this does on win32.  (Tried it on irix.)  Types could use a
  * general going-over.
  */
 
+/* Constants defined by ES5 15.9.1.10. */
+const double HoursPerDay = 24.0;
+const double MinutesPerHour = 60;
+const double SecondsPerMinute = 60;
+const double msPerSecond = 1000;
+const double msPerMinute = msPerSecond * SecondsPerMinute;
+const double msPerHour = msPerMinute * MinutesPerHour;
+
 /* ES5 15.9.1.2. */
 const double msPerDay = 86400000;
 
 inline double
 Day(double t)
 {
     return floor(t / msPerDay);
 }
@@ -305,18 +313,24 @@ WeekDay(double t)
      */
     JS_ASSERT(ToInteger(t) == t);
     int result = (int(Day(t)) + 4) % 7;
     if (result < 0)
         result += 7;
     return result;
 }
 
-/* ES5 15.9.1.7. */
-static double LocalTZA; // set by js_InitDateClass
+/* ES5 15.9.1.7. Set by UpdateLocalTZA(). */
+static double LocalTZA;
+
+inline void
+UpdateLocalTZA()
+{
+    LocalTZA = -(PRMJ_LocalGMTDifference() * msPerSecond);
+}
 
 inline int
 DayFromMonth(int month, bool isLeapYear)
 {
     /*
      * The following array contains the day of year for the first day of
      * each month, where index 0 is January, and day 0 is January 1.
      */
@@ -445,23 +459,16 @@ LocalTime(double t, JSContext *cx)
 
 static double
 UTC(double t, JSContext *cx)
 {
     return t - AdjustTime(t - LocalTZA, cx);
 }
 
 /* ES5 15.9.1.10. */
-const double HoursPerDay = 24.0;
-const double MinutesPerHour = 60;
-const double SecondsPerMinute = 60;
-const double msPerSecond = 1000;
-const double msPerMinute = msPerSecond * SecondsPerMinute;
-const double msPerHour = msPerMinute * MinutesPerHour;
-
 static double
 HourFromTime(double t)
 {
     double result = fmod(floor(t/msPerHour), HoursPerDay);
     if (result < 0)
         result += HoursPerDay;
     return result;
 }
@@ -1283,20 +1290,30 @@ SetDateToNaN(JSContext *cx, JSObject *ob
 }
 
 /*
  * Cache the local time, year, month, and so forth of the object.
  * If UTC time is not finite (e.g., NaN), the local time
  * slots will be set to the UTC time without conversion.
  */
 static bool
-FillLocalTimes(JSContext *cx, JSObject *obj)
+CacheLocalTime(JSContext *cx, JSObject *obj)
 {
     JS_ASSERT(obj->isDate());
 
+    /* Check if the cache is already populated. */
+    if (!obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_TIME).isUndefined() &&
+        obj->getSlot(JSObject::JSSLOT_DATE_TZA).toDouble() == LocalTZA)
+    {
+        return true;
+    }
+
+    /* Remember timezone used to generate the local cache. */
+    obj->setSlot(JSObject::JSSLOT_DATE_TZA, DoubleValue(LocalTZA));
+
     double utcTime = obj->getDateUTCTime().toNumber();
 
     if (!MOZ_DOUBLE_IS_FINITE(utcTime)) {
         for (size_t ind = JSObject::JSSLOT_DATE_COMPONENTS_START;
              ind < JSObject::DATE_CLASS_RESERVED_SLOTS;
              ind++) {
             obj->setSlot(ind, DoubleValue(utcTime));
         }
@@ -1395,52 +1412,34 @@ FillLocalTimes(JSContext *cx, JSObject *
         step = next;
         month = 11;
     } while (0);
 
     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_MONTH, Int32Value(month));
     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_DATE, Int32Value(day - step));
 
     int weekday = WeekDay(localTime);
-
     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_DAY, Int32Value(weekday));
 
     int seconds = yearSeconds % 60;
-
     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_SECONDS, Int32Value(seconds));
 
     int minutes = (yearSeconds / 60) % 60;
-
     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_MINUTES, Int32Value(minutes));
 
     int hours = (yearSeconds / (60 * 60)) % 24;
-
     obj->setSlot(JSObject::JSSLOT_DATE_LOCAL_HOURS, Int32Value(hours));
 
     return true;
 }
 
-/* Cache the local times in obj, if necessary. */
-static inline bool
-GetAndCacheLocalTime(JSContext *cx, JSObject *obj)
+inline bool
+GetCachedLocalTime(JSContext *cx, JSObject *obj, double *time)
 {
-    JS_ASSERT(obj->isDate());
-
-    /* If the local time is undefined, we need to fill in the cached values. */
-    if (obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_TIME).isUndefined()) {
-        if (!FillLocalTimes(cx, obj))
-            return false;
-    }
-    return true;
-}
-
-static inline bool
-GetAndCacheLocalTime(JSContext *cx, JSObject *obj, double *time)
-{
-    if (!obj || !GetAndCacheLocalTime(cx, obj))
+    if (!obj || !CacheLocalTime(cx, obj))
         return false;
 
     *time = obj->getSlot(JSObject::JSSLOT_DATE_LOCAL_TIME).toDouble();
     return true;
 }
 
 /*
  * See ECMA 15.9.5.4 thru 15.9.5.23
@@ -1466,17 +1465,17 @@ date_getYear(JSContext *cx, unsigned arg
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getYear, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     Value yearVal = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_YEAR);
     if (yearVal.isInt32()) {
         /* Follow ECMA-262 to the letter, contrary to IE JScript. */
         int year = yearVal.toInt32() - 1900;
         args.rval().setInt32(year);
     } else {
@@ -1492,17 +1491,17 @@ date_getFullYear(JSContext *cx, unsigned
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getFullYear, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_YEAR);
     return true;
 }
 
 static JSBool
 date_getUTCFullYear(JSContext *cx, unsigned argc, Value *vp)
@@ -1529,17 +1528,17 @@ date_getMonth(JSContext *cx, unsigned ar
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getMonth, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_MONTH);
     return true;
 }
 
 static JSBool
 date_getUTCMonth(JSContext *cx, unsigned argc, Value *vp)
@@ -1563,17 +1562,17 @@ date_getDate(JSContext *cx, unsigned arg
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getDate, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_DATE);
     return true;
 }
 
 static JSBool
 date_getUTCDate(JSContext *cx, unsigned argc, Value *vp)
@@ -1600,17 +1599,17 @@ date_getDay(JSContext *cx, unsigned argc
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getDay, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_DAY);
     return true;
 }
 
 static JSBool
 date_getUTCDay(JSContext *cx, unsigned argc, Value *vp)
@@ -1637,17 +1636,17 @@ date_getHours(JSContext *cx, unsigned ar
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getHours, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_HOURS);
     return true;
 }
 
 static JSBool
 date_getUTCHours(JSContext *cx, unsigned argc, Value *vp)
@@ -1674,17 +1673,17 @@ date_getMinutes(JSContext *cx, unsigned 
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getMinutes, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_MINUTES);
     return true;
 }
 
 static JSBool
 date_getUTCMinutes(JSContext *cx, unsigned argc, Value *vp)
@@ -1713,17 +1712,17 @@ date_getUTCSeconds(JSContext *cx, unsign
     CallArgs args = CallArgsFromVp(argc, vp);
 
     JSObject *thisObj;
     if (!NonGenericMethodGuard(cx, args, date_getUTCSeconds, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
-    if (!GetAndCacheLocalTime(cx, thisObj))
+    if (!CacheLocalTime(cx, thisObj))
         return false;
 
     args.rval() = thisObj->getSlot(JSObject::JSSLOT_DATE_LOCAL_SECONDS);
     return true;
 }
 
 /* Date.getMilliseconds is mapped to getUTCMilliseconds */
 
@@ -1755,17 +1754,17 @@ date_getTimezoneOffset(JSContext *cx, un
     if (!NonGenericMethodGuard(cx, args, date_getTimezoneOffset, &DateClass, &thisObj))
         return false;
     if (!thisObj)
         return true;
 
     double utctime = thisObj->getDateUTCTime().toNumber();
 
     double localtime;
-    if (!GetAndCacheLocalTime(cx, thisObj, &localtime))
+    if (!GetCachedLocalTime(cx, thisObj, &localtime))
         return false;
 
     /*
      * Return the time zone offset in minutes for the current locale that is
      * appropriate for this time. This value would be a constant except for
      * daylight savings time.
      */
     double result = (utctime - localtime) / msPerMinute;
@@ -3064,18 +3063,17 @@ js_Date(JSContext *cx, unsigned argc, Va
     return true;
 }
 
 JSObject *
 js_InitDateClass(JSContext *cx, JSObject *obj)
 {
     JS_ASSERT(obj->isNative());
 
-    /* Set the static LocalTZA. */
-    LocalTZA = -(PRMJ_LocalGMTDifference() * msPerSecond);
+    UpdateLocalTZA();
 
     Rooted<GlobalObject*> global(cx, &obj->asGlobal());
 
     RootedObject dateProto(cx, global->createBlankPrototype(cx, &DateClass));
     if (!dateProto)
         return NULL;
     SetDateToNaN(cx, dateProto);
 
@@ -3131,73 +3129,79 @@ js_NewDateObject(JSContext* cx, int year
     double msec_time;
 
     JS_ASSERT(mon < 12);
     msec_time = date_msecFromDate(year, mon, mday, hour, min, sec, 0);
     obj = js_NewDateObjectMsec(cx, UTC(msec_time, cx));
     return obj;
 }
 
+void
+js_ClearDateCaches()
+{
+    UpdateLocalTZA();
+}
+
 JS_FRIEND_API(JSBool)
 js_DateIsValid(JSContext *cx, JSObject* obj)
 {
     return obj->isDate() && !MOZ_DOUBLE_IS_NaN(obj->getDateUTCTime().toNumber());
 }
 
 JS_FRIEND_API(int)
 js_DateGetYear(JSContext *cx, JSObject* obj)
 {
     double localtime;
 
     /* Preserve legacy API behavior of returning 0 for invalid dates. */
-    if (!GetAndCacheLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
+    if (!GetCachedLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
         return 0;
 
     return (int) YearFromTime(localtime);
 }
 
 JS_FRIEND_API(int)
 js_DateGetMonth(JSContext *cx, JSObject* obj)
 {
     double localtime;
 
-    if (!GetAndCacheLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
+    if (!GetCachedLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
         return 0;
 
     return (int) MonthFromTime(localtime);
 }
 
 JS_FRIEND_API(int)
 js_DateGetDate(JSContext *cx, JSObject* obj)
 {
     double localtime;
 
-    if (!GetAndCacheLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
+    if (!GetCachedLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
         return 0;
 
     return (int) DateFromTime(localtime);
 }
 
 JS_FRIEND_API(int)
 js_DateGetHours(JSContext *cx, JSObject* obj)
 {
     double localtime;
 
-    if (!GetAndCacheLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
+    if (!GetCachedLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
         return 0;
 
     return (int) HourFromTime(localtime);
 }
 
 JS_FRIEND_API(int)
 js_DateGetMinutes(JSContext *cx, JSObject* obj)
 {
     double localtime;
 
-    if (!GetAndCacheLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
+    if (!GetCachedLocalTime(cx, obj, &localtime) || MOZ_DOUBLE_IS_NaN(localtime))
         return 0;
 
     return (int) MinFromTime(localtime);
 }
 
 JS_FRIEND_API(int)
 js_DateGetSeconds(JSContext *cx, JSObject* obj)
 {
--- a/js/src/jsdate.h
+++ b/js/src/jsdate.h
@@ -60,16 +60,19 @@ js_NewDateObjectMsec(JSContext* cx, doub
  * Assert that mon < 12 to help catch off-by-one user errors, which are common
  * due to the 0-based month numbering copied into JS from Java (java.util.Date
  * in 1995).
  */
 extern JS_FRIEND_API(JSObject*)
 js_NewDateObject(JSContext* cx, int year, int mon, int mday,
                  int hour, int min, int sec);
 
+extern void
+js_ClearDateCaches();
+
 extern JS_FRIEND_API(int)
 js_DateGetYear(JSContext *cx, JSObject* obj);
 
 extern JS_FRIEND_API(int)
 js_DateGetMonth(JSContext *cx, JSObject* obj);
 
 extern JS_FRIEND_API(int)
 js_DateGetDate(JSContext *cx, JSObject* obj);
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -611,34 +611,35 @@ struct JSObject : public js::ObjectImpl
     bool arrayGetOwnDataElement(JSContext *cx, size_t i, js::Value *vp);
 
   public:
     /*
      * Date-specific getters and setters.
      */
 
     static const uint32_t JSSLOT_DATE_UTC_TIME = 0;
+    static const uint32_t JSSLOT_DATE_TZA = 1;
 
     /*
      * Cached slots holding local properties of the date.
      * These are undefined until the first actual lookup occurs
      * and are reset to undefined whenever the date's time is modified.
      */
-    static const uint32_t JSSLOT_DATE_COMPONENTS_START = 1;
+    static const uint32_t JSSLOT_DATE_COMPONENTS_START = 2;
 
-    static const uint32_t JSSLOT_DATE_LOCAL_TIME = 1;
-    static const uint32_t JSSLOT_DATE_LOCAL_YEAR = 2;
-    static const uint32_t JSSLOT_DATE_LOCAL_MONTH = 3;
-    static const uint32_t JSSLOT_DATE_LOCAL_DATE = 4;
-    static const uint32_t JSSLOT_DATE_LOCAL_DAY = 5;
-    static const uint32_t JSSLOT_DATE_LOCAL_HOURS = 6;
-    static const uint32_t JSSLOT_DATE_LOCAL_MINUTES = 7;
-    static const uint32_t JSSLOT_DATE_LOCAL_SECONDS = 8;
+    static const uint32_t JSSLOT_DATE_LOCAL_TIME    = JSSLOT_DATE_COMPONENTS_START + 0;
+    static const uint32_t JSSLOT_DATE_LOCAL_YEAR    = JSSLOT_DATE_COMPONENTS_START + 1;
+    static const uint32_t JSSLOT_DATE_LOCAL_MONTH   = JSSLOT_DATE_COMPONENTS_START + 2;
+    static const uint32_t JSSLOT_DATE_LOCAL_DATE    = JSSLOT_DATE_COMPONENTS_START + 3;
+    static const uint32_t JSSLOT_DATE_LOCAL_DAY     = JSSLOT_DATE_COMPONENTS_START + 4;
+    static const uint32_t JSSLOT_DATE_LOCAL_HOURS   = JSSLOT_DATE_COMPONENTS_START + 5;
+    static const uint32_t JSSLOT_DATE_LOCAL_MINUTES = JSSLOT_DATE_COMPONENTS_START + 6;
+    static const uint32_t JSSLOT_DATE_LOCAL_SECONDS = JSSLOT_DATE_COMPONENTS_START + 7;
 
-    static const uint32_t DATE_CLASS_RESERVED_SLOTS = 9;
+    static const uint32_t DATE_CLASS_RESERVED_SLOTS = JSSLOT_DATE_LOCAL_SECONDS + 1;
 
     inline const js::Value &getDateUTCTime() const;
     inline void setDateUTCTime(const js::Value &pthis);
 
     /*
      * Function-specific getters and setters.
      */
 
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3054,19 +3054,86 @@ class Identity MOZ_FINAL : public nsISup
 {
     NS_DECL_ISUPPORTS
 };
 
 NS_IMPL_ISUPPORTS0(Identity)
 
 xpc::SandboxProxyHandler xpc::sandboxProxyHandler;
 
+// A proxy handler that lets us wrap callables and invoke them with
+// the correct this object, while forwarding all other operations down
+// to them directly.
+class SandboxCallableProxyHandler : public js::DirectWrapper {
+public:
+    SandboxCallableProxyHandler() : js::DirectWrapper(0)
+    {
+    }
+
+    virtual bool call(JSContext *cx, JSObject *proxy, unsigned argc,
+                      Value *vp);
+};
+
+bool
+SandboxCallableProxyHandler::call(JSContext *cx, JSObject *proxy, unsigned argc,
+                                  Value *vp)
+{
+    // We forward the call to our underlying callable. The callable to forward
+    // to can be gotten via GetProxyCall.
+
+    // The parent of our proxy is the SandboxProxyHandler proxy
+    JSObject *sandboxProxy = JS_GetParent(proxy);
+    MOZ_ASSERT(js::IsProxy(sandboxProxy) &&
+               js::GetProxyHandler(sandboxProxy) == &xpc::sandboxProxyHandler);
+
+    // The parent of the sandboxProxy is the sandbox global, and the
+    // target object is the original proto.
+    JSObject *sandboxGlobal = JS_GetParent(sandboxProxy);
+    MOZ_ASSERT(js::GetObjectJSClass(sandboxGlobal) == &SandboxClass);
+
+    // If our this object is the sandbox global, we call with this set to the
+    // original proto instead.  Note that we very carefully avoid using JS_THIS
+    // or JS_THIS_OBJECT here, because we do NOT want to box undefined into the
+    // global.  Instead, we just pass it through to our callable, and it will
+    // compute the global based on its own scope chain, which will do the right
+    // thing.
+    JS::Value thisVal = JS_THIS_VALUE(cx, vp);
+    if (thisVal == ObjectValue(*sandboxGlobal)) {
+        thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
+    }
+
+    return JS::Call(cx, thisVal, js::GetProxyCall(proxy), argc,
+                    JS_ARGV(cx, vp), vp);
+}
+
+static SandboxCallableProxyHandler sandboxCallableProxyHandler;
+
+// Wrap a callable such that if we're called with oldThisObj as the
+// "this" we will instead call it with newThisObj as the this.
+static JSObject*
+WrapCallable(JSContext *cx, JSObject *callable, JSObject *sandboxProtoProxy)
+{
+    MOZ_ASSERT(JS_ObjectIsCallable(cx, callable));
+    // Our proxy is wrapping the callable.  So we need to use the
+    // callable as the private.  We use the given sandboxProtoProxy as
+    // the parent, and our call() hook depends on that.
+    MOZ_ASSERT(js::IsProxy(sandboxProtoProxy) &&
+               js::GetProxyHandler(sandboxProtoProxy) ==
+                 &xpc::sandboxProxyHandler);
+
+    // We need to pass the given callable in as the "call" and
+    // "construct" so we get a function proxy.
+    return js::NewProxyObject(cx, &sandboxCallableProxyHandler,
+                              ObjectValue(*callable), nsnull,
+                              sandboxProtoProxy, callable, callable);
+}
+
 template<typename Op>
-bool BindPropertyOp(JSContext *cx, JSObject *targetObj, Op& op,
-                    PropertyDescriptor *desc, jsid id, unsigned attrFlag)
+bool BindPropertyOp(JSContext *cx, Op& op, PropertyDescriptor *desc, jsid id,
+                    unsigned attrFlag, JSObject *sandboxProtoProxy)
 {
     if (!op) {
         return true;
     }
 
     JSObject *func;
     if (desc->attrs & attrFlag) {
         // Already an object
@@ -3074,17 +3141,17 @@ bool BindPropertyOp(JSContext *cx, JSObj
     } else {
         // We have an actual property op.  For getters, we use 0
         // args, for setters we use 1 arg.
         uint32_t args = (attrFlag == JSPROP_GETTER) ? 0 : 1;
         func = GeneratePropertyOp(cx, desc->obj, id, args, op);
         if (!func)
             return false;
     }
-    func = JS_BindCallable(cx, func, targetObj);
+    func = WrapCallable(cx, func, sandboxProtoProxy);
     if (!func)
         return false;
     op = JS_DATA_TO_FUNC_PTR(Op, func);
     desc->attrs |= attrFlag;
     return true;
 }
 
 extern JSBool
@@ -3118,26 +3185,26 @@ xpc::SandboxProxyHandler::getPropertyDes
     // to make that happen.  Since we really only need to rebind the DOM methods
     // here, not rebindings holder_get and holder_set is OK.
     //
     // Similarly, don't mess with XPC_WN_Helper_GetProperty and
     // XPC_WN_Helper_SetProperty, for the same reasons: that could confuse our
     // access to expandos when we're not doing Xrays.
     if (desc->getter != xpc::holder_get &&
         desc->getter != XPC_WN_Helper_GetProperty &&
-        !BindPropertyOp(cx, obj, desc->getter, desc, id, JSPROP_GETTER))
+        !BindPropertyOp(cx, desc->getter, desc, id, JSPROP_GETTER, proxy))
         return false;
     if (desc->setter != xpc::holder_set &&
         desc->setter != XPC_WN_Helper_SetProperty &&
-        !BindPropertyOp(cx, obj, desc->setter, desc, id, JSPROP_SETTER))
+        !BindPropertyOp(cx, desc->setter, desc, id, JSPROP_SETTER, proxy))
         return false;
     if (desc->value.isObject()) {
         JSObject* val = &desc->value.toObject();
         if (JS_ObjectIsCallable(cx, val)) {
-            val = JS_BindCallable(cx, val, obj);
+            val = WrapCallable(cx, val, proxy);
             if (!val)
                 return false;
             desc->value = ObjectValue(*val);
         }
     }
 
     return true;
 }
--- a/js/xpconnect/tests/chrome/Makefile.in
+++ b/js/xpconnect/tests/chrome/Makefile.in
@@ -47,16 +47,17 @@ MOCHITEST_CHROME_FILES = \
 		file_expandosharing.jsm \
 		test_getweakmapkeys.xul \
 		test_nodelists.xul \
 		test_precisegc.xul \
 		test_sandboxImport.xul \
 		test_weakmaps.xul \
 		test_weakref.xul \
 		test_wrappers.xul \
+		test_bug771429.xul \
 		$(NULL)
 
 # Disabled until this test gets updated to test the new proxy based
 # wrappers.
 #		test_wrappers-2.xul \
 
 # Disabled due to apparent conservative stack scanner false positives on Linux64 debug.
 #		test_watchpoints.xul \
--- a/js/xpconnect/tests/chrome/test_bug726949.xul
+++ b/js/xpconnect/tests/chrome/test_bug726949.xul
@@ -26,14 +26,16 @@ https://bugzilla.mozilla.org/show_bug.cg
           t = Cu.evalInSandbox('top', s);
           is(t, window.top, "Should have gotten the right thing back");
           desc = Cu.evalInSandbox('Object.getOwnPropertyDescriptor(Object.getPrototypeOf(this), "Cu")', s);
           isnot(desc, undefined,
                 "Should have an own 'Cu' property");
           is(desc.value, Cu, "Should have the right value");
           var loc = Cu.evalInSandbox('location', s);
           is(loc.href, location.href, "Should have the right location");
+          var display = Cu.evalInSandbox('getComputedStyle(document.documentElement, "").display', s);
+          is(display, "block", "Should be able to call getComputedStyle");
       } catch (e) {
           ok(false, "Should not get an exception: " + e);
       }
   ]]>
   </script>
 </window>
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/chrome/test_bug771429.xul
@@ -0,0 +1,48 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=771429
+-->
+<window title="Mozilla Bug 771429"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=771429"
+     target="_blank">Mozilla Bug 771429</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+      /** Test for Bug 771429 **/
+      function f() {}
+      function g() { return this; }
+      function h() { "use strict"; return this; }
+      f.x = 2;
+      f.g = g;
+      var Cu = Components.utils;
+      var s = new Cu.Sandbox(window, { sandboxPrototype: window } );
+      try {
+        is(Cu.evalInSandbox('g()', s), window,
+           "Should get window as this object of non-strict global function");
+        is(Cu.evalInSandbox('h()', s), undefined,
+           "Should get undefined as this object of strict global function");
+        is(Cu.evalInSandbox('f.x', s), 2,
+           "Should have gotten the right thing back");
+        is(Cu.evalInSandbox('f.g()', s), f,
+           "Should have the right function object");
+        is(Cu.evalInSandbox('var x = { z: 7 }; g.call(x).z', s), 7,
+           "Should not rebind calls that are already bound");
+        // And test what happens when we use the normal Function.prototype.call
+        // on g instead of whatever our proxies happen to return.
+        is(Cu.evalInSandbox('var x = { z: 7 }; Function.prototype.call.call(g, x).z', s), 7,
+           "Should not rebind calls that are already bound");
+      } catch (e) {
+        ok(false, "Should not get an exception: " + e);
+      }
+  ]]>
+  </script>
+</window>
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -67,21 +67,16 @@ static inline MaskLayerImageCache* GetMa
 {
   if (!gMaskLayerImageCache) {
     gMaskLayerImageCache = new MaskLayerImageCache();
   }
 
   return gMaskLayerImageCache;
 }
 
-class RefCountedRegion : public RefCounted<RefCountedRegion> {
-public:
-  nsRegion mRegion;
-};
-
 static void DestroyRefCountedRegion(void* aPropertyValue)
 {
   static_cast<RefCountedRegion*>(aPropertyValue)->Release();
 }
 
 /**
  * This property represents a region that should be invalidated in every
  * ThebesLayer child whose parent ContainerLayer is associated with the
@@ -716,63 +711,59 @@ FrameLayerBuilder::WillEndTransaction(La
                "Some frame must have a layer!");
 }
 
 /**
  * If *aThebesLayerInvalidRegion is non-null, use it as this frame's
  * region property. Otherwise set it to the frame's region property.
  */
 static void
-SetHasContainerLayer(nsIFrame* aFrame, nsPoint aOffsetToRoot,
-                     RefCountedRegion** aThebesLayerInvalidRegion)
+SetHasContainerLayer(nsIFrame* aFrame, nsPoint aOffsetToRoot)
 {
   aFrame->AddStateBits(NS_FRAME_HAS_CONTAINER_LAYER);
   for (nsIFrame* f = aFrame;
        f && !(f->GetStateBits() & NS_FRAME_HAS_CONTAINER_LAYER_DESCENDANT);
        f = nsLayoutUtils::GetCrossDocParentFrame(f)) {
     f->AddStateBits(NS_FRAME_HAS_CONTAINER_LAYER_DESCENDANT);
   }
 
   FrameProperties props = aFrame->Properties();
   nsPoint* lastPaintOffset = static_cast<nsPoint*>
     (props.Get(ThebesLayerLastPaintOffsetProperty()));
   if (lastPaintOffset) {
     *lastPaintOffset = aOffsetToRoot;
   } else {
     props.Set(ThebesLayerLastPaintOffsetProperty(), new nsPoint(aOffsetToRoot));
   }
-
-  // Reset or create the invalid region now so we can start collecting
-  // new dirty areas.
-  if (*aThebesLayerInvalidRegion) {
-    (*aThebesLayerInvalidRegion)->AddRef();
-    props.Set(ThebesLayerInvalidRegionProperty(), *aThebesLayerInvalidRegion);
-  } else {
-    RefCountedRegion* invalidRegion = static_cast<RefCountedRegion*>
-      (props.Get(ThebesLayerInvalidRegionProperty()));
-    if (invalidRegion) {
-      invalidRegion->mRegion.SetEmpty();
-    } else {
-      invalidRegion = new RefCountedRegion();
-      invalidRegion->AddRef();
-      props.Set(ThebesLayerInvalidRegionProperty(), invalidRegion);
-    }
-    *aThebesLayerInvalidRegion = invalidRegion;
-  }
 }
 
 static void
 SetNoContainerLayer(nsIFrame* aFrame)
 {
   FrameProperties props = aFrame->Properties();
   props.Delete(ThebesLayerInvalidRegionProperty());
   props.Delete(ThebesLayerLastPaintOffsetProperty());
   aFrame->RemoveStateBits(NS_FRAME_HAS_CONTAINER_LAYER);
 }
 
+/* static */ void
+FrameLayerBuilder::SetAndClearInvalidRegion(DisplayItemDataEntry* aEntry)
+{
+  if (aEntry->mInvalidRegion) {
+    nsIFrame* f = aEntry->GetKey();
+    FrameProperties props = f->Properties();
+
+    RefCountedRegion* invalidRegion;
+    aEntry->mInvalidRegion.forget(&invalidRegion);
+
+    invalidRegion->mRegion.SetEmpty();
+    props.Set(ThebesLayerInvalidRegionProperty(), invalidRegion);
+  }
+}
+
 /* static */ PLDHashOperator
 FrameLayerBuilder::UpdateDisplayItemDataForFrame(DisplayItemDataEntry* aEntry,
                                                  void* aUserArg)
 {
   FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>(aUserArg);
   nsIFrame* f = aEntry->GetKey();
   FrameProperties props = f->Properties();
   DisplayItemDataEntry* newDisplayItems =
@@ -785,30 +776,39 @@ FrameLayerBuilder::UpdateDisplayItemData
     SetNoContainerLayer(f);
     return PL_DHASH_REMOVE;
   }
 
   if (!newDisplayItems->HasNonEmptyContainerLayer()) {
     SetNoContainerLayer(f);
   }
 
-  // Steal the list of display item layers
+  // Steal the list of display item layers and invalid region
   aEntry->mData.SwapElements(newDisplayItems->mData);
+  aEntry->mInvalidRegion.swap(newDisplayItems->mInvalidRegion);
+  // Clear and reset the invalid region now so we can start collecting new
+  // dirty areas.
+  SetAndClearInvalidRegion(aEntry);
   // Don't need to process this frame again
   builder->mNewDisplayItemData.RawRemoveEntry(newDisplayItems);
   return PL_DHASH_NEXT;
 }
 
 /* static */ PLDHashOperator
 FrameLayerBuilder::StoreNewDisplayItemData(DisplayItemDataEntry* aEntry,
                                            void* aUserArg)
 {
   LayerManagerData* data = static_cast<LayerManagerData*>(aUserArg);
   nsIFrame* f = aEntry->GetKey();
   FrameProperties props = f->Properties();
+
+  // Clear and reset the invalid region now so we can start collecting new
+  // dirty areas.
+  SetAndClearInvalidRegion(aEntry);
+
   // Remember that this frame has display items in retained layers
   NS_ASSERTION(!data->mFramesWithLayers.GetEntry(f),
                "We shouldn't get here if we're already in mFramesWithLayers");
   DisplayItemDataEntry *newEntry = data->mFramesWithLayers.PutEntry(f);
   NS_ASSERTION(!props.Get(LayerManagerDataProperty()),
                "mFramesWithLayers out of sync");
 
   newEntry->mData.SwapElements(aEntry->mData);
@@ -2132,42 +2132,54 @@ FrameLayerBuilder::BuildContainerLayerFo
 
   ContainerParameters scaleParameters =
     ChooseScaleAndSetTransform(this, aContainerFrame, aTransform, aParameters,
                                containerLayer);
   ContainerState state(aBuilder, aManager, aContainerFrame, containerLayer,
                        scaleParameters);
 
   if (aManager == mRetainingManager) {
+    FrameProperties props = aContainerFrame->Properties();
+    RefCountedRegion* thebesLayerInvalidRegion = nsnull;
     DisplayItemDataEntry* entry = mNewDisplayItemData.PutEntry(aContainerFrame);
     if (entry) {
       entry->mData.AppendElement(
           DisplayItemData(containerLayer, containerDisplayItemKey, LAYER_ACTIVE));
+      thebesLayerInvalidRegion = static_cast<RefCountedRegion*>
+        (props.Get(ThebesLayerInvalidRegionProperty()));
+      if (!thebesLayerInvalidRegion) {
+        thebesLayerInvalidRegion = new RefCountedRegion();
+      }
+      entry->mInvalidRegion = thebesLayerInvalidRegion;
     }
     nsPoint currentOffset;
     ApplyThebesLayerInvalidation(aBuilder, aContainerFrame, aContainerItem, state,
                                  &currentOffset);
-    RefCountedRegion* thebesLayerInvalidRegion = nsnull;
-    SetHasContainerLayer(aContainerFrame, currentOffset, &thebesLayerInvalidRegion);
+    SetHasContainerLayer(aContainerFrame, currentOffset);
 
     nsAutoTArray<nsIFrame*,4> mergedFrames;
     if (aContainerItem) {
       aContainerItem->GetMergedFrames(&mergedFrames);
     }
     for (PRUint32 i = 0; i < mergedFrames.Length(); ++i) {
       nsIFrame* mergedFrame = mergedFrames[i];
       DisplayItemDataEntry* entry = mNewDisplayItemData.PutEntry(mergedFrame);
       if (entry) {
         // Ensure that UpdateDisplayItemDataForFrame recognizes that we
         // still have a container layer associated with this frame.
         entry->mIsSharingContainerLayer = true;
+
+        // Store the invalid region property in case this frame is represented
+        // by multiple container layers. This is cleared and set when iterating
+        // over the DisplayItemDataEntry's in WillEndTransaction.
+        entry->mInvalidRegion = thebesLayerInvalidRegion;
       }
       ApplyThebesLayerInvalidation(aBuilder, mergedFrame, nsnull, state,
                                    &currentOffset);
-      SetHasContainerLayer(mergedFrame, currentOffset, &thebesLayerInvalidRegion);
+      SetHasContainerLayer(mergedFrame, currentOffset);
     }
   }
 
   Clip clip;
   state.ProcessDisplayItems(aChildren, clip);
 
   // Set CONTENT_COMPONENT_ALPHA if any of our children have it.
   // This is suboptimal ... a child could have text that's over transparent
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -27,16 +27,21 @@ enum LayerState {
   LAYER_ACTIVE,
   // Force an active layer even if it causes incorrect rendering, e.g.
   // when the layer has rounded rect clips.