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 209586 4f52e486476c26e76f83d7ad9beeb993f8b6b5ab
parent 209585 b43ed5b85b1aa6bb76f66249e73934433b20c0c0
child 209587 7199ca292bee96ef94af5bc1d8cfe3726c21275f
push id50227
push usersfink@mozilla.com
push dateThu, 09 Oct 2014 16:14:04 +0000
treeherdermozilla-inbound@4f52e486476c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, terrence, jdm, roc, khuey
bugs1061288
milestone35.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
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;