Bug 831107 - Part 2: Add general ArrayBuffer support to TCPSocket. r=vlad,fzzzy a=tef+
authorJosh Matthews <josh@joshmatthews.net>
Fri, 19 Apr 2013 11:29:08 +0200
changeset 119136 b72ffc9ec1745490a997fea1713b9b48bc1dca48
parent 119135 baafe342359045b520152704e4b26049e21cead9
child 119137 9ef08dac0122c08cb3e8cf62965cb279d6382242
push id692
push userjosh@joshmatthews.net
push dateFri, 19 Apr 2013 12:12:21 +0000
reviewersvlad, fzzzy, tef
bugs831107
milestone18.0
Bug 831107 - Part 2: Add general ArrayBuffer support to TCPSocket. r=vlad,fzzzy a=tef+
dom/network/interfaces/nsIDOMTCPSocket.idl
dom/network/interfaces/nsITCPSocketChild.idl
dom/network/src/TCPSocket.js
dom/network/src/TCPSocketChild.cpp
dom/network/src/TCPSocketParent.cpp
dom/network/src/TCPSocketParentIntermediary.js
dom/network/tests/unit/test_tcpsocket.js
netwerk/base/public/Makefile.in
netwerk/base/public/nsIArrayBufferInputStream.idl
netwerk/base/src/ArrayBufferInputStream.cpp
netwerk/base/src/ArrayBufferInputStream.h
netwerk/base/src/Makefile.in
netwerk/build/nsNetModule.cpp
--- a/dom/network/interfaces/nsIDOMTCPSocket.idl
+++ b/dom/network/interfaces/nsIDOMTCPSocket.idl
@@ -34,17 +34,17 @@ interface nsIDOMTCPSocket : nsISupports
    *
    * @param host The hostname of the server to connect to.
    * @param port The port to connect to.
    * @param options An object specifying one or more parameters which
    *                determine the details of the socket.
    *
    *        useSSL: true to create an SSL socket. Defaults to false.
    *
-   *        binaryType: "arraybuffer" to use UInt8 array
+   *        binaryType: "arraybuffer" to use ArrayBuffer
    *          instances in the ondata callback and as the argument
    *          to send. Defaults to "string", to use JavaScript strings.
    *
    * @return The new TCPSocket instance.
    */
   nsIDOMTCPSocket open(in DOMString host, in unsigned short port, [optional] in jsval options);
 
   /**
@@ -84,33 +84,37 @@ interface nsIDOMTCPSocket : nsISupports
    */
   void close();
 
   /**
    * Write data to the socket.
    *
    * @param data The data to write to the socket. If
    *             binaryType: "arraybuffer" was passed in the options
-   *             object, then this object should be an Uint8Array instance.
+   *             object, then this object should be an ArrayBuffer instance.
    *             If binaryType: "string" was passed, or if no binaryType
    *             option was specified, then this object should be an
    *             ordinary JavaScript string.
+   * @param byteOffset The offset within the data from which to begin writing.
+   *                   Has no effect on non-ArrayBuffer data.
+   * @param byteLength The number of bytes to write. Has no effect on
+   *                   non-ArrayBuffer data.
    *
    * @return Send returns true or false as a hint to the caller that
    *         they may either continue sending more data immediately, or
    *         may want to wait until the other side has read some of the
    *         data which has already been written to the socket before
    *         buffering more. If send returns true, then less than 64k
    *         has been buffered and it's safe to immediately write more.
    *         If send returns false, then more than 64k has been buffered,
    *         and the caller may wish to wait until the ondrain event
    *         handler has been called before buffering more data by more
    *         calls to send.
    */
-  boolean send(in jsval data);
+  boolean send(in jsval data, [optional] in unsigned long byteOffset, [optional] in unsigned long byteLength);
 
   /**
    * The readyState attribute indicates which state the socket is currently
    * in. The state will be either "connecting", "open", "closing", or "closed".
    */
   readonly attribute DOMString readyState;
 
   /**
@@ -138,17 +142,17 @@ interface nsIDOMTCPSocket : nsISupports
    * to the network, at which point the client can resume calling send again.
    */
   attribute jsval ondrain;
 
   /**
    * The ondata handler will be called repeatedly and asynchronously after
    * onopen has been called, every time some data was available from the server
    * and was read. If binaryType: "arraybuffer" was passed to open, the data
-   * attribute of the event object will be an Uint8Array. If not, it will be a
+   * attribute of the event object will be an ArrayBuffer. If not, it will be a
    * normal JavaScript string.
    *
    * At any time, the client may choose to pause reading and receiving ondata
    * callbacks, by calling the socket's suspend() method. Further invocations
    * of ondata will be paused until resume() is called.
    */
   attribute jsval ondata;
 
