Bug 1061288 - Make it harder for ArrayBuffer data pointers to be held across invalidations. r=smaug,terrence,jdm,roc,khuey
authorSteve Fink <sfink@mozilla.com>
Tue, 07 Oct 2014 10:44:07 -0700
changeset 209626 4f52e486476c26e76f83d7ad9beeb993f8b6b5ab
parent 209625 b43ed5b85b1aa6bb76f66249e73934433b20c0c0
child 209627 7199ca292bee96ef94af5bc1d8cfe3726c21275f
push id9258
push userkwierso@gmail.com
push dateFri, 10 Oct 2014 00:12:09 +0000
treeherderfx-team@27a288fc5313 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, terrence, jdm, roc, khuey
bugs1061288
milestone35.0a1
Bug 1061288 - Make it harder for ArrayBuffer data pointers to be held across invalidations. r=smaug,terrence,jdm,roc,khuey
content/base/src/nsContentUtils.cpp
content/base/src/nsDOMFileReader.cpp
content/base/src/nsDOMFileReader.h
content/media/webaudio/AudioBuffer.cpp
content/media/webspeech/synth/nsSpeechTask.cpp
content/media/webspeech/synth/nsSpeechTask.h
dom/bindings/TypedArray.h
dom/canvas/CanvasRenderingContext2D.cpp
dom/fmradio/FMRadio.cpp
dom/network/TCPSocketChild.cpp
dom/network/TCPSocketParent.cpp
dom/workers/FileReaderSync.cpp
ipc/ril/Ril.cpp
js/src/ctypes/CTypes.cpp
js/src/devtools/rootAnalysis/annotations.js
js/src/jsapi-tests/testArrayBuffer.cpp
js/src/jsapi-tests/testMappedArrayBuffer.cpp
js/src/jsapi-tests/testTypedArrays.cpp
js/src/jsfriendapi.h
js/src/shell/js.cpp
js/src/vm/ArrayBufferObject.cpp
js/src/vm/TypedArrayObject.cpp
js/xpconnect/src/XPCConvert.cpp
netwerk/base/src/ArrayBufferInputStream.cpp
netwerk/base/src/ArrayBufferInputStream.h
xpcom/io/nsBinaryStream.cpp
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5978,17 +5978,18 @@ nsContentUtils::CreateArrayBuffer(JSCont
   int32_t dataLen = aData.Length();
   *aResult = JS_NewArrayBuffer(aCx, dataLen);
   if (!*aResult) {
     return NS_ERROR_FAILURE;
   }
 
   if (dataLen > 0) {
     NS_ASSERTION(JS_IsArrayBufferObject(*aResult), "What happened?");
-    memcpy(JS_GetArrayBufferData(*aResult), aData.BeginReading(), dataLen);
+    JS::AutoCheckCannotGC nogc;
+    memcpy(JS_GetArrayBufferData(*aResult, nogc), aData.BeginReading(), dataLen);
   }
 
   return NS_OK;
 }
 
 // Initial implementation: only stores to RAM, not file
 // TODO: bug 704447: large file support
 nsresult
