Merge inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 06 Sep 2013 16:41:07 -0400
changeset 158875 bdbdeda69f0534a1a255a510eb1dcd939de55130
parent 158840 d65841c24ab2b1a6a89402a395d37d076a63f765 (current diff)
parent 158874 55d63d4b07e294eea26b8aae6ab3e77ad144896f (diff)
child 158876 62ac395d5f0ba328e2f8e91c3d2747881553149f
child 158900 312d21ee124afad4f57c32e0e35eccb74afac9a9
child 158907 1b8afdbb8cf112e90eaab17533e85e16de2275cb
child 158940 8cad45f03f6aefd3cf27fe2c955fd6826bd867f8
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c.
gfx/layers/ipc/LayerTransaction.ipdlh
js/src/builtin/BinaryData.cpp
js/src/builtin/BinaryData.h
js/src/jit-test/tests/binary-data/fuzz1.js
js/src/jit-test/tests/binary-data/fuzz10.js
js/src/jit-test/tests/binary-data/fuzz11.js
js/src/jit-test/tests/binary-data/fuzz2.js
js/src/jit-test/tests/binary-data/fuzz3.js
js/src/jit-test/tests/binary-data/fuzz4.js
js/src/jit-test/tests/binary-data/fuzz5.js
js/src/jit-test/tests/binary-data/fuzz6.js
js/src/jit-test/tests/binary-data/fuzz7.js
js/src/jit-test/tests/binary-data/fuzz8.js
js/src/jit-test/tests/binary-data/fuzz9.js
js/src/jit-test/tests/binary-data/jit-prefix.js
js/src/jit-test/tests/binary-data/jit-read-float64.js
js/src/jit-test/tests/binary-data/jit-read-int.js
js/src/tests/ecma_6/BinaryData/architecture.js
js/src/tests/ecma_6/BinaryData/arrayofstructs.js
js/src/tests/ecma_6/BinaryData/arraytype.js
js/src/tests/ecma_6/BinaryData/memory.js
js/src/tests/ecma_6/BinaryData/numerictypes.js
js/src/tests/ecma_6/BinaryData/shell.js
js/src/tests/ecma_6/BinaryData/size_and_alignment.js
js/src/tests/ecma_6/BinaryData/structequiv.js
js/src/tests/ecma_6/BinaryData/structtypeenumerate.js
js/src/tests/ecma_6/BinaryData/structtypeindexedfields.js
js/src/tests/ecma_6/BinaryData/structtypeprototype.js
js/src/tests/ecma_6/BinaryData/structtypereflection.js
js/src/tests/ecma_6/BinaryData/structtypestructuralassign.js
--- a/b2g/chrome/content/payment.js
+++ b/b2g/chrome/content/payment.js
@@ -4,24 +4,41 @@
  * 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/. */
 
 // This JS shim contains the callbacks to fire DOMRequest events for
 // navigator.pay API within the payment processor's scope.
 
 "use strict";
 
-let _DEBUG = false;
-function _debug(s) { dump("== Payment flow == " + s + "\n"); }
-_debug("Frame script injected");
-
 let { classes: Cc, interfaces: Ci, utils: Cu }  = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+const PREF_DEBUG = "dom.payment.debug";
+
+let _debug;
+try {
+  _debug = Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
+           && Services.prefs.getBoolPref(PREF_DEBUG);
+} catch(e){
+  _debug = false;
+}
+
+function LOG(s) {
+  if (!_debug) {
+    return;
+  }
+  dump("== Payment flow == " + s + "\n");
+}
+
+if (_debug) {
+  LOG("Frame script injected");
+}
+
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
@@ -141,32 +158,32 @@ let PaymentProvider = {
     gBrowser.shell.sendChromeEvent(detail);
 
 #ifdef MOZ_B2G_RIL
     this._cleanUp();
 #endif
   },
 
   paymentSuccess: function paymentSuccess(aResult) {
-    if (_DEBUG) {
-      _debug("paymentSuccess " + aResult);
+    if (_debug) {
+      LOG("paymentSuccess " + aResult);
     }
 
     PaymentProvider._closePaymentFlowDialog(function notifySuccess() {
       if (!gRequestId) {
         return;
       }
       cpmm.sendAsyncMessage("Payment:Success", { result: aResult,
                                                  requestId: gRequestId });
     });
   },
 
   paymentFailed: function paymentFailed(aErrorMsg) {
-    if (_DEBUG) {
-      _debug("paymentFailed " + aErrorMsg);
+    if (_debug) {
+      LOG("paymentFailed " + aErrorMsg);
     }
 
     PaymentProvider._closePaymentFlowDialog(function notifyError() {
       if (!gRequestId) {
         return;
       }
       cpmm.sendAsyncMessage("Payment:Failed", { errorMsg: aErrorMsg,
                                                 requestId: gRequestId });
@@ -190,28 +207,28 @@ let PaymentProvider = {
   get mnc() {
     return [iccProvider.iccInfo.mnc];
   },
 
   _silentNumbers: null,
   _silentSmsObservers: null,
 
   sendSilentSms: function sendSilentSms(aNumber, aMessage) {
-    if (_DEBUG) {
-      _debug("Sending silent message " + aNumber + " - " + aMessage);
+    if (_debug) {
+      LOG("Sending silent message " + aNumber + " - " + aMessage);
     }
 
     let request = new SilentSmsRequest();
     smsService.send(aNumber, aMessage, true, request);
     return request;
   },
 
   observeSilentSms: function observeSilentSms(aNumber, aCallback) {
-    if (_DEBUG) {
-      _debug("observeSilentSms " + aNumber);
+    if (_debug) {
+      LOG("observeSilentSms " + aNumber);
     }
 
     if (!this._silentSmsObservers) {
       this._silentSmsObservers = {};
       this._silentNumbers = [];
       Services.obs.addObserver(this._onSilentSms.bind(this),
                                kSilentSmsReceivedTopic,
                                false);
@@ -224,61 +241,61 @@ let PaymentProvider = {
     }
 
     if (this._silentSmsObservers[aNumber].indexOf(aCallback) == -1) {
       this._silentSmsObservers[aNumber].push(aCallback);
     }
   },
 
   removeSilentSmsObserver: function removeSilentSmsObserver(aNumber, aCallback) {
-    if (_DEBUG) {
-      _debug("removeSilentSmsObserver " + aNumber);
+    if (_debug) {
+      LOG("removeSilentSmsObserver " + aNumber);
     }
 
     if (!this._silentSmsObservers || !this._silentSmsObservers[aNumber]) {
-      if (_DEBUG) {
-        _debug("No observers for " + aNumber);
+      if (_debug) {
+        LOG("No observers for " + aNumber);
       }
       return;
     }
 
     let index = this._silentSmsObservers[aNumber].indexOf(aCallback);
     if (index != -1) {
       this._silentSmsObservers[aNumber].splice(index, 1);
       if (this._silentSmsObservers[aNumber].length == 0) {
         this._silentSmsObservers[aNumber] = null;
         this._silentNumbers.splice(this._silentNumbers.indexOf(aNumber), 1);
         smsService.removeSilentNumber(aNumber);
       }
-    } else if (_DEBUG) {
-      _debug("No callback found for " + aNumber);
+    } else if (_debug) {
+      LOG("No callback found for " + aNumber);
     }
   },
 
   _onSilentSms: function _onSilentSms(aSubject, aTopic, aData) {
-    if (_DEBUG) {
-      _debug("Got silent message! " + aSubject.sender + " - " + aSubject.body);
+    if (_debug) {
+      LOG("Got silent message! " + aSubject.sender + " - " + aSubject.body);
     }
 
     let number = aSubject.sender;
     if (!number || this._silentNumbers.indexOf(number) == -1) {
-      if (_DEBUG) {
-        _debug("No observers for " + number);
+      if (_debug) {
+        LOG("No observers for " + number);
       }
       return;
     }
 
     this._silentSmsObservers[number].forEach(function(callback) {
       callback(aSubject);
     });
   },
 
   _cleanUp: function _cleanUp() {
-    if (_DEBUG) {
-      _debug("Cleaning up!");
+    if (_debug) {
+      LOG("Cleaning up!");
     }
 
     if (!this._silentNumbers) {
       return;
     }
 
     while (this._silentNumbers.length) {
       let number = this._silentNumbers.pop();
--- a/b2g/components/PaymentGlue.js
+++ b/b2g/components/PaymentGlue.js
@@ -12,25 +12,30 @@ Cu.import("resource://gre/modules/Servic
 // JS shim that contains the callback functions to be triggered from the
 // payment provider's code in order to fire DOMRequest events.
 const kPaymentShimFile = "chrome://browser/content/payment.js";
 
 // Type of MozChromEvents to handle payment dialogs.
 const kOpenPaymentConfirmationEvent = "open-payment-confirmation-dialog";
 const kOpenPaymentFlowEvent = "open-payment-flow-dialog";
 
+const PREF_DEBUG = "dom.payment.debug";
+
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
-function debug (s) {
-  //dump("-*- PaymentGlue: " + s + "\n");
-};
-
 function PaymentUI() {
+  try {
+    this._debug =
+      Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
+      && Services.prefs.getBoolPref(PREF_DEBUG);
+  } catch(e) {
+    this._debug = false;
+  }
 }
 
 PaymentUI.prototype = {
 
   confirmPaymentRequest: function confirmPaymentRequest(aRequestId,
                                                         aRequests,
                                                         aSuccessCb,
                                                         aErrorCb) {
@@ -124,17 +129,20 @@ PaymentUI.prototype = {
       let frame = evt.detail.frame;
       let frameLoader = frame.QueryInterface(Ci.nsIFrameLoaderOwner)
                         .frameLoader;
       let mm = frameLoader.messageManager;
       try {
         mm.loadFrameScript(kPaymentShimFile, true);
         mm.sendAsyncMessage("Payment:LoadShim", { requestId: aRequestId });
       } catch (e) {
-        debug("Error loading " + kPaymentShimFile + " as a frame script: " + e);
+        if (this._debug) {
+          this.LOG("Error loading " + kPaymentShimFile + " as a frame script: "
+                    + e);
+        }
         _error("ERROR_LOADING_PAYMENT_SHIM");
       } finally {
         content.removeEventListener("mozContentEvent", loadPaymentShim);
       }
     }).bind(this));
 
     // We also listen for UI notifications about a closed payment flow. The UI
     // should provide the reason of the closure within the 'errorMsg' parameter
@@ -163,14 +171,21 @@ PaymentUI.prototype = {
     }
     content.removeEventListener("mozContentEvent", this._notifyPayFlowClosed);
   },
 
   getRandomId: function getRandomId() {
     return uuidgen.generateUUID().toString();
   },
 
+  LOG: function LOG(s) {
+    if (!this._debug) {
+      return;
+    }
+    dump("-*- PaymentGlue: " + s + "\n");
+  },
+
   classID: Components.ID("{8b83eabc-7929-47f4-8b48-4dea8d887e4b}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPaymentUIGlue])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentUI]);
--- a/browser/locales/all-locales
+++ b/browser/locales/all-locales
@@ -1,88 +1,40 @@
-af
-ak
 ar
-as
 ast
-be
-bg
-bn-BD
-bn-IN
-br
-bs
-ca
 cs
-cy
-da
 de
-el
 en-GB
-en-ZA
 eo
 es-AR
 es-CL
 es-ES
 es-MX
-et
-eu
 fa
-fi
 fr
 fy-NL
-ga-IE
-gd
 gl
-gu-IN
 he
-hi-IN
-hr
 hu
-hy-AM
 id
-is
 it
 ja
 ja-JP-mac
-ka
 kk
-km
-kn
 ko
-ku
-lg
 lt
 lv
-mai
-mk
-ml
-mn
-mr
 nb-NO
 nl
 nn-NO
-nso
 oc
-or
-pa-IN
 pl
 pt-BR
 pt-PT
-rm
-ro
 ru
-si
 sk
-sl
-son
-sq
-sr
 sv-SE
-ta
-ta-LK
-te
 th
 tr
 uk
 vi
 zh-CN
 zh-TW
-zu
--- a/configure.in
+++ b/configure.in
@@ -6478,27 +6478,29 @@ dnl ====================================
 dnl = Content process sandboxing
 dnl ========================================================
 if test -n "$gonkdir"; then
     MOZ_CONTENT_SANDBOX=1
 fi
 
 MOZ_ARG_ENABLE_BOOL(content-sandbox,
 [  --enable-content-sandbox        Enable sandboxing support for content-processes],
-    MOZ_CONTENT_SANDBOX=1)
+    MOZ_CONTENT_SANDBOX=1,
+    MOZ_CONTENT_SANDBOX=)
 
 if test -n "$MOZ_CONTENT_SANDBOX"; then
     AC_DEFINE(MOZ_CONTENT_SANDBOX)
 fi
 
 AC_SUBST(MOZ_CONTENT_SANDBOX)
 
 MOZ_ARG_ENABLE_BOOL(content-sandbox-reporter,
 [ --enable-content-sandbox-reporter        Enable syscall reporter to troubleshoot syscalls denied by the content-processes sandbox],
-    MOZ_CONTENT_SANDBOX_REPORTER=1)
+    MOZ_CONTENT_SANDBOX_REPORTER=1,
+    MOZ_CONTENT_SANDBOX_REPORTER=)
 
 if test -n "$MOZ_CONTENT_SANDBOX_REPORTER"; then
     AC_DEFINE(MOZ_CONTENT_SANDBOX_REPORTER)
 fi
 
 AC_SUBST(MOZ_CONTENT_SANDBOX_REPORTER)
 
 dnl ========================================================
--- a/content/base/crashtests/308120-1.xul
+++ b/content/base/crashtests/308120-1.xul
@@ -1,3 +1,3 @@
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var button=document.getElementsByTagName('button')[0]; try { button.appendChild(document.getAnonymousNodes(button)[0]); } catch(e) { }">
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var button=document.getElementsByTagName('button')[0]; try { button.appendChild(SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(button))[0]); } catch(e) { }">
   <button/>
 </window>
--- a/content/base/crashtests/330925-1.xhtml
+++ b/content/base/crashtests/330925-1.xhtml
@@ -5,17 +5,17 @@
 <script>
 
 function init()
 {
   var foopy = document.getElementById("foopy");
   var emb = document.getElementById("emb");
 
   try {  
-    foopy.appendChild(document.getAnonymousNodes(emb)[0]);
+    foopy.appendChild(SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(emb))[0]);
     emb.parentNode.removeChild(emb);
     foopy.parentNode.removeChild(foopy);
   } catch (e) {
   }
   
   document.documentElement.removeAttribute("class");
 }
 
--- a/content/base/crashtests/401993-1.html
+++ b/content/base/crashtests/401993-1.html
@@ -6,17 +6,17 @@ function s()
 {
   var x = document.getElementById("x");
   x.style.MozBinding = "url(401993-1.xml#foo)";
   
   setTimeout(boom, 0);
   
   function boom()
   {
-    var nodes = document.getAnonymousNodes(x);
+    var nodes = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(x));
     if (!nodes) {
       setTimeout(boom, 10);
       return;
     }
 
     var newSpan = document.createElement("span");
     newSpan.contentEditable = "true";
     nodes[0].appendChild(newSpan);
--- a/content/base/test/test_base.xhtml
+++ b/content/base/test/test_base.xhtml
@@ -35,17 +35,17 @@ addLoadEvent(function() {
   }
   is(expected.length, 0, "found all expected nodes");
 
   var svgExpected =
     ["http://mochi.test:8888/tests/content/base/test/file_base_xbl.xml",
      "http://mochi.test:8888/tests/content/base/test/file_base_xbl.xml",
      "http://mochi.test:8888/tests/content/base/test/file_base_xbl.xml#shesellsseashellsbytheseashore",
      ];
-  node = document.getAnonymousNodes(document.getElementById("bound"))[0];
+  node = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById("bound")))[0];
   while(node) {
     is(node.baseURI, svgExpected.shift(), "node base");
     node = node.firstChild;
   }
   is(svgExpected.length, 0, "found all expected nodes");
 
   SimpleTest.finish();
 });
--- a/content/base/test/test_bug330925.xhtml
+++ b/content/base/test/test_bug330925.xhtml
@@ -41,30 +41,31 @@ xbl textnode2
 
 // We have to wait until onload because XBL doesn't bind immediately.
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(init);
 
 function init()
 {
   var t = document.getElementById("t");
+  var d = SpecialPowers.wrap(document);
 
-  is(document.getBindingParent(document.getAnonymousNodes(t)[0]), 
+  is(SpecialPowers.unwrap(d.getBindingParent(d.getAnonymousNodes(t)[0])),
      t,
      "Wrong binding parent for anonymous node");
      
-  is(document.getBindingParent(document.getAnonymousNodes(t)[1].childNodes[0]), 
+  is(SpecialPowers.unwrap(d.getBindingParent(d.getAnonymousNodes(t)[1].childNodes[0])),
      t,
      "Wrong binding parent for child of anonymous node");
      
-  is(document.getBindingParent(t), 
+  is(d.getBindingParent(t), 
      null,
      "Non-anonymous node should not have a binding parent");
      
-  is(document.getBindingParent(document.documentElement), 
+  is(d.getBindingParent(document.documentElement), 
      null,
      "Document element should not have a binding parent");
      
   SimpleTest.finish();
 }
 
 ]]>
 </script>
--- a/content/base/test/test_bug372086.html
+++ b/content/base/test/test_bug372086.html
@@ -37,34 +37,34 @@ function runTest() {
   range.setStart(c, 1);
   range.setEnd(c, 3);
   is(range.startContainer, c, "Unexpected start container");
   is(range.startOffset, 1, "Unexpected start offset");
   is(range.endContainer, c, "Unexpected end container");
   is(range.endOffset, 3, "Unexpected end offset");
   is(range.toString(), "bc", "Unexpected range serialization");
 
-  var anon = document.getAnonymousNodes($("d"))[0];
+  var anon = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes($("d")))[0];
   // Should collapse the range, because can't determine order
   range.setEnd(anon, 2);
   is(range.startContainer, anon, "Unexpected collapsed start container");
   is(range.startOffset, 2, "Unexpected collapsed start offset");
   is(range.endContainer, anon, "Unexpected collapsed end container");
   is(range.endOffset, 2, "Unexpected collapsed end offset");
   is(range.toString(), "", "Unexpected collapsed range serialization");
 
   range.setEnd(a, 2);
   range.setStart(a, 0);
   is(range.startContainer, a, "Unexpected start container after");
   is(range.startOffset, 0, "Unexpected start offset after");
   is(range.endContainer, a, "Unexpected end container after");
   is(range.endOffset, 2, "Unexpected end offset after");
   is(range.toString(), "de", "Unexpected range serialization after");
 
-  anon = document.getAnonymousNodes($("d"))[2];
+  anon = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes($("d")))[2];
   // Collapses because one endpoint is anonymous from point of view of
   // the other.  
   range.setStart(anon, 1);
   is(range.startContainer, anon, "Unexpected newly collapsed start container");
   is(range.startOffset, 1, "Unexpected newly collapsed start offset");
   is(range.endContainer, anon, "Unexpected newly collapsed end container");
   is(range.endOffset, 1, "Unexpected newly collapsed end offset");
   is(range.toString(), "", "Unexpected collapsed range serialization");
--- a/content/base/test/test_bug444030.xhtml
+++ b/content/base/test/test_bug444030.xhtml
@@ -21,17 +21,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 /** Test for Bug 444030 **/
 
 function doTest() {
-  var anonTextNode = document.getAnonymousNodes(document.getElementById("boundElement"))[0];
+  var anonTextNode = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById("boundElement")))[0];
   var hadException = false;
   try {
     var wholeText = anonTextNode.wholeText;
   } catch(e) {
     hadException = true;
   }
   ok(hadException,
      "Should have got an exception when using .wholeText with a text node child of the binding parent");
--- a/content/canvas/public/nsICanvasRenderingContextInternal.h
+++ b/content/canvas/public/nsICanvasRenderingContextInternal.h
@@ -9,18 +9,18 @@
 #include "nsISupports.h"
 #include "nsIInputStream.h"
 #include "nsIDocShell.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "gfxPattern.h"
 #include "mozilla/RefPtr.h"
 
 #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \
-{ 0x8b8da863, 0xd151, 0x4014, \
-  { 0x8b, 0xdc, 0x62, 0xb5, 0x0d, 0xc0, 0x2b, 0x62 } }
+{ 0x9a6a5bdf, 0x1261, 0x4057, \
+  { 0x85, 0xcc, 0xaf, 0x97, 0x6c, 0x36, 0x99, 0xa9 } }
 
 class gfxContext;
 class gfxASurface;
 class nsDisplayListBuilder;
 
 namespace mozilla {
 namespace layers {
 class CanvasLayer;
@@ -60,16 +60,19 @@ public:
 
   NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, int32_t width, int32_t height) = 0;
 
   // Render the canvas at the origin of the given gfxContext
   NS_IMETHOD Render(gfxContext *ctx,
                     gfxPattern::GraphicsFilter aFilter,
                     uint32_t aFlags = RenderFlagPremultAlpha) = 0;
 
+  // Creates an image buffer. Returns null on failure.
+  virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat) = 0;
+
   // Gives you a stream containing the image represented by this context.
   // The format is given in aMimeTime, for example "image/png".
   //
   // If the image format does not support transparency or aIncludeTransparency
   // is false, alpha will be discarded and the result will be the image
   // composited on black.
   NS_IMETHOD GetInputStream(const char *aMimeType,
                             const PRUnichar *aEncoderOptions,
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -43,17 +43,17 @@
 #include "nsIDocShell.h"
 #include "nsIDOMWindow.h"
 #include "nsPIDOMWindow.h"
 #include "nsIXPConnect.h"
 #include "nsDisplayList.h"
 
 #include "nsTArray.h"
 
-#include "imgIEncoder.h"
+#include "ImageEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxASurface.h"
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 #include "gfxFont.h"
 #include "gfxBlur.h"
 #include "gfxUtils.h"
@@ -1042,81 +1042,81 @@ CanvasRenderingContext2D::Render(gfxCont
       NS_ABORT_IF_FALSE(gis, "If non-premult alpha, must be able to get image surface!");
 
       gfxUtils::UnpremultiplyImageSurface(gis);
   }
 
   return rv;
 }
 
+void
+CanvasRenderingContext2D::GetImageBuffer(uint8_t** aImageBuffer,
+                                         int32_t* aFormat)
+{
+  *aImageBuffer = nullptr;
+  *aFormat = 0;
+
+  nsRefPtr<gfxASurface> surface;
+  nsresult rv = GetThebesSurface(getter_AddRefs(surface));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  static const fallible_t fallible = fallible_t();
+  uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
+  if (!imageBuffer) {
+    return;
+  }
+
+  nsRefPtr<gfxImageSurface> imgsurf =
+    new gfxImageSurface(imageBuffer,
+                        gfxIntSize(mWidth, mHeight),
+                        mWidth * 4,
+                        gfxASurface::ImageFormatARGB32);
+
+  if (!imgsurf || imgsurf->CairoStatus()) {
+    delete[] imageBuffer;
+    return;
+  }
+
+  nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf);
+  if (!ctx || ctx->HasError()) {
+    delete[] imageBuffer;
+    return;
+  }
+
+  ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+  ctx->SetSource(surface, gfxPoint(0, 0));
+  ctx->Paint();
+
+  *aImageBuffer = imageBuffer;
+  *aFormat = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+}
+
 NS_IMETHODIMP
 CanvasRenderingContext2D::GetInputStream(const char *aMimeType,
                                          const PRUnichar *aEncoderOptions,
                                          nsIInputStream **aStream)
 {
-  EnsureTarget();
-  if (!IsTargetValid()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsRefPtr<gfxASurface> surface;
-
-  if (NS_FAILED(GetThebesSurface(getter_AddRefs(surface)))) {
+  uint8_t* imageBuffer = nullptr;
+  int32_t format = 0;
+  GetImageBuffer(&imageBuffer, &format);
+  if (!imageBuffer) {
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv;
-  const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type=";
-  static const fallible_t fallible = fallible_t();
-  nsAutoArrayPtr<char> conid(new (fallible) char[strlen(encoderPrefix) + strlen(aMimeType) + 1]);
-
-  if (!conid) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  strcpy(conid, encoderPrefix);
-  strcat(conid, aMimeType);
-
-  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid);
+  nsCString enccid("@mozilla.org/image/encoder;2?type=");
+  enccid += aMimeType;
+  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
   if (!encoder) {
     return NS_ERROR_FAILURE;
   }
 
-  nsAutoArrayPtr<uint8_t> imageBuffer(new (fallible) uint8_t[mWidth * mHeight * 4]);
-  if (!imageBuffer) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  nsRefPtr<gfxImageSurface> imgsurf =
-    new gfxImageSurface(imageBuffer.get(),
-                        gfxIntSize(mWidth, mHeight),
-                        mWidth * 4,
-                        gfxASurface::ImageFormatARGB32);
-
-  if (!imgsurf || imgsurf->CairoStatus()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf);
-
-  if (!ctx || ctx->HasError()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
-  ctx->SetSource(surface, gfxPoint(0, 0));
-  ctx->Paint();
-
-  rv = encoder->InitFromData(imageBuffer.get(),
-                              mWidth * mHeight * 4, mWidth, mHeight, mWidth * 4,
-                              imgIEncoder::INPUT_FORMAT_HOSTARGB,
-                              nsDependentString(aEncoderOptions));
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return CallQueryInterface(encoder, aStream);
+  return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
+                                      encoder, aEncoderOptions, aStream);
 }
 
 SurfaceFormat
 CanvasRenderingContext2D::GetSurfaceFormat() const
 {
   return mOpaque ? FORMAT_B8G8R8X8 : FORMAT_B8G8R8A8;
 }
 
@@ -3745,16 +3745,19 @@ CanvasRenderingContext2D::PutImageData_e
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::GetThebesSurface(gfxASurface **surface)
 {
   EnsureTarget();
+  if (!IsTargetValid()) {
+    return NS_ERROR_FAILURE;
+  }
 
   nsRefPtr<gfxASurface> thebesSurface =
       gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mTarget);
 
   if (!thebesSurface) {
     return NS_ERROR_FAILURE;
   }
 
--- a/content/canvas/src/CanvasRenderingContext2D.h
+++ b/content/canvas/src/CanvasRenderingContext2D.h
@@ -15,16 +15,17 @@
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "CanvasUtils.h"
 #include "gfxFont.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/CanvasGradient.h"
 #include "mozilla/dom/CanvasRenderingContext2DBinding.h"
 #include "mozilla/dom/CanvasPattern.h"
 #include "mozilla/gfx/Rect.h"
+#include "imgIEncoder.h"
 
 class nsXULElement;
 
 namespace mozilla {
 namespace gfx {
 class SourceSurface;
 }
 
@@ -443,16 +444,18 @@ public:
       mDSPathBuilder->BezierTo(transform * aCP1,
                                 transform * aCP2,
                                 transform * aCP3);
     }
   }
 
   friend class CanvasRenderingContext2DUserData;
 
+  virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat);
+
 protected:
   nsresult GetImageDataArray(JSContext* aCx, int32_t aX, int32_t aY,
                              uint32_t aWidth, uint32_t aHeight,
                              JSObject** aRetval);
 
   nsresult PutImageData_explicit(int32_t x, int32_t y, uint32_t w, uint32_t h,
                                  unsigned char *aData, uint32_t aDataLen,
                                  bool hasDirtyRect, int32_t dirtyX, int32_t dirtyY,
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/ImageEncoder.cpp
@@ -0,0 +1,296 @@
+/* -*- 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 "ImageEncoder.h"
+
+#include "mozilla/dom/CanvasRenderingContext2D.h"
+
+namespace mozilla {
+namespace dom {
+
+class EncodingCompleteEvent : public nsRunnable
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  EncodingCompleteEvent(JSContext* aJSContext,
+                        nsIThread* aEncoderThread,
+                        nsIFileCallback* aCallback)
+    : mImgSize(0)
+    , mType()
+    , mImgData(nullptr)
+    , mJSContext(aJSContext)
+    , mEncoderThread(aEncoderThread)
+    , mCallback(aCallback)
+  {}
+  virtual ~EncodingCompleteEvent() {}
+
+  NS_IMETHOD Run()
+  {
+    nsRefPtr<nsDOMMemoryFile> blob =
+      new nsDOMMemoryFile(mImgData, mImgSize, mType);
+
+    if (mJSContext) {
+      JS_updateMallocCounter(mJSContext, mImgSize);
+    }
+    nsresult rv = mCallback->Receive(blob);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mEncoderThread->Shutdown();
+    return rv;
+  }
+
+  void SetMembers(void* aImgData, uint64_t aImgSize, const nsAutoString& aType)
+  {
+    mImgData = aImgData;
+    mImgSize = aImgSize;
+    mType = aType;
+  }
+
+private:
+  uint64_t mImgSize;
+  nsAutoString mType;
+  void* mImgData;
+  JSContext* mJSContext;
+  nsCOMPtr<nsIThread> mEncoderThread;
+  nsCOMPtr<nsIFileCallback> mCallback;
+};
+
+NS_IMPL_ISUPPORTS1(EncodingCompleteEvent, nsIRunnable);
+
+class EncodingRunnable : public nsRunnable
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  EncodingRunnable(const nsAString& aType,
+                   const nsAString& aOptions,
+                   uint8_t* aImageBuffer,
+                   imgIEncoder* aEncoder,
+                   nsIThread* aOriginThread,
+                   EncodingCompleteEvent* aEncodingCompleteEvent,
+                   int32_t aFormat,
+                   const nsIntSize aSize,
+                   bool aUsingCustomOptions)
+    : mType(aType)
+    , mOptions(aOptions)
+    , mImageBuffer(aImageBuffer)
+    , mEncoder(aEncoder)
+    , mOriginThread(aOriginThread)
+    , mEncodingCompleteEvent(aEncodingCompleteEvent)
+    , mFormat(aFormat)
+    , mSize(aSize)
+    , mUsingCustomOptions(aUsingCustomOptions)
+  {}
+  virtual ~EncodingRunnable() {}
+
+  NS_IMETHOD Run()
+  {
+    nsCOMPtr<nsIInputStream> stream;
+    nsresult rv = ImageEncoder::ExtractDataInternal(mType,
+                                                    mOptions,
+                                                    mImageBuffer,
+                                                    mFormat,
+                                                    mSize,
+                                                    nullptr,
+                                                    getter_AddRefs(stream),
+                                                    mEncoder);
+
+    // If there are unrecognized custom parse options, we should fall back to
+    // the default values for the encoder without any options at all.
+    if (rv == NS_ERROR_INVALID_ARG && mUsingCustomOptions) {
+      rv = ImageEncoder::ExtractDataInternal(mType,
+                                             EmptyString(),
+                                             mImageBuffer,
+                                             mFormat,
+                                             mSize,
+                                             nullptr,
+                                             getter_AddRefs(stream),
+                                             mEncoder);
+    }
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    uint64_t imgSize;
+    rv = stream->Available(&imgSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
+
+    void* imgData = nullptr;
+    rv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mEncodingCompleteEvent->SetMembers(imgData, imgSize, mType);
+    rv = mOriginThread->Dispatch(mEncodingCompleteEvent, NS_DISPATCH_NORMAL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return rv;
+  }
+
+private:
+  nsAutoString mType;
+  nsAutoString mOptions;
+  nsAutoArrayPtr<uint8_t> mImageBuffer;
+  nsCOMPtr<imgIEncoder> mEncoder;
+  nsCOMPtr<nsIThread> mOriginThread;
+  nsRefPtr<EncodingCompleteEvent> mEncodingCompleteEvent;
+  int32_t mFormat;
+  const nsIntSize mSize;
+  bool mUsingCustomOptions;
+};
+
+NS_IMPL_ISUPPORTS1(EncodingRunnable, nsIRunnable)
+
+/* static */
+nsresult
+ImageEncoder::ExtractData(nsAString& aType,
+                          const nsAString& aOptions,
+                          const nsIntSize aSize,
+                          nsICanvasRenderingContextInternal* aContext,
+                          nsIInputStream** aStream)
+{
+  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+  if (!encoder) {
+    return NS_IMAGELIB_ERROR_NO_ENCODER;
+  }
+
+  return ExtractDataInternal(aType, aOptions, nullptr, 0, aSize, aContext,
+                             aStream, encoder);
+}
+
+/* static */
+nsresult
+ImageEncoder::ExtractDataAsync(nsAString& aType,
+                               const nsAString& aOptions,
+                               bool aUsingCustomOptions,
+                               uint8_t* aImageBuffer,
+                               int32_t aFormat,
+                               const nsIntSize aSize,
+                               nsICanvasRenderingContextInternal* aContext,
+                               JSContext* aJSContext,
+                               nsIFileCallback* aCallback)
+{
+  nsCOMPtr<imgIEncoder> encoder = ImageEncoder::GetImageEncoder(aType);
+  if (!encoder) {
+    return NS_IMAGELIB_ERROR_NO_ENCODER;
+  }
+
+  nsCOMPtr<nsIThread> encoderThread;
+  nsresult rv = NS_NewThread(getter_AddRefs(encoderThread), nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsRefPtr<EncodingCompleteEvent> completeEvent =
+    new EncodingCompleteEvent(aJSContext, encoderThread, aCallback);
+
+  nsCOMPtr<nsIRunnable> event = new EncodingRunnable(aType,
+                                                     aOptions,
+                                                     aImageBuffer,
+                                                     encoder,
+                                                     NS_GetCurrentThread(),
+                                                     completeEvent,
+                                                     aFormat,
+                                                     aSize,
+                                                     aUsingCustomOptions);
+  return encoderThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+/*static*/ nsresult
+ImageEncoder::GetInputStream(int32_t aWidth,
+                             int32_t aHeight,
+                             uint8_t* aImageBuffer,
+                             int32_t aFormat,
+                             imgIEncoder* aEncoder,
+                             const PRUnichar* aEncoderOptions,
+                             nsIInputStream** aStream)
+{
+  nsresult rv =
+    aEncoder->InitFromData(aImageBuffer,
+                           aWidth * aHeight * 4, aWidth, aHeight, aWidth * 4,
+                           aFormat,
+                           nsDependentString(aEncoderOptions));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return CallQueryInterface(aEncoder, aStream);
+}
+
+/* static */
+nsresult
+ImageEncoder::ExtractDataInternal(const nsAString& aType,
+                                  const nsAString& aOptions,
+                                  uint8_t* aImageBuffer,
+                                  int32_t aFormat,
+                                  const nsIntSize aSize,
+                                  nsICanvasRenderingContextInternal* aContext,
+                                  nsIInputStream** aStream,
+                                  imgIEncoder* aEncoder)
+{
+  nsCOMPtr<nsIInputStream> imgStream;
+
+  // get image bytes
+  nsresult rv;
+  if (aImageBuffer) {
+    rv = ImageEncoder::GetInputStream(
+      aSize.width,
+      aSize.height,
+      aImageBuffer,
+      aFormat,
+      aEncoder,
+      nsPromiseFlatString(aOptions).get(),
+      getter_AddRefs(imgStream));
+  } else if (aContext) {
+    NS_ConvertUTF16toUTF8 encoderType(aType);
+    rv = aContext->GetInputStream(encoderType.get(),
+                                  nsPromiseFlatString(aOptions).get(),
+                                  getter_AddRefs(imgStream));
+  } else {
+    // no context, so we have to encode an empty image
+    // note that if we didn't have a current context, the spec says we're
+    // supposed to just return transparent black pixels of the canvas
+    // dimensions.
+    nsRefPtr<gfxImageSurface> emptyCanvas =
+      new gfxImageSurface(gfxIntSize(aSize.width, aSize.height),
+                          gfxASurface::ImageFormatARGB32);
+    if (emptyCanvas->CairoStatus()) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    rv = aEncoder->InitFromData(emptyCanvas->Data(),
+                                aSize.width * aSize.height * 4,
+                                aSize.width,
+                                aSize.height,
+                                aSize.width * 4,
+                                imgIEncoder::INPUT_FORMAT_HOSTARGB,
+                                aOptions);
+    if (NS_SUCCEEDED(rv)) {
+      imgStream = do_QueryInterface(aEncoder);
+    }
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  imgStream.forget(aStream);
+  return rv;
+}
+
+/* static */
+already_AddRefed<imgIEncoder>
+ImageEncoder::GetImageEncoder(nsAString& aType)
+{
+  // Get an image encoder for the media type.
+  nsCString encoderCID("@mozilla.org/image/encoder;2?type=");
+  NS_ConvertUTF16toUTF8 encoderType(aType);
+  encoderCID += encoderType;
+  nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get());
+
+  if (!encoder && aType != NS_LITERAL_STRING("image/png")) {
+    // Unable to create an encoder instance of the specified type. Falling back
+    // to PNG.
+    aType.AssignLiteral("image/png");
+    nsCString PNGEncoderCID("@mozilla.org/image/encoder;2?type=image/png");
+    encoder = do_CreateInstance(PNGEncoderCID.get());
+  }
+
+  return encoder.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/ImageEncoder.h
@@ -0,0 +1,92 @@
+/* -*- 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 ImageEncoder_h
+#define ImageEncoder_h
+
+#include "imgIEncoder.h"
+#include "nsDOMFile.h"
+#include "nsError.h"
+#include "nsIDOMHTMLCanvasElement.h"
+#include "nsLayoutUtils.h"
+#include "nsNetUtil.h"
+#include "nsSize.h"
+
+class nsICanvasRenderingContextInternal;
+
+namespace mozilla {
+namespace dom {
+
+class EncodingRunnable;
+
+class ImageEncoder
+{
+public:
+  // Extracts data synchronously and gives you a stream containing the image
+  // represented by aContext. aType may change to "image/png" if we had to fall
+  // back to a PNG encoder. A return value of NS_OK implies successful data
+  // extraction. If there are any unrecognized custom parse options in
+  // aOptions, NS_ERROR_INVALID_ARG will be returned. When encountering this
+  // error it is usual to call this function again without any options at all.
+  static nsresult ExtractData(nsAString& aType,
+                              const nsAString& aOptions,
+                              const nsIntSize aSize,
+                              nsICanvasRenderingContextInternal* aContext,
+                              nsIInputStream** aStream);
+
+  // Extracts data asynchronously. aType may change to "image/png" if we had to
+  // fall back to a PNG encoder. aOptions are the options to be passed to the
+  // encoder and aUsingCustomOptions specifies whether custom parse options were
+  // used (i.e. by using -moz-parse-options). If there are any unrecognized
+  // custom parse options, we fall back to the default values for the encoder
+  // without any options at all. A return value of NS_OK only implies
+  // successful dispatching of the extraction step to the encoding thread.
+  static nsresult ExtractDataAsync(nsAString& aType,
+                                   const nsAString& aOptions,
+                                   bool aUsingCustomOptions,
+                                   uint8_t* aImageBuffer,
+                                   int32_t aFormat,
+                                   const nsIntSize aSize,
+                                   nsICanvasRenderingContextInternal* aContext,
+                                   JSContext* aJSContext,
+                                   nsIFileCallback* aCallback);
+
+  // Gives you a stream containing the image represented by aImageBuffer.
+  // The format is given in aFormat, for example
+  // imgIEncoder::INPUT_FORMAT_HOSTARGB.
+  static nsresult GetInputStream(int32_t aWidth,
+                                 int32_t aHeight,
+                                 uint8_t* aImageBuffer,
+                                 int32_t aFormat,
+                                 imgIEncoder* aEncoder,
+                                 const PRUnichar* aEncoderOptions,
+                                 nsIInputStream** aStream);
+
+private:
+  // When called asynchronously, aContext is null.
+  static nsresult
+  ExtractDataInternal(const nsAString& aType,
+                      const nsAString& aOptions,
+                      uint8_t* aImageBuffer,
+                      int32_t aFormat,
+                      const nsIntSize aSize,
+                      nsICanvasRenderingContextInternal* aContext,
+                      nsIInputStream** aStream,
+                      imgIEncoder* aEncoder);
+
+  // Creates and returns an encoder instance of the type specified in aType.
+  // aType may change to "image/png" if no instance of the original type could
+  // be created and we had to fall back to a PNG encoder. A return value of
+  // NULL should be interpreted as NS_IMAGELIB_ERROR_NO_ENCODER and aType is
+  // undefined in this case.
+  static already_AddRefed<imgIEncoder> GetImageEncoder(nsAString& aType);
+
+  friend class EncodingRunnable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // ImageEncoder_h
\ No newline at end of file
--- a/content/canvas/src/Makefile.in
+++ b/content/canvas/src/Makefile.in
@@ -17,10 +17,11 @@ CXXFLAGS	+= $(MOZ_CAIRO_CFLAGS) $(MOZ_PI
 INCLUDES	+= \
 		-I$(srcdir)/../../../layout/xul/base/src \
 		-I$(srcdir)/../../../layout/style \
 		-I$(srcdir)/../../../layout/generic \
 		-I$(srcdir)/../../base/src \
 		-I$(srcdir)/../../html/content/src \
 		-I$(srcdir)/../../../js/xpconnect/src \
 		-I$(srcdir)/../../../dom/base \
+		-I$(srcdir)/../../../image/src \
 		-I$(topsrcdir)/content/xul/content/src \
 		$(NULL)
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -22,17 +22,17 @@
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
 #include "nsError.h"
 #include "nsIGfxInfo.h"
 #include "nsIWidget.h"
 
 #include "nsIVariant.h"
 
-#include "imgIEncoder.h"
+#include "ImageEncoder.h"
 
 #include "gfxContext.h"
 #include "gfxPattern.h"
 #include "gfxUtils.h"
 
 #include "CanvasUtils.h"
 #include "nsDisplayList.h"
 
@@ -716,67 +716,89 @@ void WebGLContext::LoseOldestWebGLContex
     } else if (numContexts > kMaxWebGLContexts) {
         GenerateWarning("Exceeded %d live WebGL contexts, losing the least recently used one.",
                         kMaxWebGLContexts);
         MOZ_ASSERT(oldestContext); // if we reach this point, this can't be null
         const_cast<WebGLContext*>(oldestContext)->LoseContext();
     }
 }
 
+void
+WebGLContext::GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat)
+{
+    *aImageBuffer = nullptr;
+    *aFormat = 0;
+
+    nsRefPtr<gfxImageSurface> imgsurf =
+        new gfxImageSurface(gfxIntSize(mWidth, mHeight),
+                            gfxASurface::ImageFormatARGB32);
+
+    if (!imgsurf || imgsurf->CairoStatus()) {
+        return;
+    }
+
+    nsRefPtr<gfxContext> ctx = new gfxContext(imgsurf);
+    if (!ctx || ctx->HasError()) {
+        return;
+    }
+
+    // Use Render() to make sure that appropriate y-flip gets applied
+    uint32_t flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0;
+    nsresult rv = Render(ctx, gfxPattern::FILTER_NEAREST, flags);
+    if (NS_FAILED(rv)) {
+        return;
+    }
+
+    int32_t format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+    if (!mOptions.premultipliedAlpha) {
+        // We need to convert to INPUT_FORMAT_RGBA, otherwise
+        // we are automatically considered premult, and unpremult'd.
+        // Yes, it is THAT silly.
+        // Except for different lossy conversions by color,
+        // we could probably just change the label, and not change the data.
+        gfxUtils::ConvertBGRAtoRGBA(imgsurf);
+        format = imgIEncoder::INPUT_FORMAT_RGBA;
+    }
+
+    static const fallible_t fallible = fallible_t();
+    uint8_t* imageBuffer = new (fallible) uint8_t[mWidth * mHeight * 4];
+    if (!imageBuffer) {
+        return;
+    }
+    memcpy(imageBuffer, imgsurf->Data(), mWidth * mHeight * 4);
+
+    *aImageBuffer = imageBuffer;
+    *aFormat = format;
+}
+
 NS_IMETHODIMP
 WebGLContext::GetInputStream(const char* aMimeType,
                              const PRUnichar* aEncoderOptions,
                              nsIInputStream **aStream)
 {
     NS_ASSERTION(gl, "GetInputStream on invalid context?");
     if (!gl)
         return NS_ERROR_FAILURE;
 
-    nsRefPtr<gfxImageSurface> surf = new gfxImageSurface(gfxIntSize(mWidth, mHeight),
-                                                         gfxASurface::ImageFormatARGB32);
-    if (surf->CairoStatus() != 0)
+    uint8_t* imageBuffer = nullptr;
+    int32_t format = 0;
+    GetImageBuffer(&imageBuffer, &format);
+    if (!imageBuffer) {
         return NS_ERROR_FAILURE;
-
-    nsRefPtr<gfxContext> tmpcx = new gfxContext(surf);
-    // Use Render() to make sure that appropriate y-flip gets applied
-    uint32_t flags = mOptions.premultipliedAlpha ? RenderFlagPremultAlpha : 0;
-    nsresult rv = Render(tmpcx, gfxPattern::FILTER_NEAREST, flags);
-    if (NS_FAILED(rv))
-        return rv;
-
-    const char encoderPrefix[] = "@mozilla.org/image/encoder;2?type=";
-    nsAutoArrayPtr<char> conid(new char[strlen(encoderPrefix) + strlen(aMimeType) + 1]);
-
-    strcpy(conid, encoderPrefix);
-    strcat(conid, aMimeType);
-
-    nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(conid);
-    if (!encoder)
-        return NS_ERROR_FAILURE;
-
-    int format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
-    if (!mOptions.premultipliedAlpha) {
-        // We need to convert to INPUT_FORMAT_RGBA, otherwise
-        // we are automatically considered premult, and unpremult'd.
-        // Yes, it is THAT silly.
-        // Except for different lossy conversions by color,
-        // we could probably just change the label, and not change the data.
-        gfxUtils::ConvertBGRAtoRGBA(surf);
-        format = imgIEncoder::INPUT_FORMAT_RGBA;
     }
 
-    rv = encoder->InitFromData(surf->Data(),
-                               mWidth * mHeight * 4,
-                               mWidth, mHeight,
-                               surf->Stride(),
-                               format,
-                               nsDependentString(aEncoderOptions));
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsCString enccid("@mozilla.org/image/encoder;2?type=");
+    enccid += aMimeType;
+    nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get());
+    if (!encoder) {
+        return NS_ERROR_FAILURE;
+    }
 
-    return CallQueryInterface(encoder, aStream);
+    return ImageEncoder::GetInputStream(mWidth, mHeight, imageBuffer, format,
+                                        encoder, aEncoderOptions, aStream);
 }
 
 NS_IMETHODIMP
 WebGLContext::GetThebesSurface(gfxASurface **surface)
 {
     return NS_ERROR_NOT_AVAILABLE;
 }
 
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -155,16 +155,17 @@ public:
     NS_IMETHOD SetDimensions(int32_t width, int32_t height) MOZ_OVERRIDE;
     NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, int32_t width, int32_t height) MOZ_OVERRIDE
         { return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD Reset() MOZ_OVERRIDE
         { /* (InitializeWithSurface) */ return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD Render(gfxContext *ctx,
                       gfxPattern::GraphicsFilter f,
                       uint32_t aFlags = RenderFlagPremultAlpha) MOZ_OVERRIDE;
+    virtual void GetImageBuffer(uint8_t** aImageBuffer, int32_t* aFormat);
     NS_IMETHOD GetInputStream(const char* aMimeType,
                               const PRUnichar* aEncoderOptions,
                               nsIInputStream **aStream) MOZ_OVERRIDE;
     NS_IMETHOD GetThebesSurface(gfxASurface **surface) MOZ_OVERRIDE;
     mozilla::TemporaryRef<mozilla::gfx::SourceSurface> GetSurfaceSnapshot() MOZ_OVERRIDE
         { return nullptr; }
 
     NS_IMETHOD SetIsOpaque(bool b) MOZ_OVERRIDE { return NS_OK; };
--- a/content/canvas/src/moz.build
+++ b/content/canvas/src/moz.build
@@ -17,16 +17,17 @@ EXPORTS.mozilla.dom += [
 
 CPP_SOURCES += [
     'CanvasImageCache.cpp',
     'CanvasRenderingContext2D.cpp',
     'CanvasUtils.cpp',
     'DocumentRendererChild.cpp',
     'DocumentRendererParent.cpp',
     'ImageData.cpp',
+    'ImageEncoder.cpp',
 ]
 
 if CONFIG['MOZ_WEBGL']:
     CPP_SOURCES += [
         'WebGLActiveInfo.cpp',
         'WebGLBuffer.cpp',
         'WebGL1Context.cpp',
         'WebGL2Context.cpp',
--- a/content/canvas/test/test_mozGetAsFile.html
+++ b/content/canvas/test/test_mozGetAsFile.html
@@ -2,49 +2,50 @@
 <title>Canvas test: mozGetAsFile</title>
 <script src="/MochiKit/MochiKit.js"></script>
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 <body>
 <canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 
-var gCompares = 0;
-
-function compareAsync(file, canvas, type)
+function compareAsync(file, canvas, type, callback)
 {
-  ++gCompares;
-
   var reader = new FileReader();
   reader.onload = 
     function(e) {
       is(e.target.result, canvas.toDataURL(type),
  "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
-      if (--gCompares == 0) {
-        SimpleTest.finish();
-      }
+      callback(canvas);
     };
   reader.readAsDataURL(file);
 }
 
+function test1(canvas)
+{
+  var pngfile = canvas.mozGetAsFile("foo.png");
+  is(pngfile.type, "image/png", "Default type for mozGetAsFile should be PNG");
+  compareAsync(pngfile, canvas, "image/png", test2);
+  is(pngfile.name, "foo.png", "File name should be what we passed in");
+}
+
+function test2(canvas)
+{
+  var jpegfile = canvas.mozGetAsFile("bar.jpg", "image/jpeg");
+  is(jpegfile.type, "image/jpeg",
+     "When a valid type is specified that should be returned");
+  compareAsync(jpegfile, canvas, "image/jpeg", SimpleTest.finish);
+  is(jpegfile.name, "bar.jpg", "File name should be what we passed in");
+}
+
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function () {
 
 var canvas = document.getElementById('c');
 var ctx = canvas.getContext('2d');
-
 ctx.drawImage(document.getElementById('yellow75.png'), 0, 0);
 
-var pngfile = canvas.mozGetAsFile("foo.png");
-is(pngfile.type, "image/png", "Default type for mozGetAsFile should be PNG");
-compareAsync(pngfile, canvas, "image/png");
-is(pngfile.name, "foo.png", "File name should be what we passed in");
-
-var jpegfile = canvas.mozGetAsFile("bar.jpg", "image/jpeg");
-is(jpegfile.type, "image/jpeg",
-   "When a valid type is specified that should be returned");
-compareAsync(jpegfile, canvas, "image/jpeg");
-is(jpegfile.name, "bar.jpg", "File name should be what we passed in");
+test1(canvas);
 
 });
 </script>
 <img src="image_yellow75.png" id="yellow75.png" class="resource">
 
--- a/content/canvas/test/test_toBlob.html
+++ b/content/canvas/test/test_toBlob.html
@@ -1,42 +1,48 @@
 <!DOCTYPE HTML>
-<title>Canvas test: mozGetAsFile</title>
+<title>Canvas test: toBlob</title>
 <script src="/MochiKit/MochiKit.js"></script>
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" href="/tests/SimpleTest/test.css">
 <body>
 <canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 
-var gCompares = 2;
-
-function BlobListener(type, canvas, file)
+function BlobListener(type, canvas, callback, file)
 {
   is(file.type, type,
      "When a valid type is specified that should be returned");
   var reader = new FileReader();
-  reader.onload = 
+  reader.onload =
     function(e) {
       is(e.target.result, canvas.toDataURL(type),
- "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
-      if (--gCompares == 0) {
-        SimpleTest.finish();
-      }
+  "<canvas>.mozGetAsFile().getAsDataURL() should equal <canvas>.toDataURL()");
+      callback(canvas);
     };
   reader.readAsDataURL(file);
 }
 
+function test1(canvas)
+{
+  canvas.toBlob(BlobListener.bind(undefined, "image/png", canvas, test2));
+}
+
+function test2(canvas)
+{
+  canvas.toBlob(
+    BlobListener.bind(undefined, "image/jpeg", canvas, SimpleTest.finish),
+    "image/jpeg");
+}
+
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function () {
 
 var canvas = document.getElementById('c');
 var ctx = canvas.getContext('2d');
-
 ctx.drawImage(document.getElementById('yellow75.png'), 0, 0);
 
-canvas.toBlob(BlobListener.bind(undefined, "image/png", canvas));
-canvas.toBlob(BlobListener.bind(undefined, "image/jpeg", canvas), "image/jpeg");
+test1(canvas);
 
 });
 </script>
 <img src="image_yellow75.png" id="yellow75.png" class="resource">
 
--- a/content/html/content/crashtests/465466-1.xhtml
+++ b/content/html/content/crashtests/465466-1.xhtml
@@ -6,17 +6,17 @@
 </binding></bindings>
 
 
 <script type="text/javascript">
 
 function boom()
 {
   var f = document.getElementById("f");
-  var anon = document.getAnonymousNodes(f)[0];
+  var anon = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(f))[0];
   document.body.removeChild(f);
   anon.appendChild(document.createElement("label"));
 }
 
 </script>
 </head>
 
 <body onload="boom();"><form id="f" style="-moz-binding: url(#u);"><label></label></form></body>
--- a/content/html/content/public/HTMLCanvasElement.h
+++ b/content/html/content/public/HTMLCanvasElement.h
@@ -222,20 +222,19 @@ protected:
   nsIntSize GetWidthHeight();
 
   nsresult UpdateContext(JSContext* aCx, JS::Handle<JS::Value> options);
   nsresult ParseParams(JSContext* aCx,
                        const nsAString& aType,
                        const JS::Value& aEncoderOptions,
                        nsAString& aParams,
                        bool* usingCustomParseOptions);
-  nsresult ExtractData(const nsAString& aType,
+  nsresult ExtractData(nsAString& aType,
                        const nsAString& aOptions,
-                       nsIInputStream** aStream,
-                       bool& aFellBackToPNG);
+                       nsIInputStream** aStream);
   nsresult ToDataURLImpl(JSContext* aCx,
                          const nsAString& aMimeType,
                          const JS::Value& aEncoderOptions,
                          nsAString& aDataURL);
   nsresult MozGetAsFileImpl(const nsAString& aName,
                             const nsAString& aType,
                             nsIDOMFile** aResult);
   nsresult GetContextHelper(const nsAString& aContextId,
--- a/content/html/content/src/HTMLCanvasElement.cpp
+++ b/content/html/content/src/HTMLCanvasElement.cpp
@@ -1,19 +1,19 @@
 /* -*- 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 "mozilla/dom/HTMLCanvasElement.h"
 
-#include "Layers.h"
-#include "imgIEncoder.h"
+#include "ImageEncoder.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
+#include "Layers.h"
 #include "mozilla/Base64.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/dom/HTMLCanvasElementBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/gfx/Rect.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
@@ -40,38 +40,16 @@ using namespace mozilla::layers;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
 
 namespace {
 
 typedef mozilla::dom::HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement
 HTMLImageOrCanvasOrVideoElement;
 
-class ToBlobRunnable : public nsRunnable
-{
-public:
-  ToBlobRunnable(nsIFileCallback* aCallback,
-                 nsIDOMBlob* aBlob)
-    : mCallback(aCallback),
-      mBlob(aBlob)
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  }
-
-  NS_IMETHOD Run()
-  {
-    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-    mCallback->Receive(mBlob);
-    return NS_OK;
-  }
-private:
-  nsCOMPtr<nsIFileCallback> mCallback;
-  nsCOMPtr<nsIDOMBlob> mBlob;
-};
-
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 
 class HTMLCanvasPrintState : public nsIDOMMozCanvasPrintState
 {
 public:
@@ -362,20 +340,20 @@ HTMLCanvasElement::ToDataURL(const nsASt
 NS_IMETHODIMP
 HTMLCanvasElement::MozFetchAsStream(nsIInputStreamCallback *aCallback,
                                     const nsAString& aType)
 {
   if (!nsContentUtils::IsCallerChrome())
     return NS_ERROR_FAILURE;
 
   nsresult rv;
-  bool fellBackToPNG = false;
   nsCOMPtr<nsIInputStream> inputData;
 
-  rv = ExtractData(aType, EmptyString(), getter_AddRefs(inputData), fellBackToPNG);
+  nsAutoString type(aType);
+  rv = ExtractData(type, EmptyString(), getter_AddRefs(inputData));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIAsyncInputStream> asyncData = do_QueryInterface(inputData, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIThread> mainThread;
   rv = NS_GetMainThread(getter_AddRefs(mainThread));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -405,78 +383,25 @@ HTMLCanvasElement::GetMozPrintCallback()
 NS_IMETHODIMP
 HTMLCanvasElement::GetMozPrintCallback(nsIPrintCallback** aCallback)
 {
   NS_IF_ADDREF(*aCallback = GetMozPrintCallback());
   return NS_OK;
 }
 
 nsresult
-HTMLCanvasElement::ExtractData(const nsAString& aType,
+HTMLCanvasElement::ExtractData(nsAString& aType,
                                const nsAString& aOptions,
-                               nsIInputStream** aStream,
-                               bool& aFellBackToPNG)
+                               nsIInputStream** aStream)
 {
-  // note that if we don't have a current context, the spec says we're
-  // supposed to just return transparent black pixels of the canvas
-  // dimensions.
-  nsRefPtr<gfxImageSurface> emptyCanvas;
-  nsIntSize size = GetWidthHeight();
-  if (!mCurrentContext) {
-    emptyCanvas = new gfxImageSurface(gfxIntSize(size.width, size.height), gfxASurface::ImageFormatARGB32);
-    if (emptyCanvas->CairoStatus()) {
-      return NS_ERROR_INVALID_ARG;
-    }
-  }
-
-  nsresult rv;
-
-  // get image bytes
-  nsCOMPtr<nsIInputStream> imgStream;
-  NS_ConvertUTF16toUTF8 encoderType(aType);
-
- try_again:
-  if (mCurrentContext) {
-    rv = mCurrentContext->GetInputStream(encoderType.get(),
-                                         nsPromiseFlatString(aOptions).get(),
-                                         getter_AddRefs(imgStream));
-  } else {
-    // no context, so we have to encode the empty image we created above
-    nsCString enccid("@mozilla.org/image/encoder;2?type=");
-    enccid += encoderType;
-
-    nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(enccid.get(), &rv);
-    if (NS_SUCCEEDED(rv) && encoder) {
-      rv = encoder->InitFromData(emptyCanvas->Data(),
-                                 size.width * size.height * 4,
-                                 size.width,
-                                 size.height,
-                                 size.width * 4,
-                                 imgIEncoder::INPUT_FORMAT_HOSTARGB,
-                                 aOptions);
-      if (NS_SUCCEEDED(rv)) {
-        imgStream = do_QueryInterface(encoder);
-      }
-    } else {
-      rv = NS_ERROR_FAILURE;
-    }
-  }
-
-  if (NS_FAILED(rv) && !aFellBackToPNG) {
-    // Try image/png instead.
-    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
-    aFellBackToPNG = true;
-    encoderType.AssignLiteral("image/png");
-    goto try_again;
-  }
-
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  imgStream.forget(aStream);
-  return NS_OK;
+  return ImageEncoder::ExtractData(aType,
+                                   aOptions,
+                                   GetSize(),
+                                   mCurrentContext,
+                                   aStream);
 }
 
 nsresult
 HTMLCanvasElement::ParseParams(JSContext* aCx,
                                const nsAString& aType,
                                const JS::Value& aEncoderOptions,
                                nsAString& aParams,
                                bool* usingCustomParseOptions)
@@ -517,18 +442,16 @@ HTMLCanvasElement::ParseParams(JSContext
 }
 
 nsresult
 HTMLCanvasElement::ToDataURLImpl(JSContext* aCx,
                                  const nsAString& aMimeType,
                                  const JS::Value& aEncoderOptions,
                                  nsAString& aDataURL)
 {
-  bool fallbackToPNG = false;
-
   nsIntSize size = GetWidthHeight();
   if (size.height == 0 || size.width == 0) {
     aDataURL = NS_LITERAL_STRING("data:,");
     return NS_OK;
   }
 
   nsAutoString type;
   nsresult rv = nsContentUtils::ASCIIToLower(aMimeType, type);
@@ -539,43 +462,37 @@ HTMLCanvasElement::ToDataURLImpl(JSConte
   nsAutoString params;
   bool usingCustomParseOptions;
   rv = ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   nsCOMPtr<nsIInputStream> stream;
-  rv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG);
+  rv = ExtractData(type, params, getter_AddRefs(stream));
 
   // If there are unrecognized custom parse options, we should fall back to
   // the default values for the encoder without any options at all.
   if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
-    fallbackToPNG = false;
-    rv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG);
+    rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
   }
 
   NS_ENSURE_SUCCESS(rv, rv);
 
   // build data URL string
-  if (fallbackToPNG)
-    aDataURL = NS_LITERAL_STRING("data:image/png;base64,");
-  else
-    aDataURL = NS_LITERAL_STRING("data:") + type +
-      NS_LITERAL_STRING(";base64,");
+  aDataURL = NS_LITERAL_STRING("data:") + type + NS_LITERAL_STRING(";base64,");
 
   uint64_t count;
   rv = stream->Available(&count);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(count <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   return Base64EncodeInputStream(stream, aDataURL, (uint32_t)count, aDataURL.Length());
 }
 
-// XXXkhuey the encoding should be off the main thread, but we're lazy.
 NS_IMETHODIMP
 HTMLCanvasElement::ToBlob(nsIFileCallback* aCallback,
                           const nsAString& aType,
                           const JS::Value& aEncoderOptions,
                           JSContext* aCx)
 {
   // do a trust check if this is a write-only canvas
   if (mWriteOnly && !nsContentUtils::IsCallerChrome()) {
@@ -594,53 +511,34 @@ HTMLCanvasElement::ToBlob(nsIFileCallbac
 
   nsAutoString params;
   bool usingCustomParseOptions;
   rv = ParseParams(aCx, type, aEncoderOptions, params, &usingCustomParseOptions);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  bool fallbackToPNG = false;
+  JSContext* cx = nsContentUtils::GetCurrentJSContext();
+  nsCOMPtr<nsIThread> currentThread = NS_GetCurrentThread();
 
-  nsCOMPtr<nsIInputStream> stream;
-  rv = ExtractData(type, params, getter_AddRefs(stream), fallbackToPNG);
-  // If there are unrecognized custom parse options, we should fall back to
-  // the default values for the encoder without any options at all.
-  if (rv == NS_ERROR_INVALID_ARG && usingCustomParseOptions) {
-    fallbackToPNG = false;
-    rv = ExtractData(type, EmptyString(), getter_AddRefs(stream), fallbackToPNG);
-  }
-
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (fallbackToPNG) {
-    type.AssignLiteral("image/png");
+  uint8_t* imageBuffer = nullptr;
+  int32_t format = 0;
+  if (mCurrentContext) {
+    mCurrentContext->GetImageBuffer(&imageBuffer, &format);
   }
 
-  uint64_t imgSize;
-  rv = stream->Available(&imgSize);
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
-
-  void* imgData = nullptr;
-  rv = NS_ReadInputStreamToBuffer(stream, &imgData, imgSize);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // The DOMFile takes ownership of the buffer
-  nsRefPtr<nsDOMMemoryFile> blob =
-    new nsDOMMemoryFile(imgData, imgSize, type);
-
-  JSContext* cx = nsContentUtils::GetCurrentJSContext();
-  if (cx) {
-    JS_updateMallocCounter(cx, imgSize);
-  }
-
-  nsRefPtr<ToBlobRunnable> runnable = new ToBlobRunnable(aCallback, blob);
-  return NS_DispatchToCurrentThread(runnable);
+  return ImageEncoder::ExtractDataAsync(type,
+                                        params,
+                                        usingCustomParseOptions,
+                                        imageBuffer,
+                                        format,
+                                        GetSize(),
+                                        mCurrentContext,
+                                        cx,
+                                        aCallback);
 }
 
 already_AddRefed<nsIDOMFile>
 HTMLCanvasElement::MozGetAsFile(const nsAString& aName,
                                 const nsAString& aType,
                                 ErrorResult& aRv)
 {
   nsCOMPtr<nsIDOMFile> file;
@@ -664,28 +562,21 @@ HTMLCanvasElement::MozGetAsFile(const ns
   return MozGetAsFileImpl(aName, aType, aResult);
 }
 
 nsresult
 HTMLCanvasElement::MozGetAsFileImpl(const nsAString& aName,
                                     const nsAString& aType,
                                     nsIDOMFile** aResult)
 {
-  bool fallbackToPNG = false;
-
   nsCOMPtr<nsIInputStream> stream;
-  nsresult rv = ExtractData(aType, EmptyString(), getter_AddRefs(stream),
-                            fallbackToPNG);
+  nsAutoString type(aType);
+  nsresult rv = ExtractData(type, EmptyString(), getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsAutoString type(aType);
-  if (fallbackToPNG) {
-    type.AssignLiteral("image/png");
-  }
-
   uint64_t imgSize;
   rv = stream->Available(&imgSize);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(imgSize <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG);
 
   void* imgData = nullptr;
   rv = NS_ReadInputStreamToBuffer(stream, &imgData, (uint32_t)imgSize);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/content/html/content/src/Makefile.in
+++ b/content/html/content/src/Makefile.in
@@ -17,12 +17,13 @@ INCLUDES	+= \
 		-I$(srcdir)/../../../../layout/tables \
 		-I$(srcdir)/../../../../layout/xul/base/src \
 		-I$(srcdir)/../../../../layout/generic \
 		-I$(srcdir)/../../../../dom/base \
 		-I$(srcdir)/../../../../editor/libeditor/base \
 		-I$(srcdir)/../../../../editor/libeditor/text \
 		-I$(srcdir)/../../../../editor/txmgr/src \
 		-I$(srcdir)/../../../../netwerk/base/src \
+		-I$(srcdir)/../../../../content/canvas/src \
 		-I$(srcdir) \
 		-I$(topsrcdir)/xpcom/ds \
 		-I$(topsrcdir)/content/media/ \
 		$(NULL)
--- a/content/xbl/crashtests/472260-1.xhtml
+++ b/content/xbl/crashtests/472260-1.xhtml
@@ -7,17 +7,17 @@
 </bindings>
 
 <script type="text/javascript">
 // <![CDATA[
 
 function boom()
 {
   var bo = document.getElementById("bo");
-  var anon = document.getAnonymousNodes(bo)[0];
+  var anon = SpecialPowers.wrap(document).getAnonymousNodes(bo)[0];
 
   bo.style.MozBinding = "url(#bar)";
 
   var fr = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
   fr.setAttribute("src", "javascript:void 0;"); 
   anon.appendChild(fr);
   
   document.documentElement.removeAttribute("class");
--- a/content/xbl/test/file_bug379959_cross.html
+++ b/content/xbl/test/file_bug379959_cross.html
@@ -11,15 +11,15 @@
   -moz-binding: url(http://example.com/tests/content/xbl/test/file_bug379959_xbl.xml#xbltest);
 }
 </style>
 <body>
 <div id="div1"></div>
 <div id="div2"></div>
 <script>
 onload = function() {
-  nodes = document.getAnonymousNodes(document.getElementById('div1'));
+  nodes = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('div1'));
   parent.postMessage(nodes ? nodes.length : 0, "http://mochi.test:8888");
-  nodes = document.getAnonymousNodes(document.getElementById('div2'));
+  nodes = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('div2'));
   parent.postMessage(nodes ? nodes.length : 0, "http://mochi.test:8888");
 }
 </script>
 </html>
--- a/content/xbl/test/file_bug379959_data.html
+++ b/content/xbl/test/file_bug379959_data.html
@@ -6,13 +6,13 @@
   color: green;
   -moz-binding: url(data:text/xml;charset=utf-8,%3C%3Fxml%20version%3D%221.0%22%3F%3E%0A%3Cbindings%20id%3D%22xbltestBindings%22%20xmlns%3D%22http%3A//www.mozilla.org/xbl%22%3E%0A%20%20%3Cbinding%20id%3D%22xbltest%22%3E%3Ccontent%3EPASS%3C/content%3E%3C/binding%3E%0A%3C/bindings%3E%0A);
 }
 </style>
 <body>
 <div id="d"></div>
 <script>
 onload = function() {
-  nodes = document.getAnonymousNodes(document.getElementById('d'));
+  nodes = SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('d'));
   parent.postMessage(nodes ? nodes.length : 0, "http://mochi.test:8888");
 }
 </script>
 </html>
--- a/content/xbl/test/file_bug591198_inner.html
+++ b/content/xbl/test/file_bug591198_inner.html
@@ -17,17 +17,17 @@ function sendResults() {
 
   ps = document.getElementsByTagName('span');
   for (var i = 0; i < ps.length; i++) {
     res.widths.push(ps[i].offsetWidth);
   }
   
   try {
     res.anonChildCount =
-      document.getAnonymousNodes(document.getElementById('b')).length;
+      SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('b')).length;
   }
   catch (ex) {}
 
   parent.postMessage(JSON.stringify(res), "*");
 }
     </script>
   </head>
   <body onload="sendResults();">
--- a/content/xbl/test/test_bug378866.xhtml
+++ b/content/xbl/test/test_bug378866.xhtml
@@ -28,17 +28,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 /** Test for Bug 378866 **/
 
 function runTest() {
-  var anon = document.getAnonymousNodes(document.getElementById('grandparent'));
+  var anon = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(document.getElementById('grandparent')));
   var child = document.getElementById('child');
   var insertionPoint = anon[0].childNodes[0];
   insertionPoint.parentNode.removeChild(insertionPoint);
   child.appendChild(insertionPoint);
 
   var e = document.createEvent("Event");
   e.initEvent("foo", true, true);
   child.dispatchEvent(e);
--- a/content/xbl/test/test_bug468210.xhtml
+++ b/content/xbl/test/test_bug468210.xhtml
@@ -27,17 +27,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <![CDATA[
 
 /** Test for Bug 468210 **/
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   var div = $("d");
   var n = document.anchors.length;
   is(n, 1, "Unexpected number of anchors");
-  var anon = document.getAnonymousNodes(div)[0];
+  var anon = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(div))[0];
   is(anon instanceof HTMLSpanElement, true, "Unexpected node");
   is(anon.parentNode, div, "Unexpected parent");
   document.body.appendChild(div);
   is(anon.parentNode, null, "Parent should have become null");
   // An attr set to test notifications
   anon.setAttribute("h", "i");
 });
 addLoadEvent(SimpleTest.finish);
--- a/content/xslt/tests/mochitest/test_bug319374.xhtml
+++ b/content/xslt/tests/mochitest/test_bug319374.xhtml
@@ -26,45 +26,48 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 319374 **/
 
   function testChangesInAnonymousTree() {
     // Test 1: Make sure that modifying anonymous content doesn't 
     //         cause non-anonymous XPath result to throw exceptions..
     var counter = 0;
     var error = null;
+    function getAnonymousNodes(e) {
+      return SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(e));
+    }
     try {
       var xp = new XPathEvaluator();
       var result = xp.evaluate("*",
                                document.getElementById('content'),
                                null,
                                SpecialPowers.Ci.nsIDOMXPathResult.
                                  UNORDERED_NODE_ITERATOR_TYPE,
                                null);
       var res = null;
       while (res = result.iterateNext()) {
         ++counter; 
-        var anon = document.getAnonymousNodes(res);
+        var anon = getAnonymousNodes(res);
         anon[0].removeChild(anon[0].firstChild); // Removing a child node
         anon[0].removeAttribute("attr1"); // Removing an attribute
         anon[1].firstChild.data = "anon text changed" // Modifying text data
       }
     } catch (e) {
       error = e;
     }
     ok(!error, error);
     ok(counter == 3, "XPathEvaluator should have found 3 elements.")
 
     // Test 2: If the context node is in anonymous content, changing some
     //         other anonymous tree shouldn't cause XPath result to throw.
     var anonAttr1 =
-      document.getAnonymousNodes(document.getElementById('content').
+      getAnonymousNodes(document.getElementById('content').
         firstChild)[0].getAttributeNode('attr');
     var anonAttr2 =
-      document.getAnonymousNodes(document.getElementById('content').
+      getAnonymousNodes(document.getElementById('content').
         lastChild)[0].getAttributeNode('attr');
     var resultAttr = null;
     try {
       var xp2 = xp.evaluate(".",
                             anonAttr1,
                             null,
                             SpecialPowers.Ci.nsIDOMXPathResult.
                               UNORDERED_NODE_ITERATOR_TYPE,
--- a/content/xul/content/crashtests/326864-1.xul
+++ b/content/xul/content/crashtests/326864-1.xul
@@ -1,23 +1,27 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 
 
 <script>
 
+function getAnonymousNodes(e) {
+  return SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(e));
+}
+
 function init()
 {
   var tt = document.getElementById("textbox");
-  var hb = document.getAnonymousNodes(tt)[0]; // hbox
-  var men = document.getAnonymousNodes(hb)[1]; // menupopup
+  var hb = getAnonymousNodes(tt)[0]; // hbox
+  var men = getAnonymousNodes(hb)[1]; // menupopup
   var menitem = men.childNodes[0]; // menuitem
-  var hb2 = document.getAnonymousNodes(menitem)[1]; // hbox
+  var hb2 = getAnonymousNodes(menitem)[1]; // hbox
   var label2 = hb2.childNodes[0]; // label
 
   men.menu = null;
   label2.click();
 }
 
 window.addEventListener("load", init, false);
 
--- a/content/xul/content/crashtests/384740-1.xul
+++ b/content/xul/content/crashtests/384740-1.xul
@@ -7,17 +7,17 @@
 <script>
 
 function boom()
 {
   var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
   var scrollbar = document.createElementNS(XUL_NS, 'scrollbar');
   document.documentElement.appendChild(scrollbar);
-  var sbb = document.getAnonymousNodes(scrollbar)[0];
+  var sbb = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(scrollbar))[0];
   var action = document.createElementNS(XUL_NS, 'action');
   action.setAttribute('datasources', "");
   sbb.appendChild(action);
 }
 
 </script>
 
 </window>
--- a/dom/base/crashtests/404869-1.xul
+++ b/dom/base/crashtests/404869-1.xul
@@ -5,17 +5,17 @@
   <binding id="empty"><content></content></binding>
 </bindings>
 
 <script type="text/javascript">
 
 function boom()
 {
   var menupopup = document.getElementById("menupopup");
-  var x = document.getAnonymousNodes(menupopup)[0];
+  var x = SpecialPowers.wrap(document).getAnonymousNodes(menupopup)[0];
   menupopup.style.MozBinding = "url('#empty')";
   for (var ppp in x) {
   }
 }
 
 </script>
 
 <menupopup id="menupopup"/>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -21,16 +21,18 @@
 #include "nsIXPConnect.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XPCQuickStubs.h"
 #include "XrayWrapper.h"
 #include "nsPrintfCString.h"
 #include "prprf.h"
 
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMErrorBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/HTMLSharedObjectElement.h"
 #include "mozilla/dom/HTMLEmbedElementBinding.h"
 #include "mozilla/dom/HTMLAppletElementBinding.h"
 
 namespace mozilla {
 namespace dom {
@@ -181,16 +183,51 @@ ErrorResult::ReportJSException(JSContext
     JS_SetPendingException(cx, mJSException);
   }
   // If JS_WrapValue failed, not much we can do about it...  No matter
   // what, go ahead and unroot mJSException.
   JS_RemoveValueRoot(cx, &mJSException);
 }
 
 void
+ErrorResult::ReportJSExceptionFromJSImplementation(JSContext* aCx)
+{
+  MOZ_ASSERT(!mMightHaveUnreportedJSException,
+             "Why didn't you tell us you planned to handle JS exceptions?");
+
+  dom::DOMError* domError;
+  nsresult rv = UNWRAP_OBJECT(DOMError, aCx, &mJSException.toObject(),
+                              domError);
+  if (NS_FAILED(rv)) {
+    // Unwrapping really shouldn't fail here, if mExceptionHandling is set to
+    // eRethrowContentExceptions then the CallSetup destructor only stores an
+    // exception if it unwraps to DOMError. If we reach this then either
+    // mExceptionHandling wasn't set to eRethrowContentExceptions and we
+    // shouldn't be calling ReportJSExceptionFromJSImplementation or something
+    // went really wrong.
+    NS_RUNTIMEABORT("We stored a non-DOMError exception!");
+  }
+
+  nsString message;
+  domError->GetMessage(message);
+
+  JSErrorReport errorReport;
+  memset(&errorReport, 0, sizeof(JSErrorReport));
+  errorReport.errorNumber = JSMSG_USER_DEFINED_ERROR;
+  errorReport.ucmessage = message.get();
+  errorReport.exnType = JSEXN_ERR;
+  JS_ThrowReportedError(aCx, nullptr, &errorReport);
+  JS_RemoveValueRoot(aCx, &mJSException);
+  
+  // We no longer have a useful exception but we do want to signal that an error
+  // occured.
+  mResult = NS_ERROR_FAILURE;
+}
+
+void
 ErrorResult::StealJSException(JSContext* cx,
                               JS::MutableHandle<JS::Value> value)
 {
   MOZ_ASSERT(!mMightHaveUnreportedJSException,
              "Must call WouldReportJSException unconditionally in all codepaths that might call StealJSException");
   MOZ_ASSERT(IsJSException(), "No exception to steal");
 
   value.set(mJSException);
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -84,24 +84,29 @@ Throw(JSContext* cx, nsresult rv)
   }
   return false;
 }
 
 template<bool mainThread>
 inline bool
 ThrowMethodFailedWithDetails(JSContext* cx, ErrorResult& rv,
                              const char* ifaceName,
-                             const char* memberName)
+                             const char* memberName,
+                             bool reportJSContentExceptions = false)
 {
   if (rv.IsTypeError()) {
     rv.ReportTypeError(cx);
     return false;
   }
   if (rv.IsJSException()) {
-    rv.ReportJSException(cx);
+    if (reportJSContentExceptions) {
+      rv.ReportJSExceptionFromJSImplementation(cx);
+    } else {
+      rv.ReportJSException(cx);
+    }
     return false;
   }
   if (rv.IsNotEnoughArgsError()) {
     rv.ReportNotEnoughArgsError(cx, ifaceName, memberName);
   }
   return Throw<mainThread>(cx, rv.ErrorCode());
 }
 
@@ -178,18 +183,18 @@ UnwrapDOMObjectToISupports(JSObject* aOb
 inline bool
 IsDOMObject(JSObject* obj)
 {
   js::Class* clasp = js::GetObjectClass(obj);
   return IsDOMClass(clasp) || IsDOMProxy(obj, clasp);
 }
 
 #define UNWRAP_OBJECT(Interface, cx, obj, value)                             \
-  UnwrapObject<prototypes::id::Interface,                                    \
-               mozilla::dom::Interface##Binding::NativeType>(cx, obj, value)
+  mozilla::dom::UnwrapObject<mozilla::dom::prototypes::id::Interface,        \
+    mozilla::dom::Interface##Binding::NativeType>(cx, obj, value)
 
 // Some callers don't want to set an exception when unwrapping fails
 // (for example, overload resolution uses unwrapping to tell what sort
 // of thing it's looking at).
 // U must be something that a T* can be assigned to (e.g. T* or an nsRefPtr<T>).
 template <prototypes::ID PrototypeID, class T, typename U>
 MOZ_ALWAYS_INLINE nsresult
 UnwrapObject(JSContext* cx, JSObject* obj, U& value)
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -1,15 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "mozilla/dom/CallbackObject.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/DOMError.h"
+#include "mozilla/dom/DOMErrorBinding.h"
 #include "jsfriendapi.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsCxPusher.h"
 #include "nsIScriptSecurityManager.h"
@@ -35,18 +38,20 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 CallbackObject::CallSetup::CallSetup(JS::Handle<JSObject*> aCallback,
                                      ErrorResult& aRv,
-                                     ExceptionHandling aExceptionHandling)
+                                     ExceptionHandling aExceptionHandling,
+                                     JSCompartment* aCompartment)
   : mCx(nullptr)
+  , mCompartment(aCompartment)
   , mErrorResult(aRv)
   , mExceptionHandling(aExceptionHandling)
 {
   // We need to produce a useful JSContext here.  Ideally one that the callback
   // is in some sense associated with, so that we can sort of treat it as a
   // "script entry point".  Though once we actually have script entry points,
   // we'll need to do the script entry point bits once we have an actual
   // callable.
@@ -118,35 +123,65 @@ CallbackObject::CallSetup::CallSetup(JS:
 
   // Enter the compartment of our callback, so we can actually work with it.
   mAc.construct(cx, aCallback);
 
   // And now we're ready to go.
   mCx = cx;
 
   // Make sure the JS engine doesn't report exceptions we want to re-throw
-  if (mExceptionHandling == eRethrowExceptions) {
+  if (mExceptionHandling == eRethrowContentExceptions ||
+      mExceptionHandling == eRethrowExceptions) {
     mSavedJSContextOptions = JS_GetOptions(cx);
     JS_SetOptions(cx, mSavedJSContextOptions | JSOPTION_DONT_REPORT_UNCAUGHT);
   }
 }
 
+bool
+CallbackObject::CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException)
+{
+  if (mExceptionHandling == eRethrowExceptions) {
+    return true;
+  }
+
+  MOZ_ASSERT(mExceptionHandling == eRethrowContentExceptions);
+
+  // For eRethrowContentExceptions we only want to throw an exception if the
+  // object that was thrown is a DOMError object in the caller compartment
+  // (which we stored in mCompartment).
+
+  if (!aException.isObject()) {
+    return false;
+  }
+
+  JS::Rooted<JSObject*> obj(mCx, &aException.toObject());
+  obj = js::UncheckedUnwrap(obj, /* stopAtOuter = */ false);
+  if (js::GetObjectCompartment(obj) != mCompartment) {
+    return false;
+  }
+
+  DOMError* domError;
+  return NS_SUCCEEDED(UNWRAP_OBJECT(DOMError, mCx, obj, domError));
+}
+
 CallbackObject::CallSetup::~CallSetup()
 {
   // First things first: if we have a JSContext, report any pending
   // errors on it, unless we were told to re-throw them.
   if (mCx) {
     bool dealtWithPendingException = false;
-    if (mExceptionHandling == eRethrowExceptions) {
+    if (mExceptionHandling == eRethrowContentExceptions ||
+        mExceptionHandling == eRethrowExceptions) {
       // Restore the old context options
       JS_SetOptions(mCx, mSavedJSContextOptions);
       mErrorResult.MightThrowJSException();
       if (JS_IsExceptionPending(mCx)) {
         JS::Rooted<JS::Value> exn(mCx);
-        if (JS_GetPendingException(mCx, exn.address())) {
+        if (JS_GetPendingException(mCx, exn.address()) &&
+            ShouldRethrowException(exn)) {
           mErrorResult.ThrowJSException(mCx, exn);
           JS_ClearPendingException(mCx);
           dealtWithPendingException = true;
         }
       }
     }
 
     if (!dealtWithPendingException) {
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -73,17 +73,23 @@ public:
   JS::Handle<JSObject*> CallbackPreserveColor() const
   {
     // Calling fromMarkedLocation() is safe because we trace our mCallback, and
     // because the value of mCallback cannot change after if has been set.
     return JS::Handle<JSObject*>::fromMarkedLocation(mCallback.address());
   }
 
   enum ExceptionHandling {
+    // Report any exception and don't throw it to the caller code.
     eReportExceptions,
+    // Throw an exception to the caller code if the thrown exception is a
+    // binding object for a DOMError from the caller's scope, otherwise report
+    // it.
+    eRethrowContentExceptions,
+    // Throw any exception to the caller code.
     eRethrowExceptions
   };
 
 protected:
   explicit CallbackObject(CallbackObject* aCallbackObject)
   {
     Init(aCallbackObject->mCallback);
   }
@@ -113,32 +119,41 @@ protected:
   {
     /**
      * A class that performs whatever setup we need to safely make a
      * call while this class is on the stack, After the constructor
      * returns, the call is safe to make if GetContext() returns
      * non-null.
      */
   public:
+    // If aExceptionHandling == eRethrowContentExceptions then aCompartment
+    // needs to be set to the caller's compartment.
     CallSetup(JS::Handle<JSObject*> aCallable, ErrorResult& aRv,
-              ExceptionHandling aExceptionHandling);
+              ExceptionHandling aExceptionHandling,
+              JSCompartment* aCompartment = nullptr);
     ~CallSetup();
 
     JSContext* GetContext() const
     {
       return mCx;
     }
 
   private:
     // We better not get copy-constructed
     CallSetup(const CallSetup&) MOZ_DELETE;
 
+    bool ShouldRethrowException(JS::Handle<JS::Value> aException);
+
     // Members which can go away whenever
     JSContext* mCx;
 
+    // Caller's compartment. This will only have a sensible value if
+    // mExceptionHandling == eRethrowContentExceptions.
+    JSCompartment* mCompartment;
+
     // And now members whose construction/destruction order we need to control.
 
     // Put our nsAutoMicrotask first, so it gets destroyed after everything else
     // is gone
     nsAutoMicroTask mMt;
 
     nsCxPusher mCxPusher;
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -4802,25 +4802,36 @@ if (global.Failed()) {
         # For JS-implemented interfaces we do not want to base the
         # needsCx decision on the types involved, just on our extended
         # attributes.
         needsCx = needCx(returnType, arguments, self.extendedAttributes,
                          descriptor, not descriptor.interface.isJSImplemented())
         if needsCx and not (static and descriptor.workers):
             argsPre.append("cx")
 
-        needsUnwrap = isConstructor
-        if needScopeObject(returnType, arguments, self.extendedAttributes,
-                           descriptor, descriptor.wrapperCache,
-                           not descriptor.interface.isJSImplemented()):
+        needsUnwrap = False
+        if isConstructor:
+            needsUnwrap = True
+            needsUnwrappedVar = False
+            unwrappedVar = "obj"
+        elif descriptor.interface.isJSImplemented():
+            needsUnwrap = True
+            needsUnwrappedVar = True
+            argsPre.append("js::GetObjectCompartment(unwrappedObj.empty() ? obj : unwrappedObj.ref())")
+        elif needScopeObject(returnType, arguments, self.extendedAttributes,
+                             descriptor, descriptor.wrapperCache, True):
+            needsUnwrap = True
+            needsUnwrappedVar = True
+            argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()")
+
+        if needsUnwrap and needsUnwrappedVar:
             # We cannot assign into obj because it's a Handle, not a
             # MutableHandle, so we need a separate Rooted.
             cgThings.append(CGGeneric("Maybe<JS::Rooted<JSObject*> > unwrappedObj;"))
-            argsPre.append("unwrappedObj.empty() ? obj : unwrappedObj.ref()")
-            needsUnwrap = True
+            unwrappedVar = "unwrappedObj.ref()"
 
         if idlNode.isMethod() and idlNode.isLegacycaller():
             # If we can have legacycaller with identifier, we can't
             # just use the idlNode to determine whether we're
             # generating code for the legacycaller or not.
             assert idlNode.isIdentifierLess()
             # Pass in our thisVal
             argsPre.append("args.thisv()")
@@ -4839,27 +4850,27 @@ if (global.Failed()) {
                                              invalidEnumValueFatal=not setter,
                                              allowTreatNonCallableAsNull=setter,
                                              lenientFloatCode=lenientFloatCode) for
                          i in range(argConversionStartsAt, self.argCount)])
 
         if needsUnwrap:
             # Something depends on having the unwrapped object, so unwrap it now.
             xraySteps = []
-            if not isConstructor:
+            if needsUnwrappedVar:
                 xraySteps.append(
                     CGGeneric("unwrappedObj.construct(cx, obj);"))
 
             # XXXkhuey we should be able to MOZ_ASSERT that ${obj} is
             # not null.
             xraySteps.append(
                 CGGeneric(string.Template("""${obj} = js::CheckedUnwrap(${obj});
 if (!${obj}) {
   return false;
-}""").substitute({ 'obj' : 'obj' if isConstructor else 'unwrappedObj.ref()' })))
+}""").substitute({ 'obj' : unwrappedVar })))
             if isConstructor:
                 # If we're called via an xray, we need to enter the underlying
                 # object's compartment and then wrap up all of our arguments into
                 # that compartment as needed.  This is all happening after we've
                 # already done the conversions from JS values to WebIDL (C++)
                 # values, so we only need to worry about cases where there are 'any'
                 # or 'object' types, or other things that we represent as actual
                 # JSAPI types, present.  Effectively, we're emulating a
@@ -4910,20 +4921,24 @@ if (!${obj}) {
         except MethodNotCreatorError, err:
             assert not isCreator
             raise TypeError("%s being returned from non-creator method or property %s.%s" %
                             (err.typename,
                              self.descriptor.interface.identifier.name,
                              self.idlNode.identifier.name))
 
     def getErrorReport(self):
-        return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s");'
+        jsImplemented = ""
+        if self.descriptor.interface.isJSImplemented():
+            jsImplemented = ", true"
+        return CGGeneric('return ThrowMethodFailedWithDetails<%s>(cx, rv, "%s", "%s"%s);'
                          % (toStringBool(not self.descriptor.workers),
                             self.descriptor.interface.identifier.name,
-                            self.idlNode.identifier.name))
+                            self.idlNode.identifier.name,
+                            jsImplemented))
 
     def define(self):
         return (self.cgRoot.define() + "\n" + self.wrap_return_value())
 
 class CGSwitch(CGList):
     """
     A class to generate code for a switch statement.
 
@@ -9504,49 +9519,78 @@ class CGExampleRoot(CGThing):
 
     def define(self):
         return self.root.define()
 
 
 def jsImplName(name):
     return name + "JSImpl"
 
-class CGJSImplMethod(CGNativeMember):
+class CGJSImplMember(CGNativeMember):
+    """
+    Base class for generating code for the members of the implementation class
+    for a JS-implemented WebIDL interface.
+    """
+    def __init__(self, descriptorProvider, member, name, signature,
+                 extendedAttrs, breakAfter=True, passJSBitsAsNeeded=True,
+                 visibility="public", jsObjectsArePtr=False,
+                 variadicIsSequence=False):
+        CGNativeMember.__init__(self, descriptorProvider, member, name,
+                                signature, extendedAttrs, breakAfter=breakAfter,
+                                passJSBitsAsNeeded=passJSBitsAsNeeded,
+                                visibility=visibility,
+                                jsObjectsArePtr=jsObjectsArePtr,
+                                variadicIsSequence=variadicIsSequence)
+        self.body = self.getImpl()
+
+    def getArgs(self, returnType, argList):
+        args = CGNativeMember.getArgs(self, returnType, argList)
+        args.insert(0, Argument("JSCompartment*", "aCompartment"))
+        return args
+
+class CGJSImplMethod(CGJSImplMember):
+    """
+    Class for generating code for the methods for a JS-implemented WebIDL
+    interface.
+    """
     def __init__(self, descriptor, method, signature, isConstructor, breakAfter=True):
-        CGNativeMember.__init__(self, descriptor, method,
+        self.signature = signature
+        self.descriptor = descriptor
+        self.isConstructor = isConstructor
+        CGJSImplMember.__init__(self, descriptor, method,
                                 CGSpecializedMethod.makeNativeName(descriptor,
                                                                    method),
                                 signature,
                                 descriptor.getExtendedAttributes(method),
                                 breakAfter=breakAfter,
                                 variadicIsSequence=True,
                                 passJSBitsAsNeeded=False)
-        self.signature = signature
-        self.descriptor = descriptor
-        if isConstructor:
-            self.body = self.getConstructorImpl()
-        else:
-            self.body = self.getImpl()
+
+    def getArgs(self, returnType, argList):
+        if self.isConstructor:
+            # Skip the JSCompartment bits for constructors; it's handled
+            # manually in getImpl.
+            return CGNativeMember.getArgs(self, returnType, argList)
+        return CGJSImplMember.getArgs(self, returnType, argList)
 
     def getImpl(self):
-        callbackArgs = [arg.name for arg in self.getArgs(self.signature[0], self.signature[1])]
-        return 'return mImpl->%s(%s);' % (self.name, ", ".join(callbackArgs))
-
-    def getConstructorImpl(self):
+        args = self.getArgs(self.signature[0], self.signature[1])
+        if not self.isConstructor:
+            return 'return mImpl->%s(%s);' % (self.name, ", ".join(arg.name for arg in args))
+
         assert self.descriptor.interface.isJSImplemented()
         if self.name != 'Constructor':
             raise TypeError("Named constructors are not supported for JS implemented WebIDL. See bug 851287.")
         if len(self.signature[1]) != 0:
-            args = self.getArgs(self.signature[0], self.signature[1])
             # The first two arguments to the constructor implementation are not
             # arguments to the WebIDL constructor, so don't pass them to __Init()
             assert args[0].argType == 'const GlobalObject&'
             assert args[1].argType == 'JSContext*'
-            args = args[2:]
-            constructorArgs = [arg.name for arg in args]
+            constructorArgs = [arg.name for arg in args[2:]]
+            constructorArgs.insert(0, "js::GetObjectCompartment(scopeObj)")
             initCall = """
 // Wrap the object before calling __Init so that __DOM_IMPL__ is available.
 nsCOMPtr<nsIGlobalObject> globalHolder = do_QueryInterface(window);
 JS::Rooted<JSObject*> scopeObj(cx, globalHolder->GetGlobalJSObject());
 JS::Rooted<JS::Value> wrappedVal(cx);
 if (!WrapNewBindingObject(cx, scopeObj, impl, &wrappedVal)) {
   MOZ_ASSERT(JS_IsExceptionPending(cx));
   aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -9583,42 +9627,48 @@ def genConstructorBody(descriptor, initC
 
 # We're always fallible
 def callbackGetterName(attr):
     return "Get" + MakeNativeName(attr.identifier.name)
 
 def callbackSetterName(attr):
     return "Set" + MakeNativeName(attr.identifier.name)
 
-class CGJSImplGetter(CGNativeMember):
+class CGJSImplGetter(CGJSImplMember):
+    """
+    Class for generating code for the getters of attributes for a JS-implemented
+    WebIDL interface.
+    """
     def __init__(self, descriptor, attr):
-        CGNativeMember.__init__(self, descriptor, attr,
+        CGJSImplMember.__init__(self, descriptor, attr,
                                 CGSpecializedGetter.makeNativeName(descriptor,
                                                                    attr),
                                 (attr.type, []),
                                 descriptor.getExtendedAttributes(attr,
                                                                  getter=True),
                                 passJSBitsAsNeeded=False)
-        self.body = self.getImpl()
 
     def getImpl(self):
         callbackArgs = [arg.name for arg in self.getArgs(self.member.type, [])]
         return 'return mImpl->%s(%s);' % (callbackGetterName(self.member), ", ".join(callbackArgs))
 
-class CGJSImplSetter(CGNativeMember):
+class CGJSImplSetter(CGJSImplMember):
+    """
+    Class for generating code for the setters of attributes for a JS-implemented
+    WebIDL interface.
+    """
     def __init__(self, descriptor, attr):
-        CGNativeMember.__init__(self, descriptor, attr,
+        CGJSImplMember.__init__(self, descriptor, attr,
                                 CGSpecializedSetter.makeNativeName(descriptor,
                                                                    attr),
                                 (BuiltinTypes[IDLBuiltinType.Types.void],
                                  [FakeArgument(attr.type, attr)]),
                                 descriptor.getExtendedAttributes(attr,
                                                                  setter=True),
                                 passJSBitsAsNeeded=False)
-        self.body = self.getImpl()
 
     def getImpl(self):
         callbackArgs = [arg.name for arg in self.getArgs(BuiltinTypes[IDLBuiltinType.Types.void],
                                                          [FakeArgument(self.member.type, self.member)])]
         return 'mImpl->%s(%s);' % (callbackSetterName(self.member), ", ".join(callbackArgs))
 
 class CGJSImplClass(CGBindingImplClass):
     def __init__(self, descriptor):
@@ -9924,21 +9974,23 @@ class FakeMember():
         # Claim to be a [Creator] so we can avoid the "mark this
         # resultNotAddRefed" comments CGNativeMember codegen would
         # otherwise stick in.
         if name == "Creator":
             return True
         return None
 
 class CallbackMember(CGNativeMember):
-    def __init__(self, sig, name, descriptorProvider, needThisHandling):
+    def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
         """
         needThisHandling is True if we need to be able to accept a specified
         thisObj, False otherwise.
         """
+        assert not rethrowContentException or not needThisHandling
+
         self.retvalType = sig[0]
         self.originalSig = sig
         args = sig[1]
         self.argCount = len(args)
         if self.argCount > 0:
             # Check for variadic arguments
             lastArg = args[self.argCount-1]
             if lastArg.variadic:
@@ -9946,16 +9998,17 @@ class CallbackMember(CGNativeMember):
                     "(%d - 1) + %s.Length()" % (self.argCount,
                                                 lastArg.identifier.name))
             else:
                 self.argCountStr = "%d" % self.argCount
         self.needThisHandling = needThisHandling
         # If needThisHandling, we generate ourselves as private and the caller
         # will handle generating public versions that handle the "this" stuff.
         visibility = "private" if needThisHandling else "public"
+        self.rethrowContentException = rethrowContentException
         # We don't care, for callback codegen, whether our original member was
         # a method or attribute or whatnot.  Just always pass FakeMember()
         # here.
         CGNativeMember.__init__(self, descriptorProvider, FakeMember(),
                                 name, (self.retvalType, args),
                                 extendedAttrs={},
                                 passJSBitsAsNeeded=False,
                                 visibility=visibility,
@@ -10104,34 +10157,47 @@ class CallbackMember(CGNativeMember):
             default = " " + default
         return default
 
     def getArgs(self, returnType, argList):
         args = CGNativeMember.getArgs(self, returnType, argList)
         if not self.needThisHandling:
             # Since we don't need this handling, we're the actual method that
             # will be called, so we need an aRethrowExceptions argument.
-            return args + [Argument("ExceptionHandling", "aExceptionHandling",
-                                    "eReportExceptions")]
+            if self.rethrowContentException:
+                args.insert(0, Argument("JSCompartment*", "aCompartment"))
+            else:
+                args.append(Argument("ExceptionHandling", "aExceptionHandling",
+                                     "eReportExceptions"))
+            return args
         # We want to allow the caller to pass in a "this" object, as
         # well as a JSContext.
         return [Argument("JSContext*", "cx"),
                 Argument("JS::Handle<JSObject*>", "aThisObj")] + args
 
     def getCallSetup(self):
         if self.needThisHandling:
             # It's been done for us already
             return ""
+        callSetup = "CallSetup s(CallbackPreserveColor(), aRv"
+        if self.rethrowContentException:
+            # getArgs doesn't add the aExceptionHandling argument but does add
+            # aCompartment for us.
+            callSetup += ", eRethrowContentExceptions, aCompartment"
+        else:
+            callSetup += ", aExceptionHandling"
+        callSetup += ");"
         return string.Template(
-            "CallSetup s(CallbackPreserveColor(), aRv, aExceptionHandling);\n"
+            "${callSetup}\n"
             "JSContext* cx = s.GetContext();\n"
             "if (!cx) {\n"
             "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
             "  return${errorReturn};\n"
             "}\n").substitute({
+                "callSetup": callSetup,
                 "errorReturn" : self.getDefaultRetval(),
                 })
 
     def getArgcDecl(self):
         return CGGeneric("unsigned argc = %s;" % self.argCountStr);
 
     @staticmethod
     def ensureASCIIName(idlObject):
@@ -10144,19 +10210,19 @@ class CallbackMember(CGNativeMember):
         if re.match('"', idlObject.identifier.name):
             raise SyntaxError("Callback %s name '%s' contains "
                               "double-quote character.  We can't handle "
                               "that.  %s" %
                               (type, idlObject.identifier.name,
                                idlObject.location))
 
 class CallbackMethod(CallbackMember):
-    def __init__(self, sig, name, descriptorProvider, needThisHandling):
+    def __init__(self, sig, name, descriptorProvider, needThisHandling, rethrowContentException=False):
         CallbackMember.__init__(self, sig, name, descriptorProvider,
-                                needThisHandling)
+                                needThisHandling, rethrowContentException)
     def getRvalDecl(self):
         return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
             "thisObj": self.getThisObj(),
             "getCallable": self.getCallableDecl()
@@ -10184,20 +10250,20 @@ class CallCallback(CallbackMethod):
 
     def getCallableDecl(self):
         return "JS::Rooted<JS::Value> callable(cx, JS::ObjectValue(*mCallback));\n"
 
 class CallbackOperationBase(CallbackMethod):
     """
     Common class for implementing various callback operations.
     """
-    def __init__(self, signature, jsName, nativeName, descriptor, singleOperation):
+    def __init__(self, signature, jsName, nativeName, descriptor, singleOperation, rethrowContentException=False):
         self.singleOperation = singleOperation
         self.methodName = jsName
-        CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation)
+        CallbackMethod.__init__(self, signature, nativeName, descriptor, singleOperation, rethrowContentException)
 
     def getThisObj(self):
         if not self.singleOperation:
             return "mCallback"
         # This relies on getCallableDecl declaring a boolean
         # isCallable in the case when we're a single-operation
         # interface.
         return "isCallable ? aThisObj.get() : mCallback"
@@ -10227,27 +10293,29 @@ class CallbackOperation(CallbackOperatio
     """
     Codegen actual WebIDL operations on callback interfaces.
     """
     def __init__(self, method, signature, descriptor):
         self.ensureASCIIName(method)
         jsName = method.identifier.name
         CallbackOperationBase.__init__(self, signature,
                                        jsName, MakeNativeName(jsName),
-                                       descriptor, descriptor.interface.isSingleOperationInterface())
+                                       descriptor, descriptor.interface.isSingleOperationInterface(),
+                                       rethrowContentException=descriptor.interface.isJSImplemented())
 
 class CallbackGetter(CallbackMember):
     def __init__(self, attr, descriptor):
         self.ensureASCIIName(attr)
         self.attrName = attr.identifier.name
         CallbackMember.__init__(self,
                                 (attr.type, []),
                                 callbackGetterName(attr),
                                 descriptor,
-                                needThisHandling=False)
+                                needThisHandling=False,
+                                rethrowContentException=descriptor.interface.isJSImplemented())
 
     def getRvalDecl(self):
         return "JS::Rooted<JS::Value> rval(cx, JS::UndefinedValue());\n"
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
             "attrName": self.attrName
@@ -10262,17 +10330,18 @@ class CallbackSetter(CallbackMember):
     def __init__(self, attr, descriptor):
         self.ensureASCIIName(attr)
         self.attrName = attr.identifier.name
         CallbackMember.__init__(self,
                                 (BuiltinTypes[IDLBuiltinType.Types.void],
                                  [FakeArgument(attr.type, attr)]),
                                 callbackSetterName(attr),
                                 descriptor,
-                                needThisHandling=False)
+                                needThisHandling=False,
+                                rethrowContentException=descriptor.interface.isJSImplemented())
 
     def getRvalDecl(self):
         # We don't need an rval
         return ""
 
     def getCall(self):
         replacements = {
             "errorReturn" : self.getDefaultRetval(),
@@ -10291,17 +10360,17 @@ class CallbackSetter(CallbackMember):
 
 class CGJSImplInitOperation(CallbackOperationBase):
     """
     Codegen the __Init() method used to pass along constructor arguments for JS-implemented WebIDL.
     """
     def __init__(self, sig, descriptor):
         assert sig in descriptor.interface.ctor().signatures()
         CallbackOperationBase.__init__(self, (BuiltinTypes[IDLBuiltinType.Types.void], sig[1]),
-                                       "__init", "__Init", descriptor, False)
+                                       "__init", "__Init", descriptor, False, True)
 
 class GlobalGenRoots():
     """
     Roots for global codegen.
 
     To generate code, call the method associated with the target, and then
     call the appropriate define/declare method.
     """
--- a/dom/bindings/ErrorResult.h
+++ b/dom/bindings/ErrorResult.h
@@ -65,21 +65,24 @@ public:
   void ThrowTypeError(const dom::ErrNum errorNumber, ...);
   void ReportTypeError(JSContext* cx);
   void ClearMessage();
   bool IsTypeError() const { return ErrorCode() == NS_ERROR_TYPE_ERR; }
 
   // Facilities for throwing a preexisting JS exception value via this
   // ErrorResult.  The contract is that any code which might end up calling
   // ThrowJSException() must call MightThrowJSException() even if no exception
-  // is being thrown.  Code that would call ReportJSException or
+  // is being thrown.  Code that would call ReportJSException* or
   // StealJSException as needed must first call WouldReportJSException even if
   // this ErrorResult has not failed.
   void ThrowJSException(JSContext* cx, JS::Handle<JS::Value> exn);
   void ReportJSException(JSContext* cx);
+  // Used to implement throwing exceptions from the JS implementation of
+  // bindings to callers of the binding.
+  void ReportJSExceptionFromJSImplementation(JSContext* aCx);
   bool IsJSException() const { return ErrorCode() == NS_ERROR_DOM_JS_EXCEPTION; }
 
   void ThrowNotEnoughArgsError() { mResult = NS_ERROR_XPC_NOT_ENOUGH_ARGS; }
   void ReportNotEnoughArgsError(JSContext* cx,
                                 const char* ifaceName,
                                 const char* memberName);
   bool IsNotEnoughArgsError() const { return ErrorCode() == NS_ERROR_XPC_NOT_ENOUGH_ARGS; }
 
--- a/dom/indexedDB/test/head.js
+++ b/dom/indexedDB/test/head.js
@@ -55,17 +55,17 @@ function triggerSecondaryCommand(popup, 
 {
   info("triggering secondary command, " + index);
   let notifications = popup.childNodes;
   ok(notifications.length > 0, "at least one notification displayed");
   let notification = notifications[0];
 
   // Cancel the arrow panel slide-in transition (bug 767133) such that
   // it won't interfere with us interacting with the dropdown.
-  document.getAnonymousNodes(popup)[0].style.transition = "none";
+  SpecialPowers.wrap(document).getAnonymousNodes(popup)[0].style.transition = "none";
 
   notification.button.focus();
 
   popup.addEventListener("popupshown", function () {
     popup.removeEventListener("popupshown", arguments.callee, false);
 
     // Press down until the desired command is selected
     for (let i = 0; i <= index; i++)
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -354,40 +354,38 @@ RTCPeerConnection.prototype = {
    *                   { url:"turn:turn.example.org",
    *                     username:"jib", credential:"mypass"} ] }
    *
    * WebIDL normalizes structure for us, so we test well-formed stun/turn urls,
    * but not validity of servers themselves, before passing along to C++.
    * ErrorMsg is passed in to detail which array-entry failed, if any.
    */
   _mustValidateRTCConfiguration: function(rtcConfig, errorMsg) {
+    var errorCtor = this._win.DOMError;
     function nicerNewURI(uriStr, errorMsg) {
       let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
       try {
         return ios.newURI(uriStr, null, null);
       } catch (e if (e.result == Cr.NS_ERROR_MALFORMED_URI)) {
-        throw new Components.Exception(errorMsg + " - malformed URI: " + uriStr,
-                                       Cr.NS_ERROR_MALFORMED_URI);
+        throw new errorCtor("", errorMsg + " - malformed URI: " + uriStr);
       }
     }
     function mustValidateServer(server) {
       let url = nicerNewURI(server.url, errorMsg);
       if (url.scheme in { turn:1, turns:1 }) {
         if (!server.username) {
-          throw new Components.Exception(errorMsg + " - missing username: " +
-                                         server.url, Cr.NS_ERROR_MALFORMED_URI);
+          throw new errorCtor("", errorMsg + " - missing username: " + server.url);
         }
         if (!server.credential) {
-          throw new Components.Exception(errorMsg + " - missing credential: " +
-                                         server.url, Cr.NS_ERROR_MALFORMED_URI);
+          throw new errorCtor("", errorMsg + " - missing credential: " +
+                              server.url);
         }
       }
       else if (!(url.scheme in { stun:1, stuns:1 })) {
-        throw new Components.Exception(errorMsg + " - improper scheme: " + url.scheme,
-                                       Cr.NS_ERROR_MALFORMED_URI);
+        throw new errorCtor("", errorMsg + " - improper scheme: " + url.scheme);
       }
     }
     if (rtcConfig.iceServers) {
       let len = rtcConfig.iceServers.length;
       for (let i=0; i < len; i++) {
         mustValidateServer (rtcConfig.iceServers[i], errorMsg);
       }
     }
--- a/dom/payment/Payment.js
+++ b/dom/payment/Payment.js
@@ -11,24 +11,22 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 
 const PAYMENTCONTENTHELPER_CID =
   Components.ID("{a920adc0-c36e-4fd0-8de0-aac1ac6ebbd0}");
 
 const PAYMENT_IPC_MSG_NAMES = ["Payment:Success",
                                "Payment:Failed"];
 
+const PREF_DEBUG = "dom.payment.debug";
+
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
-function debug (s) {
-  //dump("-*- PaymentContentHelper: " + s + "\n");
-};
-
 function PaymentContentHelper() {
 };
 
 PaymentContentHelper.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavigatorPayment,
                                          Ci.nsIDOMGlobalPropertyInitializer,
@@ -47,18 +45,20 @@ PaymentContentHelper.prototype = {
   pay: function pay(aJwts) {
     let request = this.createRequest();
     let requestId = this.getRequestId(request);
 
     let docShell = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShell);
     if (!docShell.isActive) {
-      debug("The caller application is a background app. No request " +
-            "will be sent");
+      if (this._debug) {
+        this.LOG("The caller application is a background app. No request " +
+                  "will be sent");
+      }
       let runnable = {
         run: function run() {
           Services.DOMRequest.fireError(request, "BACKGROUND_APP");
         }
       }
       Services.tm.currentThread.dispatch(runnable,
                                          Ci.nsIThread.DISPATCH_NORMAL);
       return request;
@@ -75,34 +75,52 @@ PaymentContentHelper.prototype = {
     return request;
   },
 
   // nsIDOMGlobalPropertyInitializer
 
   init: function(aWindow) {
     this._window = aWindow;
     this.initDOMRequestHelper(aWindow, PAYMENT_IPC_MSG_NAMES);
+
+    try {
+      this._debug =
+        Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
+        && Services.prefs.getBoolPref(PREF_DEBUG);
+    } catch(e) {
+      this._debug = false;
+    }
+
     return this.pay.bind(this);
   },
 
   // nsIFrameMessageListener
 
   receiveMessage: function receiveMessage(aMessage) {
     let name = aMessage.name;
     let msg = aMessage.json;
-    debug("Received message '" + name + "': " + JSON.stringify(msg));
+    if (this._debug) {
+      this.LOG("Received message '" + name + "': " + JSON.stringify(msg));
+    }
     let requestId = msg.requestId;
     let request = this.takeRequest(requestId);
     if (!request) {
       return;
     }
     switch (name) {
       case "Payment:Success":
         Services.DOMRequest.fireSuccess(request, msg.result);
         break;
       case "Payment:Failed":
         Services.DOMRequest.fireError(request, msg.errorMsg);
         break;
     }
+  },
+
+  LOG: function LOG(s) {
+    if (!this._debug) {
+      return;
+    }
+    dump("-*- PaymentContentHelper: " + s + "\n");
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PaymentContentHelper]);
--- a/dom/payment/Payment.jsm
+++ b/dom/payment/Payment.jsm
@@ -12,29 +12,26 @@ Cu.import("resource://gre/modules/Servic
 this.EXPORTED_SYMBOLS = [];
 
 const PAYMENT_IPC_MSG_NAMES = ["Payment:Pay",
                                "Payment:Success",
                                "Payment:Failed"];
 
 const PREF_PAYMENTPROVIDERS_BRANCH = "dom.payment.provider.";
 const PREF_PAYMENT_BRANCH = "dom.payment.";
+const PREF_DEBUG = "dom.payment.debug";
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageListenerManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "prefService",
                                    "@mozilla.org/preferences-service;1",
                                    "nsIPrefService");
 
-function debug (s) {
-  //dump("-*- PaymentManager: " + s + "\n");
-};
-
 let PaymentManager =  {
   init: function init() {
     // Payment providers data are stored as a preference.
     this.registeredProviders = null;
 
     this.messageManagers = {};
 
     // The dom.payment.skipHTTPSCheck pref is supposed to be used only during
@@ -48,25 +45,35 @@ let PaymentManager =  {
       }
     } catch(e) {}
 
     for each (let msgname in PAYMENT_IPC_MSG_NAMES) {
       ppmm.addMessageListener(msgname, this);
     }
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
+
+    try {
+      this._debug =
+        Services.prefs.getPrefType(PREF_DEBUG) == Ci.nsIPrefBranch.PREF_BOOL
+        && Services.prefs.getBoolPref(PREF_DEBUG);
+    } catch(e) {
+      this._debug = false;
+    }
   },
 
   /**
    * Process a message from the content process.
    */
   receiveMessage: function receiveMessage(aMessage) {
     let name = aMessage.name;
     let msg = aMessage.json;
-    debug("Received '" + name + "' message from content process");
+    if (this._debug) {
+      this.LOG("Received '" + name + "' message from content process");
+    }
 
     switch (name) {
       case "Payment:Pay": {
         // First of all, we register the payment providers.
         if (!this.registeredProviders) {
           this.registeredProviders = {};
           this.registerPaymentProviders();
         }
@@ -107,43 +114,50 @@ let PaymentManager =  {
 
         // After getting the list of valid payment requests, we ask the user
         // for confirmation before sending any request to any payment provider.
         // If there is more than one choice, we also let the user select the one
         // that he prefers.
         let glue = Cc["@mozilla.org/payment/ui-glue;1"]
                    .createInstance(Ci.nsIPaymentUIGlue);
         if (!glue) {
-          debug("Could not create nsIPaymentUIGlue instance");
+          if (this._debug) {
+            this.LOG("Could not create nsIPaymentUIGlue instance");
+          }
           this.paymentFailed(requestId,
                              "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
           return;
         }
 
         let confirmPaymentSuccessCb = function successCb(aRequestId,
                                                          aResult) {
           // Get the appropriate payment provider data based on user's choice.
           let selectedProvider = this.registeredProviders[aResult];
           if (!selectedProvider || !selectedProvider.uri) {
-            debug("Could not retrieve a valid provider based on user's " +
-                  "selection");
+            if (this._debug) {
+              this.LOG("Could not retrieve a valid provider based on user's " +
+                        "selection");
+            }
             this.paymentFailed(aRequestId,
                                "INTERNAL_ERROR_NO_VALID_SELECTED_PROVIDER");
             return;
           }
 
           let jwt;
           for (let i in paymentRequests) {
             if (paymentRequests[i].type == aResult) {
               jwt = paymentRequests[i].jwt;
               break;
             }
           }
           if (!jwt) {
-            debug("The selected request has no JWT information associated");
+            if (this._debug) {
+              this.LOG("The selected request has no JWT information " +
+                        "associated");
+            }
             this.paymentFailed(aRequestId,
                                "INTERNAL_ERROR_NO_JWT_ASSOCIATED_TO_REQUEST");
             return;
           }
 
           this.showPaymentFlow(aRequestId, selectedProvider, jwt);
         };
 
@@ -201,20 +215,24 @@ let PaymentManager =  {
           continue;
         }
         this.registeredProviders[type] = {
           name: branch.getCharPref("name"),
           uri: branch.getCharPref("uri"),
           description: branch.getCharPref("description"),
           requestMethod: branch.getCharPref("requestMethod")
         };
-        debug("Registered Payment Providers: " +
-              JSON.stringify(this.registeredProviders[type]));
+        if (this._debug) {
+          this.LOG("Registered Payment Providers: " +
+                    JSON.stringify(this.registeredProviders[type]));
+        }
       } catch (ex) {
-        debug("An error ocurred registering a payment provider. " + ex);
+        if (this._debug) {
+          this.LOG("An error ocurred registering a payment provider. " + ex);
+        }
       }
     }
   },
 
   /**
    * Helper for sending a Payment:Failed message to the parent process.
    */
   paymentFailed: function paymentFailed(aRequestId, aErrorMsg) {
@@ -237,33 +255,37 @@ let PaymentManager =  {
 
     // First thing, we check that the jwt type is an allowed type and has a
     // payment provider flow information associated.
 
     // A jwt string consists in three parts separated by period ('.'): header,
     // payload and signature.
     let segments = aJwt.split('.');
     if (segments.length !== 3) {
-      debug("Error getting payment provider's uri. " +
-            "Not enough or too many segments");
+      if (this._debug) {
+        this.LOG("Error getting payment provider's uri. " +
+                  "Not enough or too many segments");
+      }
       this.paymentFailed(aRequestId,
                          "PAY_REQUEST_ERROR_WRONG_SEGMENTS_COUNT");
       return true;
     }
 
     let payloadObject;
     try {
       // We only care about the payload segment, which contains the jwt type
       // that should match with any of the stored payment provider's data and
       // the payment request information to be shown to the user.
       // Before decoding the JWT string we need to normalize it to be compliant
       // with RFC 4648.
       segments[1] = segments[1].replace("-", "+", "g").replace("_", "/", "g");
       let payload = atob(segments[1]);
-      debug("Payload " + payload);
+      if (this._debug) {
+        this.LOG("Payload " + payload);
+      }
       if (!payload.length) {
         this.paymentFailed(aRequestId, "PAY_REQUEST_ERROR_EMPTY_PAYLOAD");
         return true;
       }
       payloadObject = JSON.parse(payload);
       if (!payloadObject) {
         this.paymentFailed(aRequestId,
                            "PAY_REQUEST_ERROR_ERROR_PARSING_JWT_PAYLOAD");
@@ -290,31 +312,35 @@ let PaymentManager =  {
     // Once we got the jwt 'typ' value we look for a match within the payment
     // providers stored preferences. If the jwt 'typ' is not recognized as one
     // of the allowed values for registered payment providers, we skip the jwt
     // validation but we don't fire any error. This way developers might have
     // a default set of well formed JWTs that might be used in different B2G
     // devices with a different set of allowed payment providers.
     let provider = this.registeredProviders[payloadObject.typ];
     if (!provider) {
-      debug("Not registered payment provider for jwt type: " +
-            payloadObject.typ);
+      if (this._debug) {
+        this.LOG("Not registered payment provider for jwt type: " +
+                  payloadObject.typ);
+      }
       return false;
     }
 
     if (!provider.uri || !provider.name) {
       this.paymentFailed(aRequestId,
                          "INTERNAL_ERROR_WRONG_REGISTERED_PAY_PROVIDER");
       return true;
     }
 
     // We only allow https for payment providers uris.
     if (this.checkHttps && !/^https/.exec(provider.uri.toLowerCase())) {
       // We should never get this far.
-      debug("Payment provider uris must be https: " + provider.uri);
+      if (this._debug) {
+        this.LOG("Payment provider uris must be https: " + provider.uri);
+      }
       this.paymentFailed(aRequestId,
                          "INTERNAL_ERROR_NON_HTTPS_PROVIDER_URI");
       return true;
     }
 
     let pldRequest = payloadObject.request;
     let request = Cc["@mozilla.org/payment/request-info;1"]
                   .createInstance(Ci.nsIDOMPaymentRequestInfo);
@@ -336,17 +362,19 @@ let PaymentManager =  {
                           .createInstance(Ci.nsIPaymentFlowInfo);
     paymentFlowInfo.uri = aPaymentProvider.uri;
     paymentFlowInfo.requestMethod = aPaymentProvider.requestMethod;
     paymentFlowInfo.jwt = aJwt;
 
     let glue = Cc["@mozilla.org/payment/ui-glue;1"]
                .createInstance(Ci.nsIPaymentUIGlue);
     if (!glue) {
-      debug("Could not create nsIPaymentUIGlue instance");
+      if (this._debug) {
+        this.LOG("Could not create nsIPaymentUIGlue instance");
+      }
       this.paymentFailed(aRequestId,
                          "INTERNAL_ERROR_CREATE_PAYMENT_GLUE_FAILED");
       return false;
     }
     glue.showPaymentFlow(aRequestId,
                          paymentFlowInfo,
                          this.paymentFailed.bind(this));
   },
@@ -359,11 +387,18 @@ let PaymentManager =  {
         ppmm.removeMessageListener(msgname, this);
       }
       this.registeredProviders = null;
       this.messageManagers = null;
 
       Services.obs.removeObserver(this, "xpcom-shutdown");
     }
   },
+
+  LOG: function LOG(s) {
+    if (!this._debug) {
+      return;
+    }
+    dump("-*- PaymentManager: " + s + "\n");
+  }
 };
 
 PaymentManager.init();
--- a/dom/tests/mochitest/chrome/window_focus.xul
+++ b/dom/tests/mochitest/chrome/window_focus.xul
@@ -1122,19 +1122,26 @@ function otherWindowFocused(otherWindow)
   is(gEvents, "", "when lowered no events fired");
 
   var other = otherWindow.document.getElementById("other");
   other.focus();
   is(fm.focusedElement, other, "focus method in second window");
 
   otherWindow.close();
 
+  getById("n2").focus();
+
   // next, check modal dialogs
-  getById("n2").focus();
-  var nextWindow = window.openDialog("focus_window2.xul", "_blank", "chrome,modal", modalWindowOpened);
+  // XXXndeakin Bug 621399 - modal dialog test sometimes fails on Windows 8 so disable it.
+  if (navigator.userAgent.indexOf("Windows NT 6.2") >= 0) {
+    window.open("focus_frameset.html", "_blank", "width=400,height=400,toolbar=no");
+  }
+  else {
+    window.openDialog("focus_window2.xul", "_blank", "chrome,modal", modalWindowOpened);
+  }
 }
 
 function modalWindowOpened(modalWindow)
 {
   var elem = modalWindow.document.getElementById("other");
   if (gPartialTabbing)
     elem.focus();
   else
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -272,21 +272,24 @@ partial interface Document {
   //(Not implemented)Element?  find(DOMString selectors, optional (Element or sequence<Node>)? refNodes);
   //(Not implemented)NodeList  findAll(DOMString selectors, optional (Element or sequence<Node>)? refNodes);
 };
 
 //  Mozilla extensions of various sorts
 partial interface Document {
   // nsIDOMDocumentXBL.  Wish we could make these [ChromeOnly], but
   // that would likely break bindings running with the page principal.
+  [Func="IsChromeOrXBL"]
   NodeList? getAnonymousNodes(Element elt);
+  [Func="IsChromeOrXBL"]
   Element? getAnonymousElementByAttribute(Element elt, DOMString attrName,
                                           DOMString attrValue);
+  [Func="IsChromeOrXBL"]
   Element? getBindingParent(Node node);
-  [Throws]
+  [Throws, Func="IsChromeOrXBL"]
   void loadBindingDocument(DOMString documentURL);
 
   // nsIDOMDocumentTouch
   // XXXbz I can't find the sane spec for this stuff, so just cribbing
   // from our xpidl for now.
   [Creator, Func="nsGenericHTMLElement::TouchEventsEnabled"]
   Touch createTouch(optional Window? view = null,
                     optional EventTarget? target = null,
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -21,17 +21,17 @@
 #include "mozilla/Telemetry.h"          // for Accumulate
 #include "mozilla/TelemetryHistogramEnums.h"
 #include "mozilla/gfx/2D.h"             // for DrawTarget
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/layers/AsyncPanZoomController.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerComposite
-#include "mozilla/layers/LayerTransaction.h"  // for TransformFunction, etc
+#include "mozilla/layers/LayersMessages.h"  // for TransformFunction, etc
 #include "nsAString.h"
 #include "nsCSSValue.h"                 // for nsCSSValue::Array, etc
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsStyleStruct.h"              // for nsTimingFunction, etc
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
--- a/gfx/layers/client/ClientCanvasLayer.h
+++ b/gfx/layers/client/ClientCanvasLayer.h
@@ -7,17 +7,17 @@
 #define GFX_CLIENTCANVASLAYER_H
 
 #include "mozilla/layers/CanvasClient.h"  // for CanvasClient, etc
 #include "ClientLayerManager.h"         // for ClientLayerManager, etc
 #include "CopyableCanvasLayer.h"        // for CopyableCanvasLayer
 #include "Layers.h"                     // for CanvasLayer, etc
 #include "mozilla/Attributes.h"         // for MOZ_OVERRIDE
 #include "mozilla/RefPtr.h"             // for RefPtr
-#include "mozilla/layers/LayerTransaction.h"  // for CanvasLayerAttributes, etc
+#include "mozilla/layers/LayersMessages.h"  // for CanvasLayerAttributes, etc
 #include "mozilla/mozalloc.h"           // for operator delete
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
 using namespace mozilla::gfx;
 
--- a/gfx/layers/client/ClientColorLayer.cpp
+++ b/gfx/layers/client/ClientColorLayer.cpp
@@ -1,16 +1,16 @@
 /* -*- 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 "ClientLayerManager.h"         // for ClientLayerManager, etc
 #include "Layers.h"                     // for ColorLayer, etc
-#include "mozilla/layers/LayerTransaction.h"  // for ColorLayerAttributes, etc
+#include "mozilla/layers/LayersMessages.h"  // for ColorLayerAttributes, etc
 #include "mozilla/mozalloc.h"           // for operator new
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
--- a/gfx/layers/client/ClientImageLayer.cpp
+++ b/gfx/layers/client/ClientImageLayer.cpp
@@ -6,17 +6,17 @@
 #include "ClientLayerManager.h"         // for ClientLayerManager, etc
 #include "ImageContainer.h"             // for AutoLockImage, etc
 #include "ImageLayers.h"                // for ImageLayer
 #include "gfxASurface.h"                // for gfxASurface
 #include "mozilla/Attributes.h"         // for MOZ_OVERRIDE
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/ImageClient.h"  // for ImageClient, etc
-#include "mozilla/layers/LayerTransaction.h"  // for ImageLayerAttributes, etc
+#include "mozilla/layers/LayersMessages.h"  // for ImageLayerAttributes, etc
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr, getter_AddRefs, etc
 #include "nsCOMPtr.h"                   // for already_AddRefed
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for Layer::AddRef, etc
 #include "nsRegion.h"                   // for nsIntRegion
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -12,17 +12,17 @@
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/Hal.h"
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/dom/TabChild.h"       // for TabChild
 #include "mozilla/hal_sandbox/PHal.h"   // for ScreenConfiguration
 #include "mozilla/layers/CompositableClient.h"  // for CompositableChild, etc
 #include "mozilla/layers/ContentClient.h"  // for ContentClientRemote
 #include "mozilla/layers/ISurfaceAllocator.h"
-#include "mozilla/layers/LayerTransaction.h"  // for EditReply, etc
+#include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor
 #include "mozilla/layers/PLayerChild.h"  // for PLayerChild
 #include "mozilla/layers/PLayerTransactionChild.h"
 #include "nsAString.h"
 #include "nsIWidget.h"                  // for nsIWidget
 #include "nsTArray.h"                   // for AutoInfallibleTArray
 #include "nsXULAppAPI.h"                // for XRE_GetProcessType, etc
 #ifdef MOZ_WIDGET_ANDROID
--- a/gfx/layers/client/ClientTiledThebesLayer.cpp
+++ b/gfx/layers/client/ClientTiledThebesLayer.cpp
@@ -8,17 +8,17 @@
 #include "ClientLayerManager.h"         // for ClientLayerManager, etc
 #include "gfx3DMatrix.h"                // for gfx3DMatrix
 #include "gfxPlatform.h"                // for gfxPlatform
 #include "gfxPoint.h"                   // for gfxSize
 #include "gfxRect.h"                    // for gfxRect
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Rect.h"           // for Rect, RectTyped
-#include "mozilla/layers/LayerTransaction.h"
+#include "mozilla/layers/LayersMessages.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsRect.h"                     // for nsIntRect
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
 namespace mozilla {
 namespace layers {
 
 
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -15,17 +15,17 @@
 #include "ipc/ShadowLayers.h"           // for ShadowLayerForwarder
 #include "mozilla/Util.h"               // for ArrayLength
 #include "mozilla/gfx/2D.h"             // for DrawTarget, Factory
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
 #include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/gfx/Rect.h"           // for Rect
 #include "mozilla/gfx/Types.h"
 #include "mozilla/layers/LayerManagerComposite.h"
-#include "mozilla/layers/LayerTransaction.h"  // for ThebesBufferData
+#include "mozilla/layers/LayersMessages.h"  // for ThebesBufferData
 #include "mozilla/layers/LayersTypes.h"
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for gfxContext::Release, etc
 #include "nsIWidget.h"                  // for nsIWidget
 #include "prenv.h"                      // for PR_GetEnv
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -9,17 +9,17 @@
 #include "Units.h"                      // for LayerPoint, etc
 #include "mozilla/layers/LayerManagerComposite.h"  // for LayerManagerComposite
 #include "gfx3DMatrix.h"                // for gfx3DMatrix
 #include "mozilla/Attributes.h"         // for MOZ_DELETE, MOZ_FINAL, etc
 #include "mozilla/RefPtr.h"             // for RefCounted
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/dom/ScreenOrientation.h"  // for ScreenOrientation
 #include "mozilla/gfx/BasePoint.h"      // for BasePoint
-#include "mozilla/layers/LayerTransaction.h"  // for TargetConfig
+#include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsISupportsImpl.h"            // for LayerManager::AddRef, etc
 #include "nsTraceRefcnt.h"              // for MOZ_COUNT_CTOR, etc
 
 namespace mozilla {
 namespace layers {
 
 class AsyncPanZoomController;
--- a/gfx/layers/composite/ContentHost.cpp
+++ b/gfx/layers/composite/ContentHost.cpp
@@ -6,17 +6,17 @@
 #include "mozilla/layers/ContentHost.h"
 #include "LayersLogging.h"              // for AppendToString
 #include "gfx2DGlue.h"                  // for ContentForFormat
 #include "gfxPoint.h"                   // for gfxIntSize
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/Effects.h"     // for TexturedEffect, Effect, etc
-#include "mozilla/layers/LayerTransaction.h"  // for ThebesBufferData
+#include "mozilla/layers/LayersMessages.h"  // for ThebesBufferData
 #include "nsAString.h"
 #include "nsPrintfCString.h"            // for nsPrintfCString
 #include "nsString.h"                   // for nsAutoCString
 
 class gfxImageSurface;
 
 namespace mozilla {
 namespace gfx {
--- a/gfx/layers/ipc/CompositableTransactionParent.h
+++ b/gfx/layers/ipc/CompositableTransactionParent.h
@@ -6,17 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H
 #define MOZILLA_LAYERS_COMPOSITABLETRANSACTIONPARENT_H
 
 #include <vector>                       // for vector
 #include "mozilla/Attributes.h"         // for MOZ_OVERRIDE
 #include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
-#include "mozilla/layers/LayerTransaction.h"  // for EditReply, etc
+#include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 
 namespace mozilla {
 namespace layers {
 
 typedef std::vector<mozilla::layers::EditReply> EditReplyVector;
 
 // Since PCompositble has two potential manager protocols, we can't just call
 // the Manager() method usually generated when there's one manager protocol,
--- a/gfx/layers/ipc/CompositorParent.h
+++ b/gfx/layers/ipc/CompositorParent.h
@@ -22,17 +22,17 @@
 #include "base/platform_thread.h"       // for PlatformThreadId
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/Attributes.h"         // for MOZ_OVERRIDE
 #include "mozilla/Monitor.h"            // for Monitor
 #include "mozilla/RefPtr.h"             // for RefPtr
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/layers/GeckoContentController.h"
-#include "mozilla/layers/LayerTransaction.h"  // for TargetConfig
+#include "mozilla/layers/LayersMessages.h"  // for TargetConfig
 #include "mozilla/layers/PCompositorParent.h"
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsISupportsImpl.h"
 #include "nsSize.h"                     // for nsIntSize
 
 class CancelableTask;
 class MessageLoop;
 class gfxContext;
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -20,17 +20,17 @@
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/Monitor.h"            // for Monitor, MonitorAutoLock
 #include "mozilla/ReentrantMonitor.h"   // for ReentrantMonitor, etc
 #include "mozilla/ipc/AsyncChannel.h"   // for AsyncChannel, etc
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/layers/CompositableClient.h"  // for CompositableChild, etc
 #include "mozilla/layers/ISurfaceAllocator.h"  // for ISurfaceAllocator
 #include "mozilla/layers/ImageClient.h"  // for ImageClient
-#include "mozilla/layers/LayerTransaction.h"  // for CompositableOperation
+#include "mozilla/layers/LayersMessages.h"  // for CompositableOperation
 #include "mozilla/layers/PCompositableChild.h"  // for PCompositableChild
 #include "mozilla/layers/TextureClient.h"  // for TextureClient
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsISupportsImpl.h"            // for ImageContainer::AddRef, etc
 #include "nsTArray.h"                   // for nsAutoTArray, nsTArray, etc
 #include "nsTArrayForwardDeclare.h"     // for AutoInfallibleTArray
 #include "nsThreadUtils.h"              // for NS_IsMainThread
--- a/gfx/layers/ipc/ImageBridgeParent.cpp
+++ b/gfx/layers/ipc/ImageBridgeParent.cpp
@@ -13,17 +13,17 @@
 #include "base/tracked.h"               // for FROM_HERE
 #include "gfxPoint.h"                   // for gfxIntSize
 #include "mozilla/ipc/AsyncChannel.h"   // for AsyncChannel, etc
 #include "mozilla/ipc/ProtocolUtils.h"
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/layers/CompositableTransactionParent.h"
 #include "mozilla/layers/CompositorParent.h"  // for CompositorParent
 #include "mozilla/layers/LayerManagerComposite.h"
-#include "mozilla/layers/LayerTransaction.h"  // for EditReply
+#include "mozilla/layers/LayersMessages.h"  // for EditReply
 #include "mozilla/layers/LayersSurfaces.h"  // for PGrallocBufferParent
 #include "mozilla/layers/PCompositableParent.h"
 #include "mozilla/layers/PImageBridgeParent.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT, etc
 #include "nsISupportsImpl.h"            // for ImageBridgeParent::Release, etc
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -18,17 +18,17 @@
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/gfx/BasePoint3D.h"    // for BasePoint3D
 #include "mozilla/layers/CanvasLayerComposite.h"
 #include "mozilla/layers/ColorLayerComposite.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/ContainerLayerComposite.h"
 #include "mozilla/layers/ImageLayerComposite.h"
 #include "mozilla/layers/LayerManagerComposite.h"
-#include "mozilla/layers/LayerTransaction.h"  // for EditReply, etc
+#include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for PGrallocBufferParent
 #include "mozilla/layers/LayersTypes.h"  // for MOZ_LAYERS_LOG
 #include "mozilla/layers/PCompositableParent.h"
 #include "mozilla/layers/PLayerParent.h"  // for PLayerParent
 #include "mozilla/layers/ThebesLayerComposite.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsCoord.h"                    // for NSAppUnitsToFloatPixels
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
rename from gfx/layers/ipc/LayerTransaction.ipdlh
rename to gfx/layers/ipc/LayersMessages.ipdlh
--- a/gfx/layers/ipc/PImageBridge.ipdl
+++ b/gfx/layers/ipc/PImageBridge.ipdl
@@ -1,15 +1,15 @@
 /* -*- 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/. */
 
 include LayersSurfaces;
-include LayerTransaction;
+include LayersMessages;
 include protocol PGrallocBuffer;
 include protocol PCompositable;
 
 include "mozilla/layers/CompositorTypes.h";
 include "mozilla/GfxMessageUtils.h";
 
 using ImageHandle;
 using mozilla::layers::TextureInfo;
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=8 et :
  */
 /* 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 LayersSurfaces;
-include LayerTransaction;
+include LayersMessages;
 include protocol PCompositable;
 include protocol PCompositor;
 include protocol PGrallocBuffer;
 include protocol PLayer;
 include protocol PRenderFrame;
 
 include "mozilla/WidgetUtils.h";
 include "mozilla/dom/ScreenOrientation.h";
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -15,17 +15,17 @@
 #include "RenderTrace.h"                // for RenderTraceScope
 #include "ShadowLayerChild.h"           // for ShadowLayerChild
 #include "gfxImageSurface.h"            // for gfxImageSurface
 #include "gfxPlatform.h"                // for gfxImageFormat, gfxPlatform
 #include "gfxSharedImageSurface.h"      // for gfxSharedImageSurface
 #include "ipc/IPCMessageUtils.h"        // for gfxContentType, null_t
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc
 #include "mozilla/layers/CompositableClient.h"  // for CompositableClient, etc
-#include "mozilla/layers/LayerTransaction.h"  // for Edit, etc
+#include "mozilla/layers/LayersMessages.h"  // for Edit, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for SurfaceDescriptor, etc
 #include "mozilla/layers/LayersTypes.h"  // for MOZ_LAYERS_LOG
 #include "mozilla/layers/PLayerTransactionChild.h"
 #include "ShadowLayerUtils.h"
 #include "mozilla/layers/TextureClient.h"  // for TextureClient
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr, getter_AddRefs, etc
 #include "nsDebug.h"                    // for NS_ABORT_IF_FALSE, etc
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -258,17 +258,17 @@ CPP_SOURCES += [
     'ThebesLayerComposite.cpp',
     'ThebesLayerOGL.cpp',
     'TiledContentClient.cpp',
     'TiledContentHost.cpp',
     'YCbCrImageDataSerializer.cpp',
 ]
 
 IPDL_SOURCES = [
-    'ipc/LayerTransaction.ipdlh',
+    'ipc/LayersMessages.ipdlh',
     'ipc/LayersSurfaces.ipdlh',
     'ipc/PCompositable.ipdl',
     'ipc/PCompositor.ipdl',
     'ipc/PGrallocBuffer.ipdl',
     'ipc/PImageBridge.ipdl',
     'ipc/PLayer.ipdl',
     'ipc/PLayerTransaction.ipdl',
 ]
--- a/image/test/mochitest/test_animSVGImage.html
+++ b/image/test/mochitest/test_animSVGImage.html
@@ -49,64 +49,56 @@ function takeReferenceSnapshot() {
 
   // Re-hide reference div, and take another snapshot to be sure it's gone
   referenceDiv.style.display = "none";
   let blankSnapshot2 = snapshotWindow(window, false);
   ok(compareSnapshots(blankSnapshot, blankSnapshot2, true)[0],
      "reference div should disappear when it becomes display:none");
 }
 
-function myOnStopFrame(aRequest) {
+function myOnStopFrame() {
   gOnStopFrameCounter++;
   ok(true, "myOnStopFrame called");
   let currentSnapshot = snapshotWindow(window, false);
   if (compareSnapshots(currentSnapshot, gReferenceSnapshot, true)[0]) {
     // SUCCESS!
     ok(true, "Animated image looks correct, " +
              "at call #" + gOnStopFrameCounter + " to onStopFrame");
     cleanUpAndFinish();
   }
-  setTimeout(function() { myOnStopFrame(0, 0); }, 1000);
+  else
+    setTimeout(myOnStopFrame, 1);
 }
 
 function failTest() {
   ok(false, "timing out after " + FAILURE_TIMEOUT + "ms.  " +
             "Animated image still doesn't look correct, " +
             "after call #" + gOnStopFrameCounter + " to onStopFrame");
   cleanUpAndFinish();
 }
 
 function cleanUpAndFinish() {
   // On the off chance that failTest and myOnStopFrame are triggered
   // back-to-back, use a flag to prevent multiple calls to SimpleTest.finish.
   if (gIsTestFinished) {
     return;
   }
-  let imgLoadingContent = gImg.QueryInterface(Ci.nsIImageLoadingContent);
-  imgLoadingContent.removeObserver(gMyDecoderObserver);
   SimpleTest.finish();
   gIsTestFinished = true;
 }
 
 function main() {
   takeReferenceSnapshot();
 
-  // Create, customize & attach decoder observer
-  observer = new ImageDecoderObserverStub();
-  observer.frameComplete = myOnStopFrame;
-  gMyDecoderObserver =
-    Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-      .createScriptedObserver(observer);
-  let imgLoadingContent = gImg.QueryInterface(Ci.nsIImageLoadingContent);
-  imgLoadingContent.addObserver(gMyDecoderObserver);
-
   // We want to test the cold loading behavior, so clear cache in case an
   // earlier test got our image in there already.
   clearImageCache();
 
+  setTimeout(myOnStopFrame, 1);
+
   // kick off image-loading! myOnStopFrame handles the rest.
   gImg.setAttribute("src", "lime-anim-100x100.svg");
 
   // In case something goes wrong, fail earlier than mochitest timeout,
   // and with more information.
   setTimeout(failTest, FAILURE_TIMEOUT);
 }
 
--- a/js/src/TraceLogging.cpp
+++ b/js/src/TraceLogging.cpp
@@ -60,17 +60,17 @@ rdtsc(void)
     result = upper;
     result = result<<32;
     result = result|lower;
 
     return(result);
 }
 #endif
 
-const char* const TraceLogging::type_name[] = {
+const char* const TraceLogging::typeName[] = {
     "1,s",  // start script
     "0,s",  // stop script
     "1,c",  // start ion compilation
     "0,c",  // stop ion compilation
     "1,r",  // start regexp JIT execution
     "0,r",  // stop regexp JIT execution
     "1,G",  // start major GC
     "0,G",  // stop major GC
@@ -81,53 +81,55 @@ const char* const TraceLogging::type_nam
     "1,pl", // start lazy parsing
     "0,pl", // stop lazy parsing
     "1,pf", // start Function parsing
     "0,pf", // stop Function parsing
     "e,i",  // engine interpreter
     "e,b",  // engine baseline
     "e,o"   // engine ionmonkey
 };
-TraceLogging* TraceLogging::_defaultLogger = NULL;
+TraceLogging* TraceLogging::loggers[] = {NULL, NULL};
+bool TraceLogging::atexitSet = false;
+uint64_t TraceLogging::startupTime = 0;
 
-TraceLogging::TraceLogging()
-  : startupTime(rdtsc()),
-    loggingTime(0),
+TraceLogging::TraceLogging(Logger id)
+  : loggingTime(0),
     nextTextId(1),
     entries(NULL),
     curEntry(0),
     numEntries(1000000),
     fileno(0),
-    out(NULL)
+    out(NULL),
+    id(id)
 {
     textMap.init();
 }
 
 TraceLogging::~TraceLogging()
 {
-    if (out != NULL) {
+    if (out) {
         fclose(out);
         out = NULL;
     }
 
-    if (entries != NULL) {
+    if (entries) {
         flush();
         free(entries);
         entries = NULL;
     }
 }
 
 void
 TraceLogging::grow()
 {
     Entry* nentries = (Entry*) realloc(entries, numEntries*2*sizeof(Entry));
 
     // Allocating a bigger array failed.
     // Keep using the current storage, but remove all entries by flushing them.
-    if (nentries == NULL) {
+    if (!nentries) {
         flush();
         return;
     }
 
     entries = nentries;
     numEntries *= 2;
 }
 
@@ -190,86 +192,98 @@ TraceLogging::log(const char* log)
 {
     this->log(INFO, log, 0);
 }
 
 void
 TraceLogging::flush()
 {
     // Open the logging file, when not opened yet.
-    if (out == NULL)
-        out = fopen(TRACE_LOG_DIR "tracelogging.log", "w");
+    if (!out) {
+        switch(id) {
+          case DEFAULT:
+            out = fopen(TRACE_LOG_DIR "tracelogging.log", "w");
+            break;
+          case ION_BACKGROUND_COMPILER:
+            out = fopen(TRACE_LOG_DIR "tracelogging-compile.log", "w");
+            break;
+          default:
+            MOZ_ASSUME_UNREACHABLE("Bad trigger");
+            return;
+        }
+    }
 
     // Print all log entries into the file
     for (unsigned int i = 0; i < curEntry; i++) {
         Entry entry = entries[i];
         int written;
         if (entry.type() == INFO) {
             written = fprintf(out, "I,%s\n", entry.text());
         } else {
             if (entry.textId() > 0) {
                 if (entry.text()) {
                     written = fprintf(out, "%llu,%s,%s,%d\n",
                                       (unsigned long long)entry.tick(),
-                                      type_name[entry.type()],
+                                      typeName[entry.type()],
                                       entry.text(),
                                       entry.lineno());
                 } else {
                     written = fprintf(out, "%llu,%s,%d,%d\n",
                                       (unsigned long long)entry.tick(),
-                                      type_name[entry.type()],
+                                      typeName[entry.type()],
                                       entry.textId(),
                                       entry.lineno());
                 }
             } else {
                 written = fprintf(out, "%llu,%s\n",
                                   (unsigned long long)entry.tick(),
-                                  type_name[entry.type()]);
+                                  typeName[entry.type()]);
             }
         }
 
         // A logging file can only be 2GB of length (fwrite limit).
-        // When we exceed this limit, the writing will fail.
-        // In that case try creating a extra file to write the log entries.
         if (written < 0) {
+            fprintf(stderr, "Writing tracelog to disk failed,");
+            fprintf(stderr, "probably because the file would've exceeded the maximum size of 2GB");
             fclose(out);
-            if (fileno >= 9999)
-                exit(-1);
-
-            char filename[21 + sizeof(TRACE_LOG_DIR)];
-            sprintf (filename, TRACE_LOG_DIR "tracelogging-%d.log", ++fileno);
-            out = fopen(filename, "w");
-            i--; // Try to print message again
-            continue;
+            exit(-1);
         }
 
         if (entries[i].text() != NULL) {
             free(entries[i].text());
             entries[i].text_ = NULL;
         }
     }
     curEntry = 0;
 }
 
 TraceLogging*
-TraceLogging::defaultLogger()
+TraceLogging::getLogger(Logger id)
 {
-    if (_defaultLogger == NULL) {
-        _defaultLogger = new TraceLogging();
-        atexit (releaseDefaultLogger);
+    if (!loggers[id]) {
+        loggers[id] = new TraceLogging(id);
+        if (!atexitSet) {
+            startupTime = rdtsc();
+            atexit (releaseLoggers);
+            atexitSet = true;
+        }
     }
-    return _defaultLogger;
+
+    return loggers[id];
 }
 
 void
-TraceLogging::releaseDefaultLogger()
+TraceLogging::releaseLoggers()
 {
-    if (_defaultLogger != NULL) {
-        delete _defaultLogger;
-        _defaultLogger = NULL;
+    for (size_t i = 0; i < LAST_LOGGER; i++) {
+        if (!loggers[i])
+            continue;
+
+        delete loggers[i];
+        loggers[i] = NULL;
     }
 }
 
 /* Helper functions for asm calls */
 void
 js::TraceLog(TraceLogging* logger, TraceLogging::Type type, JSScript* script)
 {
     logger->log(type, script);
--- a/js/src/TraceLogging.h
+++ b/js/src/TraceLogging.h
@@ -41,16 +41,22 @@ class TraceLogging
         PARSER_COMPILE_LAZY_STOP,
         PARSER_COMPILE_FUNCTION_START,
         PARSER_COMPILE_FUNCTION_STOP,
         INFO_ENGINE_INTERPRETER,
         INFO_ENGINE_BASELINE,
         INFO_ENGINE_IONMONKEY,
         INFO
     };
+    enum Logger {
+        DEFAULT,
+        ION_BACKGROUND_COMPILER,
+
+        LAST_LOGGER
+    };
 
   private:
     struct Entry {
         uint64_t tick_;
         char* text_;
         uint32_t textId_;
         uint32_t lineno_;
         uint8_t type_;
@@ -69,40 +75,45 @@ class TraceLogging
         Type type() const { return (Type) type_; }
     };
 
     typedef HashMap<const char *,
                         uint32_t,
                         PointerHasher<const char *, 3>,
                         SystemAllocPolicy> TextHashMap;
 
-    uint64_t startupTime;
     uint64_t loggingTime;
     TextHashMap textMap;
     uint32_t nextTextId;
     Entry *entries;
     unsigned int curEntry;
     unsigned int numEntries;
     int fileno;
     FILE *out;
+    Logger id;
 
-    static const char * const type_name[];
-    static TraceLogging* _defaultLogger;
+    static bool atexitSet;
+    static const char * const typeName[];
+    static TraceLogging* loggers[];
+    static uint64_t startupTime;
   public:
-    TraceLogging();
+    TraceLogging(Logger id);
     ~TraceLogging();
 
     void log(Type type, const char* text = NULL, unsigned int number = 0);
     void log(Type type, const JS::CompileOptions &options);
     void log(Type type, JSScript* script);
     void log(const char* log);
     void flush();
 
-    static TraceLogging* defaultLogger();
-    static void releaseDefaultLogger();
+    static TraceLogging* getLogger(Logger id);
+    static TraceLogging* defaultLogger() {
+        return getLogger(DEFAULT);
+    }
+    static void releaseLoggers();
 
   private:
     void grow();
 };
 
 /* Helpers functions for asm calls */
 void TraceLog(TraceLogging* logger, TraceLogging::Type type, JSScript* script);
 void TraceLog(TraceLogging* logger, const char* log);
--- a/js/src/assembler/assembler/X86Assembler.h
+++ b/js/src/assembler/assembler/X86Assembler.h
@@ -1491,37 +1491,47 @@ public:
     void movw_rm_disp32(RegisterID src, int offset, RegisterID base)
     {
         spew("movw       %s, %s0x%x(%s)",
              nameIReg(2,src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.prefix(PRE_OPERAND_SIZE);
         m_formatter.oneByteOp_disp32(OP_MOV_EvGv, src, base, offset);
     }
 
+    void movw_rm(RegisterID src, int offset, RegisterID base, RegisterID index, int scale)
+    {
+        spew("movw       %s, %d(%s,%s,%d)",
+             nameIReg(2, src), offset, nameIReg(base), nameIReg(index), 1<<scale);
+        m_formatter.prefix(PRE_OPERAND_SIZE);
+        m_formatter.oneByteOp(OP_MOV_EvGv, src, base, index, scale, offset);
+    }
+
+#if !WTF_CPU_X86_64
+    void movw_rm(RegisterID src, const void* addr)
+    {
+        spew("movw       %s, %p",
+             nameIReg(2, src), addr);
+        m_formatter.prefix(PRE_OPERAND_SIZE);
+        m_formatter.oneByteOp(OP_MOV_EvGv, src, addr);
+    }
+#endif
+
     void movl_rm(RegisterID src, int offset, RegisterID base)
     {
         spew("movl       %s, %s0x%x(%s)",
              nameIReg(4,src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.oneByteOp(OP_MOV_EvGv, src, base, offset);
     }
 
     void movl_rm_disp32(RegisterID src, int offset, RegisterID base)
     {
         FIXME_INSN_PRINTING;
         m_formatter.oneByteOp_disp32(OP_MOV_EvGv, src, base, offset);
     }
 
-    void movw_rm(RegisterID src, int offset, RegisterID base, RegisterID index, int scale)
-    {
-        spew("movw       %s, %d(%s,%s,%d)",
-             nameIReg(2, src), offset, nameIReg(base), nameIReg(index), 1<<scale);
-        m_formatter.prefix(PRE_OPERAND_SIZE);
-        m_formatter.oneByteOp(OP_MOV_EvGv, src, base, index, scale, offset);
-    }
-
     void movl_rm(RegisterID src, int offset, RegisterID base, RegisterID index, int scale)
     {
         spew("movl       %s, %d(%s,%s,%d)",
              nameIReg(4, src), offset, nameIReg(base), nameIReg(index), 1<<scale);
         m_formatter.oneByteOp(OP_MOV_EvGv, src, base, index, scale, offset);
     }
 
     void movl_mEAX(const void* addr)
@@ -1559,16 +1569,28 @@ public:
 
     void movl_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
     {
         spew("movl       %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(4, dst));
         m_formatter.oneByteOp(OP_MOV_GvEv, dst, base, index, scale, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movl_mr(const void* addr, RegisterID dst)
+    {
+        spew("movl       %p, %s",
+             addr, nameIReg(4, dst));
+        if (dst == X86Registers::eax)
+            movl_mEAX(addr);
+        else
+            m_formatter.oneByteOp(OP_MOV_GvEv, dst, addr);
+    }
+#endif
+
     void movl_i32r(int imm, RegisterID dst)
     {
         spew("movl       $0x%x, %s",
              imm, nameIReg(4, dst));
         m_formatter.oneByteOp(OP_MOV_EAXIv, dst);
         m_formatter.immediate32(imm);
     }
 
@@ -1775,26 +1797,16 @@ public:
         spew("movl       %s, %p",
              nameIReg(4, src), addr);
         if (src == X86Registers::eax)
             movl_EAXm(addr);
         else
             m_formatter.oneByteOp(OP_MOV_EvGv, src, addr);
     }
 
-    void movl_mr(const void* addr, RegisterID dst)
-    {
-        spew("movl       %p, %s",
-             addr, nameIReg(4, dst));
-        if (dst == X86Registers::eax)
-            movl_mEAX(addr);
-        else
-            m_formatter.oneByteOp(OP_MOV_GvEv, dst, addr);
-    }
-
     void movl_i32m(int imm, const void* addr)
     {
         FIXME_INSN_PRINTING;
         m_formatter.oneByteOp(OP_GROUP11_EvIz, GROUP11_MOV, addr);
         m_formatter.immediate32(imm);
     }
 #endif
 
@@ -1814,16 +1826,25 @@ public:
 
     void movb_rm(RegisterID src, int offset, RegisterID base, RegisterID index, int scale)
     {
         spew("movb       %s, %d(%s,%s,%d)",
              nameIReg(1, src), offset, nameIReg(base), nameIReg(index), 1<<scale);
         m_formatter.oneByteOp8(OP_MOV_EbGv, src, base, index, scale, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movb_rm(RegisterID src, const void* addr)
+    {
+        spew("movb       %s, %p",
+             nameIReg(1, src), addr);
+        m_formatter.oneByteOp(OP_MOV_EbGv, src, addr);
+    }
+#endif
+
     void movzbl_mr(int offset, RegisterID base, RegisterID dst)
     {
         spew("movzbl     %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameIReg(4, dst));
         m_formatter.twoByteOp(OP2_MOVZX_GvEb, dst, base, offset);
     }
 
     void movzbl_mr_disp32(int offset, RegisterID base, RegisterID dst)
@@ -1835,16 +1856,25 @@ public:
 
     void movzbl_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
     {
         spew("movzbl     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(dst));
         m_formatter.twoByteOp(OP2_MOVZX_GvEb, dst, base, index, scale, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movzbl_mr(const void* addr, RegisterID dst)
+    {
+        spew("movzbl     %p, %s",
+             addr, nameIReg(dst));
+        m_formatter.twoByteOp(OP2_MOVZX_GvEb, dst, addr);
+    }
+#endif
+
     void movsbl_mr(int offset, RegisterID base, RegisterID dst)
     {
         spew("movsbl     %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameIReg(4, dst));
         m_formatter.twoByteOp(OP2_MOVSX_GvEb, dst, base, offset);
     }
 
     void movsbl_mr_disp32(int offset, RegisterID base, RegisterID dst)
@@ -1856,16 +1886,25 @@ public:
 
     void movsbl_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
     {
         spew("movsbl     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(dst));
         m_formatter.twoByteOp(OP2_MOVSX_GvEb, dst, base, index, scale, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movsbl_mr(const void* addr, RegisterID dst)
+    {
+        spew("movsbl     %p, %s",
+             addr, nameIReg(4, dst));
+        m_formatter.twoByteOp(OP2_MOVSX_GvEb, dst, addr);
+    }
+#endif
+
     void movzwl_mr(int offset, RegisterID base, RegisterID dst)
     {
         spew("movzwl     %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameIReg(4, dst));
         m_formatter.twoByteOp(OP2_MOVZX_GvEw, dst, base, offset);
     }
 
     void movzwl_mr_disp32(int offset, RegisterID base, RegisterID dst)
@@ -1877,16 +1916,25 @@ public:
 
     void movzwl_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
     {
         spew("movzwl     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(dst));
         m_formatter.twoByteOp(OP2_MOVZX_GvEw, dst, base, index, scale, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movzwl_mr(const void* addr, RegisterID dst)
+    {
+        spew("movzwl     %p, %s",
+             addr, nameIReg(4, dst));
+        m_formatter.twoByteOp(OP2_MOVZX_GvEw, dst, addr);
+    }
+#endif
+
     void movswl_mr(int offset, RegisterID base, RegisterID dst)
     {
         spew("movswl     %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameIReg(4, dst));
         m_formatter.twoByteOp(OP2_MOVSX_GvEw, dst, base, offset);
     }
 
     void movswl_mr_disp32(int offset, RegisterID base, RegisterID dst)
@@ -1898,16 +1946,25 @@ public:
 
     void movswl_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
     {
         spew("movswl     %d(%s,%s,%d), %s",
              offset, nameIReg(base), nameIReg(index), 1<<scale, nameIReg(dst));
         m_formatter.twoByteOp(OP2_MOVSX_GvEw, dst, base, index, scale, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movswl_mr(const void* addr, RegisterID dst)
+    {
+        spew("movswl     %p, %s",
+             addr, nameIReg(4, dst));
+        m_formatter.twoByteOp(OP2_MOVSX_GvEw, dst, addr);
+    }
+#endif
+
     void movzbl_rr(RegisterID src, RegisterID dst)
     {
         spew("movzbl     %s, %s",
              nameIReg(1,src), nameIReg(4,dst));
         m_formatter.twoByteOp8_movx(OP2_MOVZX_GvEb, dst, src);
     }
 
     void leal_mr(int offset, RegisterID base, RegisterID index, int scale, RegisterID dst)
@@ -2329,32 +2386,52 @@ public:
     void movss_rm_disp32(XMMRegisterID src, int offset, RegisterID base)
     {
         spew("movss      %s, %s0x%x(%s)",
              nameFPReg(src), PRETTY_PRINT_OFFSET(offset), nameIReg(base));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp_disp32(OP2_MOVSD_WsdVsd, (RegisterID)src, base, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movss_rm(XMMRegisterID src, const void* addr)
+    {
+        spew("movss      %s, %p",
+             nameFPReg(src), addr);
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp(OP2_MOVSD_WsdVsd, (RegisterID)src, addr);
+    }
+#endif
+
     void movss_mr(int offset, RegisterID base, XMMRegisterID dst)
     {
         spew("movss      %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp(OP2_MOVSD_VsdWsd, (RegisterID)dst, base, offset);
     }
 
     void movss_mr_disp32(int offset, RegisterID base, XMMRegisterID dst)
     {
         spew("movss      %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameFPReg(dst));
         m_formatter.prefix(PRE_SSE_F3);
         m_formatter.twoByteOp_disp32(OP2_MOVSD_VsdWsd, (RegisterID)dst, base, offset);
     }
 
+#if !WTF_CPU_X86_64
+    void movss_mr(const void* addr, XMMRegisterID dst)
+    {
+        spew("movss      %p, %s",
+             addr, nameFPReg(dst));
+        m_formatter.prefix(PRE_SSE_F3);
+        m_formatter.twoByteOp(OP2_MOVSD_VsdWsd, (RegisterID)dst, addr);
+    }
+#endif
+
     void movsd_rm(XMMRegisterID src, int offset, RegisterID base, RegisterID index, int scale)
     {
         spew("movsd      %s, %d(%s,%s,%d)",
              nameFPReg(src), offset, nameIReg(base), nameIReg(index), 1<<scale);
         m_formatter.prefix(PRE_SSE_F2);
         m_formatter.twoByteOp(OP2_MOVSD_WsdVsd, (RegisterID)src, base, index, scale, offset);
     }
 
--- a/js/src/builtin/TypeRepresentation.cpp
+++ b/js/src/builtin/TypeRepresentation.cpp
@@ -222,37 +222,37 @@ StructTypeRepresentation::init(JSContext
     uint32_t totalSize = 0;
 
     for (size_t i = 0; i < ids.length(); i++) {
         TypeRepresentation *fieldTypeRepr = fromOwnerObject(typeReprOwners[i]);
 
         uint32_t alignedSize = alignTo(totalSize, fieldTypeRepr->alignment());
         if (alignedSize < totalSize) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                                 JSMSG_BINARYDATA_TOO_BIG);
+                                 JSMSG_TYPEDOBJECT_TOO_BIG);
             return false;
         }
 
         new(fields() + i) StructField(i, ids[i], fieldTypeRepr, alignedSize);
         alignment_ = js::Max(alignment_, fieldTypeRepr->alignment());
 
         uint32_t incrementedSize = alignedSize + fieldTypeRepr->size();
         if (incrementedSize < alignedSize) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                                 JSMSG_BINARYDATA_TOO_BIG);
+                                 JSMSG_TYPEDOBJECT_TOO_BIG);
             return false;
         }
 
         totalSize = incrementedSize;
     }
 
     uint32_t alignedSize = alignTo(totalSize, alignment_);
     if (alignedSize < totalSize) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_TOO_BIG);
+                             JSMSG_TYPEDOBJECT_TOO_BIG);
         return false;
     }
 
     size_ = alignedSize;
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////
@@ -322,17 +322,17 @@ ArrayTypeRepresentation::Create(JSContex
     JSCompartment *comp = cx->compartment();
 
     // Overly conservative, since we are using `size_t` to represent
     // size, but `SafeMul` operators on `int32_t` types. Still, it
     // should be good enough for now.
     int32_t temp;
     if (!SafeMul(element->size(), length, &temp)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_TOO_BIG);
+                             JSMSG_TYPEDOBJECT_TOO_BIG);
         return NULL;
     }
 
     ArrayTypeRepresentation sample(element, length);
     TypeRepresentationSet::AddPtr p = comp->typeReprs.lookupForAdd(&sample);
     if (p)
         return (*p)->ownerObject();
 
rename from js/src/builtin/BinaryData.cpp
rename to js/src/builtin/TypedObject.cpp
--- a/js/src/builtin/BinaryData.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 "builtin/BinaryData.h"
+#include "builtin/TypedObject.h"
 
 #include "jscompartment.h"
 #include "jsfun.h"
 #include "jsobj.h"
 #include "jsutil.h"
 
 #include "builtin/TypeRepresentation.h"
 #include "gc/Marking.h"
@@ -284,17 +284,17 @@ TypeEquivalent(JSContext *cx, unsigned i
     if (args.length() < 1) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_MORE_ARGS_NEEDED,
                              "Type.equivalent", "1", "s");
         return false;
     }
 
     RootedObject otherObj(cx, ToObjectIfObject(args[0]));
     if (!otherObj || !IsBinaryType(otherObj)) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_BINARYDATA_NOT_TYPE_OBJECT);
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_TYPEDOBJECT_NOT_TYPE_OBJECT);
         return false;
     }
 
     TypeRepresentation *thisRepr = typeRepresentation(thisObj);
     TypeRepresentation *otherRepr = typeRepresentation(otherObj);
     args.rval().setBoolean(thisRepr == otherRepr);
     return true;
 }
@@ -779,17 +779,17 @@ ArraySubarray(JSContext *cx, unsigned in
         JS_ReportErrorNumber(cx, js_GetErrorMessage,
                              NULL, JSMSG_MORE_ARGS_NEEDED,
                              "subarray()", "0", "s");
         return false;
     }
 
     if (!args[0].isInt32()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, "1");
+                             JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, "1");
         return false;
     }
 
     RootedObject thisObj(cx, &args.thisv().toObject());
     if (!IsBinaryArray(cx, thisObj)) {
         ReportCannotConvertTo(cx, thisObj, "binary array");
         return false;
     }
@@ -799,17 +799,17 @@ ArraySubarray(JSContext *cx, unsigned in
     size_t length = typeRepr->length();
 
     int32_t begin = args[0].toInt32();
     int32_t end = length;
 
     if (args.length() >= 2) {
         if (!args[1].isInt32()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                    JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, "2");
+                    JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, "2");
             return false;
         }
 
         end = args[1].toInt32();
     }
 
     if (begin < 0)
         begin = length + begin;
@@ -987,39 +987,39 @@ ArrayType::construct(JSContext *cx, unsi
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (argc != 2 ||
         !args[0].isObject() ||
         !args[1].isNumber() ||
         args[1].toNumber() < 0)
     {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS);
+                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
         return false;
     }
 
     RootedObject arrayTypeGlobal(cx, &args.callee());
     RootedObject elementType(cx, &args[0].toObject());
 
     if (!IsBinaryType(elementType)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS);
+                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
         return false;
     }
 
     if (!args[1].isInt32()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS);
+                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
         return false;
     }
 
     int32_t length = args[1].toInt32();
     if (length < 0) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                             JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS);
+                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
         return false;
     }
 
     RootedObject obj(cx, create(cx, arrayTypeGlobal, elementType, length));
     if (!obj)
         return false;
     args.rval().setObject(*obj);
     return true;
@@ -1264,17 +1264,17 @@ StructType::construct(JSContext *cx, uns
         RootedObject obj(cx, create(cx, structTypeGlobal, fields));
         if (!obj)
             return false;
         args.rval().setObject(*obj);
         return true;
     }
 
     JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
-                         JSMSG_BINARYDATA_STRUCTTYPE_BAD_ARGS);
+                         JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS);
     return false;
 }
 
 bool
 StructType::toSource(JSContext *cx, unsigned int argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -1593,17 +1593,17 @@ DefineNumericClass(JSContext *cx,
     if (!JS_DefineFunction(cx, numFun, "toSource",
                            NumericTypeToString<type>, 0, 0))
         return false;
 
     return true;
 }
 
 JSObject *
-js_InitBinaryDataClasses(JSContext *cx, HandleObject obj)
+js_InitTypedObjectClasses(JSContext *cx, HandleObject obj)
 {
     JS_ASSERT(obj->is<GlobalObject>());
     Rooted<GlobalObject *> global(cx, &obj->as<GlobalObject>());
 
     if (!InitType(cx, obj))
         return NULL;
 
 #define BINARYDATA_NUMERIC_DEFINE(constant_, type_, name_)                    \
@@ -1743,17 +1743,17 @@ BinaryBlock::createNull(JSContext *cx, H
     obj->initFixedSlot(SLOT_BLOCKREFOWNER, owner);
 
     // Tag the type object for this instance with the type
     // representation, if that has not been done already.
     if (cx->typeInferenceEnabled()) {
         RootedTypeObject typeObj(cx, obj->getType(cx));
         if (typeObj) {
             TypeRepresentation *typeRepr = typeRepresentation(type);
-            if (!typeObj->addBinaryDataAddendum(cx, typeRepr))
+            if (!typeObj->addTypedObjectAddendum(cx, typeRepr))
                 return NULL;
         }
     }
 
     return obj;
 }
 
 /*static*/ JSObject *
@@ -2137,17 +2137,17 @@ BinaryBlock::obj_setElement(JSContext *c
       case ScalarTypeRepresentation::Struct:
         break;
 
       case ScalarTypeRepresentation::Array: {
         ArrayTypeRepresentation *arrayTypeRepr = typeRepr->asArray();
 
         if (index >= arrayTypeRepr->length()) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage,
-                                 NULL, JSMSG_BINARYDATA_BINARYARRAY_BAD_INDEX);
+                                 NULL, JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX);
             return false;
         }
 
         size_t offset = arrayTypeRepr->element()->size() * index;
         uint8_t *mem = BlockMem(obj) + offset;
         return ConvertAndCopyTo(cx, arrayTypeRepr->element(), vp, mem);
       }
     }
rename from js/src/builtin/BinaryData.h
rename to js/src/builtin/TypedObject.h
--- a/js/src/builtin/BinaryData.h
+++ b/js/src/builtin/TypedObject.h
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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 builtin_BinaryData_h
-#define builtin_BinaryData_h
+#ifndef builtin_TypedObject_h
+#define builtin_TypedObject_h
 
 #include "jsobj.h"
 
 #include "builtin/TypeRepresentation.h"
 
 namespace js {
 
 // Slots common to all type descriptors:
@@ -223,9 +223,9 @@ class BinaryBlock
 
     // user-accessible constructor (`new TypeDescriptor(...)`)
     static bool construct(JSContext *cx, unsigned int argc, jsval *vp);
 };
 
 
 } // namespace js
 
-#endif /* builtin_BinaryData_h */
+#endif /* builtin_TypedObject_h */
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1130,18 +1130,18 @@ ScanTypeObject(GCMarker *gcmarker, types
 
     if (type->addendum) {
         switch (type->addendum->kind) {
           case types::TypeObjectAddendum::NewScript:
             PushMarkStack(gcmarker, type->newScript()->fun);
             PushMarkStack(gcmarker, type->newScript()->shape.get());
             break;
 
-          case types::TypeObjectAddendum::BinaryData:
-            PushMarkStack(gcmarker, type->binaryData()->typeRepr->ownerObject());
+          case types::TypeObjectAddendum::TypedObject:
+            PushMarkStack(gcmarker, type->typedObject()->typeRepr->ownerObject());
             break;
         }
     }
 
     if (type->interpretedFunction)
         PushMarkStack(gcmarker, type->interpretedFunction);
 }
 
@@ -1163,18 +1163,18 @@ gc::MarkChildren(JSTracer *trc, types::T
 
     if (type->addendum) {
         switch (type->addendum->kind) {
           case types::TypeObjectAddendum::NewScript:
             MarkObject(trc, &type->newScript()->fun, "type_new_function");
             MarkShape(trc, &type->newScript()->shape, "type_new_shape");
             break;
 
-          case types::TypeObjectAddendum::BinaryData:
-            type->binaryData()->typeRepr->mark(trc);
+          case types::TypeObjectAddendum::TypedObject:
+            type->typedObject()->typeRepr->mark(trc);
             break;
         }
     }
 
     if (type->interpretedFunction)
         MarkObject(trc, &type->interpretedFunction, "type_function");
 }
 
rename from js/src/jit-test/tests/binary-data/fuzz1.js
rename to js/src/jit-test/tests/TypedObject/fuzz1.js
rename from js/src/jit-test/tests/binary-data/fuzz10.js
rename to js/src/jit-test/tests/TypedObject/fuzz10.js
rename from js/src/jit-test/tests/binary-data/fuzz11.js
rename to js/src/jit-test/tests/TypedObject/fuzz11.js
rename from js/src/jit-test/tests/binary-data/fuzz2.js
rename to js/src/jit-test/tests/TypedObject/fuzz2.js
rename from js/src/jit-test/tests/binary-data/fuzz3.js
rename to js/src/jit-test/tests/TypedObject/fuzz3.js
rename from js/src/jit-test/tests/binary-data/fuzz4.js
rename to js/src/jit-test/tests/TypedObject/fuzz4.js
rename from js/src/jit-test/tests/binary-data/fuzz5.js
rename to js/src/jit-test/tests/TypedObject/fuzz5.js
rename from js/src/jit-test/tests/binary-data/fuzz6.js
rename to js/src/jit-test/tests/TypedObject/fuzz6.js
rename from js/src/jit-test/tests/binary-data/fuzz7.js
rename to js/src/jit-test/tests/TypedObject/fuzz7.js
rename from js/src/jit-test/tests/binary-data/fuzz8.js
rename to js/src/jit-test/tests/TypedObject/fuzz8.js
rename from js/src/jit-test/tests/binary-data/fuzz9.js
rename to js/src/jit-test/tests/TypedObject/fuzz9.js
rename from js/src/jit-test/tests/binary-data/jit-prefix.js
rename to js/src/jit-test/tests/TypedObject/jit-prefix.js
rename from js/src/jit-test/tests/binary-data/jit-read-float64.js
rename to js/src/jit-test/tests/TypedObject/jit-read-float64.js
rename from js/src/jit-test/tests/binary-data/jit-read-int.js
rename to js/src/jit-test/tests/TypedObject/jit-read-int.js
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz1.js
@@ -0,0 +1,8 @@
+if (!this.hasOwnProperty("Type"))
+  exit(0);
+
+function eval() {
+    yield(undefined)
+}
+new(StructType)
+(eval())
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz10.js
@@ -0,0 +1,8 @@
+// |jit-test| error:Error
+
+if (!this.hasOwnProperty("Type"))
+  throw new Error("type too large");
+
+var AA = new ArrayType(new ArrayType(uint8, (2147483647)), 5);
+var aa = new AA();
+var aa0 = aa[0];
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz11.js
@@ -0,0 +1,13 @@
+// |jit-test| error:Error
+
+if (!this.hasOwnProperty("Type"))
+  throw new Error("type too large");
+
+var A = new ArrayType(uint8, (2147483647));
+var S = new StructType({a: A,
+                        b: A,
+                        c: A,
+                        d: A,
+                        e: A});
+var aa = new S();
+var aa0 = aa.a;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz2.js
@@ -0,0 +1,4 @@
+if (!this.hasOwnProperty("Type"))
+  exit(0);
+
+new StructType([])
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz3.js
@@ -0,0 +1,7 @@
+// |jit-test| error:RangeError;
+
+if (!this.hasOwnProperty("Type"))
+  throw new RangeError();
+
+this.__proto__ =  Proxy.create({});
+new StructType;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz4.js
@@ -0,0 +1,10 @@
+// |jit-test| error:Error;
+
+if (!this.hasOwnProperty("Type"))
+  throw new Error();
+
+var A = new ArrayType(uint8, 10);
+var a = new A();
+a.forEach(function(val, i) {
+  assertEq(arguments[5], a);
+});
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz5.js
@@ -0,0 +1,6 @@
+if (!this.hasOwnProperty("Type"))
+  exit(0);
+
+var Color = new StructType({r: uint8, g: uint8, b: uint8});
+var white2 = new Color({r: 255, toString: null, b: 253});
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz6.js
@@ -0,0 +1,6 @@
+// |jit-test| error:TypeError
+
+if (!this.hasOwnProperty("Type"))
+  throw new TypeError();
+
+new StructType(RegExp);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz7.js
@@ -0,0 +1,4 @@
+if (!this.hasOwnProperty("Type"))
+  exit(0);
+
+new StructType(RegExp());
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz8.js
@@ -0,0 +1,6 @@
+// |jit-test| error:Error
+
+if (!this.hasOwnProperty("Type"))
+  throw new Error();
+
+new ArrayType(uint8, .0000000009);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/fuzz9.js
@@ -0,0 +1,10 @@
+// |jit-test| error: TypeError
+
+if (!this.hasOwnProperty("Type"))
+  throw new TypeError();
+
+var Vec3 = new ArrayType(float32, 3);
+var Sprite = new ArrayType(Vec3, 3);
+var mario = new Sprite();
+mario[/\u00ee[]/] = new Vec3([1, 0, 0]);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/jit-prefix.js
@@ -0,0 +1,25 @@
+var PointType2 = new StructType({x: float64,
+                                 y: float64});
+
+var PointType3 = new StructType({x: float64,
+                                 y: float64,
+                                 z: float64});
+
+function xPlusY(p) {
+  return p.x + p.y;
+}
+
+function foo() {
+  var N = 30000;
+  var points = [];
+  for (var i = 0; i < N; i++) {
+    var s;
+    if ((i % 2) == 0 || true)
+      s = xPlusY(new PointType2({x: i, y: i+1}));
+    else
+      s = xPlusY(new PointType3({x: i, y: i+1, z: i+2}));
+    assertEq(s, i + i + 1);
+  }
+}
+
+foo();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/jit-read-float64.js
@@ -0,0 +1,14 @@
+var PointType = new StructType({x: float64,
+                                y: float64,
+                                z: float64});
+
+function foo() {
+  for (var i = 0; i < 30000; i += 3) {
+    var pt = new PointType({x: i, y: i+1, z: i+2});
+    var sum = pt.x + pt.y + pt.z;
+    print(pt.x, pt.y, pt.z);
+    assertEq(sum, 3*i + 3);
+  }
+}
+
+foo();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/TypedObjects/jit-read-int.js
@@ -0,0 +1,13 @@
+var PointType = new StructType({x: uint32,
+                                y: uint32,
+                                z: uint32});
+
+function foo() {
+  for (var i = 0; i < 30000; i += 3) {
+    var pt = new PointType({x: i, y: i+1, z: i+2});
+    var sum = pt.x + pt.y + pt.z;
+    assertEq(sum, 3*i + 3);
+  }
+}
+
+foo();
--- a/js/src/jit-test/tests/asm.js/testHeapAccess.js
+++ b/js/src/jit-test/tests/asm.js/testHeapAccess.js
@@ -160,10 +160,288 @@ var code = asmCompile('glob', 'imp', 'b'
 asmLink(code, this, null, new ArrayBuffer(4096));
 
 asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f(i) { i=i|0; u32[12] = i } return f'), this, null, BUF_64KB)(11);
 assertEq(new Int32Array(BUF_64KB)[12], 11);
 assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[12]|0 } return f'), this, null, BUF_64KB)(), 11);
 new Float64Array(BUF_64KB)[0] = 3.5;
 assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +-f64[0] } return f'), this, null, BUF_64KB)(), -3.5);
 
+// Test constant loads.
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[1] = -1 } return f'), this, null, buf)();
+assertEq(new Uint8Array(buf)[0], 0);
+assertEq(new Uint8Array(buf)[1], 255);
+assertEq(new Uint8Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[4095] = -1 } return f'), this, null, buf)();
+assertEq(new Uint8Array(buf)[4094], 0);
+assertEq(new Uint8Array(buf)[4095], 255);
+assertEq(new Uint8Array(buf)[4096], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[4096] = -1 } return f'), this, null, buf)();
+assertEq(new Uint8Array(buf)[4095], 0);
+assertEq(new Uint8Array(buf)[4096], 255);
+assertEq(new Uint8Array(buf)[4097], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[8192] = -1 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[258048] = -1 } return f'), this, null, buf)();
+assertEq(new Uint8Array(buf)[258047], 0);
+assertEq(new Uint8Array(buf)[258048], 255);
+assertEq(new Uint8Array(buf)[258049], 0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[1] = -1 } return f'), this, null, buf)();
+assertEq(new Int8Array(buf)[0], 0);
+assertEq(new Int8Array(buf)[1], -1);
+assertEq(new Int8Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[4095] = -1 } return f'), this, null, buf)();
+assertEq(new Int8Array(buf)[4094], 0);
+assertEq(new Int8Array(buf)[4095], -1);
+assertEq(new Int8Array(buf)[4096], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[4096] = -1 } return f'), this, null, buf)();
+assertEq(new Int8Array(buf)[4095], 0);
+assertEq(new Int8Array(buf)[4096], -1);
+assertEq(new Int8Array(buf)[4097], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[8192] = -1 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[258048] = -1 } return f'), this, null, buf)();
+assertEq(new Int8Array(buf)[258047], 0);
+assertEq(new Int8Array(buf)[258048], -1);
+assertEq(new Int8Array(buf)[258049], 0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[1] = -1 } return f'), this, null, buf)();
+assertEq(new Uint16Array(buf)[0], 0);
+assertEq(new Uint16Array(buf)[1], 65535);
+assertEq(new Uint16Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[2047] = -1 } return f'), this, null, buf)();
+assertEq(new Uint16Array(buf)[2046], 0);
+assertEq(new Uint16Array(buf)[2047], 65535);
+assertEq(new Uint16Array(buf)[2048], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[2048] = -1 } return f'), this, null, buf)();
+assertEq(new Uint16Array(buf)[2047], 0);
+assertEq(new Uint16Array(buf)[2048], 65535);
+assertEq(new Uint16Array(buf)[2049], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[4096] = -1 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[126976] = -1 } return f'), this, null, buf)();
+assertEq(new Uint16Array(buf)[126975], 0);
+assertEq(new Uint16Array(buf)[126976], 65535);
+assertEq(new Uint16Array(buf)[126977], 0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[1] = -1 } return f'), this, null, buf)();
+assertEq(new Int16Array(buf)[0], 0);
+assertEq(new Int16Array(buf)[1], -1);
+assertEq(new Int16Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[2047] = -1 } return f'), this, null, buf)();
+assertEq(new Int16Array(buf)[2046], 0);
+assertEq(new Int16Array(buf)[2047], -1);
+assertEq(new Int16Array(buf)[2048], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[2048] = -1 } return f'), this, null, buf)();
+assertEq(new Int16Array(buf)[2047], 0);
+assertEq(new Int16Array(buf)[2048], -1);
+assertEq(new Int16Array(buf)[2049], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[4096] = -1 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[126976] = -1 } return f'), this, null, buf)();
+assertEq(new Int16Array(buf)[126975], 0);
+assertEq(new Int16Array(buf)[126976], -1);
+assertEq(new Int16Array(buf)[126977], 0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[1] = -1 } return f'), this, null, buf)();
+assertEq(new Uint32Array(buf)[0], 0);
+assertEq(new Uint32Array(buf)[1], 4294967295);
+assertEq(new Uint32Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[1023] = -1 } return f'), this, null, buf)();
+assertEq(new Uint32Array(buf)[1022], 0);
+assertEq(new Uint32Array(buf)[1023], 4294967295);
+assertEq(new Uint32Array(buf)[1024], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[1024] = -1 } return f'), this, null, buf)();
+assertEq(new Uint32Array(buf)[1023], 0);
+assertEq(new Uint32Array(buf)[1024], 4294967295);
+assertEq(new Uint32Array(buf)[1025], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[2048] = -1 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[61440] = -1 } return f'), this, null, buf)();
+assertEq(new Uint32Array(buf)[61439], 0);
+assertEq(new Uint32Array(buf)[61440], 4294967295);
+assertEq(new Uint32Array(buf)[61441], 0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[1] = -1 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[0], 0);
+assertEq(new Int32Array(buf)[1], -1);
+assertEq(new Int32Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[1023] = -1 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[1022], 0);
+assertEq(new Int32Array(buf)[1023], -1);
+assertEq(new Int32Array(buf)[124], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[1024] = -1 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[1023], 0);
+assertEq(new Int32Array(buf)[1024], -1);
+assertEq(new Int32Array(buf)[1025], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[2048] = -1 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[61440] = -1 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[61439], 0);
+assertEq(new Int32Array(buf)[61440], -1);
+assertEq(new Int32Array(buf)[61441], 0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[1] = -1.0 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[0], 0);
+assertEq(new Float32Array(buf)[1], -1.0);
+assertEq(new Int32Array(buf)[2], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[1023] = -1.0 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[1022], 0);
+assertEq(new Float32Array(buf)[1023], -1.0);
+assertEq(new Int32Array(buf)[124], 0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[1024] = -1.0 } return f'), this, null, buf)();
+assertEq(new Int32Array(buf)[1023], 0);
+assertEq(new Float32Array(buf)[1024], -1.0);
+assertEq(new Int32Array(buf)[1025], 0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[2048] = -1.0 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[61440] = -1.0 } return f'), this, null, buf)();
+assertEq(new Float32Array(buf)[61439], 0.0);
+assertEq(new Float32Array(buf)[61440], -1.0);
+assertEq(new Float32Array(buf)[61441], 0.0);
+
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[1] = -1.0 } return f'), this, null, buf)();
+assertEq(new Float64Array(buf)[0], 0.0);
+assertEq(new Float64Array(buf)[1], -1.0);
+assertEq(new Float64Array(buf)[2], 0.0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[511] = -1.0 } return f'), this, null, buf)();
+assertEq(new Float64Array(buf)[510], 0.0);
+assertEq(new Float64Array(buf)[511], -1.0);
+assertEq(new Float64Array(buf)[512], 0.0);
+var buf = new ArrayBuffer(8192);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[512] = -1.0 } return f'), this, null, buf)();
+assertEq(new Float64Array(buf)[511], 0.0);
+assertEq(new Float64Array(buf)[512], -1.0);
+assertEq(new Float64Array(buf)[513], 0.0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[1024] = -1.0 } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[28672] = -1.0 } return f'), this, null, buf)();
+assertEq(new Float64Array(buf)[28671], 0.0);
+assertEq(new Float64Array(buf)[28672], -1.0);
+assertEq(new Float64Array(buf)[28673], 0.0);
+
+
+var buf = new ArrayBuffer(8192);
+new Uint8Array(buf)[1] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[1]|0; } return f'), this, null, buf)(),255);
+new Int8Array(buf)[1] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i8[1]|0; } return f'), this, null, buf)(),-1);
+var buf = new ArrayBuffer(262144);
+new Uint8Array(buf)[126976] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[126976]|0; } return f'), this, null, buf)(),255);
+new Int8Array(buf)[126976] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i8[126976]|0; } return f'), this, null, buf)(),-1);
+
+var buf = new ArrayBuffer(8192);
+new Uint16Array(buf)[1] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u16[1]|0; } return f'), this, null, buf)(),65535);
+new Int16Array(buf)[1] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i16[1]|0; } return f'), this, null, buf)(),-1);
+var buf = new ArrayBuffer(262144);
+new Uint16Array(buf)[126976] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u16[126976]|0; } return f'), this, null, buf)(),65535);
+new Int16Array(buf)[126976] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i16[126976]|0; } return f'), this, null, buf)(),-1);
+
+var buf = new ArrayBuffer(8192);
+new Uint32Array(buf)[1] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[1]|0; } return f'), this, null, buf)(),-1);
+new Int32Array(buf)[1] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[1]|0; } return f'), this, null, buf)(),-1);
+var buf = new ArrayBuffer(262144);
+new Int32Array(buf)[61440] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[61440]|0; } return f'), this, null, buf)(),-1);
+
+var buf = new ArrayBuffer(8192);
+new Float32Array(buf)[1] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[1]; } return f'), this, null, buf)(),-1.0);
+new Float32Array(buf)[1023] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[1023]; } return f'), this, null, buf)(),-1.0);
+new Float32Array(buf)[1024] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[1024]; } return f'), this, null, buf)(),-1.0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[2048]; } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+new Float32Array(buf)[61440] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[61440]; } return f'), this, null, buf)(),-1.0);
+
+var buf = new ArrayBuffer(8192);
+new Float64Array(buf)[1] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[1]; } return f'), this, null, buf)(),-1.0);
+new Float64Array(buf)[511] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[511]; } return f'), this, null, buf)(),-1.0);
+new Float64Array(buf)[512] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[512]; } return f'), this, null, buf)(),-1.0);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[1024]; } return f'), this, null, buf);
+var buf = new ArrayBuffer(262144);
+new Float64Array(buf)[28672] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[28672]; } return f'), this, null, buf)(),-1.0);
+
+// Test bitwise-and optimizations.
+var buf = new ArrayBuffer(8192);
+new Uint8Array(buf)[8191] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[8191&8191]|0; } return f'), this, null, buf)(),255);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u8[(8191&8191)>>0]|0; } return f'), this, null, buf)(),255);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[8192&8191] = -1; u8[0] = 0; return u8[8192&8191]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u8[(8192&8191)>>0] = -1; u8[0] = 0; return u8[(8192&8191)>>0]|0; } return f'), this, null, buf)(),0);
+new Int8Array(buf)[8191] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i8[8191&8191]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i8[(8191&8191)>>0]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[8192&8191] = -1; i8[0] = 0; return i8[8192&8191]|0; } return f'), this, null, buf)(),0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i8[(8192&8191)>>0] = -1; i8[0] = 0; return i8[(8192&8191)>>0]|0; } return f'), this, null, buf)(),0);
+var buf = new ArrayBuffer(8192);
+new Uint16Array(buf)[4095] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u16[(8190&8191)>>1]|0; } return f'), this, null, buf)(),65535);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u16[(8191&8191)>>1]|0; } return f'), this, null, buf)(),65535);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u16[(8192&8191)>>1] = -1; u16[0] = 0; return u16[(8192&8191)>>1]|0; } return f'), this, null, buf)(),0);
+new Int16Array(buf)[4095] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i16[(8190&8191)>>1]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i16[(8191&8191)>>1]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i16[(8192&8191)>>1] = -1; i16[0] = 0; return i16[(8192&8191)>>1]|0; } return f'), this, null, buf)(),0);
+
+var buf = new ArrayBuffer(8192);
+new Uint32Array(buf)[2047] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(8188&8191)>>2]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return u32[(8191&8191)>>2]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { u32[(8192&8191)>>2] = -1; u32[0] = 0; return u32[(8192&8191)>>2]|0; } return f'), this, null, buf)(),0);
+new Int32Array(buf)[2047] = -1;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[(8188&8191)>>2]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return i32[(8191&8191)>>2]|0; } return f'), this, null, buf)(),-1);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { i32[(8192&8191)>>2] = -1; i32[0] = 0; return i32[(8192&8191)>>2]|0; } return f'), this, null, buf)(),0);
+
+var buf = new ArrayBuffer(8192);
+new Float32Array(buf)[2047] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[(8188&8191)>>2]; } return f'), this, null, buf)(),-1.0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f32[(8191&8191)>>2]; } return f'), this, null, buf)(),-1.0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f32[(8192&8191)>>2] = -1.0; f32[0] = 0.0; return +f32[(8192&8191)>>2]; } return f'), this, null, buf)(),0.0);
+
+var buf = new ArrayBuffer(8192);
+new Float64Array(buf)[1023] = -1.0;
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[(8184&8191)>>3]; } return f'), this, null, buf)(),-1.0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { return +f64[(8191&8191)>>3]; } return f'), this, null, buf)(),-1.0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + HEAP_IMPORTS + 'function f() { f64[(8192&8191)>>3] = -1.0; f64[0] = 0.0; return +f64[(8192&8191)>>3]; } return f'), this, null, buf)(),0.0);
+
+
 // Bug 882012
 assertEq(asmLink(asmCompile('stdlib', 'foreign', 'heap', USE_ASM + "var id=foreign.id;var doubles=new stdlib.Float64Array(heap);function g(){doubles[0]=+id(2.0);return +doubles[0];}return g"), this, {id: function(x){return x;}}, BUF_64KB)(), 2.0);
--- a/js/src/jit-test/tests/asm.js/testZOOB.js
+++ b/js/src/jit-test/tests/asm.js/testZOOB.js
@@ -1,21 +1,55 @@
 load(libdir + "asm.js");
 
 // constants
 var buf = new ArrayBuffer(4096);
-assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x7fffffff]|0 } return f'), this, null, buf)(), 0);
-assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x80000000]|0 } return f'), this, null, buf)(), 0);
-assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x8fffffff]|0 } return f'), this, null, buf)(), 0);
-assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0xffffffff]|0 } return f'), this, null, buf)(), 0);
-assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[-1]|0 } return f'), this, null, buf)(), 0);
-assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[-2]|0 } return f'), this, null, buf)(), 0);
+
+// An unshifted literal constant byte index in the range 0 to 2^31-1 inclusive should give a link failure.
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0x7fffffff]|0 } return f'), this, null, buf);
+assertAsmLinkFail(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x1fffffff]|0 } return f'), this, null, buf);
+
+
+// An unshifted literal constant byte index outside the range 0 to 2^31-1 inclusive should cause an error compiling.
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x20000000]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x3fffffff]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x40000000]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x7fffffff]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x80000000]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x8fffffff]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0xffffffff]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x100000000]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0x80000000]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0xffffffff]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0x100000000]|0 } return f');
 assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int16Array(b); function f() {return arr[-1]|0 } return f');
 assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[-2]|0 } return f');
 
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[10-12]|0 } return f');
+
+// An intish shifted literal constant index should not fail to compile or link.
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0x3fffffff>>0]|0 } return f'), this, null, buf)(), 0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x3fffffff>>2]|0 } return f'), this, null, buf)(), 0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0xffffffff>>0]|0 } return f'), this, null, buf)(), 0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0xffffffff>>2]|0 } return f'), this, null, buf)(), 0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[-1>>0]|0 } return f'), this, null, buf)(), 0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[-1>>2]|0 } return f'), this, null, buf)(), 0);
+// Unsigned (intish) folded constant index.
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0xffffffff>>>0]|0 } return f'), this, null, buf)(), 0);
+assertEq(asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {arr[0] = 1; return arr[(0xffffffff+1)>>>0]|0 } return f'), this, null, buf)(), 1);
+
+// A non-intish shifted literal constant index should cause an error compiling.
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b); function f() {return arr[0x100000000>>0]|0 } return f');
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int32Array(b); function f() {return arr[0x100000000>>2]|0 } return f');
+
+// Folded non-intish constant expressions should cause an error compiling.
+assertAsmTypeFail('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.Int8Array(b);  function f() {return arr[0xffffffff+1]|0 } return f');
+
+
+
 function testInt(ctor, shift, scale, disp) {
     var ab = new ArrayBuffer(4096);
     var arr = new ctor(ab);
     for (var i = 0; i < arr.length; i++)
         arr[i] = i;
     var f = asmLink(asmCompile('glob', 'imp', 'b', USE_ASM + 'var arr=new glob.' + ctor.name + '(b); function f(i) {i=i|0; return arr[((i<<' + scale + ')+' + disp + ')>>' + shift + ']|0 } return f'), this, null, ab);
     for (var i of [0,1,2,3,4,1023,1024,1025,4095,4096,4097])
         assertEq(f(i), arr[((i<<scale)+disp)>>shift]|0);
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -904,16 +904,21 @@ TypedArrayStoreType(ArrayBufferView::Vie
       case ArrayBufferView::TYPE_FLOAT32:
       case ArrayBufferView::TYPE_FLOAT64:
         return ArrayStore_Doublish;
       default:;
     }
     MOZ_ASSUME_UNREACHABLE("Unexpected array type");
 }
 
+enum NeedsBoundsCheck {
+    NO_BOUNDS_CHECK,
+    NEEDS_BOUNDS_CHECK
+};
+
 /*****************************************************************************/
 
 namespace {
 
 typedef Vector<PropertyName*,1> LabelVector;
 typedef Vector<MBasicBlock*,8> BlockVector;
 
 // ModuleCompiler encapsulates the compilation of an entire asm.js module. Over
@@ -1468,16 +1473,25 @@ class MOZ_STACK_CLASS ModuleCompiler
         if (!module_->addExit(ffiIndex, exitIndex))
             return false;
         return exits_.add(p, OldMove(exitDescriptor), *exitIndex);
     }
     bool addGlobalAccess(AsmJSGlobalAccess access) {
         return globalAccesses_.append(access);
     }
 
+    // Note a constraint on the minimum size of the heap.  The heap size is
+    // constrained when linking to be at least the maximum of all such constraints.
+    void requireHeapLengthToBeAtLeast(uint32_t len) {
+        module_->requireHeapLengthToBeAtLeast(len);
+    }
+    uint32_t minHeapLength() const {
+        return module_->minHeapLength();
+    }
+
     bool collectAccesses(MIRGenerator &gen) {
         if (!module_->addHeapAccesses(gen.heapAccesses()))
             return false;
         if (!globalAccesses_.appendAll(gen.globalAccesses()))
             return false;
         return true;
     }
 
@@ -1946,30 +1960,35 @@ class FunctionCompiler
 
     void assign(const Local &local, MDefinition *def)
     {
         if (!curBlock_)
             return;
         curBlock_->setSlot(info().localSlot(local.slot), def);
     }
 
-    MDefinition *loadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr)
+    MDefinition *loadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, NeedsBoundsCheck chk)
     {
         if (!curBlock_)
             return NULL;
         MAsmJSLoadHeap *load = MAsmJSLoadHeap::New(vt, ptr);
         curBlock_->add(load);
+        if (chk == NO_BOUNDS_CHECK)
+            load->setSkipBoundsCheck(true);
         return load;
     }
 
-    void storeHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v)
+    void storeHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v, NeedsBoundsCheck chk)
     {
         if (!curBlock_)
             return;
-        curBlock_->add(MAsmJSStoreHeap::New(vt, ptr, v));
+        MAsmJSStoreHeap *store = MAsmJSStoreHeap::New(vt, ptr, v);
+        curBlock_->add(store);
+        if (chk == NO_BOUNDS_CHECK)
+            store->setSkipBoundsCheck(true);
     }
 
     MDefinition *loadGlobalVar(const ModuleCompiler::Global &global)
     {
         if (!curBlock_)
             return NULL;
         MIRType type = global.varType().toMIRType();
         unsigned globalDataOffset = module().globalVarIndexToGlobalDataOffset(global.varIndex());
@@ -3083,96 +3102,160 @@ CheckVarRef(FunctionCompiler &f, ParseNo
         }
         return true;
     }
 
     return f.failName(varRef, "'%s' not found in local or asm.js module scope", name);
 }
 
 static bool
+FoldMaskedArrayIndex(FunctionCompiler &f, ParseNode **indexExpr, int32_t *mask,
+                     NeedsBoundsCheck *needsBoundsCheck)
+{
+    ParseNode *indexNode = BinaryLeft(*indexExpr);
+    ParseNode *maskNode = BinaryRight(*indexExpr);
+
+    uint32_t mask2;
+    if (IsLiteralUint32(maskNode, &mask2)) {
+        // Flag the access to skip the bounds check if the mask ensures that an 'out of
+        // bounds' access can not occur based on the current heap length constraint.
+        if (mozilla::CountLeadingZeroes32(f.m().minHeapLength() - 1) <= mozilla::CountLeadingZeroes32(mask2))
+            *needsBoundsCheck = NO_BOUNDS_CHECK;
+        *mask &= mask2;
+        *indexExpr = indexNode;
+        return true;
+    }
+
+    return false;
+}
+
+static bool
 CheckArrayAccess(FunctionCompiler &f, ParseNode *elem, ArrayBufferView::ViewType *viewType,
-                 MDefinition **def)
+                 MDefinition **def, NeedsBoundsCheck *needsBoundsCheck)
 {
     ParseNode *viewName = ElemBase(elem);
     ParseNode *indexExpr = ElemIndex(elem);
+    *needsBoundsCheck = NEEDS_BOUNDS_CHECK;
 
     if (!viewName->isKind(PNK_NAME))
         return f.fail(viewName, "base of array access must be a typed array view name");
 
     const ModuleCompiler::Global *global = f.lookupGlobal(viewName->name());
     if (!global || global->which() != ModuleCompiler::Global::ArrayView)
         return f.fail(viewName, "base of array access must be a typed array view name");
 
     *viewType = global->viewType();
 
     uint32_t pointer;
     if (IsLiteralUint32(indexExpr, &pointer)) {
+        if (pointer > (uint32_t(INT32_MAX) >> TypedArrayShift(*viewType)))
+            return f.fail(indexExpr, "constant index out of range");
         pointer <<= TypedArrayShift(*viewType);
+        // It is adequate to note pointer+1 rather than rounding up to the next
+        // access-size boundary because access is always aligned and the constraint
+        // will be rounded up to a larger alignment later.
+        f.m().requireHeapLengthToBeAtLeast(uint32_t(pointer) + 1);
+        *needsBoundsCheck = NO_BOUNDS_CHECK;
         *def = f.constant(Int32Value(pointer));
         return true;
     }
 
+    // Mask off the low bits to account for the clearing effect of a right shift
+    // followed by the left shift implicit in the array access. E.g., H32[i>>2]
+    // loses the low two bits.
+    int32_t mask = ~((uint32_t(1) << TypedArrayShift(*viewType)) - 1);
+
     MDefinition *pointerDef;
     if (indexExpr->isKind(PNK_RSH)) {
         ParseNode *shiftNode = BinaryRight(indexExpr);
         ParseNode *pointerNode = BinaryLeft(indexExpr);
 
         uint32_t shift;
         if (!IsLiteralUint32(shiftNode, &shift))
             return f.failf(shiftNode, "shift amount must be constant");
 
         unsigned requiredShift = TypedArrayShift(*viewType);
         if (shift != requiredShift)
             return f.failf(shiftNode, "shift amount must be %u", requiredShift);
 
+        if (pointerNode->isKind(PNK_BITAND))
+            FoldMaskedArrayIndex(f, &pointerNode, &mask, needsBoundsCheck);
+
+        // Fold a 'literal constant right shifted' now, and skip the bounds check if
+        // currently possible. This handles the optimization of many of these uses without
+        // the need for range analysis, and saves the generation of a MBitAnd op.
+        if (IsLiteralUint32(pointerNode, &pointer) && pointer <= uint32_t(INT32_MAX)) {
+            // Cases: b[c>>n], and b[(c&m)>>n]
+            pointer &= mask;
+            if (pointer < f.m().minHeapLength())
+                *needsBoundsCheck = NO_BOUNDS_CHECK;
+            *def = f.constant(Int32Value(pointer));
+            return true;
+        }
+
         Type pointerType;
         if (!CheckExpr(f, pointerNode, &pointerDef, &pointerType))
             return false;
 
         if (!pointerType.isIntish())
             return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
     } else {
         if (TypedArrayShift(*viewType) != 0)
             return f.fail(indexExpr, "index expression isn't shifted; must be an Int8/Uint8 access");
 
+        JS_ASSERT(mask == -1);
+        bool folded = false;
+
+        if (indexExpr->isKind(PNK_BITAND))
+            folded = FoldMaskedArrayIndex(f, &indexExpr, &mask, needsBoundsCheck);
+
         Type pointerType;
         if (!CheckExpr(f, indexExpr, &pointerDef, &pointerType))
             return false;
 
-        if (!pointerType.isInt())
-            return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
-    }
-
-    // Mask off the low bits to account for clearing effect of a right shift
-    // followed by the left shift implicit in the array access. E.g., H32[i>>2]
-    // loses the low two bits.
-    int32_t mask = ~((uint32_t(1) << TypedArrayShift(*viewType)) - 1);
-    *def = f.bitwise<MBitAnd>(pointerDef, f.constant(Int32Value(mask)));
+        if (folded) {
+            if (!pointerType.isIntish())
+                return f.failf(indexExpr, "%s is not a subtype of intish", pointerType.toChars());
+        } else {
+            if (!pointerType.isInt())
+                return f.failf(indexExpr, "%s is not a subtype of int", pointerType.toChars());
+        }
+    }
+
+    // Don't generate the mask op if there is no need for it which could happen for
+    // a shift of zero.
+    if (mask == -1)
+        *def = pointerDef;
+    else
+        *def = f.bitwise<MBitAnd>(pointerDef, f.constant(Int32Value(mask)));
+
     return true;
 }
 
 static bool
 CheckArrayLoad(FunctionCompiler &f, ParseNode *elem, MDefinition **def, Type *type)
 {
     ArrayBufferView::ViewType viewType;
     MDefinition *pointerDef;
-    if (!CheckArrayAccess(f, elem, &viewType, &pointerDef))
+    NeedsBoundsCheck needsBoundsCheck;
+    if (!CheckArrayAccess(f, elem, &viewType, &pointerDef, &needsBoundsCheck))
         return false;
 
-    *def = f.loadHeap(viewType, pointerDef);
+    *def = f.loadHeap(viewType, pointerDef, needsBoundsCheck);
     *type = TypedArrayLoadType(viewType);
     return true;
 }
 
 static bool
 CheckStoreArray(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition **def, Type *type)
 {
     ArrayBufferView::ViewType viewType;
     MDefinition *pointerDef;
-    if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef))
+    NeedsBoundsCheck needsBoundsCheck;
+    if (!CheckArrayAccess(f, lhs, &viewType, &pointerDef, &needsBoundsCheck))
         return false;
 
     MDefinition *rhsDef;
     Type rhsType;
     if (!CheckExpr(f, rhs, &rhsDef, &rhsType))
         return false;
 
     switch (TypedArrayStoreType(viewType)) {
@@ -3181,17 +3264,17 @@ CheckStoreArray(FunctionCompiler &f, Par
             return f.failf(lhs, "%s is not a subtype of intish", rhsType.toChars());
         break;
       case ArrayStore_Doublish:
         if (!rhsType.isDoublish())
             return f.failf(lhs, "%s is not a subtype of doublish", rhsType.toChars());
         break;
     }
 
-    f.storeHeap(viewType, pointerDef, rhsDef);
+    f.storeHeap(viewType, pointerDef, rhsDef, needsBoundsCheck);
 
     *def = rhsDef;
     *type = rhsType;
     return true;
 }
 
 static bool
 CheckAssignName(FunctionCompiler &f, ParseNode *lhs, ParseNode *rhs, MDefinition **def, Type *type)
@@ -4708,17 +4791,24 @@ CheckFunction(ModuleCompiler &m, LifoAll
     if (func->defined())
         return m.failName(fn, "function '%s' already defined", FunctionName(fn));
 
     func->define(fn->pn_pos.begin);
     func->accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC);
 
     m.parser().release(mark);
 
+    // Copy the cumulative minimum heap size constraint to the MIR for use in analysis.  The length
+    // is also constrained to be a power of two, so firstly round up - a larger 'heap required
+    // length' can help range analysis to prove that bounds checks are not needed.
+    size_t len = mozilla::RoundUpPow2((size_t) m.minHeapLength());
+    m.requireHeapLengthToBeAtLeast(len);
+
     *mir = f.extractMIR();
+    (*mir)->noteMinAsmJSHeapLength(len);
     *funcOut = func;
     return true;
 }
 
 static bool
 GenerateCode(ModuleCompiler &m, ModuleCompiler::Func &func, MIRGenerator &mir, LIRGraph &lir)
 {
     int64_t before = PRMJ_Now();
--- a/js/src/jit/AsmJSLink.cpp
+++ b/js/src/jit/AsmJSLink.cpp
@@ -213,16 +213,22 @@ DynamicallyLinkModule(JSContext *cx, Cal
         if (!IsTypedArrayBuffer(bufferVal))
             return LinkFail(cx, "bad ArrayBuffer argument");
 
         heap = &bufferVal.toObject().as<ArrayBufferObject>();
 
         if (!IsPowerOfTwo(heap->byteLength()) || heap->byteLength() < AsmJSAllocationGranularity)
             return LinkFail(cx, "ArrayBuffer byteLength must be a power of two greater than or equal to 4096");
 
+        // This check is sufficient without considering the size of the loaded datum because heap
+        // loads and stores start on an aligned boundary and the heap byteLength has larger alignment.
+        JS_ASSERT((module.minHeapLength() - 1) <= INT32_MAX);
+        if (heap->byteLength() < module.minHeapLength())
+            return LinkFail(cx, "ArrayBuffer byteLength less than the largest source code heap length constraint.");
+
         if (!ArrayBufferObject::prepareForAsmJS(cx, heap))
             return LinkFail(cx, "Unable to prepare ArrayBuffer for asm.js use");
 
         module.patchHeapAccesses(heap, cx);
     }
 
     AutoObjectVector ffis(cx);
     if (!ffis.resize(module.numFFIs()))
--- a/js/src/jit/AsmJSModule.cpp
+++ b/js/src/jit/AsmJSModule.cpp
@@ -21,21 +21,25 @@
 
 using namespace js;
 
 void
 AsmJSModule::patchHeapAccesses(ArrayBufferObject *heap, JSContext *cx)
 {
     JS_ASSERT(IsPowerOfTwo(heap->byteLength()));
 #if defined(JS_CPU_X86)
-    void *heapOffset = (void*)heap->dataPointer();
+    uint8_t *heapOffset = heap->dataPointer();
     void *heapLength = (void*)heap->byteLength();
     for (unsigned i = 0; i < heapAccesses_.length(); i++) {
-        JSC::X86Assembler::setPointer(heapAccesses_[i].patchLengthAt(code_), heapLength);
-        JSC::X86Assembler::setPointer(heapAccesses_[i].patchOffsetAt(code_), heapOffset);
+        const jit::AsmJSHeapAccess &access = heapAccesses_[i];
+        if (access.hasLengthCheck())
+            JSC::X86Assembler::setPointer(access.patchLengthAt(code_), heapLength);
+        void *addr = access.patchOffsetAt(code_);
+        uint32_t disp = reinterpret_cast<uint32_t>(JSC::X86Assembler::getPointer(addr));
+        JSC::X86Assembler::setPointer(addr, (void *)(heapOffset + disp));
     }
 #elif defined(JS_CPU_ARM)
     uint32_t bits = mozilla::CeilingLog2(heap->byteLength());
     for (unsigned i = 0; i < heapAccesses_.length(); i++)
         jit::Assembler::updateBoundsCheck(bits, (jit::Instruction*)(heapAccesses_[i].offset() + code_));
     // We already know the exact extent of areas that need to be patched, just make sure we
     // flush all of them at once.
     jit::AutoFlushCache::updateTop(uintptr_t(code_), pod.codeBytes_);
--- a/js/src/jit/AsmJSModule.h
+++ b/js/src/jit/AsmJSModule.h
@@ -313,16 +313,17 @@ class AsmJSModule
     PropertyName *                        globalArgumentName_;
     PropertyName *                        importArgumentName_;
     PropertyName *                        bufferArgumentName_;
 
     GlobalVector                          globals_;
     ExitVector                            exits_;
     ExportedFunctionVector                exports_;
     HeapAccessVector                      heapAccesses_;
+    uint32_t                              minHeapLength_;
 #if defined(MOZ_VTUNE) or defined(JS_ION_PERF)
     ProfiledFunctionVector                profiledFunctions_;
 #endif
 #if defined(JS_ION_PERF)
     ProfiledBlocksFunctionVector          perfProfiledBlocksFunctions_;
 #endif
 
     struct Pod {
@@ -344,16 +345,17 @@ class AsmJSModule
     AsmJSModuleSourceDesc                 sourceDesc_;
     FunctionCountsVector                  functionCounts_;
 
   public:
     explicit AsmJSModule()
       : globalArgumentName_(NULL),
         importArgumentName_(NULL),
         bufferArgumentName_(NULL),
+        minHeapLength_(AsmJSAllocationGranularity),
         code_(NULL),
         operationCallbackExit_(NULL),
         linked_(false)
     {
         mozilla::PodZero(&pod);
     }
 
     ~AsmJSModule();
@@ -625,16 +627,24 @@ class AsmJSModule
     jit::AsmJSHeapAccess &heapAccess(unsigned i) {
         return heapAccesses_[i];
     }
     const jit::AsmJSHeapAccess &heapAccess(unsigned i) const {
         return heapAccesses_[i];
     }
     void patchHeapAccesses(ArrayBufferObject *heap, JSContext *cx);
 
+    void requireHeapLengthToBeAtLeast(uint32_t len) {
+        if (len > minHeapLength_)
+            minHeapLength_ = len;
+    }
+    uint32_t minHeapLength() const {
+        return minHeapLength_;
+    }
+
     uint8_t *allocateCodeAndGlobalSegment(ExclusiveContext *cx, size_t bytesNeeded);
 
     uint8_t *functionCode() const {
         JS_ASSERT(code_);
         JS_ASSERT(uintptr_t(code_) % AsmJSPageSize == 0);
         return code_;
     }
 
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -1301,17 +1301,17 @@ OptimizeMIR(MIRGenerator *mir)
             AssertExtendedGraphCoherency(graph);
 
             if (mir->shouldCancel("LICM"))
                 return false;
         }
     }
 
     if (js_IonOptions.rangeAnalysis) {
-        RangeAnalysis r(graph);
+        RangeAnalysis r(mir, graph);
         if (!r.addBetaNobes())
             return false;
         IonSpewPass("Beta");
         AssertExtendedGraphCoherency(graph);
 
         if (mir->shouldCancel("RA Beta"))
             return false;
 
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -2785,23 +2785,16 @@ LIRGenerator::visitHaveSameClass(MHaveSa
 
     JS_ASSERT(lhs->type() == MIRType_Object);
     JS_ASSERT(rhs->type() == MIRType_Object);
 
     return define(new LHaveSameClass(useRegister(lhs), useRegister(rhs), temp()), ins);
 }
 
 bool
-LIRGenerator::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins)
-{
-    LAsmJSLoadHeap *lir = new LAsmJSLoadHeap(useRegisterAtStart(ins->ptr()));
-    return define(lir, ins);
-}
-
-bool
 LIRGenerator::visitAsmJSLoadGlobalVar(MAsmJSLoadGlobalVar *ins)
 {
     return define(new LAsmJSLoadGlobalVar, ins);
 }
 
 bool
 LIRGenerator::visitAsmJSStoreGlobalVar(MAsmJSStoreGlobalVar *ins)
 {
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -232,17 +232,16 @@ class LIRGenerator : public LIRGenerator
     bool visitThrow(MThrow *ins);
     bool visitIn(MIn *ins);
     bool visitInArray(MInArray *ins);
     bool visitInstanceOf(MInstanceOf *ins);
     bool visitCallInstanceOf(MCallInstanceOf *ins);
     bool visitFunctionBoundary(MFunctionBoundary *ins);
     bool visitIsCallable(MIsCallable *ins);
     bool visitHaveSameClass(MHaveSameClass *ins);
-    bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins);
     bool visitAsmJSLoadGlobalVar(MAsmJSLoadGlobalVar *ins);
     bool visitAsmJSStoreGlobalVar(MAsmJSStoreGlobalVar *ins);
     bool visitAsmJSLoadFFIFunc(MAsmJSLoadFFIFunc *ins);
     bool visitAsmJSParameter(MAsmJSParameter *ins);
     bool visitAsmJSReturn(MAsmJSReturn *ins);
     bool visitAsmJSVoidReturn(MAsmJSVoidReturn *ins);
     bool visitAsmJSPassStackArg(MAsmJSPassStackArg *ins);
     bool visitAsmJSCall(MAsmJSCall *ins);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -8153,56 +8153,65 @@ class MAsmJSUMod : public MBinaryInstruc
 
   public:
     INSTRUCTION_HEADER(AsmJSUMod);
     static MAsmJSUMod *New(MDefinition *left, MDefinition *right) {
         return new MAsmJSUMod(left, right);
     }
 };
 
-class MAsmJSLoadHeap : public MUnaryInstruction
+class MAsmJSHeapAccess
+{
+    ArrayBufferView::ViewType viewType_;
+    bool skipBoundsCheck_;
+
+  public:
+    MAsmJSHeapAccess(ArrayBufferView::ViewType vt, bool s)
+      : viewType_(vt), skipBoundsCheck_(s)
+    {}
+
+    ArrayBufferView::ViewType viewType() const { return viewType_; }
+    bool skipBoundsCheck() const { return skipBoundsCheck_; }
+    void setSkipBoundsCheck(bool v) { skipBoundsCheck_ = v; }
+};
+
+class MAsmJSLoadHeap : public MUnaryInstruction, public MAsmJSHeapAccess
 {
     MAsmJSLoadHeap(ArrayBufferView::ViewType vt, MDefinition *ptr)
-      : MUnaryInstruction(ptr), viewType_(vt)
+      : MUnaryInstruction(ptr), MAsmJSHeapAccess(vt, false)
     {
         if (vt == ArrayBufferView::TYPE_FLOAT32 || vt == ArrayBufferView::TYPE_FLOAT64)
             setResultType(MIRType_Double);
         else
             setResultType(MIRType_Int32);
     }
 
-    ArrayBufferView::ViewType viewType_;
-
   public:
     INSTRUCTION_HEADER(AsmJSLoadHeap);
 
     static MAsmJSLoadHeap *New(ArrayBufferView::ViewType vt, MDefinition *ptr) {
         return new MAsmJSLoadHeap(vt, ptr);
     }
 
-    ArrayBufferView::ViewType viewType() const { return viewType_; }
     MDefinition *ptr() const { return getOperand(0); }
 };
 
-class MAsmJSStoreHeap : public MBinaryInstruction
+class MAsmJSStoreHeap : public MBinaryInstruction, public MAsmJSHeapAccess
 {
     MAsmJSStoreHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v)
-      : MBinaryInstruction(ptr, v), viewType_(vt)
+      : MBinaryInstruction(ptr, v) , MAsmJSHeapAccess(vt, false)
     {}
 
-    ArrayBufferView::ViewType viewType_;
-
   public:
     INSTRUCTION_HEADER(AsmJSStoreHeap);
 
     static MAsmJSStoreHeap *New(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v) {
         return new MAsmJSStoreHeap(vt, ptr, v);
     }
 
-    ArrayBufferView::ViewType viewType() const { return viewType_; }
     MDefinition *ptr() const { return getOperand(0); }
     MDefinition *value() const { return getOperand(1); }
 };
 
 class MAsmJSLoadGlobalVar : public MNullaryInstruction
 {
     MAsmJSLoadGlobalVar(MIRType type, unsigned globalDataOffset)
       : globalDataOffset_(globalDataOffset)
--- a/js/src/jit/MIRGenerator.h
+++ b/js/src/jit/MIRGenerator.h
@@ -117,16 +117,22 @@ class MIRGenerator
         return performsAsmJSCall_;
     }
     bool noteHeapAccess(AsmJSHeapAccess heapAccess) {
         return asmJSHeapAccesses_.append(heapAccess);
     }
     const Vector<AsmJSHeapAccess, 0, IonAllocPolicy> &heapAccesses() const {
         return asmJSHeapAccesses_;
     }
+    void noteMinAsmJSHeapLength(uint32_t len) {
+        minAsmJSHeapLength_ = len;
+    }
+    uint32_t minAsmJSHeapLength() const {
+        return minAsmJSHeapLength_;
+    }
     bool noteGlobalAccess(unsigned offset, unsigned globalDataOffset) {
         return asmJSGlobalAccesses_.append(AsmJSGlobalAccess(offset, globalDataOffset));
     }
     const Vector<AsmJSGlobalAccess, 0, IonAllocPolicy> &globalAccesses() const {
         return asmJSGlobalAccesses_;
     }
 
   public:
@@ -140,16 +146,17 @@ class MIRGenerator
     MIRGraph *graph_;
     bool error_;
     size_t cancelBuild_;
 
     uint32_t maxAsmJSStackArgBytes_;
     bool performsAsmJSCall_;
     AsmJSHeapAccessVector asmJSHeapAccesses_;
     AsmJSGlobalAccessVector asmJSGlobalAccesses_;
+    uint32_t minAsmJSHeapLength_;
 
 #if defined(JS_ION_PERF)
     AsmJSPerfSpewer asmJSPerfSpewer_;
 
   public:
     AsmJSPerfSpewer &perfSpewer() { return asmJSPerfSpewer_; }
 #endif
 };
--- a/js/src/jit/MIRGraph.cpp
+++ b/js/src/jit/MIRGraph.cpp
@@ -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 "jit/MIRGraph.h"
 
 #include "jsanalyze.h"
 
+#include "jit/AsmJS.h"
 #include "jit/Ion.h"
 #include "jit/IonBuilder.h"
 #include "jit/IonSpewer.h"
 #include "jit/MIR.h"
 
 #include "jsinferinlines.h"
 
 using namespace js;
@@ -22,17 +23,18 @@ MIRGenerator::MIRGenerator(JSCompartment
                            TempAllocator *temp, MIRGraph *graph, CompileInfo *info)
   : compartment(compartment),
     info_(info),
     temp_(temp),
     graph_(graph),
     error_(false),
     cancelBuild_(0),
     maxAsmJSStackArgBytes_(0),
-    performsAsmJSCall_(false)
+    performsAsmJSCall_(false),
+    minAsmJSHeapLength_(AsmJSAllocationGranularity)
 { }
 
 bool
 MIRGenerator::abortFmt(const char *message, va_list ap)
 {
     IonSpewVA(IonSpew_Abort, message, ap);
     error_ = true;
     return false;
--- a/js/src/jit/RangeAnalysis.cpp
+++ b/js/src/jit/RangeAnalysis.cpp
@@ -1614,16 +1614,36 @@ RangeAnalysis::analyze()
 
             def->computeRange();
             IonSpew(IonSpew_Range, "computing range on %d", def->id());
             SpewRange(def);
         }
 
         if (block->isLoopHeader())
             analyzeLoop(block);
+
+        if (mir->compilingAsmJS()) {
+            for (MInstructionIterator i = block->begin(); i != block->end(); i++) {
+                if (i->isAsmJSLoadHeap()) {
+                    MAsmJSLoadHeap *ins = i->toAsmJSLoadHeap();
+                    Range *range = ins->ptr()->range();
+                    if (range && !range->isLowerInfinite() && range->lower() >= 0 &&
+                        !range->isUpperInfinite() &&
+                        (uint32_t) range->upper() < mir->minAsmJSHeapLength())
+                        ins->setSkipBoundsCheck(true);
+                } else if (i->isAsmJSStoreHeap()) {
+                    MAsmJSStoreHeap *ins = i->toAsmJSStoreHeap();
+                    Range *range = ins->ptr()->range();
+                    if (range && !range->isLowerInfinite() && range->lower() >= 0 &&
+                        !range->isUpperInfinite() &&
+                        (uint32_t) range->upper() < mir->minAsmJSHeapLength())
+                        ins->setSkipBoundsCheck(true);
+                }
+            }
+        }
     }
 
     return true;
 }
 
 bool
 RangeAnalysis::addRangeAssertions()
 {
--- a/js/src/jit/RangeAnalysis.h
+++ b/js/src/jit/RangeAnalysis.h
@@ -69,21 +69,22 @@ struct SymbolicBound : public TempObject
 class RangeAnalysis
 {
   protected:
     bool blockDominates(MBasicBlock *b, MBasicBlock *b2);
     void replaceDominatedUsesWith(MDefinition *orig, MDefinition *dom,
                                   MBasicBlock *block);
 
   protected:
+    MIRGenerator *mir;
     MIRGraph &graph_;
 
   public:
-    MOZ_CONSTEXPR RangeAnalysis(MIRGraph &graph) :
-        graph_(graph) {}
+    MOZ_CONSTEXPR RangeAnalysis(MIRGenerator *mir, MIRGraph &graph) :
+        mir(mir), graph_(graph) {}
     bool addBetaNobes();
     bool analyze();
     bool addRangeAssertions();
     bool removeBetaNobes();
     bool truncate();
 
   private:
     void analyzeLoop(MBasicBlock *header);
--- a/js/src/jit/RegisterSets.h
+++ b/js/src/jit/RegisterSets.h
@@ -799,44 +799,47 @@ class AsmJSHeapAccess
     uint8_t isFloat32Load_;
     jit::AnyRegister::Code loadedReg_ : 8;
 #endif
 
     JS_STATIC_ASSERT(jit::AnyRegister::Total < UINT8_MAX);
 
   public:
 #if defined(JS_CPU_X86) || defined(JS_CPU_X64)
+    // If 'cmp' equals 'offset' or if it is not supplied then the
+    // cmpDelta_ is zero indicating that there is no length to patch.
     AsmJSHeapAccess(uint32_t offset, uint32_t after, ArrayBufferView::ViewType vt,
                     AnyRegister loadedReg, uint32_t cmp = UINT32_MAX)
       : offset_(offset),
 # if defined(JS_CPU_X86)
-        cmpDelta_(offset - cmp),
+        cmpDelta_(cmp == UINT32_MAX ? 0 : offset - cmp),
 # endif
         opLength_(after - offset),
         isFloat32Load_(vt == ArrayBufferView::TYPE_FLOAT32),
         loadedReg_(loadedReg.code())
     {}
     AsmJSHeapAccess(uint32_t offset, uint8_t after, uint32_t cmp = UINT32_MAX)
       : offset_(offset),
 # if defined(JS_CPU_X86)
-        cmpDelta_(offset - cmp),
+        cmpDelta_(cmp == UINT32_MAX ? 0 : offset - cmp),
 # endif
         opLength_(after - offset),
         isFloat32Load_(false),
         loadedReg_(UINT8_MAX)
     {}
 #elif defined(JS_CPU_ARM)
     explicit AsmJSHeapAccess(uint32_t offset)
       : offset_(offset)
     {}
 #endif
 
     uint32_t offset() const { return offset_; }
     void setOffset(uint32_t offset) { offset_ = offset; }
 #if defined(JS_CPU_X86)
+    bool hasLengthCheck() const { return cmpDelta_ > 0; }
     void *patchLengthAt(uint8_t *code) const { return code + (offset_ - cmpDelta_); }
     void *patchOffsetAt(uint8_t *code) const { return code + (offset_ + opLength_); }
 #endif
 #if defined(JS_CPU_X86) || defined(JS_CPU_X64)
     unsigned opLength() const { return opLength_; }
     bool isLoad() const { return loadedReg_ != UINT8_MAX; }
     bool isFloat32Load() const { return isFloat32Load_; }
     jit::AnyRegister loadedReg() const { return jit::AnyRegister::FromCode(loadedReg_); }
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1774,42 +1774,78 @@ CodeGeneratorARM::visitStoreTypedArrayEl
 bool
 CodeGeneratorARM::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     const MAsmJSLoadHeap *mir = ins->mir();
     bool isSigned;
     int size;
     bool isFloat = false;
     switch (mir->viewType()) {
-      case ArrayBufferView::TYPE_INT8:    isSigned = true; size = 8; break;
-      case ArrayBufferView::TYPE_UINT8:   isSigned = false; size = 8; break;
-      case ArrayBufferView::TYPE_INT16:   isSigned = true; size = 16; break;
+      case ArrayBufferView::TYPE_INT8:    isSigned = true;  size =  8; break;
+      case ArrayBufferView::TYPE_UINT8:   isSigned = false; size =  8; break;
+      case ArrayBufferView::TYPE_INT16:   isSigned = true;  size = 16; break;
       case ArrayBufferView::TYPE_UINT16:  isSigned = false; size = 16; break;
       case ArrayBufferView::TYPE_INT32:
       case ArrayBufferView::TYPE_UINT32:  isSigned = true;  size = 32; break;
       case ArrayBufferView::TYPE_FLOAT64: isFloat = true;   size = 64; break;
-      case ArrayBufferView::TYPE_FLOAT32:
-        isFloat = true;
-        size = 32;
-        break;
+      case ArrayBufferView::TYPE_FLOAT32: isFloat = true;   size = 32; break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
-    Register index = ToRegister(ins->ptr());
-    BufferOffset bo = masm.ma_BoundsCheck(index);
+
+    const LAllocation *ptr = ins->ptr();
+
+    if (ptr->isConstant()) {
+        JS_ASSERT(mir->skipBoundsCheck());
+        int32_t ptrImm = ptr->toConstant()->toInt32();
+        JS_ASSERT(ptrImm >= 0);
+        if (isFloat) {
+            VFPRegister vd(ToFloatRegister(ins->output()));
+            if (size == 32) {
+                masm.ma_vldr(Operand(HeapReg, ptrImm), vd.singleOverlay(), Assembler::Always);
+                masm.as_vcvt(vd, vd.singleOverlay(), false, Assembler::Always);
+            } else {
+                masm.ma_vldr(Operand(HeapReg, ptrImm), vd, Assembler::Always);
+            }
+        }  else {
+            masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, Imm32(ptrImm),
+                                  ToRegister(ins->output()), Offset, Assembler::Always);
+        }
+        return true;
+    }
+
+    Register ptrReg = ToRegister(ptr);
+
+    if (mir->skipBoundsCheck()) {
+        if (isFloat) {
+            VFPRegister vd(ToFloatRegister(ins->output()));
+            if (size == 32) {
+                masm.ma_vldr(vd.singleOverlay(), HeapReg, ptrReg, 0, Assembler::Always);
+                masm.as_vcvt(vd, vd.singleOverlay(), false, Assembler::Always);
+            } else {
+                masm.ma_vldr(vd, HeapReg, ptrReg, 0, Assembler::Always);
+            }
+        } else {
+            masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, ptrReg,
+                                  ToRegister(ins->output()), Offset, Assembler::Always);
+        }
+        return true;
+    }
+
+    BufferOffset bo = masm.ma_BoundsCheck(ptrReg);
     if (isFloat) {
         VFPRegister vd(ToFloatRegister(ins->output()));
         if (size == 32) {
-            masm.ma_vldr(vd.singleOverlay(), HeapReg, index, 0, Assembler::Zero);
+            masm.ma_vldr(vd.singleOverlay(), HeapReg, ptrReg, 0, Assembler::Zero);
             masm.as_vcvt(vd, vd.singleOverlay(), false, Assembler::Zero);
         } else {
-            masm.ma_vldr(vd, HeapReg, index, 0, Assembler::Zero);
+            masm.ma_vldr(vd, HeapReg, ptrReg, 0, Assembler::Zero);
         }
         masm.ma_vmov(NANReg, ToFloatRegister(ins->output()), Assembler::NonZero);
-    }  else {
-        masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, index,
+    } else {
+        masm.ma_dataTransferN(IsLoad, size, isSigned, HeapReg, ptrReg,
                               ToRegister(ins->output()), Offset, Assembler::Zero);
         masm.ma_mov(Imm32(0), ToRegister(ins->output()), NoSetCond, Assembler::NonZero);
     }
     return gen->noteHeapAccess(AsmJSHeapAccess(bo.getOffset()));
 }
 
 bool
 CodeGeneratorARM::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins)
@@ -1820,35 +1856,66 @@ CodeGeneratorARM::visitAsmJSStoreHeap(LA
     bool isFloat = false;
     switch (mir->viewType()) {
       case ArrayBufferView::TYPE_INT8:
       case ArrayBufferView::TYPE_UINT8:   isSigned = false; size = 8; break;
       case ArrayBufferView::TYPE_INT16:
       case ArrayBufferView::TYPE_UINT16:  isSigned = false; size = 16; break;
       case ArrayBufferView::TYPE_INT32:
       case ArrayBufferView::TYPE_UINT32:  isSigned = true;  size = 32; break;
-      case ArrayBufferView::TYPE_FLOAT64: isFloat = true;   size = 64; break;
-      case ArrayBufferView::TYPE_FLOAT32:
-        isFloat = true;
-        size = 32;
-        break;
+      case ArrayBufferView::TYPE_FLOAT64: isFloat  = true;  size = 64; break;
+      case ArrayBufferView::TYPE_FLOAT32: isFloat = true;   size = 32; break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
-    Register index = ToRegister(ins->ptr());
+    const LAllocation *ptr = ins->ptr();
+    if (ptr->isConstant()) {
+        JS_ASSERT(mir->skipBoundsCheck());
+        int32_t ptrImm = ptr->toConstant()->toInt32();
+        JS_ASSERT(ptrImm >= 0);
+        if (isFloat) {
+            VFPRegister vd(ToFloatRegister(ins->value()));
+            if (size == 32) {
+                masm.as_vcvt(VFPRegister(ScratchFloatReg).singleOverlay(), vd, false, Assembler::Always);
+                masm.ma_vstr(VFPRegister(ScratchFloatReg).singleOverlay(), Operand(HeapReg, ptrImm), Assembler::Always);
+            } else {
+                masm.ma_vstr(vd, Operand(HeapReg, ptrImm), Assembler::Always);
+            }
+        } else {
+            masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, Imm32(ptrImm),
+                                  ToRegister(ins->value()), Offset, Assembler::Always);
+        }
+        return true;
+    }
 
-    BufferOffset bo = masm.ma_BoundsCheck(index);
+    Register ptrReg = ToRegister(ptr);
+
+    if (mir->skipBoundsCheck()) {
+        Register ptrReg = ToRegister(ptr);
+        if (isFloat) {
+            VFPRegister vd(ToFloatRegister(ins->value()));
+            if (size == 32)
+                masm.storeFloat(vd, HeapReg, ptrReg, Assembler::Always);
+            else
+                masm.ma_vstr(vd, HeapReg, ptrReg, 0, Assembler::Always);
+        } else {
+            masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, ptrReg,
+                                  ToRegister(ins->value()), Offset, Assembler::Always);
+        }
+        return true;
+    }
+
+    BufferOffset bo = masm.ma_BoundsCheck(ptrReg);
     if (isFloat) {
         VFPRegister vd(ToFloatRegister(ins->value()));
-        if (size == 32) {
-            masm.storeFloat(vd, HeapReg, index, Assembler::Zero);
-        } else {
-            masm.ma_vstr(vd, HeapReg, index, 0, Assembler::Zero);
-        }
-    }  else {
-        masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, index,
+        if (size == 32)
+            masm.storeFloat(vd, HeapReg, ptrReg, Assembler::Zero);
+        else
+            masm.ma_vstr(vd, HeapReg, ptrReg, 0, Assembler::Zero);
+    } else {
+        masm.ma_dataTransferN(IsStore, size, isSigned, HeapReg, ptrReg,
                               ToRegister(ins->value()), Offset, Assembler::Zero);
     }
     return gen->noteHeapAccess(AsmJSHeapAccess(bo.getOffset()));
 }
 
 bool
 CodeGeneratorARM::visitAsmJSPassStackArg(LAsmJSPassStackArg *ins)
 {
--- a/js/src/jit/arm/Lowering-arm.cpp
+++ b/js/src/jit/arm/Lowering-arm.cpp
@@ -497,35 +497,48 @@ bool
 LIRGeneratorARM::visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins)
 {
     JS_ASSERT(ins->input()->type() == MIRType_Int32);
     LUInt32ToDouble *lir = new LUInt32ToDouble(useRegisterAtStart(ins->input()));
     return define(lir, ins);
 }
 
 bool
+LIRGeneratorARM::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins)
+{
+    MDefinition *ptr = ins->ptr();
+    JS_ASSERT(ptr->type() == MIRType_Int32);
+    LAllocation ptrAlloc;
+
+    // For the ARM it is best to keep the 'ptr' in a register if a bounds check is needed.
+    if (ptr->isConstant() && ins->skipBoundsCheck()) {
+        int32_t ptrValue = ptr->toConstant()->value().toInt32();
+        // A bounds check is only skipped for a positive index.
+        JS_ASSERT(ptrValue >= 0);
+        ptrAlloc = LAllocation(ptr->toConstant()->vp());
+    } else
+        ptrAlloc = useRegisterAtStart(ptr);
+
+    return define(new LAsmJSLoadHeap(ptrAlloc), ins);
+}
+
+bool
 LIRGeneratorARM::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins)
 {
-    LAsmJSStoreHeap *lir;
-    switch (ins->viewType()) {
-      case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8:
-      case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16:
-      case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32:
-        lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()),
-                                  useRegisterAtStart(ins->value()));
-        break;
-      case ArrayBufferView::TYPE_FLOAT32:
-      case ArrayBufferView::TYPE_FLOAT64:
-        lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()),
-                                  useRegisterAtStart(ins->value()));
-        break;
-      default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
-    }
+    MDefinition *ptr = ins->ptr();
+    JS_ASSERT(ptr->type() == MIRType_Int32);
+    LAllocation ptrAlloc;
 
-    return add(lir, ins);
+    if (ptr->isConstant() && ins->skipBoundsCheck()) {
+        JS_ASSERT(ptr->toConstant()->value().toInt32() >= 0);
+        ptrAlloc = LAllocation(ptr->toConstant()->vp());
+    } else
+        ptrAlloc = useRegisterAtStart(ptr);
+
+    return add(new LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value())), ins);
 }
 
 bool
 LIRGeneratorARM::visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins)
 {
     return define(new LAsmJSLoadFuncPtr(useRegister(ins->index()), temp()), ins);
 }
 
--- a/js/src/jit/arm/Lowering-arm.h
+++ b/js/src/jit/arm/Lowering-arm.h
@@ -73,16 +73,17 @@ class LIRGeneratorARM : public LIRGenera
     bool visitUnbox(MUnbox *unbox);
     bool visitReturn(MReturn *ret);
     bool lowerPhi(MPhi *phi);
     bool visitGuardShape(MGuardShape *ins);
     bool visitGuardObjectType(MGuardObjectType *ins);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
+    bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
     bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
 };
 
 typedef LIRGeneratorARM LIRGeneratorSpecific;
 
 } // namespace jit
--- a/js/src/jit/shared/Lowering-shared-inl.h
+++ b/js/src/jit/shared/Lowering-shared-inl.h
@@ -259,16 +259,24 @@ LAllocation
 LIRGeneratorShared::useRegisterOrConstantAtStart(MDefinition *mir)
 {
     if (mir->isConstant())
         return LAllocation(mir->toConstant()->vp());
     return useRegisterAtStart(mir);
 }
 
 LAllocation
+LIRGeneratorShared::useRegisterOrNonNegativeConstantAtStart(MDefinition *mir)
+{
+    if (mir->isConstant() && mir->toConstant()->value().toInt32() >= 0)
+        return LAllocation(mir->toConstant()->vp());
+    return useRegisterAtStart(mir);
+}
+
+LAllocation
 LIRGeneratorShared::useRegisterOrNonDoubleConstant(MDefinition *mir)
 {
     if (mir->isConstant() && mir->type() != MIRType_Double)
         return LAllocation(mir->toConstant()->vp());
     return useRegister(mir);
 }
 
 #if defined(JS_CPU_ARM)
--- a/js/src/jit/shared/Lowering-shared.h
+++ b/js/src/jit/shared/Lowering-shared.h
@@ -81,16 +81,17 @@ class LIRGeneratorShared : public MInstr
     // "Storable" is architecture dependend, and will include registers and constants on X86
     // and only registers on ARM.
     // this is a generic "things we can expect to write into memory in 1 instruction"
     inline LAllocation useStorable(MDefinition *mir);
     inline LAllocation useStorableAtStart(MDefinition *mir);
     inline LAllocation useKeepaliveOrConstant(MDefinition *mir);
     inline LAllocation useRegisterOrConstant(MDefinition *mir);
     inline LAllocation useRegisterOrConstantAtStart(MDefinition *mir);
+    inline LAllocation useRegisterOrNonNegativeConstantAtStart(MDefinition *mir);
     inline LAllocation useRegisterOrNonDoubleConstant(MDefinition *mir);
 
 #ifdef JS_NUNBOX32
     inline LUse useType(MDefinition *mir, LUse::Policy policy);
     inline LUse usePayload(MDefinition *mir, LUse::Policy policy);
     inline LUse usePayloadAtStart(MDefinition *mir, LUse::Policy policy);
     inline LUse usePayloadInRegisterAtStart(MDefinition *mir);
 
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -389,84 +389,109 @@ CodeGeneratorX64::visitStoreTypedArrayEl
     MOZ_ASSUME_UNREACHABLE("NYI");
 }
 
 bool
 CodeGeneratorX64::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
     MAsmJSLoadHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
+    const LAllocation *ptr = ins->ptr();
 
-    Operand srcAddr(HeapReg, ToRegister(ins->ptr()), TimesOne);
+    // No need to note the access if it will never fault.
+    bool skipNote = mir->skipBoundsCheck();
+    Operand srcAddr(HeapReg);
+
+    if (ptr->isConstant()) {
+        int32_t ptrImm = ptr->toConstant()->toInt32();
+        // Note only a positive index is accepted here because a negative offset would
+        // not wrap back into the protected area reserved for the heap.
+        JS_ASSERT(ptrImm >= 0);
+        srcAddr = Operand(HeapReg, ptrImm);
+    } else {
+        srcAddr = Operand(HeapReg, ToRegister(ptr), TimesOne);
+    }
 
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
         FloatRegister dest = ToFloatRegister(ins->output());
         uint32_t before = masm.size();
         masm.movss(srcAddr, dest);
         uint32_t after = masm.size();
         masm.cvtss2sd(dest, dest);
-        return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output())));
+        return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output())));
     }
 
     uint32_t before = masm.size();
     switch (vt) {
       case ArrayBufferView::TYPE_INT8:    masm.movsbl(srcAddr, ToRegister(ins->output())); break;
       case ArrayBufferView::TYPE_UINT8:   masm.movzbl(srcAddr, ToRegister(ins->output())); break;
       case ArrayBufferView::TYPE_INT16:   masm.movswl(srcAddr, ToRegister(ins->output())); break;
       case ArrayBufferView::TYPE_UINT16:  masm.movzwl(srcAddr, ToRegister(ins->output())); break;
-      case ArrayBufferView::TYPE_INT32:   masm.movl(srcAddr, ToRegister(ins->output())); break;
+      case ArrayBufferView::TYPE_INT32:
       case ArrayBufferView::TYPE_UINT32:  masm.movl(srcAddr, ToRegister(ins->output())); break;
       case ArrayBufferView::TYPE_FLOAT64: masm.movsd(srcAddr, ToFloatRegister(ins->output())); break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
     uint32_t after = masm.size();
-    return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output())));
+    return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(ins->output())));
 }
 
 bool
 CodeGeneratorX64::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins)
 {
     MAsmJSStoreHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
+    const LAllocation *ptr = ins->ptr();
+    // No need to note the access if it will never fault.
+    bool skipNote = mir->skipBoundsCheck();
+    Operand dstAddr(HeapReg);
 
-    Operand dstAddr(HeapReg, ToRegister(ins->ptr()), TimesOne);
+    if (ptr->isConstant()) {
+        int32_t ptrImm = ptr->toConstant()->toInt32();
+        // Note only a positive index is accepted here because a negative offset would
+        // not wrap back into the protected area reserved for the heap.
+        JS_ASSERT(ptrImm >= 0);
+        dstAddr = Operand(HeapReg, ptrImm);
+    } else {
+        dstAddr = Operand(HeapReg, ToRegister(ins->ptr()), TimesOne);
+    }
 
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
         masm.convertDoubleToFloat(ToFloatRegister(ins->value()), ScratchFloatReg);
         uint32_t before = masm.size();
         masm.movss(ScratchFloatReg, dstAddr);
         uint32_t after = masm.size();
-        return gen->noteHeapAccess(AsmJSHeapAccess(before, after));
+        return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after));
     }
 
     uint32_t before = masm.size();
     if (ins->value()->isConstant()) {
         switch (vt) {
-          case ArrayBufferView::TYPE_INT8:    masm.movb(Imm32(ToInt32(ins->value())), dstAddr); break;
+          case ArrayBufferView::TYPE_INT8:
           case ArrayBufferView::TYPE_UINT8:   masm.movb(Imm32(ToInt32(ins->value())), dstAddr); break;
-          case ArrayBufferView::TYPE_INT16:   masm.movw(Imm32(ToInt32(ins->value())), dstAddr); break;
+          case ArrayBufferView::TYPE_INT16:
           case ArrayBufferView::TYPE_UINT16:  masm.movw(Imm32(ToInt32(ins->value())), dstAddr); break;
-          case ArrayBufferView::TYPE_INT32:   masm.movl(Imm32(ToInt32(ins->value())), dstAddr); break;
+          case ArrayBufferView::TYPE_INT32:
           case ArrayBufferView::TYPE_UINT32:  masm.movl(Imm32(ToInt32(ins->value())), dstAddr); break;
           default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
         }
     } else {
         switch (vt) {
-          case ArrayBufferView::TYPE_INT8:    masm.movb(ToRegister(ins->value()), dstAddr); break;
+          case ArrayBufferView::TYPE_INT8:
           case ArrayBufferView::TYPE_UINT8:   masm.movb(ToRegister(ins->value()), dstAddr); break;
-          case ArrayBufferView::TYPE_INT16:   masm.movw(ToRegister(ins->value()), dstAddr); break;
+          case ArrayBufferView::TYPE_INT16:
           case ArrayBufferView::TYPE_UINT16:  masm.movw(ToRegister(ins->value()), dstAddr); break;
-          case ArrayBufferView::TYPE_INT32:   masm.movl(ToRegister(ins->value()), dstAddr); break;
+          case ArrayBufferView::TYPE_INT32:
           case ArrayBufferView::TYPE_UINT32:  masm.movl(ToRegister(ins->value()), dstAddr); break;
           case ArrayBufferView::TYPE_FLOAT64: masm.movsd(ToFloatRegister(ins->value()), dstAddr); break;
           default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
         }
     }
     uint32_t after = masm.size();
-    return gen->noteHeapAccess(AsmJSHeapAccess(before, after));
+    return skipNote || gen->noteHeapAccess(AsmJSHeapAccess(before, after));
 }
 
 bool
 CodeGeneratorX64::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins)
 {
     MAsmJSLoadGlobalVar *mir = ins->mir();
 
     CodeOffsetLabel label;
--- a/js/src/jit/x64/Lowering-x64.cpp
+++ b/js/src/jit/x64/Lowering-x64.cpp
@@ -138,30 +138,51 @@ bool
 LIRGeneratorX64::visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins)
 {
     JS_ASSERT(ins->input()->type() == MIRType_Int32);
     LUInt32ToDouble *lir = new LUInt32ToDouble(useRegisterAtStart(ins->input()));
     return define(lir, ins);
 }
 
 bool
+LIRGeneratorX64::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins)
+{
+    MDefinition *ptr = ins->ptr();
+    JS_ASSERT(ptr->type() == MIRType_Int32);
+
+    // The X64 does not inline an explicit bounds check so has no need to keep the
+    // index in a register, however only a positive index is accepted because a
+    // negative offset encoded as an offset in the addressing mode would not wrap
+    // back into the protected area reserved for the heap.
+    if (ptr->isConstant() && ptr->toConstant()->value().toInt32() >= 0) {
+        LAsmJSLoadHeap *lir = new LAsmJSLoadHeap(LAllocation(ptr->toConstant()->vp()));
+        return define(lir, ins);
+    }
+    return define(new LAsmJSLoadHeap(useRegisterAtStart(ptr)), ins);
+}
+
+bool
 LIRGeneratorX64::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins)
 {
+    MDefinition *ptr = ins->ptr();
+    JS_ASSERT(ptr->type() == MIRType_Int32);
     LAsmJSStoreHeap *lir;
+
+    // Note only a positive constant index is accepted because a negative offset
+    // encoded as an offset in the addressing mode would not wrap back into the
+    // protected area reserved for the heap.
+    LAllocation ptrAlloc = useRegisterOrNonNegativeConstantAtStart(ptr);
     switch (ins->viewType()) {
       case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8:
       case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16:
       case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32:
-        lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()),
-                                  useRegisterOrConstantAtStart(ins->value()));
+        lir = new LAsmJSStoreHeap(ptrAlloc, useRegisterOrConstantAtStart(ins->value()));
         break;
-      case ArrayBufferView::TYPE_FLOAT32:
-      case ArrayBufferView::TYPE_FLOAT64:
-        lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()),
-                                  useRegisterAtStart(ins->value()));
+      case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64:
+        lir = new LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value()));
         break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
 
     return add(lir, ins);
 }
 
 bool
--- a/js/src/jit/x64/Lowering-x64.h
+++ b/js/src/jit/x64/Lowering-x64.h
@@ -35,16 +35,17 @@ class LIRGeneratorX64 : public LIRGenera
 
   public:
     bool visitBox(MBox *box);
     bool visitUnbox(MUnbox *unbox);
     bool visitReturn(MReturn *ret);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
+    bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
     bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
 };
 
 typedef LIRGeneratorX64 LIRGeneratorSpecific;
 
 } // namespace jit
--- a/js/src/jit/x86/Assembler-x86.h
+++ b/js/src/jit/x86/Assembler-x86.h
@@ -529,16 +529,73 @@ class Assembler : public AssemblerX86Sha
         return masm.currentOffset();
     }
 
     // Load from *(addr + index*scale) where addr can be patched.
     CodeOffsetLabel movlWithPatch(void *addr, Register index, Scale scale, Register dest) {
         masm.movl_mr(addr, index.code(), scale, dest.code());
         return masm.currentOffset();
     }
+
+    // Load from *address where address can be patched.
+    CodeOffsetLabel movsblWithPatch(const AbsoluteAddress &src, Register dest) {
+        masm.movsbl_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movzblWithPatch(const AbsoluteAddress &src, Register dest) {
+        masm.movzbl_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movswlWithPatch(const AbsoluteAddress &src, Register dest) {
+        masm.movswl_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movzwlWithPatch(const AbsoluteAddress &src, Register dest) {
+        masm.movzwl_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movlWithPatch(const AbsoluteAddress &src, Register dest) {
+        masm.movl_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movssWithPatch(const AbsoluteAddress &src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
+        masm.movss_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movsdWithPatch(const AbsoluteAddress &src, FloatRegister dest) {
+        JS_ASSERT(HasSSE2());
+        masm.movsd_mr(src.addr, dest.code());
+        return masm.currentOffset();
+    }
+
+    // Store to *address where address can be patched.
+    CodeOffsetLabel movbWithPatch(Register src, const AbsoluteAddress &dest) {
+        masm.movb_rm(src.code(), dest.addr);
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movwWithPatch(Register src, const AbsoluteAddress &dest) {
+        masm.movw_rm(src.code(), dest.addr);
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movlWithPatch(Register src, const AbsoluteAddress &dest) {
+        masm.movl_rm(src.code(), dest.addr);
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movssWithPatch(FloatRegister src, const AbsoluteAddress &dest) {
+        JS_ASSERT(HasSSE2());
+        masm.movss_rm(src.code(), dest.addr);
+        return masm.currentOffset();
+    }
+    CodeOffsetLabel movsdWithPatch(FloatRegister src, const AbsoluteAddress &dest) {
+        JS_ASSERT(HasSSE2());
+        masm.movsd_rm(src.code(), dest.addr);
+        return masm.currentOffset();
+    }
+
 };
 
 // Get a register in which we plan to put a quantity that will be used as an
 // integer argument.  This differs from GetIntArgReg in that if we have no more
 // actual argument registers to use we will fall back on using whatever
 // CallTempReg* don't overlap the argument registers, and only fail once those
 // run out too.
 static inline bool
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -398,33 +398,53 @@ class jit::OutOfLineLoadTypedArrayOutOfB
 {
     AnyRegister dest_;
   public:
     OutOfLineLoadTypedArrayOutOfBounds(AnyRegister dest) : dest_(dest) {}
     const AnyRegister &dest() const { return dest_; }
     bool accept(CodeGeneratorX86 *codegen) { return codegen->visitOutOfLineLoadTypedArrayOutOfBounds(this); }
 };
 
+template<typename T>
 void
-CodeGeneratorX86::loadViewTypeElement(ArrayBufferView::ViewType vt, const Address &srcAddr,
-                                      const LDefinition *out)
+CodeGeneratorX86::loadNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr,
+                                                const LDefinition *out)
 {
     switch (vt) {
       case ArrayBufferView::TYPE_INT8:    masm.movsblWithPatch(srcAddr, ToRegister(out)); break;
       case ArrayBufferView::TYPE_UINT8_CLAMPED:
       case ArrayBufferView::TYPE_UINT8:   masm.movzblWithPatch(srcAddr, ToRegister(out)); break;
       case ArrayBufferView::TYPE_INT16:   masm.movswlWithPatch(srcAddr, ToRegister(out)); break;
       case ArrayBufferView::TYPE_UINT16:  masm.movzwlWithPatch(srcAddr, ToRegister(out)); break;
-      case ArrayBufferView::TYPE_INT32:   masm.movlWithPatch(srcAddr, ToRegister(out)); break;
+      case ArrayBufferView::TYPE_INT32:
       case ArrayBufferView::TYPE_UINT32:  masm.movlWithPatch(srcAddr, ToRegister(out)); break;
       case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(srcAddr, ToFloatRegister(out)); break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
 }
 
+template<typename T>
+bool
+CodeGeneratorX86::loadViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr,
+                                      const LDefinition *out)
+{
+    if (vt == ArrayBufferView::TYPE_FLOAT32) {
+        FloatRegister dest = ToFloatRegister(out);
+        uint32_t before = masm.size();
+        masm.movssWithPatch(srcAddr, dest);
+        uint32_t after = masm.size();
+        masm.cvtss2sd(dest, dest);
+        return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, AnyRegister(dest)));
+    }
+    uint32_t before = masm.size();
+    loadNonFloat32ViewTypeElement(vt, srcAddr, out);
+    uint32_t after = masm.size();
+    return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out)));
+}
+
 bool
 CodeGeneratorX86::visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic *ins)
 {
     const MLoadTypedArrayElementStatic *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
 
     Register ptr = ToRegister(ins->ptr());
     const LDefinition *out = ins->output();
@@ -447,55 +467,64 @@ CodeGeneratorX86::visitLoadTypedArrayEle
         FloatRegister dest = ToFloatRegister(out);
         masm.movssWithPatch(srcAddr, dest);
         masm.cvtss2sd(dest, dest);
         masm.canonicalizeDouble(dest);
         if (ool)
             masm.bind(ool->rejoin());
         return true;
     }
-    loadViewTypeElement(vt, srcAddr, out);
+    loadNonFloat32ViewTypeElement(vt, srcAddr, out);
     if (vt == ArrayBufferView::TYPE_FLOAT64)
         masm.canonicalizeDouble(ToFloatRegister(out));
     if (ool)
         masm.bind(ool->rejoin());
     return true;
 }
 
 bool
 CodeGeneratorX86::visitAsmJSLoadHeap(LAsmJSLoadHeap *ins)
 {
-    // This is identical to LoadTypedArrayElementStatic, except that the
-    // array's base and length are not known ahead of time and can be patched
-    // later on, and the instruction is always infallible.
     const MAsmJSLoadHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
+    const LAllocation *ptr = ins->ptr();
+    const LDefinition *out = ins->output();
 
-    Register ptr = ToRegister(ins->ptr());
-    const LDefinition *out = ins->output();
+    if (ptr->isConstant()) {
+        JS_ASSERT(mir->skipBoundsCheck());
+        int32_t ptrImm = ptr->toConstant()->toInt32();
+        JS_ASSERT(ptrImm >= 0);
+        AbsoluteAddress srcAddr((void *) ptrImm);
+        return loadViewTypeElement(vt, srcAddr, out);
+    }
+
+    Register ptrReg = ToRegister(ptr);
+    Address srcAddr(ptrReg, 0);
+
+    if (mir->skipBoundsCheck())
+        return loadViewTypeElement(vt, srcAddr, out);
 
     OutOfLineLoadTypedArrayOutOfBounds *ool = new OutOfLineLoadTypedArrayOutOfBounds(ToAnyRegister(out));
     if (!addOutOfLineCode(ool))
         return false;
 
-    CodeOffsetLabel cmp = masm.cmplWithPatch(ptr, Imm32(0));
+    CodeOffsetLabel cmp = masm.cmplWithPatch(ptrReg, Imm32(0));
     masm.j(Assembler::AboveOrEqual, ool->entry());
 
-    Address srcAddr(ptr, 0);
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
         FloatRegister dest = ToFloatRegister(out);
         uint32_t before = masm.size();
         masm.movssWithPatch(srcAddr, dest);
         uint32_t after = masm.size();
         masm.cvtss2sd(dest, dest);
         masm.bind(ool->rejoin());
         return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, AnyRegister(dest), cmp.offset()));
     }
     uint32_t before = masm.size();
-    loadViewTypeElement(vt, srcAddr, out);
+    loadNonFloat32ViewTypeElement(vt, srcAddr, out);
     uint32_t after = masm.size();
     masm.bind(ool->rejoin());
     return gen->noteHeapAccess(AsmJSHeapAccess(before, after, vt, ToAnyRegister(out), cmp.offset()));
 }
 
 bool
 CodeGeneratorX86::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds *ool)
 {
@@ -504,33 +533,52 @@ CodeGeneratorX86::visitOutOfLineLoadType
     } else {
         Register destReg = ool->dest().gpr();
         masm.xorl(destReg, destReg);
     }
     masm.jmp(ool->rejoin());
     return true;
 }
 
+template<typename T>
 void
-CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
-                                       const Address &dstAddr)
+CodeGeneratorX86::storeNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
+                                                 const T &dstAddr)
 {
     switch (vt) {
-      case ArrayBufferView::TYPE_INT8:    masm.movbWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_INT8:
       case ArrayBufferView::TYPE_UINT8_CLAMPED:
       case ArrayBufferView::TYPE_UINT8:   masm.movbWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_INT16:   masm.movwWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_INT16:
       case ArrayBufferView::TYPE_UINT16:  masm.movwWithPatch(ToRegister(value), dstAddr); break;
-      case ArrayBufferView::TYPE_INT32:   masm.movlWithPatch(ToRegister(value), dstAddr); break;
+      case ArrayBufferView::TYPE_INT32:
       case ArrayBufferView::TYPE_UINT32:  masm.movlWithPatch(ToRegister(value), dstAddr); break;
       case ArrayBufferView::TYPE_FLOAT64: masm.movsdWithPatch(ToFloatRegister(value), dstAddr); break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
 }
 
+template<typename T>
+bool
+CodeGeneratorX86::storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
+                                       const T &dstAddr)
+{
+    if (vt == ArrayBufferView::TYPE_FLOAT32) {
+        masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg);
+        uint32_t before = masm.size();
+        masm.movssWithPatch(ScratchFloatReg, dstAddr);
+        uint32_t after = masm.size();
+        return gen->noteHeapAccess(AsmJSHeapAccess(before, after));
+    }
+    uint32_t before = masm.size();
+    storeNonFloat32ViewTypeElement(vt, value, dstAddr);
+    uint32_t after = masm.size();
+    return gen->noteHeapAccess(AsmJSHeapAccess(before, after));
+}
+
 bool
 CodeGeneratorX86::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic *ins)
 {
     MStoreTypedArrayElementStatic *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
 
     Register ptr = ToRegister(ins->ptr());
     const LAllocation *value = ins->value();
@@ -541,48 +589,57 @@ CodeGeneratorX86::visitStoreTypedArrayEl
 
     Address dstAddr(ptr, (int32_t) mir->base());
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
         masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg);
         masm.movssWithPatch(ScratchFloatReg, dstAddr);
         masm.bind(&rejoin);
         return true;
     }
-    storeViewTypeElement(vt, value, dstAddr);
+    storeNonFloat32ViewTypeElement(vt, value, dstAddr);
     masm.bind(&rejoin);
     return true;
 }
 
 bool
 CodeGeneratorX86::visitAsmJSStoreHeap(LAsmJSStoreHeap *ins)
 {
-    // This is identical to StoreTypedArrayElementStatic, except that the
-    // array's base and length are not known ahead of time and can be patched
-    // later on.
     MAsmJSStoreHeap *mir = ins->mir();
     ArrayBufferView::ViewType vt = mir->viewType();
+    const LAllocation *value = ins->value();
+    const LAllocation *ptr = ins->ptr();
 
-    Register ptr = ToRegister(ins->ptr());
-    const LAllocation *value = ins->value();
+    if (ptr->isConstant()) {
+        JS_ASSERT(mir->skipBoundsCheck());
+        int32_t ptrImm = ptr->toConstant()->toInt32();
+        JS_ASSERT(ptrImm >= 0);
+        AbsoluteAddress dstAddr((void *) ptrImm);
+        return storeViewTypeElement(vt, value, dstAddr);
+    }
 
-    CodeOffsetLabel cmp = masm.cmplWithPatch(ptr, Imm32(0));
+    Register ptrReg = ToRegister(ptr);
+    Address dstAddr(ptrReg, 0);
+
+    if (mir->skipBoundsCheck())
+        return storeViewTypeElement(vt, value, dstAddr);
+
+    CodeOffsetLabel cmp = masm.cmplWithPatch(ptrReg, Imm32(0));
     Label rejoin;
     masm.j(Assembler::AboveOrEqual, &rejoin);
 
-    Address dstAddr(ptr, 0);
     if (vt == ArrayBufferView::TYPE_FLOAT32) {
         masm.convertDoubleToFloat(ToFloatRegister(value), ScratchFloatReg);
         uint32_t before = masm.size();
         masm.movssWithPatch(ScratchFloatReg, dstAddr);
         uint32_t after = masm.size();
         masm.bind(&rejoin);
         return gen->noteHeapAccess(AsmJSHeapAccess(before, after, cmp.offset()));
     }
     uint32_t before = masm.size();
-    storeViewTypeElement(vt, value, dstAddr);
+    storeNonFloat32ViewTypeElement(vt, value, dstAddr);
     uint32_t after = masm.size();
     masm.bind(&rejoin);
     return gen->noteHeapAccess(AsmJSHeapAccess(before, after, cmp.offset()));
 }
 
 bool
 CodeGeneratorX86::visitAsmJSLoadGlobalVar(LAsmJSLoadGlobalVar *ins)
 {
--- a/js/src/jit/x86/CodeGenerator-x86.h
+++ b/js/src/jit/x86/CodeGenerator-x86.h
@@ -23,20 +23,28 @@ class CodeGeneratorX86 : public CodeGene
         return this;
     }
 
   protected:
     ValueOperand ToValue(LInstruction *ins, size_t pos);
     ValueOperand ToOutValue(LInstruction *ins);
     ValueOperand ToTempValue(LInstruction *ins, size_t pos);
 
-    void loadViewTypeElement(ArrayBufferView::ViewType vt, const Address &srcAddr,
+    template<typename T>
+    bool loadViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr,
                              const LDefinition *out);
-    void storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
-                              const Address &dstAddr);
+    template<typename T>
+    void loadNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const T &srcAddr,
+                                       const LDefinition *out);
+    template<typename T>
+    bool storeViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
+                              const T &dstAddr);
+    template<typename T>
+    void storeNonFloat32ViewTypeElement(ArrayBufferView::ViewType vt, const LAllocation *value,
+                                        const T &dstAddr);
     void storeElementTyped(const LAllocation *value, MIRType valueType, MIRType elementType,
                            const Register &elements, const LAllocation *index);
 
   public:
     CodeGeneratorX86(MIRGenerator *gen, LIRGraph *graph, MacroAssembler *masm);
 
   public:
     bool visitBox(LBox *box);
--- a/js/src/jit/x86/Lowering-x86.cpp
+++ b/js/src/jit/x86/Lowering-x86.cpp
@@ -212,36 +212,77 @@ bool
 LIRGeneratorX86::visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins)
 {
     JS_ASSERT(ins->input()->type() == MIRType_Int32);
     LUInt32ToDouble *lir = new LUInt32ToDouble(useRegisterAtStart(ins->input()), temp());
     return define(lir, ins);
 }
 
 bool
+LIRGeneratorX86::visitAsmJSLoadHeap(MAsmJSLoadHeap *ins)
+{
+    MDefinition *ptr = ins->ptr();
+    LAllocation ptrAlloc;
+    JS_ASSERT(ptr->type() == MIRType_Int32);
+
+    // For the x86 it is best to keep the 'ptr' in a register if a bounds check is needed.
+    if (ptr->isConstant() && ins->skipBoundsCheck()) {
+        int32_t ptrValue = ptr->toConstant()->value().toInt32();
+        // A bounds check is only skipped for a positive index.
+        JS_ASSERT(ptrValue >= 0);
+        ptrAlloc = LAllocation(ptr->toConstant()->vp());
+    } else {
+        ptrAlloc = useRegisterAtStart(ptr);
+    }
+    LAsmJSLoadHeap *lir = new LAsmJSLoadHeap(ptrAlloc);
+    return define(lir, ins);
+}
+
+bool
 LIRGeneratorX86::visitAsmJSStoreHeap(MAsmJSStoreHeap *ins)
 {
+    MDefinition *ptr = ins->ptr();
     LAsmJSStoreHeap *lir;
+    JS_ASSERT(ptr->type() == MIRType_Int32);
+
+    if (ptr->isConstant() && ins->skipBoundsCheck()) {
+        int32_t ptrValue = ptr->toConstant()->value().toInt32();
+        JS_ASSERT(ptrValue >= 0);
+        LAllocation ptrAlloc = LAllocation(ptr->toConstant()->vp());
+        switch (ins->viewType()) {
+          case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8:
+            // See comment below.
+            lir = new LAsmJSStoreHeap(ptrAlloc, useFixed(ins->value(), eax));
+            break;
+          case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16:
+          case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32:
+          case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64:
+            // See comment below.
+            lir = new LAsmJSStoreHeap(ptrAlloc, useRegisterAtStart(ins->value()));
+            break;
+          default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
+        }
+        return add(lir, ins);
+    }
+
     switch (ins->viewType()) {
       case ArrayBufferView::TYPE_INT8: case ArrayBufferView::TYPE_UINT8:
         // It's a trap! On x86, the 1-byte store can only use one of
         // {al,bl,cl,dl,ah,bh,ch,dh}. That means if the register allocator
         // gives us one of {edi,esi,ebp,esp}, we're out of luck. (The formatter
         // will assert on us.) Ideally, we'd just ask the register allocator to
         // give us one of {al,bl,cl,dl}. For now, just useFixed(al).
-        lir = new LAsmJSStoreHeap(useRegister(ins->ptr()),
-                                  useFixed(ins->value(), eax));
+        lir = new LAsmJSStoreHeap(useRegister(ins->ptr()), useFixed(ins->value(), eax));
         break;
       case ArrayBufferView::TYPE_INT16: case ArrayBufferView::TYPE_UINT16:
       case ArrayBufferView::TYPE_INT32: case ArrayBufferView::TYPE_UINT32:
       case ArrayBufferView::TYPE_FLOAT32: case ArrayBufferView::TYPE_FLOAT64:
-        // For now, don't allow constants. The immediate operand affects
-        // instruction layout which affects patching.
-        lir = new LAsmJSStoreHeap(useRegisterAtStart(ins->ptr()),
-                                  useRegisterAtStart(ins->value()));
+        // For now, don't allow constant values. The immediate operand
+        // affects instruction layout which affects patching.
+        lir = new LAsmJSStoreHeap(useRegisterAtStart(ptr), useRegisterAtStart(ins->value()));
         break;
       default: MOZ_ASSUME_UNREACHABLE("unexpected array type");
     }
 
     return add(lir, ins);
 }
 
 bool
--- a/js/src/jit/x86/Lowering-x86.h
+++ b/js/src/jit/x86/Lowering-x86.h
@@ -38,16 +38,17 @@ class LIRGeneratorX86 : public LIRGenera
 
   public:
     bool visitBox(MBox *box);
     bool visitUnbox(MUnbox *unbox);
     bool visitReturn(MReturn *ret);
     bool visitStoreTypedArrayElement(MStoreTypedArrayElement *ins);
     bool visitStoreTypedArrayElementHole(MStoreTypedArrayElementHole *ins);
     bool visitAsmJSUnsignedToDouble(MAsmJSUnsignedToDouble *ins);
+    bool visitAsmJSLoadHeap(MAsmJSLoadHeap *ins);
     bool visitAsmJSStoreHeap(MAsmJSStoreHeap *ins);
     bool visitAsmJSLoadFuncPtr(MAsmJSLoadFuncPtr *ins);
     bool visitStoreTypedArrayElementStatic(MStoreTypedArrayElementStatic *ins);
     bool lowerPhi(MPhi *phi);
 
     static bool allowTypedElementHoleCheck() {
         return true;
     }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -397,18 +397,18 @@ MSG_DEF(JSMSG_USE_ASM_LINK_FAIL,      34
 MSG_DEF(JSMSG_USE_ASM_TYPE_OK,        344, 1, JSEXN_ERR,     "successfully compiled asm.js code ({0})")
 MSG_DEF(JSMSG_BAD_ARROW_ARGS,         345, 0, JSEXN_SYNTAXERR, "invalid arrow-function arguments (parentheses around the arrow-function may help)")
 MSG_DEF(JSMSG_YIELD_IN_ARROW,         346, 0, JSEXN_SYNTAXERR, "arrow function may not contain yield")
 MSG_DEF(JSMSG_WRONG_VALUE,            347, 2, JSEXN_ERR, "expected {0} but found {1}")
 MSG_DEF(JSMSG_PAR_ARRAY_SCATTER_BAD_TARGET, 348, 1, JSEXN_ERR, "target for index {0} is not an integer")
 MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME,349, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups")
 MSG_DEF(JSMSG_DEPRECATED_SOURCE_MAP,  350, 0, JSEXN_SYNTAXERR, "Using //@ to indicate source map URL pragmas is deprecated. Use //# instead")
 MSG_DEF(JSMSG_BAD_DESTRUCT_ASSIGN,    351, 1, JSEXN_SYNTAXERR, "can't assign to {0} using destructuring assignment")
-MSG_DEF(JSMSG_BINARYDATA_ARRAYTYPE_BAD_ARGS, 352, 0, JSEXN_ERR, "Invalid arguments")
-MSG_DEF(JSMSG_BINARYDATA_BINARYARRAY_BAD_INDEX, 353, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
-MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_BAD_ARGS, 354, 0, JSEXN_RANGEERR, "invalid field descriptor")
-MSG_DEF(JSMSG_BINARYDATA_NOT_BINARYSTRUCT,   355, 1, JSEXN_TYPEERR, "{0} is not a BinaryStruct")
-MSG_DEF(JSMSG_BINARYDATA_SUBARRAY_INTEGER_ARG, 356, 1, JSEXN_ERR, "argument {0} must be an integer")
-MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_EMPTY_DESCRIPTOR, 357, 0, JSEXN_ERR, "field descriptor cannot be empty")
-MSG_DEF(JSMSG_BINARYDATA_STRUCTTYPE_BAD_FIELD, 358, 1, JSEXN_ERR, "field {0} is not a valid BinaryData Type descriptor")
+MSG_DEF(JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS, 352, 0, JSEXN_ERR, "Invalid arguments")
+MSG_DEF(JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX, 353, 0, JSEXN_RANGEERR, "invalid or out-of-range index")
+MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_ARGS, 354, 0, JSEXN_RANGEERR, "invalid field descriptor")
+MSG_DEF(JSMSG_TYPEDOBJECT_NOT_BINARYSTRUCT,   355, 1, JSEXN_TYPEERR, "{0} is not a BinaryStruct")
+MSG_DEF(JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, 356, 1, JSEXN_ERR, "argument {0} must be an integer")
+MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_EMPTY_DESCRIPTOR, 357, 0, JSEXN_ERR, "field descriptor cannot be empty")
+MSG_DEF(JSMSG_TYPEDOBJECT_STRUCTTYPE_BAD_FIELD, 358, 1, JSEXN_ERR, "field {0} is not a valid BinaryData Type descriptor")
 MSG_DEF(JSMSG_GENERATOR_FINISHED,     359, 0, JSEXN_TYPEERR, "generator has already finished")
-MSG_DEF(JSMSG_BINARYDATA_TOO_BIG, 360, 0, JSEXN_ERR, "Type is too large to allocate")
-MSG_DEF(JSMSG_BINARYDATA_NOT_TYPE_OBJECT, 361, 0, JSEXN_ERR, "Expected a type object")
+MSG_DEF(JSMSG_TYPEDOBJECT_TOO_BIG, 360, 0, JSEXN_ERR, "Type is too large to allocate")
+MSG_DEF(JSMSG_TYPEDOBJECT_NOT_TYPE_OBJECT, 361, 0, JSEXN_ERR, "Expected a type object")
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -43,22 +43,22 @@
 #include "jsworkers.h"
 #endif
 #include "jswrapper.h"
 #include "prmjtime.h"
 
 #if ENABLE_YARR_JIT
 #include "assembler/jit/ExecutableAllocator.h"
 #endif
-#include "builtin/BinaryData.h"
 #include "builtin/Eval.h"
 #include "builtin/Intl.h"
 #include "builtin/MapObject.h"
 #include "builtin/ParallelArray.h"
 #include "builtin/RegExp.h"
+#include "builtin/TypedObject.h"
 #include "frontend/BytecodeCompiler.h"
 #include "frontend/FullParseHandler.h"  // for JS_BufferIsCompileableUnit
 #include "frontend/Parser.h" // for JS_BufferIsCompileableUnit
 #include "gc/Marking.h"
 #include "jit/AsmJSLink.h"
 #include "js/CharacterEncoding.h"
 #include "js/StructuredClone.h"
 #if ENABLE_INTL_API
@@ -1361,17 +1361,17 @@ static const JSStdName standard_class_at
 #ifdef ENABLE_PARALLEL_JS
     {js_InitParallelArrayClass,         EAGER_ATOM_AND_OCLASP(ParallelArray)},
 #endif
     {js_InitProxyClass,                 EAGER_CLASS_ATOM(Proxy), OCLASP(ObjectProxy)},
 #if EXPOSE_INTL_API
     {js_InitIntlClass,                  EAGER_ATOM_AND_CLASP(Intl)},
 #endif
 #ifdef ENABLE_BINARYDATA
-    {js_InitBinaryDataClasses,          EAGER_ATOM_AND_CLASP(Type)},
+    {js_InitTypedObjectClasses,         EAGER_ATOM_AND_CLASP(Type)},
 #endif
     {NULL,                              0, NULL}
 };
 
 /*
  * Table of top-level function and constant names and their init functions.
  * If you add a "standard" global function or property, remember to update
  * this table.
@@ -1421,24 +1421,24 @@ static const JSStdName standard_class_na
     {js_InitTypedArrayClasses,  EAGER_CLASS_ATOM(Float32Array), TYPED_ARRAY_CLASP(TYPE_FLOAT32)},
     {js_InitTypedArrayClasses,  EAGER_CLASS_ATOM(Float64Array), TYPED_ARRAY_CLASP(TYPE_FLOAT64)},
     {js_InitTypedArrayClasses,  EAGER_CLASS_ATOM(Uint8ClampedArray),
                                 TYPED_ARRAY_CLASP(TYPE_UINT8_CLAMPED)},
     {js_InitTypedArrayClasses,  EAGER_CLASS_ATOM(DataView),     &DataViewObject::class_},
 
     /* Binary Data */
 #ifdef ENABLE_BINARYDATA
-    {js_InitBinaryDataClasses,          EAGER_ATOM_AND_CLASP(Type)},
-    {js_InitBinaryDataClasses,          EAGER_ATOM_AND_CLASP(Data)},
+    {js_InitTypedObjectClasses,   EAGER_ATOM_AND_CLASP(Type)},
+    {js_InitTypedObjectClasses,   EAGER_ATOM_AND_CLASP(Data)},
 #define BINARYDATA_NUMERIC_NAMES(constant_, type_, name_) \
-    {js_InitBinaryDataClasses,          EAGER_CLASS_ATOM(name_),      &NumericTypeClasses[constant_]},
+    {js_InitTypedObjectClasses,   EAGER_CLASS_ATOM(name_),      &NumericTypeClasses[constant_]},
     JS_FOR_EACH_SCALAR_TYPE_REPR(BINARYDATA_NUMERIC_NAMES)
 #undef BINARYDATA_NUMERIC_NAMES
-    {js_InitBinaryDataClasses,          EAGER_CLASS_ATOM(ArrayType),  &js::ArrayType::class_},
-    {js_InitBinaryDataClasses,          EAGER_CLASS_ATOM(StructType), &js::StructType::class_},
+    {js_InitTypedObjectClasses,   EAGER_CLASS_ATOM(ArrayType),  &js::ArrayType::class_},
+    {js_InitTypedObjectClasses,   EAGER_CLASS_ATOM(StructType), &js::StructType::class_},
 #endif
     {NULL,                      0, NULL}
 };
 
 static const JSStdName object_prototype_names[] = {
     /* Object.prototype properties (global delegates to Object.prototype). */
     {js_InitObjectClass,        EAGER_ATOM(proto), &JSObject::class_},
 #if JS_HAS_TOSOURCE
--- a/js/src/jsinfer.cpp
+++ b/js/src/jsinfer.cpp
@@ -2568,18 +2568,18 @@ TypeObject::clearAddendum(ExclusiveConte
     if (!addendum)
         return;
 
     switch (addendum->kind) {
       case TypeObjectAddendum::NewScript:
         clearNewScriptAddendum(cx);
         break;
 
-      case TypeObjectAddendum::BinaryData:
-        clearBinaryDataAddendum(cx);
+      case TypeObjectAddendum::TypedObject:
+        clearTypedObjectAddendum(cx);
         break;
     }
 
     /* We NULL out addendum *before* freeing it so the write barrier works. */
     TypeObjectAddendum *savedAddendum = addendum;
     addendum = NULL;
     js_free(savedAddendum);
 
@@ -2686,17 +2686,17 @@ TypeObject::clearNewScriptAddendum(Exclu
         }
     } else {
         // Threads with an ExclusiveContext are not allowed to run scripts.
         JS_ASSERT(!cx->perThreadData->activation());
     }
 }
 
 void
-TypeObject::clearBinaryDataAddendum(ExclusiveContext *cx)
+TypeObject::clearTypedObjectAddendum(ExclusiveContext *cx)
 {
 }
 
 void
 TypeObject::print()
 {
     TaggedProto tagged(proto);
     fprintf(stderr, "%s : %s",
@@ -4396,50 +4396,50 @@ TypeScript::printTypes(JSContext *cx, Ha
 }
 #endif /* DEBUG */
 
 /////////////////////////////////////////////////////////////////////
 // Binary data
 /////////////////////////////////////////////////////////////////////
 
 bool
-TypeObject::addBinaryDataAddendum(JSContext *cx, TypeRepresentation *repr)
+TypeObject::addTypedObjectAddendum(JSContext *cx, TypeRepresentation *repr)
 {
     if (!cx->typeInferenceEnabled())
         return true;
 
     JS_ASSERT(repr);
 
     if (flags & OBJECT_FLAG_ADDENDUM_CLEARED)
         return true;
 
     JS_ASSERT(!unknownProperties());
 
     if (addendum) {
-        JS_ASSERT(hasBinaryData());
-        JS_ASSERT(binaryData()->typeRepr == repr);
+        JS_ASSERT(hasTypedObject());
+        JS_ASSERT(typedObject()->typeRepr == repr);
         return true;
     }
 
-    TypeBinaryData *binaryData = js_new<TypeBinaryData>(repr);
-    if (!binaryData)
+    TypeTypedObject *typedObject = js_new<TypeTypedObject>(repr);
+    if (!typedObject)
         return false;
-    addendum = binaryData;
+    addendum = typedObject;
     return true;
 }
 
 /////////////////////////////////////////////////////////////////////
 // Type object addenda constructor
 /////////////////////////////////////////////////////////////////////
 
 TypeObjectAddendum::TypeObjectAddendum(Kind kind)
   : kind(kind)
 {}
 
 TypeNewScript::TypeNewScript()
   : TypeObjectAddendum(NewScript)
 {}
 
-TypeBinaryData::TypeBinaryData(TypeRepresentation *repr)
-  : TypeObjectAddendum(BinaryData),
+TypeTypedObject::TypeTypedObject(TypeRepresentation *repr)
+  : TypeObjectAddendum(TypedObject),
     typeRepr(repr)
 {
 }
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -849,45 +849,45 @@ struct Property
     inline Property(jsid id);
     inline Property(const Property &o);
 
     static uint32_t keyBits(jsid id) { return uint32_t(JSID_BITS(id)); }
     static jsid getKey(Property *p) { return p->id; }
 };
 
 struct TypeNewScript;
-struct TypeBinaryData;
+struct TypeTypedObject;
 
 struct TypeObjectAddendum
 {
     enum Kind {
         NewScript,
-        BinaryData
+        TypedObject
     };
 
     TypeObjectAddendum(Kind kind);
 
     const Kind kind;
 
     bool isNewScript() {
         return kind == NewScript;
     }
 
     TypeNewScript *asNewScript() {
         JS_ASSERT(isNewScript());
         return (TypeNewScript*) this;
     }
 
-    bool isBinaryData() {
-        return kind == BinaryData;
+    bool isTypedObject() {
+        return kind == TypedObject;
     }
 
-    TypeBinaryData *asBinaryData() {
-        JS_ASSERT(isBinaryData());
-        return (TypeBinaryData*) this;
+    TypeTypedObject *asTypedObject() {
+        JS_ASSERT(isTypedObject());
+        return (TypeTypedObject*) this;
     }
 
     static inline void writeBarrierPre(TypeObjectAddendum *newScript);
     static void writeBarrierPost(TypeObjectAddendum *newScript, void *addr) {}
 };
 
 /*
  * Information attached to a TypeObject if it is always constructed using 'new'
@@ -934,19 +934,19 @@ struct TypeNewScript : public TypeObject
           : kind(kind), offset(offset)
         {}
     };
     Initializer *initializerList;
 
     static inline void writeBarrierPre(TypeNewScript *newScript);
 };
 
-struct TypeBinaryData : public TypeObjectAddendum
+struct TypeTypedObject : public TypeObjectAddendum
 {
-    TypeBinaryData(TypeRepresentation *repr);
+    TypeTypedObject(TypeRepresentation *repr);
 
     TypeRepresentation *const typeRepr;
 };
 
 /*
  * Lazy type objects overview.
  *
  * Type objects which represent at most one JS object are constructed lazily.
@@ -1015,32 +1015,32 @@ struct TypeObject : gc::Cell
     bool hasNewScript() {
         return addendum && addendum->isNewScript();
     }
 
     TypeNewScript *newScript() {
         return addendum->asNewScript();
     }
 
-    bool hasBinaryData() {
-        return addendum && addendum->isBinaryData();
+    bool hasTypedObject() {
+        return addendum && addendum->isTypedObject();
     }
 
-    TypeBinaryData *binaryData() {
-        return addendum->asBinaryData();
+    TypeTypedObject *typedObject() {
+        return addendum->asTypedObject();
     }
 
     /*
      * Tag the type object for a binary data type descriptor, instance,
      * or handle with the type representation of the data it points at.
      * If this type object is already tagged with a binary data addendum,
      * this addendum must already be associated with the same TypeRepresentation,
      * and the method has no effect.
      */
-    bool addBinaryDataAddendum(JSContext *cx, TypeRepresentation *repr);
+    bool addTypedObjectAddendum(JSContext *cx, TypeRepresentation *repr);
 
     /*
      * Properties of this object. This may contain JSID_VOID, representing the
      * types of all integer indexes of the object, and/or JSID_EMPTY, holding
      * constraints listening to changes to the object's state.
      *
      * The type sets in the properties of a type object describe the possible
      * values that can be read out of that property in actual JS objects.
@@ -1126,17 +1126,17 @@ struct TypeObject : gc::Cell
     void addPropertyType(ExclusiveContext *cx, const char *name, Type type);
     void addPropertyType(ExclusiveContext *cx, const char *name, const Value &value);
     void markPropertyConfigured(ExclusiveContext *cx, jsid id);
     void markStateChange(ExclusiveContext *cx);
     void setFlags(ExclusiveContext *cx, TypeObjectFlags flags);
     void markUnknown(ExclusiveContext *cx);
     void clearAddendum(ExclusiveContext *cx);
     void clearNewScriptAddendum(ExclusiveContext *cx);
-    void clearBinaryDataAddendum(ExclusiveContext *cx);
+    void clearTypedObjectAddendum(ExclusiveContext *cx);
     void getFromPrototypes(JSContext *cx, jsid id, HeapTypeSet *types, bool force = false);
 
     void print();
 
     inline void clearProperties();
     inline void sweep(FreeOp *fop);
 
     size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1589,18 +1589,18 @@ TypeObjectAddendum::writeBarrierPre(Type
 #ifdef JSGC_INCREMENTAL
     if (!type)
         return;
 
     switch (type->kind) {
       case NewScript:
         return TypeNewScript::writeBarrierPre(type->asNewScript());
 
-      case BinaryData:
-        return TypeBinaryData::writeBarrierPre(type->asNewScript());
+      case TypedObject:
+        return TypeTypedObject::writeBarrierPre(type->asTypedObject());
     }
 #endif
 }
 
 inline void
 TypeNewScript::writeBarrierPre(TypeNewScript *newScript)
 {
 #ifdef JSGC_INCREMENTAL
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -49,25 +49,25 @@
     macro(Uint8ClampedArray,     30,     js_InitTypedArrayClasses) \
     macro(Proxy,                 31,     js_InitProxyClass) \
     macro(WeakMap,               32,     js_InitWeakMapClass) \
     macro(Map,                   33,     js_InitMapClass) \
     macro(Set,                   34,     js_InitSetClass) \
     macro(DataView,              35,     js_InitTypedArrayClasses) \
     macro(ParallelArray,         36,     js_InitParallelArrayClass) \
     macro(Intl,                  37,     js_InitIntlClass) \
-    macro(Type,                  38,     js_InitBinaryDataClasses) \
-    macro(Data,                  39,     js_InitBinaryDataClasses) \
-    macro(uint8Clamped,          40,     js_InitBinaryDataClasses) \
-    macro(uint8,                 41,     js_InitBinaryDataClasses) \
-    macro(uint16,                42,     js_InitBinaryDataClasses) \
-    macro(uint32,                43,     js_InitBinaryDataClasses) \
-    macro(int8,                  44,     js_InitBinaryDataClasses) \
-    macro(int16,                 45,     js_InitBinaryDataClasses) \
-    macro(int32,                 46,     js_InitBinaryDataClasses) \
-    macro(float32,               47,     js_InitBinaryDataClasses) \
-    macro(float64,               48,     js_InitBinaryDataClasses) \
-    macro(ArrayType,             49,     js_InitBinaryDataClasses) \
-    macro(StructType,            50,     js_InitBinaryDataClasses) \
-    macro(ArrayTypeObject,       51,     js_InitBinaryDataClasses) \
+    macro(Type,                  38,     js_InitTypedObjectClasses) \
+    macro(Data,                  39,     js_InitTypedObjectClasses) \
+    macro(uint8Clamped,          40,     js_InitTypedObjectClasses) \
+    macro(uint8,                 41,     js_InitTypedObjectClasses) \
+    macro(uint16,                42,     js_InitTypedObjectClasses) \
+    macro(uint32,                43,     js_InitTypedObjectClasses) \
+    macro(int8,                  44,     js_InitTypedObjectClasses) \
+    macro(int16,                 45,     js_InitTypedObjectClasses) \
+    macro(int32,                 46,     js_InitTypedObjectClasses) \
+    macro(float32,               47,     js_InitTypedObjectClasses) \
+    macro(float64,               48,     js_InitTypedObjectClasses) \
+    macro(ArrayType,             49,     js_InitTypedObjectClasses) \
+    macro(StructType,            50,     js_InitTypedObjectClasses) \
+    macro(ArrayTypeObject,       51,     js_InitTypedObjectClasses) \
     macro(GeneratorFunction,     52,     js_InitIteratorClasses) \
 
 #endif /* jsprototypes_h */
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -645,16 +645,23 @@ WorkerThread::handleIonWorkload(WorkerTh
     JS_ASSERT(state.canStartIonCompile());
     JS_ASSERT(idle());
 
     ionBuilder = state.ionWorklist.popCopy();
 
     DebugOnly<jit::ExecutionMode> executionMode = ionBuilder->info().executionMode();
     JS_ASSERT(GetIonScript(ionBuilder->script(), executionMode) == ION_COMPILING_SCRIPT);
 
+#if JS_TRACE_LOGGING
+    AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER),
+                        TraceLogging::ION_COMPILE_START,
+                        TraceLogging::ION_COMPILE_STOP,
+                        ionBuilder->script());
+#endif
+
     state.unlock();
     {
         jit::IonContext ictx(runtime, ionBuilder->script()->compartment(), &ionBuilder->temp());
         ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
     }
     state.lock();
 
     FinishOffThreadIonCompile(ionBuilder);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -73,17 +73,17 @@ EXPORTS.js += [
     '../public/TypeDecls.h',
     '../public/Utility.h',
     '../public/Value.h',
     '../public/Vector.h',
 ]
 
 CPP_SOURCES += [
     'ArgumentsObject.cpp',
-    'BinaryData.cpp',
+    'TypedObject.cpp',
     'BytecodeCompiler.cpp',
     'BytecodeEmitter.cpp',
     'CallNonGenericMethod.cpp',
     'CharacterEncoding.cpp',
     'DateTime.cpp',
     'Debugger.cpp',
     'Eval.cpp',
     'ExecutableAllocator.cpp',
rename from js/src/tests/ecma_6/BinaryData/architecture.js
rename to js/src/tests/ecma_6/TypedObject/architecture.js
rename from js/src/tests/ecma_6/BinaryData/arrayofstructs.js
rename to js/src/tests/ecma_6/TypedObject/arrayofstructs.js
--- a/js/src/tests/ecma_6/BinaryData/arrayofstructs.js
+++ b/js/src/tests/ecma_6/TypedObject/arrayofstructs.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData StructType prototype chains';
+var summary = 'TypedObjects StructType prototype chains';
 
 
 function runTests() {
   var Point = new ArrayType(float32, 3);
   var Line = new StructType({from: Point, to: Point});
   var Lines = new ArrayType(Line, 3);
 
   var lines = new Lines([
rename from js/src/tests/ecma_6/BinaryData/arraytype.js
rename to js/src/tests/ecma_6/TypedObject/arraytype.js
--- a/js/src/tests/ecma_6/BinaryData/arraytype.js
+++ b/js/src/tests/ecma_6/TypedObject/arraytype.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData ArrayType implementation';
+var summary = 'TypedObjects ArrayType implementation';
 
 function assertThrows(f) {
     var ok = false;
     try {
         f();
     } catch (exc) {
         ok = true;
     }
rename from js/src/tests/ecma_6/BinaryData/memory.js
rename to js/src/tests/ecma_6/TypedObject/memory.js
--- a/js/src/tests/ecma_6/BinaryData/memory.js
+++ b/js/src/tests/ecma_6/TypedObject/memory.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData memory check';
+var summary = 'TypedObjects memory check';
 
 function spin() {
     for (var i = 0; i < 10000; i++)
         ;
 }
 
 function runTests() {
     print(BUGNUMBER + ": " + summary);
rename from js/src/tests/ecma_6/BinaryData/numerictypes.js
rename to js/src/tests/ecma_6/TypedObject/numerictypes.js
--- a/js/src/tests/ecma_6/BinaryData/numerictypes.js
+++ b/js/src/tests/ecma_6/TypedObject/numerictypes.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData numeric types';
+var summary = 'TypedObjects numeric types';
 var actual = '';
 var expect = '';
 
 function runTests()
 {
     printBugNumber(BUGNUMBER);
     printStatus(summary);
 
@@ -166,12 +166,12 @@ function runTests()
     check(function() uint16("-0xffff") == 0);
     check(function() uint16("0xffffff") == 0xffff);
 
     // wrong types
     check(function() uint16(3.14) == 3); // c-like casts in explicit conversion
 
     print("done");
 
-    reportCompare(0, TestFailCount, "BinaryData numeric type tests");
+    reportCompare(0, TestFailCount, "TypedObjects numeric type tests");
 }
 
 runTests();
rename from js/src/tests/ecma_6/BinaryData/shell.js
rename to js/src/tests/ecma_6/TypedObject/shell.js
rename from js/src/tests/ecma_6/BinaryData/size_and_alignment.js
rename to js/src/tests/ecma_6/TypedObject/size_and_alignment.js
--- a/js/src/tests/ecma_6/BinaryData/size_and_alignment.js
+++ b/js/src/tests/ecma_6/TypedObject/size_and_alignment.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'Size and Alignment of BinaryData types';
+var summary = 'Size and Alignment of TypedObjects types';
 var actual = '';
 var expect = '';
 
 function runTests() {
   printBugNumber(BUGNUMBER);
   printStatus(summary);
 
   var typesAndAlignments = [
rename from js/src/tests/ecma_6/BinaryData/structequiv.js
rename to js/src/tests/ecma_6/TypedObject/structequiv.js
--- a/js/src/tests/ecma_6/BinaryData/structequiv.js
+++ b/js/src/tests/ecma_6/TypedObject/structequiv.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData Equivalent StructTypes';
+var summary = 'TypedObjects Equivalent StructTypes';
 
 function assertEquivalent(t1, t2) {
   assertEq(true, t1.equivalent(t2));
   assertEq(true, t2.equivalent(t1));
 }
 
 function assertNotEquivalent(t1, t2) {
   assertEq(false, t1.equivalent(t2));
rename from js/src/tests/ecma_6/BinaryData/structtypeenumerate.js
rename to js/src/tests/ecma_6/TypedObject/structtypeenumerate.js
--- a/js/src/tests/ecma_6/BinaryData/structtypeenumerate.js
+++ b/js/src/tests/ecma_6/TypedObject/structtypeenumerate.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData StructType propery enumeration';
+var summary = 'TypedObjects StructType propery enumeration';
 
 function runTests() {
   var RgbColor = new StructType({r: uint8, g: uint8, b: uint8});
   var Fade = new StructType({from: RgbColor, to: RgbColor});
 
   var white = new RgbColor({r: 255, g: 255, b: 255});
   var gray = new RgbColor({r: 129, g: 128, b: 127});
   var fade = new Fade({from: white, to: gray});
rename from js/src/tests/ecma_6/BinaryData/structtypeindexedfields.js
rename to js/src/tests/ecma_6/TypedObject/structtypeindexedfields.js
--- a/js/src/tests/ecma_6/BinaryData/structtypeindexedfields.js
+++ b/js/src/tests/ecma_6/TypedObject/structtypeindexedfields.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData: indexed properties are illegal in a StructType';
+var summary = 'TypedObjects: indexed properties are illegal in a StructType';
 
 function runTests() {
   print(BUGNUMBER + ": " + summary);
 
   var failed;
   try {
     new StructType({1: int32, 2: uint8, 3: float64});
     failed = false;
rename from js/src/tests/ecma_6/BinaryData/structtypeprototype.js
rename to js/src/tests/ecma_6/TypedObject/structtypeprototype.js
--- a/js/src/tests/ecma_6/BinaryData/structtypeprototype.js
+++ b/js/src/tests/ecma_6/TypedObject/structtypeprototype.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData StructType prototype chains';
+var summary = 'TypedObjects StructType prototype chains';
 
 
 function runTests() {
   var RgbColor1 = new StructType({r: uint8, g: uint8, b: uint8});
   var RgbColor2 = new StructType({r: uint8, g: uint8, b: uint8});
   var Fade1 = new StructType({from: RgbColor1, to: RgbColor1});
   var Fade2 = new StructType({from: RgbColor2, to: RgbColor2});
 
rename from js/src/tests/ecma_6/BinaryData/structtypereflection.js
rename to js/src/tests/ecma_6/TypedObject/structtypereflection.js
--- a/js/src/tests/ecma_6/BinaryData/structtypereflection.js
+++ b/js/src/tests/ecma_6/TypedObject/structtypereflection.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData: check reflection on StructType objects';
+var summary = 'TypedObjects: check reflection on StructType objects';
 
 function runTests() {
     print(BUGNUMBER + ": " + summary);
 
     var S = new StructType({x: int32, y: uint8, z: float64});
     assertEq(S.__proto__, StructType.prototype);
     assertEq(S.prototype.__proto__, StructType.prototype.prototype);
     assertEq(S.toSource(), "StructType({x: int32, y: uint8, z: float64})");
rename from js/src/tests/ecma_6/BinaryData/structtypestructuralassign.js
rename to js/src/tests/ecma_6/TypedObject/structtypestructuralassign.js
--- a/js/src/tests/ecma_6/BinaryData/structtypestructuralassign.js
+++ b/js/src/tests/ecma_6/TypedObject/structtypestructuralassign.js
@@ -1,11 +1,11 @@
 // |reftest| skip-if(!this.hasOwnProperty("Type"))
 var BUGNUMBER = 578700;
-var summary = 'BinaryData StructType structural assignment';
+var summary = 'TypedObjects StructType structural assignment';
 
 function assertEqColor(c1, c2) {
   assertEq(c1.r, c2.r);
   assertEq(c1.g, c2.g);
   assertEq(c1.b, c2.b);
 }
 
 function runTests() {
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -23,17 +23,17 @@ js_InitObjectClass(JSContext *cx, js::Ha
 
 extern JSObject *
 js_InitFunctionClass(JSContext *cx, js::HandleObject obj);
 
 extern JSObject *
 js_InitTypedArrayClasses(JSContext *cx, js::HandleObject obj);
 
 extern JSObject *
-js_InitBinaryDataClasses(JSContext *cx, js::HandleObject obj);
+js_InitTypedObjectClasses(JSContext *cx, js::HandleObject obj);
 
 namespace js {
 
 class Debugger;
 
 /*
  * Global object slots are reserved as follows:
  *
@@ -453,17 +453,17 @@ class GlobalObject : public JSObject
     static bool initSetIteratorProto(JSContext *cx, Handle<GlobalObject*> global);
 
     // Implemented in Intl.cpp.
     static bool initIntlObject(JSContext *cx, Handle<GlobalObject*> global);
     static bool initCollatorProto(JSContext *cx, Handle<GlobalObject*> global);
     static bool initNumberFormatProto(JSContext *cx, Handle<GlobalObject*> global);
     static bool initDateTimeFormatProto(JSContext *cx, Handle<GlobalObject*> global);
 
-    // Implemented in builtin/BinaryData.cpp
+    // Implemented in builtin/TypedObject.cpp
     static bool initTypeObject(JSContext *cx, Handle<GlobalObject*> global);
     static bool initDataObject(JSContext *cx, Handle<GlobalObject*> global);
     static bool initArrayTypeObject(JSContext *cx, Handle<GlobalObject*> global);
 
     static bool initStandardClasses(JSContext *cx, Handle<GlobalObject*> global);
 
     typedef js::Vector<js::Debugger *, 0, js::SystemAllocPolicy> DebuggerVector;
 
--- a/js/xpconnect/crashtests/471366-1.html
+++ b/js/xpconnect/crashtests/471366-1.html
@@ -1,12 +1,12 @@
 <!DOCTYPE html>
 <html>
 <head>
 <script type="text/javascript">
 
-document.getAnonymousNodes({__proto__: XPCNativeWrapper.prototype});
+SpecialPowers.wrap(document).getAnonymousNodes({__proto__: XPCNativeWrapper.prototype});
 
 </script>
 </head>
 <body>
 </body>
 </html>
--- a/js/xpconnect/crashtests/crashtests.list
+++ b/js/xpconnect/crashtests/crashtests.list
@@ -49,9 +49,9 @@ load 776328.html
 load 776333.html
 asserts(0-1) load 786142.html # We may hit bug 645229 here.
 load 791845.html
 load 797583.html
 load 806751.html
 load 833856.html
 load 854604.html
 load 854139.html
-pref(dom.use_xbl_scopes_for_remote_xul,true) skip-if(Android) load 898939.html
+pref(dom.use_xbl_scopes_for_remote_xul,true) load 898939.html
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -261,16 +261,25 @@ XPCWrappedNativeScope::EnsureXBLScope(JS
 
     // Tag it.
     EnsureCompartmentPrivate(js::UncheckedUnwrap(mXBLScope))->scope->mIsXBLScope = true;
 
     // Good to go!
     return mXBLScope;
 }
 
+bool
+XPCWrappedNativeScope::AllowXBLScope()
+{
+    // We only disallow XBL scopes in remote XUL situations.
+    MOZ_ASSERT_IF(!mAllowXBLScope,
+                  nsContentUtils::AllowXULXBLForPrincipal(GetPrincipal()));
+    return mAllowXBLScope;
+}
+
 namespace xpc {
 JSObject *GetXBLScope(JSContext *cx, JSObject *contentScopeArg)
 {
     JS::RootedObject contentScope(cx, contentScopeArg);
     JSAutoCompartment ac(cx, contentScope);
     JSObject *scope = EnsureCompartmentPrivate(contentScope)->scope->EnsureXBLScope(cx);
     NS_ENSURE_TRUE(scope, nullptr); // See bug 858642.
     scope = js::UncheckedUnwrap(scope);
--- a/js/xpconnect/src/nsXPConnect.cpp
+++ b/js/xpconnect/src/nsXPConnect.cpp
@@ -1705,15 +1705,21 @@ JS_EXPORT_API(void) DumpCompleteHeap()
 
 namespace mozilla {
 namespace dom {
 
 bool
 IsChromeOrXBL(JSContext* cx, JSObject* /* unused */)
 {
     MOZ_ASSERT(NS_IsMainThread());
-    JSCompartment* compartment = js::GetContextCompartment(cx);
-    return AccessCheck::isChrome(compartment) ||
-           IsXBLScope(compartment);
+    JSCompartment* c = js::GetContextCompartment(cx);
+
+    // For remote XUL, we run XBL in the XUL scope. Given that we care about
+    // compat and not security for remote XUL, we just always claim to be XBL.
+    //
+    // Note that, for performance, we don't check AllowXULXBLForPrincipal here,
+    // and instead rely on the fact that AllowXBLScope() only returns false in
+    // remote XUL situations.
+    return AccessCheck::isChrome(c) || IsXBLScope(c) || !AllowXBLScope(c);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/js/xpconnect/src/xpc.msg
+++ b/js/xpconnect/src/xpc.msg
@@ -161,16 +161,18 @@ XPC_MSG_DEF(NS_ERROR_FTP_CWD            
 XPC_MSG_DEF(NS_ERROR_FTP_PASV                       , "FTP error while changing to passive mode")
 XPC_MSG_DEF(NS_ERROR_FTP_PWD                        , "FTP error while retrieving current directory")
 XPC_MSG_DEF(NS_ERROR_FTP_LIST                       , "FTP error while retrieving a directory listing")
 XPC_MSG_DEF(NS_ERROR_UNKNOWN_HOST                   , "The lookup of the hostname failed")
 XPC_MSG_DEF(NS_ERROR_DNS_LOOKUP_QUEUE_FULL          , "The DNS lookup queue is full")
 XPC_MSG_DEF(NS_ERROR_UNKNOWN_PROXY_HOST             , "The lookup of the proxy hostname failed")
 XPC_MSG_DEF(NS_ERROR_UNKNOWN_SOCKET_TYPE            , "The specified socket type does not exist")
 XPC_MSG_DEF(NS_ERROR_SOCKET_CREATE_FAILED           , "The specified socket type could not be created")
+XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED   , "The specified socket address type is not supported")
+XPC_MSG_DEF(NS_ERROR_SOCKET_ADDRESS_IN_USE          , "Some other socket is already using the specified address.")
 XPC_MSG_DEF(NS_ERROR_CACHE_KEY_NOT_FOUND            , "Cache key could not be found")
 XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_STREAM           , "Cache data is a stream")
 XPC_MSG_DEF(NS_ERROR_CACHE_DATA_IS_NOT_STREAM       , "Cache data is not a stream")
 XPC_MSG_DEF(NS_ERROR_CACHE_WAIT_FOR_VALIDATION      , "Cache entry exists but needs to be validated first")
 XPC_MSG_DEF(NS_ERROR_CACHE_ENTRY_DOOMED             , "Cache entry has been  doomed")
 XPC_MSG_DEF(NS_ERROR_CACHE_READ_ACCESS_DENIED       , "Read access to cache denied")
 XPC_MSG_DEF(NS_ERROR_CACHE_WRITE_ACCESS_DENIED      , "Write access to cache denied")
 XPC_MSG_DEF(NS_ERROR_CACHE_IN_USE                   , "Cache is currently in use")
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1408,17 +1408,17 @@ public:
     // object is wrapped into the compartment of the global.
     JSObject *EnsureXBLScope(JSContext *cx);
 
     XPCWrappedNativeScope(JSContext *cx, JS::HandleObject aGlobal);
 
     nsAutoPtr<JSObject2JSObjectMap> mWaiverWrapperMap;
 
     bool IsXBLScope() { return mIsXBLScope; }
-    bool AllowXBLScope() { return mAllowXBLScope; }
+    bool AllowXBLScope();
 
 protected:
     virtual ~XPCWrappedNativeScope();
 
     static void KillDyingScopes();
 
     XPCWrappedNativeScope(); // not implemented
 
@@ -1444,30 +1444,27 @@ private:
     JS::ObjectPtr                    mXBLScope;
 
     XPCContext*                      mContext;
 
     nsAutoPtr<DOMExpandoSet> mDOMExpandoSet;
 
     bool mIsXBLScope;
 
-    // There are certain cases where we explicitly disallow XBL scopes: they
-    // can be prefed off, or we might be running in a remote XUL domain where
-    // we want to run all XBL in content to maintain compat. We separately
+    // For remote XUL domains, we run all XBL in the content scope for compat
+    // reasons (though we sometimes pref this off for automation). We separately
     // track the result of this decision (mAllowXBLScope), from the decision
     // of whether to actually _use_ an XBL scope (mUseXBLScope), which depends
     // on the type of global and whether the compartment is system principal
     // or not.
     //
-    // This distinction is useful primarily because it tells us whether we
-    // can infer the XBL-ness of a caller by checking that the caller is
-    // running in an XBL scope, or whether we need to check the XBL bit on the
-    // script. The XBL bit is nasty, so we want to consult it only if we
-    // absolutely have to, which should generally happen only in unsupported
-    // pref configurations.
+    // This distinction is useful primarily because, if true, we know that we
+    // have no way of distinguishing XBL script from content script for the
+    // given scope. In these (unsupported) situations, we just always claim to
+    // be XBL.
     bool mAllowXBLScope;
     bool mUseXBLScope;
 };
 
 /***************************************************************************/
 // XPCNativeMember represents a single idl declared method, attribute or
 // constant.
 
--- a/js/xpconnect/tests/mochitest/Makefile.in
+++ b/js/xpconnect/tests/mochitest/Makefile.in
@@ -82,13 +82,14 @@ MOCHITEST_FILES =	chrome_wrappers_helper
 		file_bug802557.html \
 		test_bug803730.html \
 		test_bug809547.html \
 		test_bug829872.html \
 		test_bug862380.html \
 		test_bug865260.html \
 		test_bug870423.html \
 		test_bug871887.html \
+		test_bug912322.html \
 		file_crosscompartment_weakmap.html \
 		test_crosscompartment_weakmap.html \
 		test_asmjs.html \
 		file_asmjs.js \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/mochitest/test_bug912322.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=912322
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 912322</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test that XBL functions aren't exposed to the web. **/
+  funs = ['getAnonymousNodes', 'getAnonymousElementByAttribute',
+          'getBindingParent', 'loadBindingDocument'];
+  for (var f of funs) {
+    ok(!(f in document), f + " should not be available to content");
+    ok(f in SpecialPowers.wrap(document), f + " should be available to chrome via Xray");
+  }
+
+
+
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=912322">Mozilla Bug 912322</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -24,16 +24,17 @@
 #include "nsSVGIntegrationUtils.h"
 #include "nsCSSAnonBoxes.h"
 #include "nsContainerFrame.h"
 #include "nsPlaceholderFrame.h"
 #include "nsBlockFrame.h"
 #include "nsViewportFrame.h"
 #include "nsSVGTextFrame2.h"
 #include "nsSVGTextPathFrame.h"
+#include "StickyScrollContainer.h"
 #include "nsIRootBox.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsContentUtils.h"
 
 #ifdef ACCESSIBILITY
 #include "nsAccessibilityService.h"
 #endif
 
@@ -321,17 +322,17 @@ RestyleManager::RecomputePosition(nsIFra
   // Changes to the offsets of a non-positioned element can safely be ignored.
   if (display->mPosition == NS_STYLE_POSITION_STATIC) {
     return true;
   }
 
   aFrame->SchedulePaint();
 
   // For relative positioning, we can simply update the frame rect
-  if (display->mPosition == NS_STYLE_POSITION_RELATIVE) {
+  if (display->IsRelativelyPositionedStyle()) {
     switch (display->mDisplay) {
       case NS_STYLE_DISPLAY_TABLE_CAPTION:
       case NS_STYLE_DISPLAY_TABLE_CELL:
       case NS_STYLE_DISPLAY_TABLE_ROW:
       case NS_STYLE_DISPLAY_TABLE_ROW_GROUP:
       case NS_STYLE_DISPLAY_TABLE_HEADER_GROUP:
       case NS_STYLE_DISPLAY_TABLE_FOOTER_GROUP:
       case NS_STYLE_DISPLAY_TABLE_COLUMN:
@@ -340,27 +341,37 @@ RestyleManager::RecomputePosition(nsIFra
         // table elements.  If we apply offsets to things we haven't
         // previously offset, we'll get confused.  So bail.
         return true;
       default:
         break;
     }
 
     nsIFrame* cb = aFrame->GetContainingBlock();
-    const nsSize size = cb->GetContentRectRelativeToSelf().Size();
-    nsPoint position = aFrame->GetNormalPosition();
     nsMargin newOffsets;
 
     // Move the frame
-    nsHTMLReflowState::ComputeRelativeOffsets(
-        cb->StyleVisibility()->mDirection,
-        aFrame, size.width, size.height, newOffsets);
-    NS_ASSERTION(newOffsets.left == -newOffsets.right &&
-                 newOffsets.top == -newOffsets.bottom,
-                 "ComputeRelativeOffsets should return valid results");
+    if (display->mPosition == NS_STYLE_POSITION_STICKY) {
+      StickyScrollContainer::ComputeStickyOffsets(aFrame);
+    } else {
+      MOZ_ASSERT(NS_STYLE_POSITION_RELATIVE == display->mPosition,
+                 "Unexpected type of positioning");
+      const nsSize size = cb->GetContentRectRelativeToSelf().Size();
+
+      nsHTMLReflowState::ComputeRelativeOffsets(
+          cb->StyleVisibility()->mDirection,
+          aFrame, size.width, size.height, newOffsets);
+      NS_ASSERTION(newOffsets.left == -newOffsets.right &&
+                   newOffsets.top == -newOffsets.bottom,
+                   "ComputeRelativeOffsets should return valid results");
+    }
+
+    nsPoint position = aFrame->GetNormalPosition();
+
+    // This handles both relative and sticky positioning.
     nsHTMLReflowState::ApplyRelativePositioning(aFrame, newOffsets, &position);
     aFrame->SetPosition(position);
 
     return true;
   }
 
   // For absolute positioning, the width can potentially change if width is
   // auto and either of left or right are not.  The height can also potentially
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -24,16 +24,20 @@ class RestyleManager;
  * Helper class that collects a list of frames that need
  * UpdateOverflow() called on them, and coalesces them
  * to avoid walking up the same ancestor tree multiple times.
  */
 class OverflowChangedTracker
 {
 public:
 
+  OverflowChangedTracker() :
+    mSubtreeRoot(nullptr)
+  {}
+
   ~OverflowChangedTracker()
   {
     NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
   }
 
   /**
    * Add a frame that has had a style change, and needs its
    * overflow updated.
@@ -63,16 +67,25 @@ public:
 
     uint32_t depth = aFrame->GetDepthInFrameTree();
     if (mEntryList.contains(Entry(aFrame, depth, false))) {
       delete mEntryList.remove(Entry(aFrame, depth, false));
     }
   }
 
   /**
+   * Set the subtree root to limit overflow updates. This must be set if and
+   * only if currently reflowing aSubtreeRoot, to ensure overflow changes will
+   * still propagate correctly.
+   */
+  void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) {
+    mSubtreeRoot = aSubtreeRoot;
+  }
+
+  /**
    * Update the overflow of all added frames, and clear the entry list.
    *
    * Start from those deepest in the frame tree and works upwards. This stops 
    * us from processing the same frame twice.
    */
   void Flush() {
     while (!mEntryList.empty()) {
       Entry *entry = mEntryList.removeMin();
@@ -95,17 +108,17 @@ public:
 
       // If the overflow changed, then we want to also update the parent's
       // overflow. We always update the parent for initial frames.
       if (!updateParent) {
         updateParent = frame->UpdateOverflow() || entry->mInitial;
       }
       if (updateParent) {
         nsIFrame *parent = frame->GetParent();
-        if (parent) {
+        if (parent && parent != mSubtreeRoot) {
           if (!mEntryList.contains(Entry(parent, entry->mDepth - 1, false))) {
             mEntryList.insert(new Entry(parent, entry->mDepth - 1, false));
           }
         }
       }
       delete entry;
     }
   }
@@ -160,16 +173,19 @@ private:
      * True if the frame had the actual style change, and we
      * want to check for pre-transform overflow areas.
      */
     bool mInitial;
   };
 
   /* A list of frames to process, sorted by their depth in the frame tree */
   SplayTree<Entry, Entry> mEntryList;
+
+  /* Don't update overflow of this frame or its ancestors. */
+  const nsIFrame* mSubtreeRoot;
 };
 
 class RestyleTracker {
 public:
   typedef mozilla::dom::Element Element;
 
   RestyleTracker(uint32_t aRestyleBits) :
     mRestyleBits(aRestyleBits),
--- a/layout/base/crashtests/379105-1.xhtml
+++ b/layout/base/crashtests/379105-1.xhtml
@@ -20,17 +20,17 @@ function boom1()
 {
   xbltarget = document.getElementById("xbltarget");
   xbltarget.style.MozBinding = "url('#x')";
   setTimeout(boom2, 0);
 }
 
 function boom2()
 {
-  var nodes = document.getAnonymousNodes(xbltarget);
+  var nodes = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes(xbltarget));
   if (!nodes) {
     setTimeout(boom2, 10);
     return;
   }
   var anox = nodes[0];
   var frame = document.createElementNS("http://www.w3.org/1999/xhtml", "frame")
   frame.src = "data:text/html,<html><body>Hi!</body></html>";
   anox.appendChild(frame);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -88,16 +88,17 @@ using namespace mozilla::layers;
 using namespace mozilla::layout;
 
 using mozilla::image::Angle;
 using mozilla::image::Flip;
 using mozilla::image::ImageOps;
 using mozilla::image::Orientation;
 
 #define FLEXBOX_ENABLED_PREF_NAME "layout.css.flexbox.enabled"
+#define STICKY_ENABLED_PREF_NAME "layout.css.sticky.enabled"
 
 #ifdef DEBUG
 // TODO: remove, see bug 598468.
 bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
 #endif // DEBUG
 
 typedef gfxPattern::GraphicsFilter GraphicsFilter;
 typedef FrameMetrics::ViewID ViewID;
@@ -115,16 +116,21 @@ static ViewID sScrollIdCounter = FrameMe
 
 // These are indices into kDisplayKTable.  They'll be initialized
 // the first time that FlexboxEnabledPrefChangeCallback() is invoked.
 static int32_t sIndexOfFlexInDisplayTable;
 static int32_t sIndexOfInlineFlexInDisplayTable;
 // This tracks whether those ^^ indices have been initialized
 static bool sAreFlexKeywordIndicesInitialized = false;
 
+// This is an index into kPositionKTable. It will be initialized
+// the first time that StickyEnabledPrefChangeCallback() is invoked.
+static int32_t sIndexOfStickyInPositionTable;
+static bool sIsStickyKeywordIndexInitialized = false;
+
 typedef nsDataHashtable<nsUint64HashKey, nsIContent*> ContentMap;
 static ContentMap* sContentMap = nullptr;
 static ContentMap& GetContentMap() {
   if (!sContentMap) {
     sContentMap = new ContentMap();
   }
   return *sContentMap;
 }
@@ -165,16 +171,48 @@ FlexboxEnabledPrefChangeCallback(const c
   if (sIndexOfInlineFlexInDisplayTable >= 0) {
     nsCSSProps::kDisplayKTable[sIndexOfInlineFlexInDisplayTable] =
       isFlexboxEnabled ? eCSSKeyword_inline_flex : eCSSKeyword_UNKNOWN;
   }
 
   return 0;
 }
 
+// When the pref "layout.css.sticky.enabled" changes, this function is invoked
+// to let us update kPositionKTable, to selectively disable or restore the
+// entry for "sticky" in that table.
+static int
+StickyEnabledPrefChangeCallback(const char* aPrefName, void* aClosure)
+{
+  MOZ_ASSERT(strncmp(aPrefName, STICKY_ENABLED_PREF_NAME,
+                     NS_ARRAY_LENGTH(STICKY_ENABLED_PREF_NAME)) == 0,
+             "We only registered this callback for a single pref, so it "
+             "should only be called for that pref");
+
+  bool isStickyEnabled =
+    Preferences::GetBool(STICKY_ENABLED_PREF_NAME, false);
+
+  if (!sIsStickyKeywordIndexInitialized) {
+    // First run: find the position of "sticky" in kPositionKTable.
+    sIndexOfStickyInPositionTable =
+      nsCSSProps::FindIndexOfKeyword(eCSSKeyword_sticky,
+                                     nsCSSProps::kPositionKTable);
+    MOZ_ASSERT(sIndexOfStickyInPositionTable >= 0,
+               "Couldn't find sticky in kPositionKTable");
+    sIsStickyKeywordIndexInitialized = true;
+  }
+
+  // OK -- now, stomp on or restore the "sticky" entry in kPositionKTable,
+  // depending on whether the sticky pref is enabled vs. disabled.
+  nsCSSProps::kPositionKTable[sIndexOfStickyInPositionTable] =
+    isStickyEnabled ? eCSSKeyword_sticky : eCSSKeyword_UNKNOWN;
+
+  return 0;
+}
+
 template <class AnimationsOrTransitions>
 static AnimationsOrTransitions*
 HasAnimationOrTransition(nsIContent* aContent,
                          nsIAtom* aAnimationProperty,
                          nsCSSProperty aProperty)
 {
   AnimationsOrTransitions* animations =
     static_cast<AnimationsOrTransitions*>(aContent->GetProperty(aAnimationProperty));
@@ -4997,29 +5035,34 @@ nsLayoutUtils::Initialize()
   Preferences::AddBoolVarCache(&sFontSizeInflationDisabledInMasterProcess,
                                "font.size.inflation.disabledInMasterProcess");
   Preferences::AddBoolVarCache(&sInvalidationDebuggingIsEnabled,
                                "nglayout.debug.invalidation");
 
   Preferences::RegisterCallback(FlexboxEnabledPrefChangeCallback,
                                 FLEXBOX_ENABLED_PREF_NAME);
   FlexboxEnabledPrefChangeCallback(FLEXBOX_ENABLED_PREF_NAME, nullptr);
+  Preferences::RegisterCallback(StickyEnabledPrefChangeCallback,
+                                STICKY_ENABLED_PREF_NAME);
+  StickyEnabledPrefChangeCallback(STICKY_ENABLED_PREF_NAME, nullptr);
 }
 
 /* static */
 void
 nsLayoutUtils::Shutdown()
 {
   if (sContentMap) {
     delete sContentMap;
     sContentMap = nullptr;
   }
 
   Preferences::UnregisterCallback(FlexboxEnabledPrefChangeCallback,
                                   FLEXBOX_ENABLED_PREF_NAME);
+  Preferences::UnregisterCallback(StickyEnabledPrefChangeCallback,
+                                  STICKY_ENABLED_PREF_NAME);
 }
 
 /* static */
 void
 nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
                                     imgIRequest* aRequest,
                                     bool* aRequestRegistered)
 {
new file mode 100644
--- /dev/null
+++ b/layout/generic/StickyScrollContainer.cpp
@@ -0,0 +1,252 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * compute sticky positioning, both during reflow and when the scrolling
+ * container scrolls
+ */
+
+#include "StickyScrollContainer.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "RestyleTracker.h"
+
+using namespace mozilla::css;
+
+namespace mozilla {
+
+void DestroyStickyScrollContainer(void* aPropertyValue)
+{
+  delete static_cast<StickyScrollContainer*>(aPropertyValue);
+}
+
+NS_DECLARE_FRAME_PROPERTY(StickyScrollContainerProperty,
+                          DestroyStickyScrollContainer)
+
+StickyScrollContainer::StickyScrollContainer(nsIScrollableFrame* aScrollFrame)
+  : mScrollFrame(aScrollFrame)
+  , mScrollPosition()
+{
+  mScrollFrame->AddScrollPositionListener(this);
+}
+
+StickyScrollContainer::~StickyScrollContainer()
+{
+  mScrollFrame->RemoveScrollPositionListener(this);
+}
+
+// static
+StickyScrollContainer*
+StickyScrollContainer::StickyScrollContainerForFrame(nsIFrame* aFrame)
+{
+  nsIScrollableFrame* scrollFrame =
+    nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
+      nsLayoutUtils::SCROLLABLE_SAME_DOC |
+      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+  NS_ASSERTION(scrollFrame, "Need a scrolling container");
+  FrameProperties props = static_cast<nsIFrame*>(do_QueryFrame(scrollFrame))->
+    Properties();
+  StickyScrollContainer* s = static_cast<StickyScrollContainer*>
+    (props.Get(StickyScrollContainerProperty()));
+  if (!s) {
+    s = new StickyScrollContainer(scrollFrame);
+    props.Set(StickyScrollContainerProperty(), s);
+  }
+  return s;
+}
+
+// static
+StickyScrollContainer*
+StickyScrollContainer::GetStickyScrollContainerForScrollFrame(nsIFrame* aFrame)
+{
+  FrameProperties props = aFrame->Properties();
+  return static_cast<StickyScrollContainer*>
+    (props.Get(StickyScrollContainerProperty()));
+}
+
+static nscoord
+ComputeStickySideOffset(Side aSide, const nsStyleSides& aOffset,
+                        nscoord aPercentBasis)
+{
+  if (eStyleUnit_Auto == aOffset.GetUnit(aSide)) {
+    return NS_AUTOOFFSET;
+  } else {
+    return nsLayoutUtils::ComputeCBDependentValue(aPercentBasis,
+                                                  aOffset.Get(aSide));
+  }
+}
+
+// static
+void
+StickyScrollContainer::ComputeStickyOffsets(nsIFrame* aFrame)
+{
+  nsIScrollableFrame* scrollableFrame =
+    nsLayoutUtils::GetNearestScrollableFrame(aFrame->GetParent(),
+      nsLayoutUtils::SCROLLABLE_SAME_DOC |
+      nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+
+  if (!scrollableFrame) {
+    // Not sure how this would happen, but bail if it does.
+    NS_ERROR("Couldn't find a scrollable frame");
+    return;
+  }
+
+  nsSize scrollContainerSize = scrollableFrame->GetScrolledFrame()->
+    GetContentRectRelativeToSelf().Size();
+
+  nsMargin computedOffsets;
+  const nsStylePosition* position = aFrame->StylePosition();
+
+  computedOffsets.left   = ComputeStickySideOffset(eSideLeft, position->mOffset,
+                                                   scrollContainerSize.width);
+  computedOffsets.right  = ComputeStickySideOffset(eSideRight, position->mOffset,
+                                                   scrollContainerSize.width);
+  computedOffsets.top    = ComputeStickySideOffset(eSideTop, position->mOffset,
+                                                   scrollContainerSize.height);
+  computedOffsets.bottom = ComputeStickySideOffset(eSideBottom, position->mOffset,
+                                                   scrollContainerSize.height);
+
+  // Store the offset
+  FrameProperties props = aFrame->Properties();
+  nsMargin* offsets = static_cast<nsMargin*>
+    (props.Get(nsIFrame::ComputedStickyOffsetProperty()));
+  if (offsets) {
+    *offsets = computedOffsets;
+  } else {
+    props.Set(nsIFrame::ComputedStickyOffsetProperty(),
+              new nsMargin(computedOffsets));
+  }
+}
+
+void
+StickyScrollContainer::ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
+                                           nsRect* aContain) const
+{
+  aStick->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
+  aContain->SetRect(nscoord_MIN/2, nscoord_MIN/2, nscoord_MAX, nscoord_MAX);
+
+  const nsMargin* computedOffsets = static_cast<nsMargin*>(
+    aFrame->Properties().Get(nsIFrame::ComputedStickyOffsetProperty()));
+  if (!computedOffsets) {
+    // We haven't reflowed the scroll frame yet, so offsets haven't been
+    // computed. Bail.
+    return;
+  }
+
+  nsIFrame* scrolledFrame = mScrollFrame->GetScrolledFrame();
+  nsIFrame* cbFrame = aFrame->GetContainingBlock();
+  NS_ASSERTION(cbFrame == scrolledFrame ||
+    nsLayoutUtils::IsProperAncestorFrame(scrolledFrame, cbFrame),
+    "Scroll frame should be an ancestor of the containing block");
+
+  nsRect rect = aFrame->GetRect();
+  nsMargin margin = aFrame->GetUsedMargin();
+
+  // Containing block limits
+  if (cbFrame != scrolledFrame) {
+    nsMargin cbBorderPadding = cbFrame->GetUsedBorderAndPadding();
+    aContain->SetRect(nsPoint(cbBorderPadding.left, cbBorderPadding.top) -
+                      aFrame->GetParent()->GetOffsetTo(cbFrame),
+                      cbFrame->GetContentRectRelativeToSelf().Size() -
+                      rect.Size());
+    aContain->Deflate(margin);
+  }
+
+  nsMargin sfPadding = scrolledFrame->GetUsedPadding();
+  nsPoint sfOffset = aFrame->GetParent()->GetOffsetTo(scrolledFrame);
+
+  // Top
+  if (computedOffsets->top != NS_AUTOOFFSET) {
+    aStick->SetTopEdge(mScrollPosition.y + sfPadding.top +
+                       computedOffsets->top - sfOffset.y);
+  }
+
+  nsSize sfSize = scrolledFrame->GetContentRectRelativeToSelf().Size();
+
+  // Bottom
+  if (computedOffsets->bottom != NS_AUTOOFFSET &&
+      (computedOffsets->top == NS_AUTOOFFSET ||
+       rect.height <= sfSize.height - computedOffsets->TopBottom())) {
+    aStick->SetBottomEdge(mScrollPosition.y + sfPadding.top + sfSize.height -
+                          computedOffsets->bottom - rect.height - sfOffset.y);
+  }
+
+  uint8_t direction = cbFrame->StyleVisibility()->mDirection;
+
+  // Left
+  if (computedOffsets->left != NS_AUTOOFFSET &&
+      (computedOffsets->right == NS_AUTOOFFSET ||
+       direction == NS_STYLE_DIRECTION_LTR ||
+       rect.width <= sfSize.width - computedOffsets->LeftRight())) {
+    aStick->SetLeftEdge(mScrollPosition.x + sfPadding.left +
+                        computedOffsets->left - sfOffset.x);
+  }
+
+  // Right
+  if (computedOffsets->right != NS_AUTOOFFSET &&
+      (computedOffsets->left == NS_AUTOOFFSET ||
+       direction == NS_STYLE_DIRECTION_RTL ||
+       rect.width <= sfSize.width - computedOffsets->LeftRight())) {
+    aStick->SetRightEdge(mScrollPosition.x + sfPadding.left + sfSize.width -
+                         computedOffsets->right - rect.width - sfOffset.x);
+  }
+}
+
+nsPoint
+StickyScrollContainer::ComputePosition(nsIFrame* aFrame) const
+{
+  nsRect stick;
+  nsRect contain;
+  ComputeStickyLimits(aFrame, &stick, &contain);
+
+  nsPoint position = aFrame->GetNormalPosition();
+
+  // For each sticky direction (top, bottom, left, right), move the frame along
+  // the appropriate axis, based on the scroll position, but limit this to keep
+  // the element's margin box within the containing block.
+  position.y = std::max(position.y, std::min(stick.y, contain.YMost()));
+  position.y = std::min(position.y, std::max(stick.YMost(), contain.y));
+  position.x = std::max(position.x, std::min(stick.x, contain.XMost()));
+  position.x = std::min(position.x, std::max(stick.XMost(), contain.x));
+
+  return position;
+}
+
+void
+StickyScrollContainer::UpdatePositions(nsPoint aScrollPosition,
+                                       nsIFrame* aSubtreeRoot)
+{
+  NS_ASSERTION(!aSubtreeRoot || aSubtreeRoot == do_QueryFrame(mScrollFrame),
+    "If reflowing, should be reflowing the scroll frame");
+  mScrollPosition = aScrollPosition;
+
+  OverflowChangedTracker oct;
+  oct.SetSubtreeRoot(aSubtreeRoot);
+  for (nsTArray<nsIFrame*>::size_type i = 0; i < mFrames.Length(); i++) {
+    nsIFrame* f = mFrames[i];
+    if (aSubtreeRoot) {
+      // Reflowing the scroll frame, so recompute offsets.
+      ComputeStickyOffsets(f);
+    }
+    f->SetPosition(ComputePosition(f));
+    oct.AddFrame(f);
+  }
+  oct.Flush();
+}
+
+void
+StickyScrollContainer::ScrollPositionWillChange(nscoord aX, nscoord aY)
+{
+}
+
+void
+StickyScrollContainer::ScrollPositionDidChange(nscoord aX, nscoord aY)
+{
+  UpdatePositions(nsPoint(aX, aY), nullptr);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/layout/generic/StickyScrollContainer.h
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+/**
+ * compute sticky positioning, both during reflow and when the scrolling
+ * container scrolls
+ */
+
+#ifndef StickyScrollContainer_h
+#define StickyScrollContainer_h
+
+#include "nsPoint.h"
+#include "nsTArray.h"
+#include "nsIScrollPositionListener.h"
+
+class nsRect;
+class nsIFrame;
+class nsIScrollableFrame;
+
+namespace mozilla {
+
+class StickyScrollContainer MOZ_FINAL : public nsIScrollPositionListener
+{
+public:
+  /**
+   * Find the StickyScrollContainer associated with the scroll container of
+   * the given frame, creating it if necessary.
+   */
+  static StickyScrollContainer* StickyScrollContainerForFrame(nsIFrame* aFrame);
+
+  /**
+   * Find the StickyScrollContainer associated with the given scroll frame,
+   * if it exists.
+   */
+  static StickyScrollContainer* GetStickyScrollContainerForScrollFrame(nsIFrame* aScrollFrame);
+
+  void AddFrame(nsIFrame* aFrame) {
+    mFrames.AppendElement(aFrame);
+  }
+  void RemoveFrame(nsIFrame* aFrame) {
+    mFrames.RemoveElement(aFrame);
+  }
+
+  // Compute the offsets for a sticky position element
+  static void ComputeStickyOffsets(nsIFrame* aFrame);
+
+  /**
+   * Compute the position of a sticky positioned frame, based on information
+   * stored in its properties along with our scroll frame and scroll position.
+   */
+  nsPoint ComputePosition(nsIFrame* aFrame) const;
+
+  /**
+   * Compute and set the position of all sticky frames, given the current
+   * scroll position of the scroll frame. If not in reflow, aSubtreeRoot should
+   * be null; otherwise, overflow-area updates will be limited to not affect
+   * aSubtreeRoot or its ancestors.
+   */
+  void UpdatePositions(nsPoint aScrollPosition, nsIFrame* aSubtreeRoot);
+
+  // nsIScrollPositionListener
+  virtual void ScrollPositionWillChange(nscoord aX, nscoord aY) MOZ_OVERRIDE;
+  virtual void ScrollPositionDidChange(nscoord aX, nscoord aY) MOZ_OVERRIDE;
+
+private:
+  StickyScrollContainer(nsIScrollableFrame* aScrollFrame);
+  ~StickyScrollContainer();
+
+  /**
+   * Compute two rectangles that determine sticky positioning: |aStick|, based
+   * on the scroll container, and |aContain|, based on the containing block.
+   * Sticky positioning keeps the frame position (its upper-left corner) always
+   * within |aContain| and secondarily within |aStick|.
+   */
+  void ComputeStickyLimits(nsIFrame* aFrame, nsRect* aStick,
+                           nsRect* aContain) const;
+
+  friend void DestroyStickyScrollContainer(void* aPropertyValue);
+
+  nsIScrollableFrame* const mScrollFrame;
+  nsTArray<nsIFrame*> mFrames;
+  nsPoint mScrollPosition;
+};
+
+} // namespace mozilla
+
+#endif /* StickyScrollContainer_h */
--- a/layout/generic/moz.build
+++ b/layout/generic/moz.build
@@ -40,16 +40,17 @@ EXPORTS.mozilla += [
 
 EXPORTS.mozilla.layout += [
     'FrameChildList.h',
 ]
 
 CPP_SOURCES += [
     'FrameChildList.cpp',
     'ScrollbarActivity.cpp',
+    'StickyScrollContainer.cpp',
     'TextOverflow.cpp',
     'nsAbsoluteContainingBlock.cpp',
     'nsBRFrame.cpp',
     'nsBlockFrame.cpp',
     'nsBlockReflowContext.cpp',
     'nsBlockReflowState.cpp',
     'nsBulletFrame.cpp',
     'nsCanvasFrame.cpp',
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -68,16 +68,17 @@
 #include "nsChangeHint.h"
 #include "nsDeckFrame.h"
 #include "nsSubDocumentFrame.h"
 #include "nsSVGTextFrame2.h"
 
 #include "gfxContext.h"
 #include "nsRenderingContext.h"
 #include "nsAbsoluteContainingBlock.h"
+#include "StickyScrollContainer.h"
 #include "nsFontInflationData.h"
 #include "gfxASurface.h"
 #include "nsRegion.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/css/ImageLoader.h"
 #include "mozilla/gfx/Tools.h"
@@ -505,16 +506,19 @@ nsFrame::Init(nsIContent*      aContent,
                        NS_FRAME_IS_NONDISPLAY);
   }
   const nsStyleDisplay *disp = StyleDisplay();
   if (disp->HasTransform(this)) {
     // The frame gets reconstructed if we toggle the -moz-transform
     // property, so we can set this bit here and then ignore it.
     mState |= NS_FRAME_MAY_BE_TRANSFORMED;
   }
+  if (disp->mPosition == NS_STYLE_POSITION_STICKY) {
+    StickyScrollContainer::StickyScrollContainerForFrame(this)->AddFrame(this);
+  }
 
   if (nsLayoutUtils::FontSizeInflationEnabled(PresContext()) || !GetParent()
 #ifdef DEBUG
       // We have assertions that check inflation invariants even when
       // font size inflation is not enabled.
       || true
 #endif
       ) {
@@ -583,16 +587,21 @@ nsFrame::DestroyFrom(nsIFrame* aDestruct
     "destroy called on frame while scripts not blocked");
   NS_ASSERTION(!GetNextSibling() && !GetPrevSibling(),
                "Frames should be removed before destruction.");
   NS_ASSERTION(aDestructRoot, "Must specify destruct root");
   MOZ_ASSERT(!HasAbsolutelyPositionedChildren());
 
   nsSVGEffects::InvalidateDirectRenderingObservers(this);
 
+  if (StyleDisplay()->mPosition == NS_STYLE_POSITION_STICKY) {
+    StickyScrollContainer::StickyScrollContainerForFrame(this)->
+      RemoveFrame(this);
+  }
+
   // Get the view pointer now before the frame properties disappear
   // when we call NotifyDestroyingFrame()
   nsView* view = GetView();
   nsPresContext* presContext = PresContext();
 
   nsIPresShell *shell = presContext->GetPresShell();
   if (mState & NS_FRAME_OUT_OF_FLOW) {
     nsPlaceholderFrame* placeholder =
@@ -2084,17 +2093,18 @@ nsIFrame::BuildDisplayListForChild(nsDis
   const nsStyleDisplay* disp = child->StyleDisplay();
   const nsStylePosition* pos = child->StylePosition();
   bool isVisuallyAtomic = child->HasOpacity()
     || child->IsTransformed()
     || nsSVGIntegrationUtils::UsingEffectsForFrame(child);
 
   bool isPositioned = disp->IsPositioned(child);
   bool isStackingContext =
-    (isPositioned && pos->mZIndex.GetUnit() == eStyleUnit_Integer) ||
+    (isPositioned && (disp->mPosition == NS_STYLE_POSITION_STICKY ||
+                      pos->mZIndex.GetUnit() == eStyleUnit_Integer)) ||
      isVisuallyAtomic || (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT);
 
   if (isVisuallyAtomic || isPositioned || (!isSVG && disp->IsFloating(child)) ||
       ((disp->mClipFlags & NS_STYLE_CLIP_RECT) &&
        IsSVGContentWithCSSClip(child)) ||
       (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT)) {
     // If you change this, also change IsPseudoStackingContextFromStyle()
     pseudoStackingContext = true;
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -43,16 +43,17 @@
 #include "nsSubDocumentFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 #include "mozilla/Attributes.h"
 #include "ScrollbarActivity.h"
 #include "nsRefreshDriver.h"
 #include "nsThemeConstants.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsIScrollPositionListener.h"
+#include "StickyScrollContainer.h"
 #include <algorithm>
 #include <cstdlib> // for std::abs(int/long)
 #include <cmath> // for std::abs(float/double)
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layout;
 
@@ -822,16 +823,17 @@ nsHTMLScrollFrame::Reflow(nsPresContext*
     state.mComputedBorder.TopBottom();
 
   aDesiredSize.SetOverflowAreasToDesiredBounds();
   if (mInner.IsIgnoringViewportClipping()) {
     aDesiredSize.mOverflowAreas.UnionWith(
       state.mContentsOverflowAreas + mInner.mScrolledFrame->GetPosition());
   }
 
+  mInner.UpdateSticky();
   FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowState, aStatus);
 
   if (!InInitialReflow() && !mInner.mHadNonInitialReflow) {
     mInner.mHadNonInitialReflow = true;
   }
 
   if (mInner.mIsRoot && !oldScrolledAreaBounds.IsEqualEdges(newScrolledAreaBounds)) {
     mInner.PostScrolledAreaEvent();
@@ -3539,16 +3541,18 @@ nsXULScrollFrame::Layout(nsBoxLayoutStat
     // Make sure we'll try scrolling to restored position
     PresContext()->PresShell()->PostReflowCallback(&mInner);
     mInner.mPostedReflowCallback = true;
   }
   if (!(GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
     mInner.mHadNonInitialReflow = true;
   }
 
+  mInner.UpdateSticky();
+
   // Set up overflow areas for block frames for the benefit of
   // text-overflow.
   nsIFrame* f = mInner.mScrolledFrame->GetContentInsertionFrame();
   if (nsLayoutUtils::GetAsBlock(f)) {
     nsRect origRect = f->GetRect();
     nsRect clippedRect = origRect;
     clippedRect.MoveBy(mInner.mScrollPort.TopLeft());
     clippedRect.IntersectRect(clippedRect, mInner.mScrollPort);
@@ -3709,16 +3713,27 @@ nsGfxScrollFrameInner::UpdateOverflow()
     mOuter->PresContext()->PresShell()->FrameNeedsReflow(
       mOuter, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
     return false;  // reflowing will update overflow
   }
   return mOuter->nsContainerFrame::UpdateOverflow();
 }
 
 void
+nsGfxScrollFrameInner::UpdateSticky()
+{
+  StickyScrollContainer* ssc = StickyScrollContainer::
+    GetStickyScrollContainerForScrollFrame(mOuter);
+  if (ssc) {
+    nsIScrollableFrame* scrollFrame = do_QueryFrame(mOuter);
+    ssc->UpdatePositions(scrollFrame->GetScrollPosition(), mOuter);
+  }
+}
+
+void
 nsGfxScrollFrameInner::AdjustScrollbarRectForResizer(
                          nsIFrame* aFrame, nsPresContext* aPresContext,
                          nsRect& aRect, bool aHasResizer, bool aVertical)
 {
   if ((aVertical ? aRect.width : aRect.height) == 0)
     return;
 
   // if a content resizer is present, use its size. Otherwise, check if the
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -271,16 +271,18 @@ public:
   bool IsScrollingActive() const { return mScrollingActive || ShouldBuildLayer(); }
   void ResetScrollPositionForLayerPixelAlignment()
   {
     mScrollPosForLayerPixelAlignment = GetScrollPosition();
   }
 
   bool UpdateOverflow();
 
+  void UpdateSticky();
+
   // adjust the scrollbar rectangle aRect to account for any visible resizer.
   // aHasResizer specifies if there is a content resizer, however this method
   // will also check if a widget resizer is present as well.
   void AdjustScrollbarRectForResizer(nsIFrame* aFrame, nsPresContext* aPresContext,
                                      nsRect& aRect, bool aHasResizer, bool aVertical);
   // returns true if a resizer should be visible
   bool HasResizer() { return mResizerBox && !mCollapsedResizer; }
   void LayoutScrollbars(nsBoxLayoutState& aState,
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -18,25 +18,27 @@
 #include "nsFlexContainerFrame.h"
 #include "nsImageFrame.h"
 #include "nsTableFrame.h"
 #include "nsTableCellFrame.h"
 #include "nsIPercentHeightObserver.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsFontInflationData.h"
+#include "StickyScrollContainer.h"
 #include <algorithm>
 
 #ifdef DEBUG
 #undef NOISY_VERTICAL_ALIGN
 #else
 #undef NOISY_VERTICAL_ALIGN
 #endif
 
 using namespace mozilla;
+using namespace mozilla::css;
 using namespace mozilla::layout;
 
 enum eNormalLineHeightControl {
   eUninitialized = -1,
   eNoExternalLeading = 0,   // does not include external leading 
   eIncludeExternalLeading,  // use whatever value font vendor provides
   eCompensateLeading        // compensate leading if leading provided by font vendor is not enough
 };
@@ -837,16 +839,19 @@ nsHTMLReflowState::ApplyRelativePosition
     *normalPosition = *aPosition;
   } else {
     props.Set(nsIFrame::NormalPositionProperty(), new nsPoint(*aPosition));
   }
 
   const nsStyleDisplay* display = aFrame->StyleDisplay();
   if (NS_STYLE_POSITION_RELATIVE == display->mPosition) {
     *aPosition += nsPoint(aComputedOffsets.left, aComputedOffsets.top);
+  } else if (NS_STYLE_POSITION_STICKY == display->mPosition) {
+    *aPosition = StickyScrollContainer::StickyScrollContainerForFrame(aFrame)->
+      ComputePosition(aFrame);
   }
 }
 
 nsIFrame*
 nsHTMLReflowState::GetHypotheticalBoxContainer(nsIFrame* aFrame,
                                                nscoord& aCBLeftEdge,
                                                nscoord& aCBWidth)
 {
@@ -1935,18 +1940,21 @@ nsHTMLReflowState::InitConstraints(nsPre
           // default to interpreting the height like 'auto'
           heightUnit = eStyleUnit_Auto;
         }
       }
     }
 
     // Compute our offsets if the element is relatively positioned.  We need
     // the correct containing block width and height here, which is why we need
-    // to do it after all the quirks-n-such above.
-    if (mStyleDisplay->IsRelativelyPositioned(frame)) {
+    // to do it after all the quirks-n-such above. (If the element is sticky
+    // positioned, we need to wait until the scroll container knows its size,
+    // so we compute offsets from StickyScrollContainer::UpdatePositions.)
+    if (mStyleDisplay->IsRelativelyPositioned(frame) &&
+        NS_STYLE_POSITION_RELATIVE == mStyleDisplay->mPosition) {
       uint8_t direction = NS_STYLE_DIRECTION_LTR;
       if (cbrs && NS_STYLE_DIRECTION_RTL == cbrs->mStyleVisibility->mDirection) {
         direction = NS_STYLE_DIRECTION_RTL;
       }
       ComputeRelativeOffsets(direction, frame, aContainingBlockWidth,
           aContainingBlockHeight, mComputedOffsets);
     } else {
       // Initialize offsets to 0
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -922,16 +922,17 @@ public:
     static NS_PROPERTY_DESCRIPTOR_CONST FramePropertyDescriptor descriptor = { nullptr, dtor }; \
     return &descriptor;                                                                        \
   }
 
   NS_DECLARE_FRAME_PROPERTY(IBSplitSpecialSibling, nullptr)
   NS_DECLARE_FRAME_PROPERTY(IBSplitSpecialPrevSibling, nullptr)
 
   NS_DECLARE_FRAME_PROPERTY(NormalPositionProperty, DestroyPoint)
+  NS_DECLARE_FRAME_PROPERTY(ComputedStickyOffsetProperty, DestroyMargin)
 
   NS_DECLARE_FRAME_PROPERTY(OutlineInnerRectProperty, DestroyRect)
   NS_DECLARE_FRAME_PROPERTY(PreEffectsBBoxProperty, DestroyRect)
   NS_DECLARE_FRAME_PROPERTY(PreTransformOverflowAreasProperty,
                             DestroyOverflowAreas)
 
   // The initial overflow area passed to FinishAndStoreOverflow. This is only set
   // on frames that Preserve3D(), and when at least one of the overflow areas
--- a/layout/inspector/tests/test_bug522601.xhtml
+++ b/layout/inspector/tests/test_bug522601.xhtml
@@ -101,17 +101,17 @@ addLoadEvent(function() {
   testFunc(walkerNonAnon, "previousNode", $("s"), "step back to span");
   testFunc(walkerNonAnon, "previousNode", $("s").previousSibling,
            "step back to some text");
   testFunc(walkerNonAnon, "previousNode", $("display"),
            "step back to root");
   testFunc(walkerNonAnon, "previousNode", null,
            "step back past root");
 
-  var anonDiv = document.getAnonymousNodes($("display"))[0];
+  var anonDiv = SpecialPowers.unwrap(SpecialPowers.wrap(document).getAnonymousNodes($("display")))[0];
 
   var walkerAnon =
     SpecialPowers.Cc["@mozilla.org/inspector/deep-tree-walker;1"]
               .createInstance(SpecialPowers.Ci.inIDeepTreeWalker);
   walkerAnon.showAnonymousContent = true;
   walkerAnon.init($("display"), NodeFilter.SHOW_ALL);
 
   is(SpecialPowers.unwrap(walkerAnon.currentNode), $("display"), "Unexpected anon root");
--- a/layout/reftests/bugs/495385-2f.xhtml
+++ b/layout/reftests/bugs/495385-2f.xhtml
@@ -8,31 +8,41 @@
   <binding id="after">
     <content><children/>Kitty</content>
   </binding>
   <binding id="empty1">
     <content><div xmlns="http://www.w3.org/1999/xhtml">Hello</div><children/></content>
   </binding>
   <binding id="empty2">
     <content><children/><div xmlns="http://www.w3.org/1999/xhtml">Kitty</div></content>
+    <implementation>
+      <constructor>
+        // We used to do this in an onload handler, but getAnonymousNodes is no
+        // longer accessible to content, and we can't use SpecialPowers in
+        // reftests. So we enable XBL scopes and take advantage of the fact that XBL
+        // scopes can access these functions. We apply this binding
+        // programatically to make absolutely sure this constructor runs after all the
+        // other bindings have been set up.
+        document.body.offsetHeight;
+        document.getAnonymousNodes(document.getElementById("d3"))[0].style.display = 'inline';
+        document.getAnonymousNodes(document.getElementById("d4"))[2].style.display = 'inline';
+      </constructor>
+    </implementation>
   </binding>
 </bindings>
 <style>
 body > div { border:1px solid black; margin:1em;
              font-family:sans-serif; letter-spacing:2px; }
 #d1 { -moz-binding:url(#before); }
 #d2 { -moz-binding:url(#after); }
 #d3 { -moz-binding:url(#empty1); }
-#d4 { -moz-binding:url(#empty2); }
 </style>
 <script>
 function loaded() {
-  document.body.offsetHeight;
-  document.getAnonymousNodes(document.getElementById("d3"))[0].style.display = 'inline';
-  document.getAnonymousNodes(document.getElementById("d4"))[2].style.display = 'inline';
+  document.getElementById('d4').style.MozBinding = "url(#empty2)";
 }
 </script>
 </head>
 <body onload="loaded()">
   <div id="d1"> <span>Kitty</span></div>
   <div id="d2"><span>Hello</span> </div>
   <div id="d3"> <span>Kitty</span></div>
   <div id="d4"><span>Hello</span> </div>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1166,18 +1166,18 @@ skip-if(B2G) fails-if(Android) == 446100
 skip-if(B2G) fails-if(Android) == 446100-1g.html about:blank
 == 446100-1h.html about:blank
 skip-if(B2G) == 447749-1.html 447749-1-ref.html
 fuzzy(127,2) == 448193.html 448193-ref.html
 # == 448987.html 448987-ref.html  # Disabled for now - it needs privileges
 != 449149-1a.html about:blank
 != 449149-1b.html about:blank
 # Retry the above with XBL scopes
-test-pref(dom.use_xbl_scopes_for_remote_xul,true) fails-if(Android) != 449149-1a.html about:blank
-test-pref(dom.use_xbl_scopes_for_remote_xul,true) fails-if(Android) != 449149-1b.html about:blank
+test-pref(dom.use_xbl_scopes_for_remote_xul,true) != 449149-1a.html about:blank
+test-pref(dom.use_xbl_scopes_for_remote_xul,true) != 449149-1b.html about:blank
 == 449149-2.html 449149-2-ref.html
 == 449171-1.html 449171-ref.html
 == 449362-1.html 449362-1-ref.html
 == 449519-1.html 449519-1-ref.html
 # == 449653-1.html 449653-1-ref.html # Disabled for now - it needs privileges
 == 450670-1.html 450670-1-ref.html
 == 451168-1.html 451168-1-ref.html
 == 451876-1.html 451876-1-ref.html
@@ -1365,17 +1365,17 @@ fails-if(Android) == 488685-1.html 48868
 == 495385-1d.html 495385-1-ref.html
 == 495385-1e.html 495385-1-ref.html
 == 495385-1f.html 495385-1-ref.html
 == 495385-2a.html 495385-2-ref.html
 == 495385-2b.html 495385-2-ref.html
 == 495385-2c.html 495385-2-ref.html
 == 495385-2d.html 495385-2-ref.html
 == 495385-2e.html 495385-2-ref.html
-== 495385-2f.xhtml 495385-2-ref.html
+pref(dom.use_xbl_scopes_for_remote_xul,true) == 495385-2f.xhtml 495385-2-ref.html
 == 495385-2g.html 495385-2-ref.html
 == 495385-2h.html 495385-2-ref.html
 == 495385-2i.html 495385-2-ref.html
 == 495385-3.html 495385-3-ref.html
 == 495385-4.html 495385-4-ref.html
 == 495385-5.html 495385-5-ref.html
 asserts(1) == 496032-1.html 496032-1-ref.html # bug 399262
 == 496840-1.html 496840-1-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-1-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #sticky {
+        position: relative;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 20px"></div>
+      <div id="sticky"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - bottom, normal position</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="bottom-1-ref.html">
+    <meta name="assert" content="Bottom-sticky with a small amount of content above: should stay in its normal position">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #sticky {
+        position: sticky;
+        bottom: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 20px"></div>
+      <div id="sticky"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-2-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #sticky {
+        position: relative;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 80px"></div>
+      <div id="sticky"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-2a.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - bottom, normal position</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="bottom-2-ref.html">
+    <meta name="assert" content="Bottom-sticky with some content above: should stay in its normal position (but will stick beyond this point)">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #sticky {
+        position: sticky;
+        bottom: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 80px"></div>
+      <div id="sticky"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-2b.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - bottom, stuck</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="bottom-2-ref.html">
+    <meta name="assert" content="Bottom-sticky with content above: should stick at the bottom of the scroll container">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #sticky {
+        position: sticky;
+        bottom: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 90px"></div>
+      <div id="sticky"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-2c.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!--  -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - bottom, stuck</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="bottom-2-ref.html">
+    <meta name="assert" content="Bottom-sticky with content above: should stick at the bottom of the scroll container">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #sticky {
+        position: sticky;
+        bottom: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 200px"></div>
+      <div id="sticky"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-3-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #contain {
+        height: 200px;
+        background-color: gray;
+      }
+      #sticky {
+        position: relative;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 20px"></div>
+      <div id="contain">
+        <div style="height: 60px"></div>
+        <div id="sticky"></div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-3.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!--  -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - bottom, stuck</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="bottom-3-ref.html">
+    <meta name="assert" content="Bottom-sticky with content above and a containing block: should stick to the bottom of the scroll container">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #contain {
+        height: 200px;
+        background-color: gray;
+      }
+      #sticky {
+        position: sticky;
+        bottom: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 20px"></div>
+      <div id="contain">
+        <div style="height: 100px"></div>
+        <div id="sticky"></div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-4-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        height: 100px;
+      }
+      #contain {
+        height: 5px;
+        background-color: gray;
+        padding: 10px 0 0;
+        border-width: 10px 0 0;
+        border-style: solid;
+        border-color: #333;
+      }
+      #sticky {
+        height: 3px;
+        margin-top: 2px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 75px"></div>
+      <div id="contain">
+        <div id="sticky"></div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/bottom-4.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - bottom, contained</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="bottom-3-ref.html">
+    <meta name="assert" content="Bottom-sticky with too much content above: should stay within the containing block">
+    <style>
+      #scroll {
+        height: 100px;
+        overflow: hidden;
+      }
+      #contain {
+        height: 200px;
+        background-color: gray;
+        padding: 10px 0;
+        border-width: 10px 0;
+        border-style: solid;
+        border-color: #333;
+      }
+      #sticky {
+        position: sticky;
+        bottom: 10px;
+        height: 10px;
+        margin-top: 2px;
+        background-color: black;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="scroll">
+      <div style="height: 75px"></div>
+      <div id="contain">
+        <div style="height: 100px"></div>
+        <div id="sticky"></div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/containing-block-1-ref.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      body {
+        height: 200px;
+        margin: 0;
+      }
+      #sticky {
+        position: relative;
+        top: 100px;
+        height: 10px;
+        background-color: black;
+      }
+      #absolute {
+        position: absolute;
+        top: 5px;
+        left: 5px;
+        width: 10px;
+        height: 10px;
+        background-color: blue;
+      }
+    </style>
+  <body>
+    <div id="sticky">
+      <div id="absolute"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/containing-block-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - absolute containing block</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="containing-block-1-ref.html">
+    <meta name="assert" content="Sticky positioned elements should establish a containing block for absolutely positioned descendants">
+    <style>
+      body {
+        height: 200px;
+        margin: 0;
+      }
+      #sticky {
+        position: sticky;
+        top: 100px;
+        height: 10px;
+        background-color: black;
+      }
+      #absolute {
+        position: absolute;
+        top: 5px;
+        left: 5px;
+        width: 10px;
+        height: 10px;
+        background-color: blue;
+      }
+    </style>
+  <body>
+    <div id="sticky">
+      <div id="absolute"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/initial-1-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      body {
+        height: 100px;
+        margin: 0;
+        /* Without this, we do multiple passes of reflow, and
+         * the sticky element ends up positioned correctly.
+         */
+        overflow-y: scroll;
+      }
+      #sticky {
+        position: relative;
+        top: 10px;
+        height: 10px;
+        margin-bottom: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="sticky"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/initial-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - initial reflow</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="initial-1-ref.html">
+    <meta name="assert" content="Sticky positioning should be calculated correctly on initial page reflow">
+    <style>
+      body {
+        height: 100px;
+        margin: 0;
+        /* Without this, we do multiple passes of reflow, and
+         * the sticky element ends up positioned correctly.
+         */
+        overflow-y: scroll;
+      }
+      #sticky {
+        position: sticky;
+        top: 10px;
+        height: 10px;
+        margin-bottom: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="sticky"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/initial-scroll-1-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        direction: rtl;
+      }
+      #inner {
+        width: 200px;
+        height: 200px;
+        direction: ltr;
+      }
+      #sticky {
+        position: relative;
+        left: 110px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div id="inner">
+        <div id="sticky">
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/initial-scroll-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - initial scroll position</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="initial-scroll-1-ref.html">
+    <meta name="assert" content="When the initial scroll position of the scroll container is not (0, 0), a sticky element inside it should be positioned correctly">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        direction: rtl;
+      }
+      #inner {
+        width: 200px;
+        height: 200px;
+        direction: ltr;
+      }
+      #sticky {
+        position: sticky;
+        left: 10px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div id="inner">
+        <div id="sticky">
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-1-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 200px;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div class="fill" style="width: 20px"></div
+      ><div id="contain">
+        <div id="sticky"></div>
+      </div
+      ><div class="fill"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-1.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - left, normal position</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="left-1-ref.html">
+    <meta name="flags" content="dom">
+    <meta name="assert" content="Sticky-left with not enough scrolling: should be in its normal position">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 200px;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        position: sticky;
+        left: 10px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div class="fill"></div
+      ><div id="contain">
+        <div id="sticky"></div>
+      </div
+      ><div class="fill"></div>
+    </div>
+    <script type="text/javascript">
+      document.getElementById("scroll").scrollLeft = 80;
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-2-ref.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      #contain {
+        width: 100%;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        position: relative;
+        left: 10px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div id="contain">
+        <div id="sticky"></div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-2.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - left, stuck</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="left-2-ref.html">
+    <meta name="flags" content="dom">
+    <meta name="assert" content="Sticky-left with scrolling: should stick to the left edge of the scrolling container">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 200px;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        position: sticky;
+        left: 10px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div class="fill"></div
+      ><div id="contain">
+        <div id="sticky"></div>
+      </div
+      ><div class="fill"></div>
+    </div>
+    <script type="text/javascript">
+      document.getElementById("scroll").scrollLeft = 100;
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-3-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 5px;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        width: 5px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div id="contain">
+        <div id="sticky"></div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-3.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - left, contained</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="left-3-ref.html">
+    <meta name="flags" content="dom">
+    <meta name="assert" content="Sticky-left with too much scrolling: should stay within the containing block">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 200px;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        position: sticky;
+        left: 10px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div class="fill"></div
+      ><div id="contain">
+        <div id="sticky"></div>
+      </div
+      ><div class="fill"></div>
+    </div>
+    <script type="text/javascript">
+      document.getElementById("scroll").scrollLeft = 295;
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-right-1-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 200px;
+        height: 10px;
+        background-color: gray;
+      }
+      #sticky {
+        position: relative;
+        right: 20px;
+        width: 10px;
+        height: 10px;
+        background-color: black;
+      }
+    </style>
+  <body>
+    <div id="scroll">
+      <div class="fill"></div
+      ><div id="sticky"></div
+      ><div class="fill"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/position-sticky/left-right-1.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+  <head>
+    <title>CSS Test: Sticky Positioning - left+right, at left</title>
+    <link rel="author" title="Corey Ford" href="mailto:cford@mozilla.com">
+    <link rel="match" href="left-right-1-ref.html">
+    <meta name="assert" content="Left+right sticky, scrolled to the left: should act as right-sticky">
+    <style>
+      #scroll {
+        width: 100px;
+        height: 100px;
+        overflow: hidden;
+        border: 1px solid black;
+        white-space: nowrap;
+      }
+      #scroll div {
+        display: inline-block;
+      }
+      .fill {
+        width: 100px;
+        height: 1px;
+      }
+      #contain {
+        width: 200px;
+        height: 10px;
+        background-color: gray;