@@ -183,17 +187,17 @@ interface nsIDOMTCPSocket : nsISupports
 interface nsITCPSocketInternal : nsISupports {
   // Trigger the callback for |type| and provide an Error() object with the given data
   void callListenerError(in DOMString type, in DOMString message, in DOMString filename,
                          in uint32_t lineNumber, in uint32_t columnNumber);
 
   // Trigger the callback for |type| and provide a string argument
   void callListenerData(in DOMString type, in DOMString data);
 
-  // Trigger the callback for |type| and provide a Uint8Array argument
+  // Trigger the callback for |type| and provide an ArrayBuffer argument
   void callListenerArrayBuffer(in DOMString type, in jsval data);
 
   // Trigger the callback for |type| with no argument
   void callListenerVoid(in DOMString type);
 
   // Update the DOM object's readyState and bufferedAmount values with the provided data
   void updateReadyStateAndBuffered(in DOMString readyState, in uint32_t bufferedAmount);
 };
@@ -221,17 +225,17 @@ interface nsITCPSocketEvent : nsISupport
    * drain
    * close
    */
   readonly attribute DOMString type;
 
   /**
    * The data related to this event, if any. In the ondata callback,
    * data will be the bytes read from the network; if the binaryType
-   * of the socket was "arraybuffer", this value will be of type Uint8Array;
+   * of the socket was "arraybuffer", this value will be of type ArrayBuffer;
    * otherwise, it will be a normal JavaScript string.
    *
    * In the onerror callback, data will be a string with a description
    * of the error.
    *
    * In the other callbacks, data will be an empty string.
    */
   readonly attribute jsval data;
--- a/dom/network/interfaces/nsITCPSocketChild.idl
+++ b/dom/network/interfaces/nsITCPSocketChild.idl
@@ -13,13 +13,13 @@ interface nsITCPSocketChild : nsISupport
 {
   // Tell the chrome process to open a corresponding connection with the given parameters
   [implicit_jscontext]
   void open(in nsITCPSocketInternal socket, in DOMString host,
             in unsigned short port, in boolean ssl, in DOMString binaryType,
             in nsIDOMWindow window, in jsval socketVal);
 
   // Tell the chrome process to perform equivalent operations to all following methods
-  [implicit_jscontext] void send(in jsval data);
+  [implicit_jscontext] void send(in jsval data, in unsigned long byteOffset, in unsigned long byteLength);
   void resume();
   void suspend();
   void close();
 };