--- a/content/base/src/nsDOMFileReader.cpp
+++ b/content/base/src/nsDOMFileReader.cpp
@@ -289,18 +289,32 @@ nsDOMFileReader::DoOnLoadEnd(nsresult aS
   // Clear out the data if necessary
   if (NS_FAILED(aStatus)) {
     FreeFileData();
     return NS_OK;
   }
 
   nsresult rv = NS_OK;
   switch (mDataFormat) {
-    case FILE_AS_ARRAYBUFFER:
-      break; //Already accumulated mResultArrayBuffer
+    case FILE_AS_ARRAYBUFFER: {
+      AutoJSAPI jsapi;
+      if (NS_WARN_IF(!jsapi.Init(mozilla::DOMEventTargetHelper::GetParentObject()))) {
+        return NS_ERROR_FAILURE;
+      }
+
+      RootResultArrayBuffer();
+      mResultArrayBuffer = JS_NewArrayBufferWithContents(jsapi.cx(), mTotal, mFileData);
+      if (!mResultArrayBuffer) {
+        JS_ClearPendingException(jsapi.cx());
+        rv = NS_ERROR_OUT_OF_MEMORY;
+      } else {
+        mFileData = nullptr; // Transfer ownership
+      }
+      break;
+    }
     case FILE_AS_BINARY:
       break; //Already accumulated mResult
     case FILE_AS_TEXT:
       if (!mFileData) {
         if (mDataLen) {
           rv = NS_ERROR_OUT_OF_MEMORY;
           break;
         }
@@ -337,45 +351,40 @@ nsDOMFileReader::DoReadData(nsIAsyncInpu
     mResult.GetMutableData(&buf, oldLen + aCount, fallible_t());
     NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
 
     uint32_t bytesRead = 0;
     aStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
                           &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
-  else if (mDataFormat == FILE_AS_ARRAYBUFFER) {
-    uint32_t bytesRead = 0;
-    aStream->Read((char*) JS_GetArrayBufferData(mResultArrayBuffer) + mDataLen,
-                  aCount, &bytesRead);
-    NS_ASSERTION(bytesRead == aCount, "failed to read data");
-  }
   else {
     //Update memory buffer to reflect the contents of the file
     if (mDataLen + aCount > UINT32_MAX) {
       // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    mFileData = (char *) moz_realloc(mFileData, mDataLen + aCount);
-    NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
+    if (mDataFormat != FILE_AS_ARRAYBUFFER) {
+      mFileData = (char *) moz_realloc(mFileData, mDataLen + aCount);
+      NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
+    }
 
     uint32_t bytesRead = 0;
     aStream->Read(mFileData + mDataLen, aCount, &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
 
   mDataLen += aCount;
   return NS_OK;
 }
 
 // Helper methods
 
 void
-nsDOMFileReader::ReadFileContent(JSContext* aCx,
-                                 File& aFile,
+nsDOMFileReader::ReadFileContent(File& aFile,
                                  const nsAString &aCharset,
                                  eDataFormat aDataFormat,
                                  ErrorResult& aRv)
 {
   //Implicit abort to clear any other activity going on
   Abort();
   mError = nullptr;
   SetDOMStringToNull(mResult);
@@ -438,21 +447,20 @@ nsDOMFileReader::ReadFileContent(JSConte
     return;
   }
 
   //FileReader should be in loading state here
   mReadyState = nsIDOMFileReader::LOADING;
   DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
 
   if (mDataFormat == FILE_AS_ARRAYBUFFER) {
-    RootResultArrayBuffer();
-    mResultArrayBuffer = JS_NewArrayBuffer(aCx, mTotal);
-    if (!mResultArrayBuffer) {
-      NS_WARNING("Failed to create JS array buffer");
-      aRv.Throw(NS_ERROR_FAILURE);
+    mFileData = js_pod_malloc<char>(mTotal);
+    if (!mFileData) {
+      NS_WARNING("Preallocation failed for ReadFileData");
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     }
   }
 }
 
 nsresult
 nsDOMFileReader::GetAsText(nsIDOMBlob *aFile,
                            const nsACString &aCharset,
                            const char *aFileData,
--- a/content/base/src/nsDOMFileReader.h
+++ b/content/base/src/nsDOMFileReader.h
@@ -65,27 +65,27 @@ public:
   }
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   // WebIDL
   static already_AddRefed<nsDOMFileReader>
   Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
   void ReadAsArrayBuffer(JSContext* aCx, File& aBlob, ErrorResult& aRv)
   {
-    ReadFileContent(aCx, aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
+    ReadFileContent(aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
   }
 
   void ReadAsText(File& aBlob, const nsAString& aLabel, ErrorResult& aRv)
   {
-    ReadFileContent(nullptr, aBlob, aLabel, FILE_AS_TEXT, aRv);
+    ReadFileContent(aBlob, aLabel, FILE_AS_TEXT, aRv);
   }
 
   void ReadAsDataURL(File& aBlob, ErrorResult& aRv)
   {
-    ReadFileContent(nullptr, aBlob, EmptyString(), FILE_AS_DATAURL, aRv);
+    ReadFileContent(aBlob, EmptyString(), FILE_AS_DATAURL, aRv);
   }
 
   using FileIOObject::Abort;
 
   // Inherited ReadyState().
 
   void GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
                  ErrorResult& aRv);
@@ -99,17 +99,17 @@ public:
   using FileIOObject::GetOnabort;
   using FileIOObject::SetOnabort;
   using FileIOObject::GetOnerror;
   using FileIOObject::SetOnerror;
   IMPL_EVENT_HANDLER(loadend)
 
   void ReadAsBinaryString(File& aBlob, ErrorResult& aRv)
   {
-    ReadFileContent(nullptr, aBlob, EmptyString(), FILE_AS_BINARY, aRv);
+    ReadFileContent(aBlob, EmptyString(), FILE_AS_BINARY, aRv);
   }
 
 
   nsresult Init();
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsDOMFileReader,
                                                          FileIOObject)
   void RootResultArrayBuffer();
@@ -119,17 +119,17 @@ protected:
 
   enum eDataFormat {
     FILE_AS_ARRAYBUFFER,
     FILE_AS_BINARY,
     FILE_AS_TEXT,
     FILE_AS_DATAURL
   };
 
-  void ReadFileContent(JSContext* aCx, File& aBlob,
+  void ReadFileContent(File& aBlob,
                        const nsAString &aCharset, eDataFormat aDataFormat,
                        ErrorResult& aRv);
   nsresult GetAsText(nsIDOMBlob *aFile, const nsACString &aCharset,
                      const char *aFileData, uint32_t aDataLen, nsAString &aResult);
   nsresult GetAsDataURL(nsIDOMBlob *aFile, const char *aFileData, uint32_t aDataLen, nsAString &aResult);
 
   void FreeFileData() {
     moz_free(mFileData);
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -110,17 +110,18 @@ AudioBuffer::RestoreJSChannelData(JSCont
       // The following code first zeroes the array and then copies our data
       // into it. We could avoid this with additional JS APIs to construct
       // an array (or ArrayBuffer) containing initial data.
       JS::Rooted<JSObject*> array(aJSContext,
                                   JS_NewFloat32Array(aJSContext, mLength));
       if (!array) {
         return false;
       }
-      memcpy(JS_GetFloat32ArrayData(array), data, sizeof(float)*mLength);
+      JS::AutoCheckCannotGC nogc;
+      mozilla::PodCopy(JS_GetFloat32ArrayData(array, nogc), data, mLength);
       mJSChannels[i] = array;
     }
 
     mSharedChannels = nullptr;
   }
 
   return true;
 }
@@ -141,19 +142,20 @@ AudioBuffer::CopyFromChannel(const Float
   }
 
   if (!mSharedChannels && JS_GetTypedArrayLength(mJSChannels[aChannelNumber]) != mLength) {
     // The array was probably neutered
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
+  JS::AutoCheckCannotGC nogc;
   const float* sourceData = mSharedChannels ?
     mSharedChannels->GetData(aChannelNumber) :
-    JS_GetFloat32ArrayData(mJSChannels[aChannelNumber]);
+    JS_GetFloat32ArrayData(mJSChannels[aChannelNumber], nogc);
   PodMove(aDestination.Data(), sourceData + aStartInChannel, length);
 }
 
 void
 AudioBuffer::CopyToChannel(JSContext* aJSContext, const Float32Array& aSource,
                            uint32_t aChannelNumber, uint32_t aStartInChannel,
                            ErrorResult& aRv)
 {
@@ -174,26 +176,28 @@ AudioBuffer::CopyToChannel(JSContext* aJ
     return;
   }
 
   if (!RestoreJSChannelData(aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
-  PodMove(JS_GetFloat32ArrayData(mJSChannels[aChannelNumber]) + aStartInChannel,
+  JS::AutoCheckCannotGC nogc;
+  PodMove(JS_GetFloat32ArrayData(mJSChannels[aChannelNumber], nogc) + aStartInChannel,
           aSource.Data(), length);
 }
 
 void
 AudioBuffer::SetRawChannelContents(uint32_t aChannel, float* aContents)
 {
   MOZ_ASSERT(!GetWrapperPreserveColor() && !mSharedChannels,
              "The AudioBuffer object should not have been handed to JS or have C++ callers neuter its typed array");
-  PodCopy(JS_GetFloat32ArrayData(mJSChannels[aChannel]), aContents, mLength);
+  JS::AutoCheckCannotGC nogc;
+  PodCopy(JS_GetFloat32ArrayData(mJSChannels[aChannel], nogc), aContents, mLength);
 }
 
 void
 AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
                             JS::MutableHandle<JSObject*> aRetval,
                             ErrorResult& aRv)
 {
   if (aChannel >= NumberOfChannels()) {
--- a/content/media/webspeech/synth/nsSpeechTask.cpp
+++ b/content/media/webspeech/synth/nsSpeechTask.cpp
@@ -157,16 +157,30 @@ nsSpeechTask::Setup(nsISpeechTaskCallbac
   AudioSegment* segment = new AudioSegment();
   mStream->AddTrack(1, aRate, 0, segment);
   mStream->AddAudioOutput(this);
   mStream->SetAudioOutputVolume(this, mVolume);
 
   return NS_OK;
 }
 
+static nsRefPtr<mozilla::SharedBuffer>
+makeSamples(int16_t* aData, uint32_t aDataLen)
+{
+  nsRefPtr<mozilla::SharedBuffer> samples =
+    SharedBuffer::Create(aDataLen * sizeof(int16_t));
+  int16_t* frames = static_cast<int16_t*>(samples->Data());
+
+  for (uint32_t i = 0; i < aDataLen; i++) {
+    frames[i] = aData[i];
+  }
+
+  return samples;
+}
+
 NS_IMETHODIMP
 nsSpeechTask::SendAudio(JS::Handle<JS::Value> aData, JS::Handle<JS::Value> aLandmarks,
                         JSContext* aCx)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_FALSE(mStream->IsDestroyed(), NS_ERROR_NOT_AVAILABLE);
@@ -189,18 +203,23 @@ nsSpeechTask::SendAudio(JS::Handle<JS::V
   } else if (JS_IsArrayObject(aCx, darray)) {
     tsrc = JS_NewInt16ArrayFromArray(aCx, darray);
   }
 
   if (!tsrc) {
     return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
   }
 
-  SendAudioImpl(JS_GetInt16ArrayData(tsrc),
-                JS_GetTypedArrayLength(tsrc));
+  uint32_t dataLen = JS_GetTypedArrayLength(tsrc);
+  nsRefPtr<mozilla::SharedBuffer> samples;
+  {
+    JS::AutoCheckCannotGC nogc;
+    samples = makeSamples(JS_GetInt16ArrayData(tsrc, nogc), dataLen);
+  }
+  SendAudioImpl(samples, dataLen);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::SendAudioNative(int16_t* aData, uint32_t aDataLen)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
@@ -209,41 +228,34 @@ nsSpeechTask::SendAudioNative(int16_t* a
   NS_ENSURE_FALSE(mStream->IsDestroyed(), NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mChannels, NS_ERROR_FAILURE);
 
   if (mIndirectAudio) {
     NS_WARNING("Can't call SendAudio from an indirect audio speech service.");
     return NS_ERROR_FAILURE;
   }
 
-  SendAudioImpl(aData, aDataLen);
+  nsRefPtr<mozilla::SharedBuffer> samples = makeSamples(aData, aDataLen);
+  SendAudioImpl(samples, aDataLen);
 
   return NS_OK;
 }
 
 void
-nsSpeechTask::SendAudioImpl(int16_t* aData, uint32_t aDataLen)
+nsSpeechTask::SendAudioImpl(nsRefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen)
 {
   if (aDataLen == 0) {
     mStream->EndAllTrackAndFinish();
     return;
   }
 
-  nsRefPtr<mozilla::SharedBuffer> samples =
-    SharedBuffer::Create(aDataLen * sizeof(int16_t));
-  int16_t* frames = static_cast<int16_t*>(samples->Data());
-
-  for (uint32_t i = 0; i < aDataLen; i++) {
-    frames[i] = aData[i];
-  }
-
   AudioSegment segment;
   nsAutoTArray<const int16_t*, 1> channelData;
-  channelData.AppendElement(frames);
-  segment.AppendFrames(samples.forget(), channelData, aDataLen);
+  channelData.AppendElement(static_cast<int16_t*>(aSamples->Data()));
+  segment.AppendFrames(aSamples.forget(), channelData, aDataLen);
   mStream->AppendToTrack(1, &segment);
   mStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchStart()
 {
   if (!mIndirectAudio) {
--- a/content/media/webspeech/synth/nsSpeechTask.h
+++ b/content/media/webspeech/synth/nsSpeechTask.h
@@ -69,17 +69,17 @@ protected:
 
   float mVolume;
 
   nsString mText;
 
 private:
   void End();
 
-  void SendAudioImpl(int16_t* aData, uint32_t aDataLen);
+  void SendAudioImpl(nsRefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen);
 
   nsRefPtr<SourceMediaStream> mStream;
 
   nsCOMPtr<nsISpeechTaskCallback> mCallback;
 
   uint32_t mChannels;
 
   nsRefPtr<SpeechSynthesis> mSpeechSynthesis;
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -134,17 +134,17 @@ public:
 
 private:
   TypedArray_base(const TypedArray_base&) MOZ_DELETE;
 };
 
 
 template<typename T,
          JSObject* UnwrapArray(JSObject*),
-         T* GetData(JSObject*),
+         T* GetData(JSObject*, const JS::AutoCheckCannotGC&),
          void GetLengthAndData(JSObject*, uint32_t*, T**),
          JSObject* CreateNew(JSContext*, uint32_t)>
 struct TypedArray : public TypedArray_base<T, UnwrapArray, GetLengthAndData> {
 private:
   typedef TypedArray_base<T, UnwrapArray, GetLengthAndData> Base;
 
 public:
   TypedArray()
@@ -176,17 +176,18 @@ public:
 private:
   static inline JSObject*
   CreateCommon(JSContext* cx, uint32_t length, const T* data) {
     JSObject* obj = CreateNew(cx, length);
     if (!obj) {
       return nullptr;
     }
     if (data) {
-      T* buf = static_cast<T*>(GetData(obj));
+      JS::AutoCheckCannotGC nogc;
+      T* buf = static_cast<T*>(GetData(obj, nogc));
       memcpy(buf, data, length*sizeof(T));
     }
     return obj;
   }
 
   TypedArray(const TypedArray&) MOZ_DELETE;
 };
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4510,28 +4510,34 @@ CanvasRenderingContext2D::GetImageDataAr
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (mZero) {
     *aRetval = darray;
     return NS_OK;
   }
 
-  uint8_t* data = JS_GetUint8ClampedArrayData(darray);
-
   IntRect dstWriteRect = srcReadRect;
   dstWriteRect.MoveBy(-aX, -aY);
 
-  uint8_t* src = data;
-  uint32_t srcStride = aWidth * 4;
+  uint8_t* src;
+  uint32_t srcStride;
+
   if (readback) {
     srcStride = readback->Stride();
     src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4;
   }
 
+  JS::AutoCheckCannotGC nogc;
+  uint8_t* data = JS_GetUint8ClampedArrayData(darray, nogc);
+  if (!readback) {
+    src = data;
+    srcStride = aWidth * 4;
+  }
+
   // NOTE! dst is the same as src, and this relies on reading
   // from src and advancing that ptr before writing to dst.
   // NOTE! I'm not sure that it is, I think this comment might have been
   // inherited from Thebes canvas and is no longer true
   uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
 
   if (mOpaque) {
     for (int32_t j = 0; j < dstWriteRect.height; ++j) {
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -338,17 +338,18 @@ void
 FMRadio::GetRdsgroup(JSContext* cx, JS::MutableHandle<JSObject*> retval)
 {
   uint64_t group;
   if (!IFMRadioService::Singleton()->GetRdsgroup(group)) {
     return;
   }
 
   JSObject *rdsgroup = Uint16Array::Create(cx, this, 4);
-  uint16_t *data = JS_GetUint16ArrayData(rdsgroup);
+  JS::AutoCheckCannotGC nogc;
+  uint16_t *data = JS_GetUint16ArrayData(rdsgroup, nogc);
   data[3] = group & 0xFFFF;
   group >>= 16;
   data[2] = group & 0xFFFF;
   group >>= 16;
   data[1] = group & 0xFFFF;
   group >>= 16;
   data[0] = group & 0xFFFF;
 
--- a/dom/network/TCPSocketChild.cpp
+++ b/dom/network/TCPSocketChild.cpp
@@ -1,15 +1,16 @@
 /* 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 <algorithm>
 #include "TCPSocketChild.h"
 #include "mozilla/unused.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/TabChild.h"
 #include "nsIDOMTCPSocket.h"
 #include "nsJSUtils.h"
 #include "nsContentUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
@@ -22,24 +23,27 @@ namespace IPC {
 bool
 DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
                        const InfallibleTArray<uint8_t>& aBuffer,
                        JS::MutableHandle<JS::Value> aVal)
 {
   mozilla::AutoSafeJSContext cx;
   JSAutoCompartment ac(cx, aObj);
 
-  JS::Rooted<JSObject*> obj(cx, JS_NewArrayBuffer(cx, aBuffer.Length()));
+  mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(js_pod_malloc<uint8_t>(aBuffer.Length()));
+  if (!data)
+      return false;
+  memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());
+
+  JSObject* obj = JS_NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
   if (!obj)
-    return false;
-  uint8_t* data = JS_GetArrayBufferData(obj);
-  if (!data)
-    return false;
-  memcpy(data, aBuffer.Elements(), aBuffer.Length());
-  aVal.set(OBJECT_TO_JSVAL(obj));
+      return false;
+  data.release();
+
+  aVal.setObject(*obj);
   return true;
 }
 
 } // namespace IPC
 
 namespace mozilla {
 namespace dom {
 
@@ -219,23 +223,26 @@ TCPSocketChild::SendSend(JS::Handle<JS::
     SendData(str, aTrackingNumber);
   } else {
     NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
     JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
     NS_ENSURE_TRUE(JS_IsArrayBufferObject(obj), NS_ERROR_FAILURE);
     uint32_t buflen = JS_GetArrayBufferByteLength(obj);
     aByteOffset = std::min(buflen, aByteOffset);
     uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
-    uint8_t* data = JS_GetArrayBufferData(obj);
-    if (!data) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
     FallibleTArray<uint8_t> fallibleArr;
-    if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes)) {
-      return NS_ERROR_OUT_OF_MEMORY;
+    {
+        JS::AutoCheckCannotGC nogc;
+        uint8_t* data = JS_GetArrayBufferData(obj, nogc);
+        if (!data) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+        if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes)) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
     }
     InfallibleTArray<uint8_t> arr;
     arr.SwapElements(fallibleArr);
     SendData(arr, aTrackingNumber);
   }
   return NS_OK;
 }
 
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -280,27 +280,37 @@ TCPSocketParent::SendEvent(const nsAStri
     data = SendableData(str);
 
   } else if (aDataVal.isUndefined() || aDataVal.isNull()) {
     data = mozilla::void_t();
 
   } else if (aDataVal.isObject()) {
     JS::Rooted<JSObject *> obj(aCx, &aDataVal.toObject());
     if (JS_IsArrayBufferObject(obj)) {
-      uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
-      uint8_t* buffer = JS_GetArrayBufferData(obj);
-      if (!buffer) {
-        FireInteralError(this, __LINE__);
-        return NS_ERROR_OUT_OF_MEMORY;
+      FallibleTArray<uint8_t> fallibleArr;
+      uint32_t errLine = 0;
+      do {
+          JS::AutoCheckCannotGC nogc;
+          uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
+          uint8_t* buffer = JS_GetArrayBufferData(obj, nogc);
+          if (!buffer) {
+              errLine = __LINE__;
+              break;
+          }
+          if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
+              errLine = __LINE__;
+              break;
+          }
+      } while (false);
+
+      if (errLine) {
+          FireInteralError(this, errLine);
+          return NS_ERROR_OUT_OF_MEMORY;
       }
-      FallibleTArray<uint8_t> fallibleArr;
-      if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
-        FireInteralError(this, __LINE__);
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
+
       InfallibleTArray<uint8_t> arr;
       arr.SwapElements(fallibleArr);
       data = SendableData(arr);
 
     } else {
       nsAutoJSString name;
 
       JS::Rooted<JS::Value> val(aCx);
--- a/dom/workers/FileReaderSync.cpp
+++ b/dom/workers/FileReaderSync.cpp
@@ -56,47 +56,45 @@ FileReaderSync::ReadAsArrayBuffer(JSCont
 {
   uint64_t blobSize;
   nsresult rv = aBlob.GetSize(&blobSize);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
-  JS::Rooted<JSObject*> jsArrayBuffer(aCx, JS_NewArrayBuffer(aCx, blobSize));
-  if (!jsArrayBuffer) {
-    // XXXkhuey we need a way to indicate to the bindings that the call failed
-    // but there's already a pending exception that we should not clobber.
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return;
-  }
-
-  uint32_t bufferLength = JS_GetArrayBufferByteLength(jsArrayBuffer);
-  uint8_t* arrayBuffer = JS_GetStableArrayBufferData(aCx, jsArrayBuffer);
-  if (!arrayBuffer) {
+  UniquePtr<char[], JS::FreePolicy> bufferData(js_pod_malloc<char>(blobSize));
+  if (!bufferData) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   nsCOMPtr<nsIInputStream> stream;
   rv = aBlob.GetInternalStream(getter_AddRefs(stream));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   uint32_t numRead;
-  rv = stream->Read((char*)arrayBuffer, bufferLength, &numRead);
+  rv = stream->Read(bufferData.get(), blobSize, &numRead);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
-  NS_ASSERTION(numRead == bufferLength, "failed to read data");
+  NS_ASSERTION(numRead == blobSize, "failed to read data");
 
-  aRetval.set(jsArrayBuffer);
+  JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aCx, blobSize, bufferData.get());
+  if (!arrayBuffer) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  bufferData.release();
+
+  aRetval.set(arrayBuffer);
 }
 
 void
 FileReaderSync::ReadAsBinaryString(File& aBlob,
                                    nsAString& aResult,
                                    ErrorResult& aRv)
 {
   nsCOMPtr<nsIInputStream> stream;
--- a/ipc/ril/Ril.cpp
+++ b/ipc/ril/Ril.cpp
@@ -87,51 +87,55 @@ PostToRIL(JSContext *aCx,
     if (args.length() != 2) {
         JS_ReportError(aCx, "Expecting two arguments with the RIL message");
         return false;
     }
 
     int clientId = args[0].toInt32();
     JS::Value v = args[1];
 
-    JSAutoByteString abs;
-    void *data;
-    size_t size;
+    UnixSocketRawData* raw = nullptr;
+
     if (v.isString()) {
+        JSAutoByteString abs;
         JS::Rooted<JSString*> str(aCx, v.toString());
         if (!abs.encodeUtf8(aCx, str)) {
             return false;
         }
 
-        data = abs.ptr();
-        size = abs.length();
+        raw = new UnixSocketRawData(abs.ptr(), abs.length());
     } else if (!v.isPrimitive()) {
         JSObject *obj = v.toObjectOrNull();
         if (!JS_IsTypedArrayObject(obj)) {
             JS_ReportError(aCx, "Object passed in wasn't a typed array");
             return false;
         }
 
         uint32_t type = JS_GetArrayBufferViewType(obj);
         if (type != js::Scalar::Int8 &&
             type != js::Scalar::Uint8 &&
             type != js::Scalar::Uint8Clamped) {
             JS_ReportError(aCx, "Typed array data is not octets");
             return false;
         }
 
-        size = JS_GetTypedArrayByteLength(obj);
-        data = JS_GetArrayBufferViewData(obj);
+        JS::AutoCheckCannotGC nogc;
+        size_t size = JS_GetTypedArrayByteLength(obj);
+        void *data = JS_GetArrayBufferViewData(obj, nogc);
+        raw = new UnixSocketRawData(data, size);
     } else {
         JS_ReportError(aCx,
                        "Incorrect argument. Expecting a string or a typed array");
         return false;
     }
 
-    UnixSocketRawData* raw = new UnixSocketRawData(data, size);
+    if (!raw) {
+        JS_ReportError(aCx, "Unable to post to RIL");
+        return false;
+    }
 
     nsRefPtr<SendRilSocketDataTask> task =
         new SendRilSocketDataTask(clientId, raw);
     NS_DispatchToMainThread(task);
     return true;
 }
 
 bool
@@ -184,18 +188,21 @@ DispatchRILEvent::RunTask(JSContext *aCx
 {
     JS::Rooted<JSObject*> obj(aCx, JS::CurrentGlobalOrNull(aCx));
 
     JS::Rooted<JSObject*> array(aCx,
                                 JS_NewUint8Array(aCx, mMessage->GetSize()));
     if (!array) {
         return false;
     }
-    memcpy(JS_GetArrayBufferViewData(array),
-           mMessage->GetData(), mMessage->GetSize());
+    {
+        JS::AutoCheckCannotGC nogc;
+        memcpy(JS_GetArrayBufferViewData(array, nogc),
+               mMessage->GetData(), mMessage->GetSize());
+    }
 
     JS::AutoValueArray<2> args(aCx);
     args[0].setNumber((uint32_t)mClientId);
     args[1].setObject(*array);
 
     JS::Rooted<JS::Value> rval(aCx);
     return JS_CallFunctionName(aCx, obj, "onRILMessage", args, &rval);
 }
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -2456,33 +2456,44 @@ ImplicitConvert(JSContext* cx,
         (*char16Buffer)[sourceLength] = 0;
         break;
       }
       default:
         return TypeError(cx, "string pointer", val);
       }
       break;
     } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) {
-      // Convert ArrayBuffer to pointer without any copy.
-      // Just as with C arrays, we make no effort to
-      // keep the ArrayBuffer alive.
-      void* p = JS_GetStableArrayBufferData(cx, valObj);
-      if (!p)
-          return false;
-      *static_cast<void**>(buffer) = p;
+      // Convert ArrayBuffer to pointer without any copy. Just as with C
+      // arrays, we make no effort to keep the ArrayBuffer alive. This
+      // functionality will be removed for all but arguments in bug 1080262.
+      void* ptr;
+      {
+          JS::AutoCheckCannotGC nogc;
+          ptr = JS_GetArrayBufferData(valObj, nogc);
+      }
+      if (!ptr) {
+        return TypeError(cx, "arraybuffer pointer", val);
+      }
+      *static_cast<void**>(buffer) = ptr;
       break;
     } if (val.isObject() && JS_IsTypedArrayObject(valObj)) {
+      // Same as ArrayBuffer, above, though note that this will take the offset
+      // of the view into account.
       if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
         return TypeError(cx, "typed array with the appropriate type", val);
       }
-
-      // Convert TypedArray to pointer without any copy.
-      // Just as with C arrays, we make no effort to
-      // keep the TypedArray alive.
-      *static_cast<void**>(buffer) = JS_GetArrayBufferViewData(valObj);
+      void* ptr;
+      {
+          JS::AutoCheckCannotGC nogc;
+          ptr = JS_GetArrayBufferViewData(valObj, nogc);
+      }
+      if (!ptr) {
+        return TypeError(cx, "typed array pointer", val);
+      }
+      *static_cast<void**>(buffer) = ptr;
       break;
     }
     return TypeError(cx, "pointer", val);
   }
   case TYPE_array: {
     RootedObject baseType(cx, ArrayType::GetBaseType(targetType));
     size_t targetLength = ArrayType::GetLength(targetType);
 
@@ -2578,33 +2589,35 @@ ImplicitConvert(JSContext* cx,
       // copy the array.
       uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
         JS_ReportError(cx, "ArrayType length does not match source ArrayBuffer length");
         return false;
       }
-      memcpy(buffer, JS_GetArrayBufferData(valObj), sourceLength);
+      JS::AutoCheckCannotGC nogc;
+      memcpy(buffer, JS_GetArrayBufferData(valObj, nogc), sourceLength);
       break;
     } else if (val.isObject() && JS_IsTypedArrayObject(valObj)) {
       // Check that array is consistent with type, then
       // copy the array.
       if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
         return TypeError(cx, "typed array with the appropriate type", val);
       }
 
       uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
         JS_ReportError(cx, "typed array length does not match source TypedArray length");
         return false;
       }
-      memcpy(buffer, JS_GetArrayBufferViewData(valObj), sourceLength);
+      JS::AutoCheckCannotGC nogc;
+      memcpy(buffer, JS_GetArrayBufferViewData(valObj, nogc), sourceLength);
       break;
     } else {
       // Don't implicitly convert to string. Users can implicitly convert
       // with `String(x)` or `""+x`.
       return TypeError(cx, "array", val);
     }
     break;
   }
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -72,16 +72,17 @@ var ignoreCallees = {
     "js::Class.trace" : true,
     "js::Class.finalize" : true,
     "JSRuntime.destroyPrincipals" : true,
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
+    "GrGLInterface.fCallback" : true,
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
         return true;
     if (fullfield in ignoreCallees)
         return true;
--- a/js/src/jsapi-tests/testArrayBuffer.cpp
+++ b/js/src/jsapi-tests/testArrayBuffer.cpp
@@ -39,26 +39,28 @@ BEGIN_TEST(testArrayBuffer_bug720949_ste
         CHECK(JS_IsArrayBufferObject(obj));
         CHECK_EQUAL(JS_GetArrayBufferByteLength(obj), size);
         JS_GetProperty(cx, obj, "byteLength", &v);
         CHECK_SAME(v, INT_TO_JSVAL(size));
         JS_GetProperty(cx, view, "byteLength", &v);
         CHECK_SAME(v, INT_TO_JSVAL(size));
 
         // Modifying the underlying data should update the value returned through the view
-        uint8_t *data = JS_GetStableArrayBufferData(cx, obj);
-        CHECK(data != nullptr);
-        *reinterpret_cast<uint32_t*>(data) = MAGIC_VALUE_2;
+        {
+            JS::AutoCheckCannotGC nogc;
+            uint8_t *data = JS_GetArrayBufferData(obj, nogc);
+            CHECK(data != nullptr);
+            *reinterpret_cast<uint32_t*>(data) = MAGIC_VALUE_2;
+        }
         CHECK(JS_GetElement(cx, view, 0, &v));
         CHECK_SAME(v, INT_TO_JSVAL(MAGIC_VALUE_2));
 
         // Steal the contents
         void *contents = JS_StealArrayBufferContents(cx, obj);
         CHECK(contents != nullptr);
-        CHECK(data != nullptr);
 
         // Check that the original ArrayBuffer is neutered
         CHECK_EQUAL(JS_GetArrayBufferByteLength(obj), 0u);
         CHECK(JS_GetProperty(cx, obj, "byteLength", &v));
         CHECK_SAME(v, INT_TO_JSVAL(0));
         CHECK(JS_GetProperty(cx, view, "byteLength", &v));
         CHECK_SAME(v, INT_TO_JSVAL(0));
         CHECK(JS_GetProperty(cx, view, "byteOffset", &v));
@@ -68,25 +70,31 @@ BEGIN_TEST(testArrayBuffer_bug720949_ste
         CHECK_EQUAL(JS_GetArrayBufferByteLength(obj), 0u);
         v = JSVAL_VOID;
         JS_GetElement(cx, obj, 0, &v);
         CHECK_SAME(v, JSVAL_VOID);
 
         // Transfer to a new ArrayBuffer
         JS::RootedObject dst(cx, JS_NewArrayBufferWithContents(cx, size, contents));
         CHECK(JS_IsArrayBufferObject(dst));
-        data = JS_GetStableArrayBufferData(cx, obj);
+        {
+            JS::AutoCheckCannotGC nogc;
+            (void) JS_GetArrayBufferData(obj, nogc);
+        }
 
         JS::RootedObject dstview(cx, JS_NewInt32ArrayWithBuffer(cx, dst, 0, -1));
         CHECK(dstview != nullptr);
 
         CHECK_EQUAL(JS_GetArrayBufferByteLength(dst), size);
-        data = JS_GetStableArrayBufferData(cx, dst);
-        CHECK(data != nullptr);
-        CHECK_EQUAL(*reinterpret_cast<uint32_t*>(data), MAGIC_VALUE_2);
+        {
+            JS::AutoCheckCannotGC nogc;
+            uint8_t *data = JS_GetArrayBufferData(dst, nogc);
+            CHECK(data != nullptr);
+            CHECK_EQUAL(*reinterpret_cast<uint32_t*>(data), MAGIC_VALUE_2);
+        }
         CHECK(JS_GetElement(cx, dstview, 0, &v));
         CHECK_SAME(v, INT_TO_JSVAL(MAGIC_VALUE_2));
     }
 
     return true;
 }
 END_TEST(testArrayBuffer_bug720949_steal)
 
--- a/js/src/jsapi-tests/testMappedArrayBuffer.cpp
+++ b/js/src/jsapi-tests/testMappedArrayBuffer.cpp
@@ -69,24 +69,26 @@ JSObject *CreateNewObject(const int offs
         JS_ReleaseMappedArrayBufferContents(ptr, length);
         return nullptr;
     }
     return obj;
 }
 
 bool VerifyObject(JS::HandleObject obj, uint32_t offset, uint32_t length, const bool mapped)
 {
+    JS::AutoCheckCannotGC nogc;
+
     CHECK(obj);
     CHECK(JS_IsArrayBufferObject(obj));
     CHECK_EQUAL(JS_GetArrayBufferByteLength(obj), length);
     if (mapped)
         CHECK(JS_IsMappedArrayBufferObject(obj));
     else
         CHECK(!JS_IsMappedArrayBufferObject(obj));
-    const char *data = reinterpret_cast<const char *>(JS_GetArrayBufferData(obj));
+    const char *data = reinterpret_cast<const char *>(JS_GetArrayBufferData(obj, nogc));
     CHECK(data);
     CHECK(memcmp(data, test_data + offset, length) == 0);
 
     return true;
 }
 
 bool TestCreateObject(uint32_t offset, uint32_t length)
 {
--- a/js/src/jsapi-tests/testTypedArrays.cpp
+++ b/js/src/jsapi-tests/testTypedArrays.cpp
@@ -31,18 +31,21 @@ BEGIN_TEST(testTypedArrays)
     CHECK(JS_IsArrayBufferObject(buffer));
 
     RootedObject proto(cx);
     JS_GetPrototype(cx, buffer, &proto);
     CHECK(!JS_IsArrayBufferObject(proto));
     RootedObject dummy(cx, JS_GetParent(proto));
     CHECK(!JS_IsArrayBufferObject(dummy));
 
-    CHECK_EQUAL(JS_GetArrayBufferByteLength(buffer), nbytes);
-    memset(JS_GetStableArrayBufferData(cx, buffer), 1, nbytes);
+    {
+        JS::AutoCheckCannotGC nogc;
+        CHECK_EQUAL(JS_GetArrayBufferByteLength(buffer), nbytes);
+        memset(JS_GetArrayBufferData(buffer, nogc), 1, nbytes);
+    }
 
     ok = ok &&
         TestArrayFromBuffer<JS_NewInt8ArrayWithBuffer, JS_NewInt8ArrayFromArray, int8_t, JS_GetInt8ArrayData>(cx) &&
         TestArrayFromBuffer<JS_NewUint8ArrayWithBuffer, JS_NewUint8ArrayFromArray, uint8_t, JS_GetUint8ArrayData>(cx) &&
         TestArrayFromBuffer<JS_NewUint8ClampedArrayWithBuffer, JS_NewUint8ClampedArrayFromArray, uint8_t, JS_GetUint8ClampedArrayData>(cx) &&
         TestArrayFromBuffer<JS_NewInt16ArrayWithBuffer, JS_NewInt16ArrayFromArray, int16_t, JS_GetInt16ArrayData>(cx) &&
         TestArrayFromBuffer<JS_NewUint16ArrayWithBuffer, JS_NewUint16ArrayFromArray, uint16_t, JS_GetUint16ArrayData>(cx) &&
         TestArrayFromBuffer<JS_NewInt32ArrayWithBuffer, JS_NewInt32ArrayFromArray, int32_t, JS_GetInt32ArrayData>(cx) &&
@@ -50,17 +53,17 @@ BEGIN_TEST(testTypedArrays)
         TestArrayFromBuffer<JS_NewFloat32ArrayWithBuffer, JS_NewFloat32ArrayFromArray, float, JS_GetFloat32ArrayData>(cx) &&
         TestArrayFromBuffer<JS_NewFloat64ArrayWithBuffer, JS_NewFloat64ArrayFromArray, double, JS_GetFloat64ArrayData>(cx);
 
     return ok;
 }
 
 template<JSObject *Create(JSContext *, uint32_t),
          typename Element,
-         Element *GetData(JSObject *)>
+         Element *GetData(JSObject *, const JS::AutoCheckCannotGC&)>
 bool
 TestPlainTypedArray(JSContext *cx)
 {
     {
         RootedObject notArray(cx, Create(cx, UINT32_MAX));
         CHECK(!notArray);
     }
 
@@ -71,58 +74,64 @@ TestPlainTypedArray(JSContext *cx)
     CHECK(!JS_IsTypedArrayObject(proto));
     RootedObject dummy(cx, JS_GetParent(proto));
     CHECK(!JS_IsTypedArrayObject(dummy));
 
     CHECK_EQUAL(JS_GetTypedArrayLength(array), 7u);
     CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u);
     CHECK_EQUAL(JS_GetTypedArrayByteLength(array), sizeof(Element) * 7);
 
-    Element *data;
-    CHECK(data = GetData(array));
-    *data = 13;
+    {
+        JS::AutoCheckCannotGC nogc;
+        Element *data;
+        CHECK(data = GetData(array, nogc));
+        *data = 13;
+    }
     RootedValue v(cx);
     CHECK(JS_GetElement(cx, array, 0, &v));
     CHECK_SAME(v, INT_TO_JSVAL(13));
 
     return true;
 }
 
 template<JSObject *CreateWithBuffer(JSContext *, JS::HandleObject, uint32_t, int32_t),
          JSObject *CreateFromArray(JSContext *, JS::HandleObject),
          typename Element,
-         Element *GetData(JSObject *)>
+         Element *GetData(JSObject *, const JS::AutoCheckCannotGC&)>
 bool
 TestArrayFromBuffer(JSContext *cx)
 {
     size_t elts = 8;
     size_t nbytes = elts * sizeof(Element);
     RootedObject buffer(cx, JS_NewArrayBuffer(cx, nbytes));
-    uint8_t *bufdata;
-    CHECK(bufdata = JS_GetStableArrayBufferData(cx, buffer));
-    memset(bufdata, 1, nbytes);
+    {
+        JS::AutoCheckCannotGC nogc;
+        memset(JS_GetArrayBufferData(buffer, nogc), 1, nbytes);
+    }
 
     {
         RootedObject notArray(cx, CreateWithBuffer(cx, buffer, UINT32_MAX, -1));
         CHECK(!notArray);
     }
 
     RootedObject array(cx, CreateWithBuffer(cx, buffer, 0, -1));
     CHECK_EQUAL(JS_GetTypedArrayLength(array), elts);
     CHECK_EQUAL(JS_GetTypedArrayByteOffset(array), 0u);
     CHECK_EQUAL(JS_GetTypedArrayByteLength(array), nbytes);
     CHECK_EQUAL(JS_GetArrayBufferViewBuffer(cx, array), (JSObject*) buffer);
 
-    Element *data;
-    CHECK(data = GetData(array));
-    CHECK(bufdata = JS_GetStableArrayBufferData(cx, buffer));
-    CHECK_EQUAL((void*) data, (void*) bufdata);
+    {
+        JS::AutoCheckCannotGC nogc;
+        Element *data;
+        CHECK(data = GetData(array, nogc));
+        CHECK_EQUAL((void*) data, (void*) JS_GetArrayBufferData(buffer, nogc));
 
-    CHECK_EQUAL(*bufdata, 1u);
-    CHECK_EQUAL(*reinterpret_cast<uint8_t*>(data), 1u);
+        CHECK_EQUAL(*reinterpret_cast<uint8_t*>(JS_GetArrayBufferData(buffer, nogc)), 1u);
+        CHECK_EQUAL(*reinterpret_cast<uint8_t*>(data), 1u);
+    }
 
     RootedObject shortArray(cx, CreateWithBuffer(cx, buffer, 0, elts / 2));
     CHECK_EQUAL(JS_GetTypedArrayLength(shortArray), elts / 2);
     CHECK_EQUAL(JS_GetTypedArrayByteOffset(shortArray), 0u);
     CHECK_EQUAL(JS_GetTypedArrayByteLength(shortArray), nbytes / 2);
 
     RootedObject ofsArray(cx, CreateWithBuffer(cx, buffer, nbytes / 2, -1));
     CHECK_EQUAL(JS_GetTypedArrayLength(ofsArray), elts / 2);
@@ -132,33 +141,48 @@ TestArrayFromBuffer(JSContext *cx)
     // Make sure all 3 views reflect the same buffer at the expected locations
     JS::RootedValue v(cx, INT_TO_JSVAL(39));
     JS_SetElement(cx, array, 0, v);
     JS::RootedValue v2(cx);
     CHECK(JS_GetElement(cx, array, 0, &v2));
     CHECK_SAME(v, v2);
     CHECK(JS_GetElement(cx, shortArray, 0, &v2));
     CHECK_SAME(v, v2);
-    CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[0]));
+    {
+        JS::AutoCheckCannotGC nogc;
+        Element *data;
+        CHECK(data = GetData(array, nogc));
+        CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[0]));
+    }
 
     v = INT_TO_JSVAL(40);
     JS_SetElement(cx, array, elts / 2, v);
     CHECK(JS_GetElement(cx, array, elts / 2, &v2));
     CHECK_SAME(v, v2);
     CHECK(JS_GetElement(cx, ofsArray, 0, &v2));
     CHECK_SAME(v, v2);
-    CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[elts / 2]));
+    {
+        JS::AutoCheckCannotGC nogc;
+        Element *data;
+        CHECK(data = GetData(array, nogc));
+        CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[elts / 2]));
+    }
 
     v = INT_TO_JSVAL(41);
     JS_SetElement(cx, array, elts - 1, v);
     CHECK(JS_GetElement(cx, array, elts - 1, &v2));
     CHECK_SAME(v, v2);
     CHECK(JS_GetElement(cx, ofsArray, elts / 2 - 1, &v2));
     CHECK_SAME(v, v2);
-    CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[elts - 1]));
+    {
+        JS::AutoCheckCannotGC nogc;
+        Element *data;
+        CHECK(data = GetData(array, nogc));
+        CHECK_EQUAL(long(v.toInt32()), long(reinterpret_cast<Element*>(data)[elts - 1]));
+    }
 
     JS::RootedObject copy(cx, CreateFromArray(cx, array));
     CHECK(JS_GetElement(cx, array, 0, &v));
     CHECK(JS_GetElement(cx, copy, 0, &v2));
     CHECK_SAME(v, v2);
 
     /* The copy should not see changes in the original */
     v2 = INT_TO_JSVAL(42);
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1947,49 +1947,42 @@ JS_GetArrayBufferViewByteLength(JSObject
  * care not to hold on across anything that could GC.
  *
  * |obj| must have passed a JS_Is*Array test, or somehow be known that it would
  * pass such a test: it is a typed array or a wrapper of a typed array, and the
  * unwrapping will succeed.
  */
 
 extern JS_FRIEND_API(uint8_t *)
-JS_GetArrayBufferData(JSObject *obj);
+JS_GetArrayBufferData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(int8_t *)
-JS_GetInt8ArrayData(JSObject *obj);
+JS_GetInt8ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(uint8_t *)
-JS_GetUint8ArrayData(JSObject *obj);
+JS_GetUint8ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(uint8_t *)
-JS_GetUint8ClampedArrayData(JSObject *obj);
+JS_GetUint8ClampedArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(int16_t *)
-JS_GetInt16ArrayData(JSObject *obj);
+JS_GetInt16ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(uint16_t *)
-JS_GetUint16ArrayData(JSObject *obj);
+JS_GetUint16ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(int32_t *)
-JS_GetInt32ArrayData(JSObject *obj);
+JS_GetInt32ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(uint32_t *)
-JS_GetUint32ArrayData(JSObject *obj);
+JS_GetUint32ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(float *)
-JS_GetFloat32ArrayData(JSObject *obj);
+JS_GetFloat32ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 extern JS_FRIEND_API(double *)
-JS_GetFloat64ArrayData(JSObject *obj);
-
-/*
- * Stable versions of the above functions where the buffer remains valid as long
- * as the object is live.
- */
-extern JS_FRIEND_API(uint8_t *)
-JS_GetStableArrayBufferData(JSContext *cx, JS::HandleObject obj);
+JS_GetFloat64ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&);
 
 /*
  * Same as above, but for any kind of ArrayBufferView. Prefer the type-specific
  * versions when possible.
  */
 extern JS_FRIEND_API(void *)
-JS_GetArrayBufferViewData(JSObject *obj);
+JS_GetArrayBufferViewData(JSObject *obj, const JS::AutoCheckCannotGC&);
 
 /*
  * Return the ArrayBuffer underlying an ArrayBufferView. If the buffer has been
  * neutered, this will still return the neutered buffer. |obj| must be an
  * object that would return true for JS_IsArrayBufferViewObject().
  */
 extern JS_FRIEND_API(JSObject *)
 JS_GetArrayBufferViewBuffer(JSContext *cx, JS::HandleObject obj);
@@ -2052,17 +2045,17 @@ JS_GetDataViewByteLength(JSObject *obj);
  * Return a pointer to the beginning of the data referenced by a DataView.
  *
  * |obj| must have passed a JS_IsDataViewObject test, or somehow be known that
  * it would pass such a test: it is a data view or a wrapper of a data view,
  * and the unwrapping will succeed. If cx is nullptr, then DEBUG builds may be
  * unable to assert when unwrapping should be disallowed.
  */
 JS_FRIEND_API(void *)
-JS_GetDataViewData(JSObject *obj);
+JS_GetDataViewData(JSObject *obj, const JS::AutoCheckCannotGC&);
 
 namespace js {
 
 /*
  * Add a watchpoint -- in the Object.prototype.watch sense -- to |obj| for the
  * property |id|, using the callable object |callable| as the function to be
  * called for notifications.
  *
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1061,21 +1061,21 @@ CacheEntry_getBytecode(HandleObject cach
     *length = arrayBuffer->byteLength();
     return arrayBuffer->dataPointer();
 }
 
 static bool
 CacheEntry_setBytecode(JSContext *cx, HandleObject cache, uint8_t *buffer, uint32_t length)
 {
     MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
+
     ArrayBufferObject::BufferContents contents =
         ArrayBufferObject::BufferContents::create<ArrayBufferObject::PLAIN_BUFFER>(buffer);
     Rooted<ArrayBufferObject*> arrayBuffer(cx, ArrayBufferObject::create(cx, length, contents));
-
-    if (!arrayBuffer || !ArrayBufferObject::ensureNonInline(cx, arrayBuffer))
+    if (!arrayBuffer)
         return false;
 
     SetReservedSlot(cache, CacheEntry_BYTECODE, OBJECT_TO_JSVAL(arrayBuffer));
     return true;
 }
 
 class AutoSaveFrameChain
 {
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -372,18 +372,23 @@ ArrayBufferObject::changeContents(JSCont
 }
 
 /* static */ bool
 ArrayBufferObject::prepareForAsmJSNoSignals(JSContext *cx, Handle<ArrayBufferObject*> buffer)
 {
     if (buffer->isAsmJSArrayBuffer())
         return true;
 
-    if (!ensureNonInline(cx, buffer))
-        return false;
+    if (!buffer->ownsData()) {
+        BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
+        if (!contents)
+            return false;
+        memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
+        buffer->changeContents(cx, contents);
+    }
 
     buffer->setIsAsmJSArrayBuffer();
     return true;
 }
 
 void
 ArrayBufferObject::releaseAsmJSArrayNoSignals(FreeOp *fop)
 {
@@ -692,30 +697,16 @@ ArrayBufferObject::createDataViewForThis
 
 bool
 ArrayBufferObject::createDataViewForThis(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsArrayBuffer, createDataViewForThisImpl>(cx, args);
 }
 
-/* static */ bool
-ArrayBufferObject::ensureNonInline(JSContext *cx, Handle<ArrayBufferObject*> buffer)
-{
-    if (!buffer->ownsData()) {
-        BufferContents contents = AllocateArrayBufferContents(cx, buffer->byteLength());
-        if (!contents)
-            return false;
-        memcpy(contents.data(), buffer->dataPointer(), buffer->byteLength());
-        buffer->changeContents(cx, contents);
-    }
-
-    return true;
-}
-
 /* static */ ArrayBufferObject::BufferContents
 ArrayBufferObject::stealContents(JSContext *cx, Handle<ArrayBufferObject*> buffer,
                                  bool hasStealableContents)
 {
     MOZ_ASSERT_IF(hasStealableContents, buffer->hasStealableContents());
 
     if (!buffer->canNeuter(cx)) {
         js_ReportOverRecursed(cx);
@@ -1052,38 +1043,24 @@ js::UnwrapArrayBufferView(JSObject *obj)
 JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferByteLength(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     return obj ? AsArrayBuffer(obj).byteLength() : 0;
 }
 
 JS_FRIEND_API(uint8_t *)
-JS_GetArrayBufferData(JSObject *obj)
+JS_GetArrayBufferData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     return AsArrayBuffer(obj).dataPointer();
 }
 
-JS_FRIEND_API(uint8_t *)
-JS_GetStableArrayBufferData(JSContext *cx, HandleObject objArg)
-{
-    JSObject *obj = CheckedUnwrap(objArg);
-    if (!obj)
-        return nullptr;
-
-    Rooted<ArrayBufferObject*> buffer(cx, &AsArrayBuffer(obj));
-    if (!ArrayBufferObject::ensureNonInline(cx, buffer))
-        return nullptr;
-
-    return buffer->dataPointer();
-}
-
 JS_FRIEND_API(bool)
 JS_NeuterArrayBuffer(JSContext *cx, HandleObject obj,
                      NeuterDataDisposition changeData)
 {
     if (!obj->is<ArrayBufferObject>()) {
         JS_ReportError(cx, "ArrayBuffer object required");
         return false;
     }
@@ -1214,17 +1191,17 @@ JS_IsMappedArrayBufferObject(JSObject *o
         return false;
 
     return obj->is<ArrayBufferObject>()
            ? obj->as<ArrayBufferObject>().isMappedArrayBuffer()
            : false;
 }
 
 JS_FRIEND_API(void *)
-JS_GetArrayBufferViewData(JSObject *obj)
+JS_GetArrayBufferViewData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     return obj->is<DataViewObject>() ? obj->as<DataViewObject>().dataPointer()
                                      : obj->as<TypedArrayObject>().viewData();
 }
 
--- a/js/src/vm/TypedArrayObject.cpp
+++ b/js/src/vm/TypedArrayObject.cpp
@@ -2146,105 +2146,105 @@ JS_GetArrayBufferViewType(JSObject *obj)
     if (obj->is<TypedArrayObject>())
         return obj->as<TypedArrayObject>().type();
     else if (obj->is<DataViewObject>())
         return Scalar::TypeMax;
     MOZ_CRASH("invalid ArrayBufferView type");
 }
 
 JS_FRIEND_API(int8_t *)
-JS_GetInt8ArrayData(JSObject *obj)
+JS_GetInt8ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int8);
     return static_cast<int8_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(uint8_t *)
-JS_GetUint8ArrayData(JSObject *obj)
+JS_GetUint8ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint8);
     return static_cast<uint8_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(uint8_t *)
-JS_GetUint8ClampedArrayData(JSObject *obj)
+JS_GetUint8ClampedArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint8Clamped);
     return static_cast<uint8_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(int16_t *)
-JS_GetInt16ArrayData(JSObject *obj)
+JS_GetInt16ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int16);
     return static_cast<int16_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(uint16_t *)
-JS_GetUint16ArrayData(JSObject *obj)
+JS_GetUint16ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint16);
     return static_cast<uint16_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(int32_t *)
-JS_GetInt32ArrayData(JSObject *obj)
+JS_GetInt32ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Int32);
     return static_cast<int32_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(uint32_t *)
-JS_GetUint32ArrayData(JSObject *obj)
+JS_GetUint32ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Uint32);
     return static_cast<uint32_t *>(tarr->viewData());
 }
 
 JS_FRIEND_API(float *)
-JS_GetFloat32ArrayData(JSObject *obj)
+JS_GetFloat32ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Float32);
     return static_cast<float *>(tarr->viewData());
 }
 
 JS_FRIEND_API(double *)
-JS_GetFloat64ArrayData(JSObject *obj)
+JS_GetFloat64ArrayData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     TypedArrayObject *tarr = &obj->as<TypedArrayObject>();
     MOZ_ASSERT((int32_t) tarr->type() == Scalar::Float64);
     return static_cast<double *>(tarr->viewData());
 }
@@ -2261,17 +2261,17 @@ JS_GetDataViewByteOffset(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return 0;
     return obj->as<DataViewObject>().byteOffset();
 }
 
 JS_FRIEND_API(void *)
-JS_GetDataViewData(JSObject *obj)
+JS_GetDataViewData(JSObject *obj, const JS::AutoCheckCannotGC&)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return nullptr;
     return obj->as<DataViewObject>().dataPointer();
 }
 
 JS_FRIEND_API(uint32_t)
--- a/js/xpconnect/src/XPCConvert.cpp
+++ b/js/xpconnect/src/XPCConvert.cpp
@@ -1361,17 +1361,18 @@ CheckTargetAndPopulate(const nsXPTType& 
     size_t byteSize = count * typeSize;
     if (count > max || !(*output = nsMemory::Alloc(byteSize))) {
         if (pErr)
             *pErr = NS_ERROR_OUT_OF_MEMORY;
 
         return false;
     }
 
-    memcpy(*output, JS_GetArrayBufferViewData(tArr), byteSize);
+    JS::AutoCheckCannotGC nogc;
+    memcpy(*output, JS_GetArrayBufferViewData(tArr, nogc), byteSize);
     return true;
 }
 
 // Fast conversion of typed arrays to native using memcpy.
 // No float or double canonicalization is done. Called by
 // JSarray2Native whenever a TypedArray is met. ArrayBuffers
 // are not accepted; create a properly typed array view on them
 // first. The element type of array must match the XPCOM
--- a/netwerk/base/src/ArrayBufferInputStream.cpp
+++ b/netwerk/base/src/ArrayBufferInputStream.cpp
@@ -7,18 +7,17 @@
 #include "ArrayBufferInputStream.h"
 #include "nsStreamUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 
 NS_IMPL_ISUPPORTS(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
 
 ArrayBufferInputStream::ArrayBufferInputStream()
-: mBuffer(nullptr)
-, mBufferLength(0)
+: mBufferLength(0)
 , mOffset(0)
 , mPos(0)
 , mClosed(false)
 {
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::SetData(JS::Handle<JS::Value> aBuffer,
@@ -29,42 +28,39 @@ ArrayBufferInputStream::SetData(JS::Hand
   if (!aBuffer.isObject()) {
     return NS_ERROR_FAILURE;
   }
   JS::RootedObject arrayBuffer(aCx, &aBuffer.toObject());
   if (!JS_IsArrayBufferObject(arrayBuffer)) {
     return NS_ERROR_FAILURE;
   }
 
-  mArrayBuffer.emplace(aCx, aBuffer);
+  mArrayBuffer.emplace(aCx, arrayBuffer);
 
   uint32_t buflen = JS_GetArrayBufferByteLength(arrayBuffer);
   mOffset = std::min(buflen, aByteOffset);
   mBufferLength = std::min(buflen - mOffset, aLength);
-  mBuffer = JS_GetStableArrayBufferData(aCx, arrayBuffer);
-  if (!mBuffer) {
-      return NS_ERROR_FAILURE;
-  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::Close()
 {
   mClosed = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::Available(uint64_t* aCount)
 {
   if (mClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
-  *aCount = mBufferLength - mPos;
+  uint32_t buflen = JS_GetArrayBufferByteLength(mArrayBuffer->get());
+  *aCount = buflen ? buflen - mPos : 0;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount)
 {
   return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
 }
@@ -75,42 +71,56 @@ ArrayBufferInputStream::ReadSegments(nsW
 {
   NS_ASSERTION(result, "null ptr");
   NS_ASSERTION(mBufferLength >= mPos, "bad stream state");
 
   if (mClosed) {
     return NS_BASE_STREAM_CLOSED;
   }
 
-  uint32_t remaining = mBufferLength - mPos;
-  if (mArrayBuffer) {
-    JSObject* buf = &mArrayBuffer->get().toObject();
-    uint32_t byteLength = JS_GetArrayBufferByteLength(buf);
-    if (byteLength == 0 && remaining != 0) {
+  MOZ_ASSERT(mArrayBuffer || (mPos == mBufferLength), "stream inited incorrectly");
+
+  *result = 0;
+  while (mPos < mBufferLength) {
+    uint32_t remaining = mBufferLength - mPos;
+    MOZ_ASSERT(mArrayBuffer);
+    uint32_t byteLength = JS_GetArrayBufferByteLength(mArrayBuffer->get());
+    if (byteLength == 0) {
       mClosed = true;
       return NS_BASE_STREAM_CLOSED;
     }
-  } else {
-    MOZ_ASSERT(remaining == 0, "stream inited incorrectly");
-  }
 
-  if (!remaining) {
-    *result = 0;
-    return NS_OK;
-  }
+    char buffer[8192];
+    uint32_t count = std::min(std::min(aCount, remaining), uint32_t(mozilla::ArrayLength(buffer)));
+    if (count == 0) {
+      break;
+    }
 
-  if (aCount > remaining) {
-    aCount = remaining;
-  }
-  nsresult rv = writer(this, closure, (char*)(mBuffer + mOffset) + mPos,
-                       0, aCount, result);
-  if (NS_SUCCEEDED(rv)) {
-    NS_ASSERTION(*result <= aCount,
+    // It is just barely possible that writer() will detach the ArrayBuffer's
+    // data, setting its length to zero. Or move the data to a different memory
+    // area. (This would only happen in a subclass that passed something other
+    // than NS_CopySegmentToBuffer as 'writer'). So copy the data out into a
+    // holding area before passing it to writer().
+    {
+      JS::AutoCheckCannotGC nogc;
+      char* src = (char*) JS_GetArrayBufferData(mArrayBuffer->get(), nogc) + mOffset + mPos;
+      memcpy(buffer, src, count);
+    }
+    uint32_t written;
+    nsresult rv = writer(this, closure, buffer, 0, count, &written);
+    if (NS_FAILED(rv)) {
+      // InputStreams do not propagate errors to caller.
+      return NS_OK;
+    }
+
+    NS_ASSERTION(written <= count,
                  "writer should not write more than we asked it to write");
-    mPos += *result;
+    mPos += written;
+    *result += written;
+    aCount -= written;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArrayBufferInputStream::IsNonBlocking(bool *aNonBlocking)
 {
--- a/netwerk/base/src/ArrayBufferInputStream.h
+++ b/netwerk/base/src/ArrayBufferInputStream.h
@@ -23,17 +23,16 @@ class ArrayBufferInputStream : public ns
 public:
   ArrayBufferInputStream();
   NS_DECL_ISUPPORTS
   NS_DECL_NSIARRAYBUFFERINPUTSTREAM
   NS_DECL_NSIINPUTSTREAM
 
 private:
   virtual ~ArrayBufferInputStream() {}
-  mozilla::Maybe<JS::PersistentRooted<JS::Value> > mArrayBuffer;
-  uint8_t* mBuffer; // start of actual buffer
+  mozilla::Maybe<JS::PersistentRooted<JSObject*> > mArrayBuffer;
   uint32_t mBufferLength; // length of slice
   uint32_t mOffset; // permanent offset from start of actual buffer
   uint32_t mPos; // offset from start of slice
   bool mClosed;
 };
 
 #endif // ArrayBufferInputStream_h
--- a/xpcom/io/nsBinaryStream.cpp
+++ b/xpcom/io/nsBinaryStream.cpp
@@ -836,51 +836,52 @@ nsBinaryInputStream::ReadArrayBuffer(uin
     return NS_ERROR_FAILURE;
   }
 
   uint32_t bufferLength = JS_GetArrayBufferByteLength(buffer);
   if (bufferLength < aLength) {
     return NS_ERROR_FAILURE;
   }
 
-  char* data = reinterpret_cast<char*>(JS_GetStableArrayBufferData(aCx, buffer));
-  if (!data) {
-    return NS_ERROR_FAILURE;
-  }
-
   uint32_t bufSize = std::min<uint32_t>(aLength, 4096);
   UniquePtr<char[]> buf = MakeUnique<char[]>(bufSize);
 
-  uint32_t remaining = aLength;
+  uint32_t pos = 0;
   *aReadLength = 0;
   do {
     // Read data into temporary buffer.
     uint32_t bytesRead;
-    uint32_t amount = std::min(remaining, bufSize);
+    uint32_t amount = std::min(aLength - pos, bufSize);
     nsresult rv = Read(buf.get(), amount, &bytesRead);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(bytesRead <= amount);
 
     if (bytesRead == 0) {
       break;
     }
 
     // Copy data into actual buffer.
+
+    JS::AutoCheckCannotGC nogc;
     if (bufferLength != JS_GetArrayBufferByteLength(buffer)) {
       return NS_ERROR_FAILURE;
     }
 
+    char* data = reinterpret_cast<char*>(JS_GetArrayBufferData(buffer, nogc));
+    if (!data) {
+      return NS_ERROR_FAILURE;
+    }
+
     *aReadLength += bytesRead;
-    PodCopy(data, buf.get(), bytesRead);
+    PodCopy(data + pos, buf.get(), bytesRead);
 
-    remaining -= bytesRead;
-    data += bytesRead;
-  } while (remaining > 0);
+    pos += bytesRead;
+  } while (pos < aLength);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsBinaryInputStream::ReadObject(bool aIsStrongRef, nsISupports** aObject)
 {
   nsCID cid;