--- a/dom/network/src/TCPSocket.js
+++ b/dom/network/src/TCPSocket.js
@@ -18,16 +18,18 @@ const InputStreamPump = CC(
       AsyncStreamCopier = CC(
         "@mozilla.org/network/async-stream-copier;1", "nsIAsyncStreamCopier", "init"),
       ScriptableInputStream = CC(
         "@mozilla.org/scriptableinputstream;1", "nsIScriptableInputStream", "init"),
       BinaryInputStream = CC(
         "@mozilla.org/binaryinputstream;1", "nsIBinaryInputStream", "setInputStream"),
       StringInputStream = CC(
         '@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream'),
+      ArrayBufferInputStream = CC(
+        '@mozilla.org/io/arraybuffer-input-stream;1', 'nsIArrayBufferInputStream'),
       MultiplexInputStream = CC(
         '@mozilla.org/io/multiplex-input-stream;1', 'nsIMultiplexInputStream');
 
 const kCONNECTING = 'connecting';
 const kOPEN = 'open';
 const kCLOSING = 'closing';
 const kCLOSED = 'closed';
 
@@ -84,23 +86,16 @@ function TCPSocket() {
   this._onclose = null;
 
   this._binaryType = "string";
 
   this._host = "";
   this._port = 0;
   this._ssl = false;
 
-  // As a workaround for bug https://bugzilla.mozilla.org/show_bug.cgi?id=786639
-  // we want to create any Uint8Array's off of the owning window so that there
-  // is no need for a wrapper to exist around the typed array from the
-  // perspective of content.  (The wrapper is bad because it only lets content
-  // see length, and forbids access to the array indices unless we excplicitly
-  // list them all.)  We will then access the array through a wrapper, but
-  // since we are chrome-privileged, this does not pose a problem.
   this.useWin = null;
 }
 
 TCPSocket.prototype = {
   __exposedProps__: {
     open: 'r',
     host: 'r',
     port: 'r',
@@ -431,53 +426,45 @@ TCPSocket.prototype = {
     }
 
     if (!this._multiplexStream.count) {
       this._socketOutputStream.close();
     }
     this._socketInputStream.close();
   },
 
-  send: function ts_send(data) {
+  send: function ts_send(data, byteOffset, byteLength) {
     if (this._readyState !== kOPEN) {
       throw new Error("Socket not open.");
     }
 
-    if (this._inChild) {
-      this._socketBridge.send(data);
+    if (this._binaryType === "arraybuffer") {
+      byteLength = byteLength || data.byteLength;
     }
 
-    let new_stream = new StringInputStream();
-    if (this._binaryType === "arraybuffer") {
-      // It would be really nice if there were an interface
-      // that took an ArrayBuffer like StringInputStream takes
-      // a string. There is one, but only in C++ and not exposed
-      // to js as far as I can tell
-      var dataLen = data.length;
-      var offset = 0;
-      var result = "";
-      while (dataLen) {
-        var fragmentLen = dataLen;
-        if (fragmentLen > 32768)
-          fragmentLen = 32768;
-        dataLen -= fragmentLen;
+    if (this._inChild) {
+      this._socketBridge.send(data, byteOffset, byteLength);
+    }
 
-        var fragment = data.subarray(offset, offset + fragmentLen);
-        offset += fragmentLen;
-        result += String.fromCharCode.apply(null, fragment);
-      }
-      data = result;
-    }
-    var newBufferedAmount = this.bufferedAmount + data.length;
+    let length = this._binaryType === "arraybuffer" ? byteLength : data.length;
+
+    var newBufferedAmount = this.bufferedAmount + length;
     var bufferNotFull = newBufferedAmount < BUFFER_SIZE;
     if (this._inChild) {
       return bufferNotFull;
     }
 
-    new_stream.setData(data, data.length);
+    let new_stream;
+    if (this._binaryType === "arraybuffer") {
+      new_stream = new ArrayBufferInputStream();
+      new_stream.setData(data, byteOffset, byteLength);
+    } else {
+      new_stream = new StringInputStream();
+      new_stream.setData(data, length);
+    }
     this._multiplexStream.appendStream(new_stream);
 
     if (newBufferedAmount >= BUFFER_SIZE) {
       // If we buffered more than some arbitrary amount of data,
       // (65535 right now) we should tell the caller so they can
       // wait until ondrain is called if they so desire. Once all the
       //buffered data has been written to the socket, ondrain is
       // called.
@@ -571,20 +558,17 @@ TCPSocket.prototype = {
     }
 
     this.callListener("close");
   },
 
   // nsIStreamListener (Triggered by _inputStreamPump.asyncRead)
   onDataAvailable: function ts_onDataAvailable(request, context, inputStream, offset, count) {
     if (this._binaryType === "arraybuffer") {
-      let buffer = this._inputStreamBinary.readArrayBuffer(count);
-      let constructor = this.useWin ? this.useWin.Uint8Array : Uint8Array;
-      let ua = new constructor(buffer);
-      this.callListener("data", ua);
+      this.callListener("data", this._inputStreamBinary.readArrayBuffer(count));
     } else {
       this.callListener("data", this._inputStreamScriptable.read(count));
     }
   },
 
   classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"),
 
   classInfo: XPCOMUtils.generateCI({
--- a/dom/network/src/TCPSocketChild.cpp
+++ b/dom/network/src/TCPSocketChild.cpp
@@ -1,46 +1,44 @@
 /* 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/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"
 
 using mozilla::net::gNeckoChild;
 
 namespace IPC {
 
 bool
-DeserializeUint8Array(JSRawObject aObj,
-                      const nsTArray<uint8_t>& aBuffer,
-                      jsval* aVal)
+DeserializeArrayBuffer(JSRawObject aObj,
+                       const nsTArray<uint8_t>& aBuffer,
+                       jsval* aVal)
 {
   JSContext* cx = nsContentUtils::GetSafeJSContext();
   JSAutoRequest ar(cx);
   JSAutoCompartment ac(cx, aObj);
 
   JSObject* obj = JS_NewArrayBuffer(cx, aBuffer.Length());
   if (!obj)
     return false;
   uint8_t* data = JS_GetArrayBufferData(obj, cx);
   if (!data)
     return false;
   memcpy(data, aBuffer.Elements(), aBuffer.Length());
-  JSObject* arr = JS_NewUint8ArrayWithBuffer(cx, obj, 0, aBuffer.Length());
-  if (!arr)
-    return false;
-  *aVal = OBJECT_TO_JSVAL(arr);
+  *aVal = OBJECT_TO_JSVAL(obj);
   return true;
 }
 
 } // namespace IPC
 
 namespace mozilla {
 namespace dom {
 
@@ -131,17 +129,17 @@ TCPSocketChild::RecvCallback(const nsStr
     rv = mSocket->CallListenerError(aType, err.message(), err.filename(),
                                    err.lineNumber(), err.columnNumber());
 
   } else if (aData.type() == CallbackData::TSendableData) {
     const SendableData& data = aData.get_SendableData();
 
     if (data.type() == SendableData::TArrayOfuint8_t) {
       jsval val;
-      IPC::DeserializeUint8Array(mSocketObj, data.get_ArrayOfuint8_t(), &val);
+      IPC::DeserializeArrayBuffer(mSocketObj, data.get_ArrayOfuint8_t(), &val);
       rv = mSocket->CallListenerArrayBuffer(aType, val);
 
     } else if (data.type() == SendableData::TnsString) {
       rv = mSocket->CallListenerData(aType, data.get_nsString());
 
     } else {
       MOZ_NOT_REACHED("Invalid callback data type!");
     }
@@ -170,37 +168,41 @@ TCPSocketChild::Resume()
 NS_IMETHODIMP
 TCPSocketChild::Close()
 {
   SendClose();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-TCPSocketChild::Send(const JS::Value& aData, JSContext* aCx)
+TCPSocketChild::Send(const JS::Value& aData,
+                     uint32_t aByteOffset,
+                     uint32_t aByteLength,
+                     JSContext* aCx)
 {
   if (aData.isString()) {
     JSString* jsstr = aData.toString();
     nsDependentJSString str;
     bool ok = str.init(aCx, jsstr);
     NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
     SendData(str);
 
   } else {
     NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
     JSObject* obj = &aData.toObject();
-    NS_ENSURE_TRUE(JS_IsTypedArrayObject(obj, aCx), NS_ERROR_FAILURE);
-    NS_ENSURE_TRUE(JS_IsUint8Array(obj, aCx), NS_ERROR_FAILURE);
-    uint32_t nbytes = JS_GetTypedArrayByteLength(obj, aCx);
-    uint8_t* data = JS_GetUint8ArrayData(obj, aCx);
+    NS_ENSURE_TRUE(JS_IsArrayBufferObject(obj, aCx), NS_ERROR_FAILURE);
+    uint32_t buflen = JS_GetArrayBufferByteLength(obj, aCx);
+    aByteOffset = std::min(buflen, aByteOffset);
+    uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
+    uint8_t* data = JS_GetArrayBufferData(obj, aCx);
     if (!data) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     FallibleTArray<uint8_t> fallibleArr;
-    if (!fallibleArr.InsertElementsAt(0, data, nbytes)) {
+    if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
     InfallibleTArray<uint8_t> arr;
     arr.SwapElements(fallibleArr);
     SendData(arr);
   }
   return NS_OK;
 }
--- a/dom/network/src/TCPSocketParent.cpp
+++ b/dom/network/src/TCPSocketParent.cpp
@@ -9,19 +9,19 @@
 #include "nsIDOMTCPSocket.h"
 #include "mozilla/unused.h"
 #include "mozilla/AppProcessChecker.h"
 
 namespace IPC {
 
 //Defined in TCPSocketChild.cpp
 extern bool
-DeserializeUint8Array(JSRawObject aObj,
-                      const nsTArray<uint8_t>& aBuffer,
-                      jsval* aVal);
+DeserializeArrayBuffer(JSRawObject aObj,
+                       const nsTArray<uint8_t>& aBuffer,
+                       jsval* aVal);
 
 }
 
 namespace mozilla {
 namespace dom {
 
 static void
 FireInteralError(mozilla::net::PTCPSocketParent* aActor, uint32_t aLineNo)
@@ -99,17 +99,17 @@ bool
 TCPSocketParent::RecvData(const SendableData& aData)
 {
   NS_ENSURE_TRUE(mIntermediary, true);
 
   nsresult rv;
   switch (aData.type()) {
     case SendableData::TArrayOfuint8_t: {
       jsval val;
-      IPC::DeserializeUint8Array(mIntermediaryObj, aData.get_ArrayOfuint8_t(), &val);
+      IPC::DeserializeArrayBuffer(mIntermediaryObj, aData.get_ArrayOfuint8_t(), &val);
       rv = mIntermediary->SendArrayBuffer(val);
       NS_ENSURE_SUCCESS(rv, true);
       break;
     }
 
     case SendableData::TnsString:
       rv = mIntermediary->SendString(aData.get_nsString());
       NS_ENSURE_SUCCESS(rv, true);
@@ -151,20 +151,19 @@ TCPSocketParent::SendCallback(const nsAS
     }
     data = str;
 
   } else if (aDataVal.isUndefined() || aDataVal.isNull()) {
     data = mozilla::void_t();
 
   } else if (aDataVal.isObject()) {
     JSObject* obj = &aDataVal.toObject();
-    if (JS_IsTypedArrayObject(obj, aCx)) {
-      NS_ENSURE_TRUE(JS_IsUint8Array(obj, aCx), NS_ERROR_FAILURE);
-      uint32_t nbytes = JS_GetTypedArrayByteLength(obj, aCx);
-      uint8_t* buffer = JS_GetUint8ArrayData(obj, aCx);
+    if (JS_IsArrayBufferObject(obj, aCx)) {
+      uint32_t nbytes = JS_GetArrayBufferByteLength(obj, aCx);
+      uint8_t* buffer = JS_GetArrayBufferData(obj, aCx);
       if (!buffer) {
         FireInteralError(this, __LINE__);
         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;
--- a/dom/network/src/TCPSocketParentIntermediary.js
+++ b/dom/network/src/TCPSocketParentIntermediary.js
@@ -38,17 +38,17 @@ TCPSocketParentIntermediary.prototype = 
     return socket;
   },
 
   sendString: function(aData) {
     return this._socket.send(aData);
   },
 
   sendArrayBuffer: function(aData) {
-    return this._socket.send(aData);
+    return this._socket.send(aData, 0, aData.byteLength);
   },
 
   classID: Components.ID("{afa42841-a6cb-4a91-912f-93099f6a3d18}"),
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsITCPSocketIntermediary
   ])
 };
 
--- a/dom/network/tests/unit/test_tcpsocket.js
+++ b/dom/network/tests/unit/test_tcpsocket.js
@@ -23,29 +23,36 @@ const CC = Components.Constructor;
 /**
  *
  * Constants
  *
  */
 
 // Some binary data to send.
 const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0],
-      TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY),
+      DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length),
+      TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER),
       HELLO_WORLD = "hlo wrld. ",
       BIG_ARRAY = new Array(65539),
       BIG_ARRAY_2 = new Array(65539);
 
+TYPED_DATA_ARRAY.set(DATA_ARRAY, 0);
+
 for (var i_big = 0; i_big < BIG_ARRAY.length; i_big++) {
   BIG_ARRAY[i_big] = Math.floor(Math.random() * 256);
   BIG_ARRAY_2[i_big] = Math.floor(Math.random() * 256);
 }
 
-const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY),
-      BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_2);
-      
+const BIG_ARRAY_BUFFER = new ArrayBuffer(BIG_ARRAY.length),
+      BIG_ARRAY_BUFFER_2 = new ArrayBuffer(BIG_ARRAY_2.length);
+const BIG_TYPED_ARRAY = new Uint8Array(BIG_ARRAY_BUFFER),
+      BIG_TYPED_ARRAY_2 = new Uint8Array(BIG_ARRAY_BUFFER_2);
+BIG_TYPED_ARRAY.set(BIG_ARRAY);
+BIG_TYPED_ARRAY_2.set(BIG_ARRAY_2);
+
 const ServerSocket = CC("@mozilla.org/network/server-socket;1",
                         "nsIServerSocket",
                         "init"),
       InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
                            "nsIInputStreamPump",
                            "init"),
       BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                              "nsIBinaryInputStream",
@@ -171,57 +178,67 @@ function makeJointSuccess(names) {
   return funcs;
 }
 
 function makeFailureCase(name) {
   return function() {
     let argstr;
     if (arguments.length) {
       argstr = '(args: ' +
-        Array.map(arguments, function(x) { return x + ""; }).join(" ") + ')';
+        Array.map(arguments, function(x) { return x.data + ""; }).join(" ") + ')';
     }
     else {
       argstr = '(no arguments)';
     }
     do_throw('got unexpected: ' + name + ' ' + argstr);
   };
 }
 
 function makeExpectData(name, expectedData, fromEvent, callback) {
   let dataBuffer = fromEvent ? null : [], done = false;
+  let dataBufferView = null;
   return function(receivedData) {
+    if (receivedData.data) {
+      receivedData = receivedData.data;
+    }
+    let recvLength = receivedData.byteLength !== undefined ?
+        receivedData.byteLength : receivedData.length;
+
     if (fromEvent) {
-      receivedData = receivedData.data;
       if (dataBuffer) {
-        let newBuffer = new Uint8Array(dataBuffer.length + receivedData.length);
-        newBuffer.set(dataBuffer, 0);
-        newBuffer.set(receivedData, dataBuffer.length);
+        let newBuffer = new ArrayBuffer(dataBuffer.byteLength + recvLength);
+        let newBufferView = new Uint8Array(newBuffer);
+        newBufferView.set(dataBufferView, 0);
+        newBufferView.set(receivedData, dataBuffer.byteLength);
         dataBuffer = newBuffer;
+        dataBufferView = newBufferView;
       }
       else {
         dataBuffer = receivedData;
+        dataBufferView = new Uint8Array(dataBuffer);
       }
     }
     else {
       dataBuffer = dataBuffer.concat(receivedData);
     }
-    do_print(name + ' received ' + receivedData.length + ' bytes');
+    do_print(name + ' received ' + recvLength + ' bytes');
 
     if (done)
       do_throw(name + ' Received data event when already done!');
 
-    if (dataBuffer.length >= expectedData.length) {
+    let dataView = dataBuffer.byteLength !== undefined ? new Uint8Array(dataBuffer) : dataBuffer;
+    if (dataView.length >= expectedData.length) {
       // check the bytes are equivalent
       for (let i = 0; i < expectedData.length; i++) {
-        if (dataBuffer[i] !== expectedData[i]) {
+        if (dataView[i] !== expectedData[i]) {
           do_throw(name + ' Received mismatched character at position ' + i);
         }
       }
-      if (dataBuffer.length > expectedData.length)
-        do_throw(name + ' Received ' + dataBuffer.length + ' bytes but only expected ' +
+      if (dataView.length > expectedData.length)
+        do_throw(name + ' Received ' + dataView.length + ' bytes but only expected ' +
                  expectedData.length + ' bytes.');
 
       done = true;
       if (callback) {
         callback();
       } else {
         run_next_test();
       }
@@ -264,17 +281,17 @@ function connectSock() {
 
 /**
  * Test that sending a small amount of data works, and that buffering
  * does not take place for this small amount of data.
  */
 
 function sendData() {
   server.ondata = makeExpectData('serverdata', DATA_ARRAY);
-  if (!sock.send(TYPED_DATA_ARRAY)) {
+  if (!sock.send(DATA_ARRAY_BUFFER)) {
     do_throw("send should not have buffered such a small amount of data");
   }
 }
 
 /**
  * Test that sending a large amount of data works, that buffering
  * takes place (send returns true), and that ondrain is called once
  * the data has been sent.
@@ -291,17 +308,17 @@ function sendBig() {
     }
   };
   sock.ondrain = function(evt) {
     if (sock.bufferedAmount) {
       do_throw("sock.bufferedAmount was > 0 in ondrain");
     }
     yays.clientdrain(evt);
   }
-  if (sock.send(BIG_TYPED_ARRAY)) {
+  if (sock.send(BIG_ARRAY_BUFFER)) {
     do_throw("expected sock.send to return false on large buffer send");
   }
 }
 
 /**
  * Test that data sent from the server correctly fires the ondata
  * callback on the client side.
  */
@@ -349,17 +366,17 @@ function clientCloses() {
  */
 
 function bufferedClose() {
   var yays = makeJointSuccess(['serverdata', 'clientclose', 'serverclose']);
   server.ondata = makeExpectData(
     "ondata", BIG_TYPED_ARRAY, false, yays.serverdata);
   server.onclose = yays.serverclose;
   sock.onclose = yays.clientclose;
-  sock.send(BIG_TYPED_ARRAY);
+  sock.send(BIG_ARRAY_BUFFER);
   sock.close();
 }
 
 /**
  * Connect to a port we know is not listening so an error is assured,
  * and make sure that onerror and onclose are fired on the client side.
  */
  
@@ -394,31 +411,31 @@ function drainTwice() {
 
   function serverSideCallback() {
     yays.ondata();
     server.ondata = makeExpectData(
       "ondata2", BIG_TYPED_ARRAY_2, false, yays.ondata2);
 
     sock.ondrain = yays.ondrain2;
 
-    if (sock.send(BIG_TYPED_ARRAY_2)) {
+    if (sock.send(BIG_ARRAY_BUFFER_2)) {
       do_throw("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering");
     }
 
     sock.close();
   }
 
   server.onclose = yays.serverclose;
   server.ondata = makeExpectData(
     "ondata", BIG_TYPED_ARRAY, false, serverSideCallback);
 
   sock.onclose = yays.clientclose;
   sock.ondrain = yays.ondrain;
 
-  if (sock.send(BIG_TYPED_ARRAY)) {
+  if (sock.send(BIG_ARRAY_BUFFER)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   }
 }
 
 function cleanup() {
   do_print("Cleaning up");
   sock.close();
   if (!gInChild)
@@ -442,20 +459,20 @@ function bufferTwice() {
   server.onclose = yays.serverclose;
   sock.onclose = yays.clientclose;
 
   sock.ondrain = function () {
     sock.close();
     yays.ondrain();
   }
 
-  if (sock.send(BIG_TYPED_ARRAY)) {
+  if (sock.send(BIG_ARRAY_BUFFER)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY) did not return false to indicate buffering");
   }
-  if (sock.send(BIG_TYPED_ARRAY_2)) {
+  if (sock.send(BIG_ARRAY_BUFFER_2)) {
     throw new Error("sock.send(BIG_TYPED_ARRAY_2) did not return false to indicate buffering on second synchronous call to send");
   }
 }
 
 // - connect, data and events work both ways
 add_test(connectSock);
 add_test(sendData);
 add_test(sendBig);
--- a/netwerk/base/public/Makefile.in
+++ b/netwerk/base/public/Makefile.in
@@ -25,16 +25,17 @@ SDK_XPIDLSRCS   = \
 		nsIURI.idl \
 		nsIURL.idl \
 		nsIFileURL.idl \
 		nsIUploadChannel.idl \
 		nsITraceableChannel.idl \
 		$(NULL)
 
 XPIDLSRCS	= \
+		nsIArrayBufferInputStream.idl \
 		nsIApplicationCache.idl \
 		nsIApplicationCacheChannel.idl \
 		nsIApplicationCacheContainer.idl \
 		nsIApplicationCacheService.idl \
 		nsIAuthInformation.idl \
 		nsIAuthPrompt.idl \
 		nsIAuthPrompt2.idl \
 		nsIAuthPromptAdapterFactory.idl \
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsIArrayBufferInputStream.idl
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsIInputStream.idl"
+
+/**
+ * nsIArrayBufferInputStream
+ *
+ * Provides scriptable methods for initializing a nsIInputStream
+ * implementation with an ArrayBuffer.
+ */
+[scriptable, uuid(3014dde6-aa1c-41db-87d0-48764a3710f6)]
+interface nsIArrayBufferInputStream : nsIInputStream
+{
+    /**
+     * SetData - assign an ArrayBuffer to the input stream.
+     *
+     * @param buffer    - stream data
+     * @param byteOffset - stream data offset
+     * @param byteLen - stream data length
+     */
+    [implicit_jscontext]
+    void setData(in jsval buffer, in unsigned long byteOffset, in unsigned long byteLen);
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ArrayBufferInputStream.cpp
@@ -0,0 +1,112 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+#include "ArrayBufferInputStream.h"
+#include "nsStreamUtils.h"
+#include "jsfriendapi.h"
+
+NS_IMPL_ISUPPORTS2(ArrayBufferInputStream, nsIArrayBufferInputStream, nsIInputStream);
+
+ArrayBufferInputStream::ArrayBufferInputStream()
+: mCx(nullptr)
+, mArrayBuffer(nullptr)
+, mBuffer(nullptr)
+, mBufferLength(0)
+, mOffset(0)
+, mClosed(false)
+{
+}
+
+ArrayBufferInputStream::~ArrayBufferInputStream()
+{
+  if (mArrayBuffer) {
+    JS_RemoveObjectRoot(mCx, &mArrayBuffer);
+  }
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::SetData(const JS::Value& aBuffer,
+                                uint32_t aByteOffset,
+                                uint32_t aLength,
+                                JSContext* aCx)
+{
+  mCx = aCx;
+  if (!aBuffer.isObject()) {
+    return NS_ERROR_FAILURE;
+  }
+  mArrayBuffer = &aBuffer.toObject();
+  JS_AddObjectRoot(mCx, &mArrayBuffer);
+  if (!JS_IsArrayBufferObject(mArrayBuffer, mCx)) {
+    return NS_ERROR_FAILURE;
+  }
+  uint32_t buflen = JS_GetArrayBufferByteLength(mArrayBuffer, mCx);
+  mOffset = std::min(buflen, aByteOffset);
+  mBufferLength = std::min(buflen - mOffset, aLength);
+  mBuffer = JS_GetArrayBufferData(mArrayBuffer, mCx);
+  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 - mOffset;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::Read(char* aBuf, uint32_t aCount, uint32_t *aReadCount)
+{
+  return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, aReadCount);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::ReadSegments(nsWriteSegmentFun writer, void *closure,
+                                     uint32_t aCount, uint32_t *result)
+{
+  NS_ASSERTION(result, "null ptr");
+  NS_ASSERTION(mBufferLength >= mOffset, "bad stream state");
+
+  if (mClosed) {
+    return NS_BASE_STREAM_CLOSED;
+  }
+
+  uint32_t remaining = mBufferLength - mOffset;
+  if (!remaining) {
+    *result = 0;
+    return NS_OK;
+  }
+
+  if (aCount > remaining) {
+    aCount = remaining;
+  }
+  nsresult rv = writer(this, closure, reinterpret_cast<char*>(mBuffer + mOffset),
+                       0, aCount, result);
+  if (NS_SUCCEEDED(rv)) {
+    NS_ASSERTION(*result <= aCount,
+                 "writer should not write more than we asked it to write");
+    mOffset += *result;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArrayBufferInputStream::IsNonBlocking(bool *aNonBlocking)
+{
+  *aNonBlocking = true;
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/ArrayBufferInputStream.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ArrayBufferInputStream_h
+#define ArrayBufferInputStream_h
+
+#include "nsIArrayBufferInputStream.h"
+#include "mozilla/Util.h"
+#include "jsapi.h"
+
+#define NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID "@mozilla.org/io/arraybuffer-input-stream;1"
+#define NS_ARRAYBUFFERINPUTSTREAM_CID                \
+{ /* 3014dde6-aa1c-41db-87d0-48764a3710f6 */       \
+    0x3014dde6,                                      \
+    0xaa1c,                                          \
+    0x41db,                                          \
+    {0x87, 0xd0, 0x48, 0x76, 0x4a, 0x37, 0x10, 0xf6} \
+}
+
+class ArrayBufferInputStream : public nsIArrayBufferInputStream {
+public:
+  ArrayBufferInputStream();
+  ~ArrayBufferInputStream();
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIARRAYBUFFERINPUTSTREAM
+  NS_DECL_NSIINPUTSTREAM
+
+private:
+  JSContext* mCx;
+  JSObject* mArrayBuffer;
+  uint8_t* mBuffer;
+  uint32_t mBufferLength;
+  uint32_t mOffset;
+  bool mClosed;
+};
+
+#endif // ArrayBufferInputStream_h
--- a/netwerk/base/src/Makefile.in
+++ b/netwerk/base/src/Makefile.in
@@ -17,16 +17,17 @@ LIBRARY_NAME	= neckobase_s
 LIBXUL_LIBRARY  = 1
 
 EXPORTS = \
 		nsMIMEInputStream.h \
 		nsURLHelper.h \
 		$(NULL)
 
 CPPSRCS		= \
+		ArrayBufferInputStream.cpp \
 		nsTransportUtils.cpp \
 		nsAsyncStreamCopier.cpp \
 		nsAsyncRedirectVerifyHelper.cpp \
 		nsAuthInformationHolder.cpp \
 		nsBaseChannel.cpp \
 		nsBaseContentStream.cpp \
 		nsBufferedStreams.cpp \
 		nsChannelClassifier.cpp \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -89,16 +89,19 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloa
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSyncStreamListener, Init)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeFileOutputStream)
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFileStream)
 
 NS_GENERIC_AGGREGATED_CONSTRUCTOR_INIT(nsLoadGroup, Init)
 
+#include "ArrayBufferInputStream.h"
+NS_GENERIC_FACTORY_CONSTRUCTOR(ArrayBufferInputStream)
+
 #include "nsEffectiveTLDService.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsEffectiveTLDService, Init)
 
 #include "nsSerializationHelper.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsSerializationHelper)
 
 #include "RedirectChannelRegistrar.h"
 typedef mozilla::net::RedirectChannelRegistrar RedirectChannelRegistrar;
@@ -676,16 +679,17 @@ NS_DEFINE_NAMED_CID(NS_PARTIALLOCALFILEI
 NS_DEFINE_NAMED_CID(NS_SAFELOCALFILEOUTPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_LOCALFILESTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_URICHECKER_CID);
 NS_DEFINE_NAMED_CID(NS_INCREMENTALDOWNLOAD_CID);
 NS_DEFINE_NAMED_CID(NS_STDURLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_NOAUTHURLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_AUTHURLPARSER_CID);
 NS_DEFINE_NAMED_CID(NS_STANDARDURL_CID);
+NS_DEFINE_NAMED_CID(NS_ARRAYBUFFERINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_BUFFEREDOUTPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_MIMEINPUTSTREAM_CID);
 NS_DEFINE_NAMED_CID(NS_PROTOCOLPROXYSERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_STREAMCONVERTERSERVICE_CID);
 #ifdef BUILD_APPLEFILE_DECODER
 NS_DEFINE_NAMED_CID(NS_APPLEFILEDECODER_CID);
 #endif
@@ -809,16 +813,17 @@ static const mozilla::Module::CIDEntry k
     { &kNS_SAFELOCALFILEOUTPUTSTREAM_CID, false, NULL, nsSafeFileOutputStreamConstructor },
     { &kNS_LOCALFILESTREAM_CID, false, NULL, nsFileStreamConstructor },
     { &kNS_URICHECKER_CID, false, NULL, nsURICheckerConstructor },
     { &kNS_INCREMENTALDOWNLOAD_CID, false, NULL, net_NewIncrementalDownload },
     { &kNS_STDURLPARSER_CID, false, NULL, nsStdURLParserConstructor },
     { &kNS_NOAUTHURLPARSER_CID, false, NULL, nsNoAuthURLParserConstructor },
     { &kNS_AUTHURLPARSER_CID, false, NULL, nsAuthURLParserConstructor },
     { &kNS_STANDARDURL_CID, false, NULL, nsStandardURLConstructor },
+    { &kNS_ARRAYBUFFERINPUTSTREAM_CID, false, NULL, ArrayBufferInputStreamConstructor },
     { &kNS_BUFFEREDINPUTSTREAM_CID, false, NULL, nsBufferedInputStream::Create },
     { &kNS_BUFFEREDOUTPUTSTREAM_CID, false, NULL, nsBufferedOutputStream::Create },
     { &kNS_MIMEINPUTSTREAM_CID, false, NULL, nsMIMEInputStreamConstructor },
     { &kNS_PROTOCOLPROXYSERVICE_CID, true, NULL, nsProtocolProxyServiceConstructor },
     { &kNS_STREAMCONVERTERSERVICE_CID, false, NULL, CreateNewStreamConvServiceFactory },
 #ifdef BUILD_APPLEFILE_DECODER
     { &kNS_APPLEFILEDECODER_CID, false, NULL, nsAppleFileDecoderConstructor },
 #endif
@@ -946,16 +951,17 @@ static const mozilla::Module::ContractID
     { NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, &kNS_SAFELOCALFILEOUTPUTSTREAM_CID },
     { NS_LOCALFILESTREAM_CONTRACTID, &kNS_LOCALFILESTREAM_CID },
     { NS_URICHECKER_CONTRACT_ID, &kNS_URICHECKER_CID },
     { NS_INCREMENTALDOWNLOAD_CONTRACTID, &kNS_INCREMENTALDOWNLOAD_CID },
     { NS_STDURLPARSER_CONTRACTID, &kNS_STDURLPARSER_CID },
     { NS_NOAUTHURLPARSER_CONTRACTID, &kNS_NOAUTHURLPARSER_CID },
     { NS_AUTHURLPARSER_CONTRACTID, &kNS_AUTHURLPARSER_CID },
     { NS_STANDARDURL_CONTRACTID, &kNS_STANDARDURL_CID },
+    { NS_ARRAYBUFFERINPUTSTREAM_CONTRACTID, &kNS_ARRAYBUFFERINPUTSTREAM_CID },
     { NS_BUFFEREDINPUTSTREAM_CONTRACTID, &kNS_BUFFEREDINPUTSTREAM_CID },
     { NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &kNS_BUFFEREDOUTPUTSTREAM_CID },
     { NS_MIMEINPUTSTREAM_CONTRACTID, &kNS_MIMEINPUTSTREAM_CID },
     { NS_PROTOCOLPROXYSERVICE_CONTRACTID, &kNS_PROTOCOLPROXYSERVICE_CID },
     { NS_STREAMCONVERTERSERVICE_CONTRACTID, &kNS_STREAMCONVERTERSERVICE_CID },
 #ifdef BUILD_APPLEFILE_DECODER
     { NS_IAPPLEFILEDECODER_CONTRACTID, &kNS_APPLEFILEDECODER_CID },
 #endif