Bug 676439 - v2: Websocket Binary Message support: DOM changes. r=smaug
authorJason Duell <jduell.mcbugs@gmail.com>
Thu, 15 Dec 2011 15:19:01 -0800
changeset 83519 55f353e4d6f4e338fe94258c5c7f1025d5286a80
parent 83518 9e94c7b5290f0c3591ba1182eeeadc65b5a318c9
child 83520 0b97cde3749314ee32c10c2257803359707493d5
push id628
push userclegnitto@mozilla.com
push dateWed, 21 Dec 2011 14:41:57 +0000
treeherdermozilla-aurora@24a61ad789e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs676439
milestone11.0a1
Bug 676439 - v2: Websocket Binary Message support: DOM changes. r=smaug
content/base/public/nsContentUtils.h
content/base/public/nsIMozWebSocket.idl
content/base/src/nsContentUtils.cpp
content/base/src/nsWebSocket.cpp
content/base/src/nsWebSocket.h
content/base/src/nsXMLHttpRequest.cpp
content/base/src/nsXMLHttpRequest.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -1597,16 +1597,22 @@ public:
                              // while there's a ref to it
                              nsIXPConnectJSObjectHolder** aHolder = nsnull,
                              bool aAllowWrapping = false)
   {
     return WrapNative(cx, scope, native, cache, nsnull, vp, aHolder,
                       aAllowWrapping);
   }
 
+  /**
+   * Creates an arraybuffer from a binary string.
+   */
+  static nsresult CreateArrayBuffer(JSContext *aCx, const nsACString& aData,
+                                    JSObject** aResult);
+
   static void StripNullChars(const nsAString& aInStr, nsAString& aOutStr);
 
   /**
    * Strip all \n, \r and nulls from the given string
    * @param aString the string to remove newlines from [in/out]
    */
   static void RemoveNewlines(nsString &aString);
 
--- a/content/base/public/nsIMozWebSocket.idl
+++ b/content/base/public/nsIMozWebSocket.idl
@@ -39,60 +39,66 @@
 
 #include "nsISupports.idl"
 
 interface nsIDOMEventListener;
 interface nsIPrincipal;
 interface nsIScriptContext;
 interface nsPIDOMWindow;
 interface nsIDOMDOMStringList;
+interface nsIVariant;
 
 %{C++
 #include "nsTArray.h"
 class nsString;
 %}
 [ref] native nsStringTArrayRef(nsTArray<nsString>);
 
 /**
  * The nsIMozWebSocket interface enables Web applications to maintain
  * bidirectional communications with server-side processes as described in:
  *
  * http://dev.w3.org/html5/websockets/
  *
  */
-[scriptable, uuid(5b124f54-7d46-4bc0-8507-e58ed22c19b9)]
+[scriptable, uuid(f463b9b5-1408-4057-9224-e4f5bc33f17b)]
 interface nsIMozWebSocket : nsISupports
 {
   readonly attribute DOMString url;
   readonly attribute DOMString extensions;
   readonly attribute DOMString protocol;
 
   //ready state
   const unsigned short CONNECTING = 0;
   const unsigned short OPEN = 1;
   const unsigned short CLOSING = 2;
   const unsigned short CLOSED = 3;
   readonly attribute unsigned short readyState;
 
   readonly attribute unsigned long bufferedAmount;
 
+  // "blob" by default: can set to "blob" or "arraybuffer": setting to other
+  // values will throw SYNTAX_ERR exception.
+  attribute DOMString binaryType;
+
   // event handler attributes
   attribute nsIDOMEventListener onopen;
   attribute nsIDOMEventListener onmessage;
   attribute nsIDOMEventListener onerror;
   attribute nsIDOMEventListener onclose;
 
   /**
-   * Transmits data using the connection.
-   *
-   * @param data The data to be transmited.
-   * @return if the connection is still established (and the data was queued or
-   *         sent successfully).
+   * Transmits data to other end of the connection.
+   * @param data The data to be transmitted.  Arraybuffers and Blobs are sent as
+   * binary data.  Strings are sent as UTF-8 text data.  Other types are
+   * converted to a String and sent as a String.
+   * @return if the connection is still established and the data was queued or
+   *         sent successfully.
    */
-  void send(in DOMString data);
+  void send(in nsIVariant data);
 
   /**
    * Closes the Web Socket connection or connection attempt, if any.
    * If the connection is already closed, it does nothing.
    */
   [optional_argc] void close([optional] in unsigned short code,
                              [optional] in DOMString reason);
 
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -40,16 +40,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 /* A namespace class for static layout utilities. */
 
 #include "mozilla/Util.h"
 
 #include "jsapi.h"
 #include "jsdbgapi.h"
+#include "jstypedarray.h"
 
 #include "nsJSUtils.h"
 #include "nsCOMPtr.h"
 #include "nsAString.h"
 #include "nsPrintfCString.h"
 #include "nsUnicharUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIScriptGlobalObject.h"
@@ -5251,16 +5252,39 @@ nsContentUtils::WrapNative(JSContext *cx
   }
   else {
     sXPConnect->Release();
   }
 
   return rv;
 }
 
+nsresult
+nsContentUtils::CreateArrayBuffer(JSContext *aCx, const nsACString& aData,
+                                  JSObject** aResult)
+{
+  if (!aCx) {
+    return NS_ERROR_FAILURE;
+  }
+
+  PRInt32 dataLen = aData.Length();
+  *aResult = js_CreateArrayBuffer(aCx, dataLen);
+  if (!*aResult) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (dataLen > 0) {
+    JSObject *abuf = js::ArrayBuffer::getArrayBuffer(*aResult);
+    NS_ASSERTION(abuf, "What happened?");
+    memcpy(JS_GetArrayBufferData(abuf), aData.BeginReading(), dataLen);
+  }
+
+  return NS_OK;
+}
+
 void
 nsContentUtils::StripNullChars(const nsAString& aInStr, nsAString& aOutStr)
 {
   // In common cases where we don't have nulls in the
   // string we can simple simply bypass the checking code.
   PRInt32 firstNullPos = aInStr.FindChar('\0');
   if (firstNullPos == kNotFound) {
     aOutStr.Assign(aInStr);
--- a/content/base/src/nsWebSocket.cpp
+++ b/content/base/src/nsWebSocket.cpp
@@ -18,16 +18,17 @@
  * The Initial Developer of the Original Code is
  * Wellington Fernando de Macedo.
  * Portions created by the Initial Developer are Copyright (C) 2009
  * the Initial Developer. All Rights Reserved.
  *
  * Contributor(s):
  *    Wellington Fernando de Macedo <wfernandom2004@gmail.com> (original author)
  *    Patrick McManus <mcmanus@ducksong.com>
+ *    Jason Duell <jduell.mcbugs@gmail.com>
  *
  * Alternatively, the contents of this file may be used under the terms of
  * either of the GNU General Public License Version 2 or later (the "GPL"),
  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -73,31 +74,34 @@
 #include "nsIScriptError.h"
 #include "nsNetUtil.h"
 #include "nsILoadGroup.h"
 #include "mozilla/Preferences.h"
 #include "nsDOMLists.h"
 #include "xpcpublic.h"
 #include "nsContentPolicyUtils.h"
 #include "nsContentErrors.h"
+#include "jstypedarray.h"
+#include "prmem.h"
+#include "nsDOMFile.h"
 
 using namespace mozilla;
 
 #define UTF_8_REPLACEMENT_CHAR    static_cast<PRUnichar>(0xFFFD)
 
-#define ENSURE_TRUE_AND_FAIL_IF_FAILED(x, ret)                            \
+#define TRUE_OR_FAIL_WEBSOCKET(x, ret)                                    \
   PR_BEGIN_MACRO                                                          \
     if (NS_UNLIKELY(!(x))) {                                              \
        NS_WARNING("ENSURE_TRUE_AND_FAIL_IF_FAILED(" #x ") failed");       \
        FailConnection();                                                  \
        return ret;                                                        \
     }                                                                     \
   PR_END_MACRO
 
-#define ENSURE_SUCCESS_AND_FAIL_IF_FAILED(res, ret)                       \
+#define SUCCESS_OR_FAIL_WEBSOCKET(res, ret)                               \
   PR_BEGIN_MACRO                                                          \
     nsresult __rv = res;                                                  \
     if (NS_FAILED(__rv)) {                                                \
       NS_ENSURE_SUCCESS_BODY(res, ret)                                    \
       FailConnection();                                                   \
       return ret;                                                         \
     }                                                                     \
   PR_END_MACRO
@@ -161,31 +165,32 @@ nsWebSocket::CloseConnection()
     return NS_OK;
 
   // Disconnect() can release this object, so we keep a
   // reference until the end of the method
   nsRefPtr<nsWebSocket> kungfuDeathGrip = this;
 
   if (mReadyState == nsIMozWebSocket::CONNECTING) {
     SetReadyState(nsIMozWebSocket::CLOSED);
-    if (mWebSocketChannel)
-      mWebSocketChannel->Close(mClientReasonCode, mClientReason);
+    if (mChannel) {
+      mChannel->Close(mClientReasonCode, mClientReason);
+    }
     Disconnect();
     return NS_OK;
   }
 
   SetReadyState(nsIMozWebSocket::CLOSING);
 
   if (mDisconnected) {
     SetReadyState(nsIMozWebSocket::CLOSED);
     Disconnect();
     return NS_OK;
   }
 
-  return mWebSocketChannel->Close(mClientReasonCode, mClientReason);
+  return mChannel->Close(mClientReasonCode, mClientReason);
 }
 
 nsresult
 nsWebSocket::ConsoleError()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
@@ -241,60 +246,69 @@ nsWebSocket::Disconnect()
   if (loadGroup)
     loadGroup->RemoveRequest(this, nsnull, NS_OK);
 
   // DontKeepAliveAnyMore() can release the object. So hold a reference to this
   // until the end of the method.
   nsRefPtr<nsWebSocket> kungfuDeathGrip = this;
 
   DontKeepAliveAnyMore();
-  mWebSocketChannel = nsnull;
+  mChannel = nsnull;
   mDisconnected = true;
 
   return NS_OK;
 }
 
 //-----------------------------------------------------------------------------
 // nsWebSocket::nsIWebSocketListener methods:
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsWebSocket::OnMessageAvailable(nsISupports *aContext, const nsACString & aMsg)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-
   NS_ABORT_IF_FALSE(!mDisconnected, "Received message after disconnecting");
 
   // Dispatch New Message
-  nsresult rv = CreateAndDispatchMessageEvent(aMsg);
+  nsresult rv = CreateAndDispatchMessageEvent(aMsg, false);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to dispatch the message event");
   }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocket::OnBinaryMessageAvailable(nsISupports *aContext,
                                       const nsACString & aMsg)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-  return NS_ERROR_NOT_IMPLEMENTED;
+  NS_ABORT_IF_FALSE(!mDisconnected, "Received message after disconnecting");
+
+  // Dispatch New Message
+  nsresult rv = CreateAndDispatchMessageEvent(aMsg, true);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to dispatch the message event");
+  }
+
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocket::OnStart(nsISupports *aContext)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   if (mDisconnected)
     return NS_OK;
 
-  if (!mRequestedProtocolList.IsEmpty())
-    mWebSocketChannel->GetProtocol(mEstablishedProtocol);
+  if (!mRequestedProtocolList.IsEmpty()) {
+    mChannel->GetProtocol(mEstablishedProtocol);
+  }
 
-  mWebSocketChannel->GetExtensions(mEstablishedExtensions);
+  mChannel->GetExtensions(mEstablishedExtensions);
 
   SetReadyState(nsIMozWebSocket::OPEN);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocket::OnStop(nsISupports *aContext, nsresult aStatusCode)
 {
@@ -387,16 +401,17 @@ nsWebSocket::nsWebSocket() : mKeepingAli
                              mCheckMustKeepAlive(true),
                              mTriggeredCloseEvent(false),
                              mClosedCleanly(false),
                              mDisconnected(false),
                              mClientReasonCode(0),
                              mServerReasonCode(nsIWebSocketChannel::CLOSE_ABNORMAL),
                              mReadyState(nsIMozWebSocket::CONNECTING),
                              mOutgoingBufferedAmount(0),
+                             mBinaryType(WS_BINARY_TYPE_BLOB),
                              mScriptLine(0),
                              mInnerWindowID(0)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsLayoutStatics::AddRef();
 }
 
 nsWebSocket::~nsWebSocket()
@@ -412,29 +427,29 @@ NS_IMPL_CYCLE_COLLECTION_CLASS(nsWebSock
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsWebSocket,
                                                   nsDOMEventTargetWrapperCache)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnOpenListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnMessageListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnCloseListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mURI)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mWebSocketChannel)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mChannel)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsWebSocket,
                                                 nsDOMEventTargetWrapperCache)
   tmp->Disconnect();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnOpenListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnMessageListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnCloseListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mOnErrorListener)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPrincipal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mURI)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mWebSocketChannel)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mChannel)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 DOMCI_DATA(MozWebSocket, nsWebSocket)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsWebSocket)
   NS_INTERFACE_MAP_ENTRY(nsIMozWebSocket)
   NS_INTERFACE_MAP_ENTRY(nsIJSNativeInitializer)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
@@ -571,29 +586,29 @@ class nsAutoCloseWS
 {
 public:
   nsAutoCloseWS(nsWebSocket *aWebSocket)
     : mWebSocket(aWebSocket)
   {}
 
   ~nsAutoCloseWS()
   {
-    if (!mWebSocket->mWebSocketChannel) {
+    if (!mWebSocket->mChannel) {
       mWebSocket->CloseConnection();
     }
   }
 private:
   nsRefPtr<nsWebSocket> mWebSocket;
 };
 
 nsresult
 nsWebSocket::EstablishConnection()
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
-  NS_ABORT_IF_FALSE(!mWebSocketChannel, "mWebSocketChannel should be null");
+  NS_ABORT_IF_FALSE(!mChannel, "mChannel should be null");
 
   nsresult rv;
 
   nsCOMPtr<nsIWebSocketChannel> wsChannel;
   nsAutoCloseWS autoClose(this);
 
   if (mSecure) {
     wsChannel =
@@ -627,17 +642,17 @@ nsWebSocket::EstablishConnection()
   rv = nsContentUtils::GetASCIIOrigin(mPrincipal, asciiOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   ToLowerCase(asciiOrigin);
 
   rv = wsChannel->AsyncOpen(mURI, asciiOrigin, this, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  mWebSocketChannel = wsChannel;
+  mChannel = wsChannel;
 
   return NS_OK;
 }
 
 class nsWSCloseEvent : public nsRunnable
 {
 public:
 nsWSCloseEvent(nsWebSocket *aWebSocket, bool aWasClean, 
@@ -685,49 +700,62 @@ nsWebSocket::CreateAndDispatchSimpleEven
   nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
   rv = privateEvent->SetTrusted(true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchDOMEvent(nsnull, event, nsnull, nsnull);
 }
 
 nsresult
-nsWebSocket::CreateAndDispatchMessageEvent(const nsACString& aData)
+nsWebSocket::CreateAndDispatchMessageEvent(const nsACString& aData,
+                                           bool isBinary)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
   rv = CheckInnerWindowCorrectness();
-  if (NS_FAILED(rv)) {
+  if (NS_FAILED(rv))
     return NS_OK;
-  }
 
-  // Let's play get the JSContext
+  // Get the JSContext
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(mOwner);
   NS_ENSURE_TRUE(sgo, NS_ERROR_FAILURE);
 
   nsIScriptContext* scriptContext = sgo->GetContext();
   NS_ENSURE_TRUE(scriptContext, NS_ERROR_FAILURE);
 
   JSContext* cx = scriptContext->GetNativeContext();
   NS_ENSURE_TRUE(cx, NS_ERROR_FAILURE);
 
-  // Now we can turn our string into a jsval
-
+  // Create appropriate JS object for message
   jsval jsData;
   {
-    NS_ConvertUTF8toUTF16 utf16Data(aData);
-    JSString* jsString;
     JSAutoRequest ar(cx);
-    jsString = JS_NewUCStringCopyN(cx,
-                                   utf16Data.get(),
-                                   utf16Data.Length());
-    NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
+    if (isBinary) {
+      if (mBinaryType == WS_BINARY_TYPE_BLOB) {
+        rv = CreateResponseBlob(aData, cx, jsData);
+        NS_ENSURE_SUCCESS(rv, rv);
+      } else if (mBinaryType == WS_BINARY_TYPE_ARRAYBUFFER) {
+        JSObject *arrayBuf;
+        rv = nsContentUtils::CreateArrayBuffer(cx, aData, &arrayBuf);
+        NS_ENSURE_SUCCESS(rv, rv);
+        jsData = OBJECT_TO_JSVAL(arrayBuf);
+      } else {
+        NS_RUNTIMEABORT("Unknown binary type!");
+        return NS_ERROR_UNEXPECTED;
+      }
+    } else {
+      // JS string
+      NS_ConvertUTF8toUTF16 utf16Data(aData);
+      JSString* jsString;
+      jsString = JS_NewUCStringCopyN(cx, utf16Data.get(), utf16Data.Length());
+      NS_ENSURE_TRUE(jsString, NS_ERROR_FAILURE);
 
-    jsData = STRING_TO_JSVAL(jsString);
+      jsData = STRING_TO_JSVAL(jsString);
+    }
   }
 
   // create an event that uses the MessageEvent interface,
   // which does not bubble, is not cancelable, and has no default action
 
   nsCOMPtr<nsIDOMEvent> event;
   rv = NS_NewDOMMessageEvent(getter_AddRefs(event), nsnull, nsnull);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -742,16 +770,35 @@ nsWebSocket::CreateAndDispatchMessageEve
 
   nsCOMPtr<nsIPrivateDOMEvent> privateEvent = do_QueryInterface(event);
   rv = privateEvent->SetTrusted(true);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return DispatchDOMEvent(nsnull, event, nsnull, nsnull);
 }
 
+// Initial implementation: only stores to RAM, not file
+// TODO: bug 704447: large file support
+nsresult
+nsWebSocket::CreateResponseBlob(const nsACString& aData, JSContext *aCx,
+                                jsval &jsData)
+{
+  PRUint32 blobLen = aData.Length();
+  void *blobData = PR_Malloc(blobLen);
+  nsCOMPtr<nsIDOMBlob> blob;
+  if (blobData) {
+    memcpy(blobData, aData.BeginReading(), blobLen);
+    blob = new nsDOMMemoryFile(blobData, blobLen, EmptyString());
+  } else {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  JSObject* scope = JS_GetGlobalForScopeChain(aCx);
+  return nsContentUtils::WrapNative(aCx, scope, blob, &jsData, nsnull, true);
+}
+
 nsresult
 nsWebSocket::CreateAndDispatchCloseEvent(bool aWasClean,
                                          PRUint16 aCode,
                                          const nsString &aReason)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
@@ -1061,16 +1108,46 @@ nsWebSocket::GetReadyState(PRUint16 *aRe
 
 NS_IMETHODIMP
 nsWebSocket::GetBufferedAmount(PRUint32 *aBufferedAmount)
 {
   *aBufferedAmount = mOutgoingBufferedAmount;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsWebSocket::GetBinaryType(nsAString& aBinaryType)
+{
+  switch (mBinaryType) {
+  case WS_BINARY_TYPE_ARRAYBUFFER:
+    aBinaryType.AssignLiteral("arraybuffer");
+    break;
+  case WS_BINARY_TYPE_BLOB:
+    aBinaryType.AssignLiteral("blob");
+    break;
+  default:
+    NS_ERROR("Should not happen");
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWebSocket::SetBinaryType(const nsAString& aBinaryType)
+{
+  if (aBinaryType.EqualsLiteral("arraybuffer")) {
+    mBinaryType = WS_BINARY_TYPE_ARRAYBUFFER;
+  } else if (aBinaryType.EqualsLiteral("blob")) {
+    mBinaryType = WS_BINARY_TYPE_BLOB;
+  } else  {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  return NS_OK;
+}
+
 #define NS_WEBSOCKET_IMPL_DOMEVENTLISTENER(_eventlistenername, _eventlistener) \
   NS_IMETHODIMP                                                                \
   nsWebSocket::GetOn##_eventlistenername(nsIDOMEventListener * *aEventListener)\
   {                                                                            \
     return GetInnerEventListener(_eventlistener, aEventListener);              \
   }                                                                            \
                                                                                \
   NS_IMETHODIMP                                                                \
@@ -1101,80 +1178,180 @@ ContainsUnpairedSurrogates(const nsAStri
       }
       continue;
     }
   }
   return false;
 }
 
 NS_IMETHODIMP
-nsWebSocket::Send(const nsAString& aData)
+nsWebSocket::Send(nsIVariant *aData)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
 
   if (mReadyState == nsIMozWebSocket::CONNECTING) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  if (ContainsUnpairedSurrogates(aData))
-    return NS_ERROR_DOM_SYNTAX_ERR;
+  nsCString msgString;
+  nsCOMPtr<nsIInputStream> msgStream;
+  bool isBinary;
+  PRUint32 msgLen;
+  nsresult rv = GetSendParams(aData, msgString, msgStream, isBinary, msgLen);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Always increment outgoing buffer len, even if closed
+  mOutgoingBufferedAmount += msgLen;
 
   if (mReadyState == nsIMozWebSocket::CLOSING ||
       mReadyState == nsIMozWebSocket::CLOSED) {
-    mOutgoingBufferedAmount += NS_ConvertUTF16toUTF8(aData).Length();
     return NS_OK;
   }
 
-  nsString message = PromiseFlatString(aData);
+  NS_ASSERTION(mReadyState == nsIMozWebSocket::OPEN,
+               "Unknown state in nsWebSocket::Send");
+
+  if (msgStream) {
+    rv = mChannel->SendBinaryStream(msgStream, msgLen);
+  } else {
+    if (isBinary) {
+      rv = mChannel->SendBinaryMsg(msgString);
+    } else {
+      rv = mChannel->SendMsg(msgString);
+    }
+  }
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  UpdateMustKeepAlive();
+
+  return NS_OK;
+}
+
+nsresult
+nsWebSocket::GetSendParams(nsIVariant *aData, nsCString &aStringOut,
+                           nsCOMPtr<nsIInputStream> &aStreamOut,
+                           bool &aIsBinary, PRUint32 &aOutgoingLength)
+{
+  // Get type of data (arraybuffer, blob, or string)
+  PRUint16 dataType;
+  nsresult rv = aData->GetDataType(&dataType);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (dataType == nsIDataType::VTYPE_INTERFACE ||
+      dataType == nsIDataType::VTYPE_INTERFACE_IS) {
+    nsCOMPtr<nsISupports> supports;
+    nsID *iid;
+    rv = aData->GetAsInterface(&iid, getter_AddRefs(supports));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsMemory::Free(iid);
+
+    // ArrayBuffer?
+    jsval realVal;
+    JSObject* obj;
+    nsresult rv = aData->GetAsJSVal(&realVal);
+    if (NS_SUCCEEDED(rv) && !JSVAL_IS_PRIMITIVE(realVal) &&
+        (obj = JSVAL_TO_OBJECT(realVal)) &&
+        (js_IsArrayBuffer(obj))) {
+      PRInt32 len = JS_GetArrayBufferByteLength(obj);
+      char* data = (char*)JS_GetArrayBufferData(obj);
+
+      aStringOut.Assign(data, len);
+      aIsBinary = true;
+      aOutgoingLength = len;
+      return NS_OK;
+    }
+
+    // Blob?
+    nsCOMPtr<nsIDOMBlob> blob = do_QueryInterface(supports);
+    if (blob) {
+      rv = blob->GetInternalStream(getter_AddRefs(aStreamOut));
+      NS_ENSURE_SUCCESS(rv, rv);
+
+      // GetSize() should not perform blocking I/O (unlike Available())
+      PRUint64 blobLen;
+      rv = blob->GetSize(&blobLen);
+      NS_ENSURE_SUCCESS(rv, rv);
+      if (blobLen > PR_UINT32_MAX) {
+        return NS_ERROR_FILE_TOO_BIG;
+      }
+      aOutgoingLength = static_cast<PRUint32>(blobLen);
+
+      aIsBinary = true;
+      return NS_OK;
+    }
+  }
+
+  // Text message: if not already a string, turn it into one.
+  // TODO: bug 704444: Correctly coerce any JS type to string
+  //
+  PRUnichar* data = nsnull;
+  PRUint32 len = 0;
+  rv = aData->GetAsWStringWithSize(&len, &data);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsString text;
+  text.Adopt(data, len);
+
+  if (ContainsUnpairedSurrogates(text)) {
+    return NS_ERROR_DOM_SYNTAX_ERR;
+  }
+
+  rv = ConvertTextToUTF8(text, aStringOut);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  aIsBinary = false;
+  aOutgoingLength = aStringOut.Length();
+  return NS_OK;
+}
+
+nsresult
+nsWebSocket::ConvertTextToUTF8(const nsString& aMessage, nsCString& buf)
+{
+  NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
   nsresult rv;
 
   nsCOMPtr<nsICharsetConverterManager> ccm =
     do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv);
-  ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv);
+  SUCCESS_OR_FAIL_WEBSOCKET(rv, rv);
 
   nsCOMPtr<nsIUnicodeEncoder> converter;
   rv = ccm->GetUnicodeEncoder("UTF-8", getter_AddRefs(converter));
-  ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv);
+  SUCCESS_OR_FAIL_WEBSOCKET(rv, rv);
 
   rv = converter->SetOutputErrorBehavior(nsIUnicodeEncoder::kOnError_Replace,
                                          nsnull, UTF_8_REPLACEMENT_CHAR);
-  ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv);
+  SUCCESS_OR_FAIL_WEBSOCKET(rv, rv);
 
-  PRInt32 inLen = message.Length();
+  PRInt32 inLen = aMessage.Length();
   PRInt32 maxLen;
-  rv = converter->GetMaxLength(message.BeginReading(), inLen, &maxLen);
-  ENSURE_SUCCESS_AND_FAIL_IF_FAILED(rv, rv);
+  rv = converter->GetMaxLength(aMessage.BeginReading(), inLen, &maxLen);
+  SUCCESS_OR_FAIL_WEBSOCKET(rv, rv);
 
-  nsCString buf;
   buf.SetLength(maxLen);
-  ENSURE_TRUE_AND_FAIL_IF_FAILED(buf.Length() == static_cast<PRUint32>(maxLen),
-                                 NS_ERROR_OUT_OF_MEMORY);
+  TRUE_OR_FAIL_WEBSOCKET(buf.Length() == static_cast<PRUint32>(maxLen),
+                         NS_ERROR_OUT_OF_MEMORY);
 
   char* start = buf.BeginWriting();
 
   PRInt32 outLen = maxLen;
-  rv = converter->Convert(message.BeginReading(), &inLen, start, &outLen);
+  rv = converter->Convert(aMessage.BeginReading(), &inLen, start, &outLen);
   if (NS_SUCCEEDED(rv)) {
     PRInt32 outLen2 = maxLen - outLen;
     rv = converter->Finish(start + outLen, &outLen2);
     outLen += outLen2;
   }
   if (NS_FAILED(rv) || rv == NS_ERROR_UENC_NOMAPPING) {
     // Yes, NS_ERROR_UENC_NOMAPPING is a success code
     return NS_ERROR_DOM_SYNTAX_ERR;
   }
 
   buf.SetLength(outLen);
-  ENSURE_TRUE_AND_FAIL_IF_FAILED(buf.Length() == static_cast<PRUint32>(outLen),
-                                 NS_ERROR_UNEXPECTED);
-
-  mOutgoingBufferedAmount += buf.Length();
-  mWebSocketChannel->SendMsg(buf);
-
-  UpdateMustKeepAlive();
+  TRUE_OR_FAIL_WEBSOCKET(buf.Length() == static_cast<PRUint32>(outLen),
+                         NS_ERROR_UNEXPECTED);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsWebSocket::Close(PRUint16 code, const nsAString & reason, PRUint8 argc)
 {
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "Not running on main thread");
--- a/content/base/src/nsWebSocket.h
+++ b/content/base/src/nsWebSocket.h
@@ -117,32 +117,42 @@ protected:
   nsresult Disconnect();
 
   nsresult ConsoleError();
   nsresult PrintErrorOnConsole(const char       *aBundleURI,
                                const PRUnichar  *aError,
                                const PRUnichar **aFormatStrings,
                                PRUint32          aFormatStringsLen);
 
+  nsresult ConvertTextToUTF8(const nsString& aMessage, nsCString& buf);
+
+  // Get msg info out of JS variable being sent (string, arraybuffer, blob)
+  nsresult GetSendParams(nsIVariant *aData, nsCString &aStringOut,
+                         nsCOMPtr<nsIInputStream> &aStreamOut,
+                         bool &aIsBinary, PRUint32 &aOutgoingLength);
+
   nsresult CreateAndDispatchSimpleEvent(const nsString& aName);
-  nsresult CreateAndDispatchMessageEvent(const nsACString& aData);
+  nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
+                                         bool isBinary);
   nsresult CreateAndDispatchCloseEvent(bool aWasClean, PRUint16 aCode,
                                        const nsString &aReason);
+  nsresult CreateResponseBlob(const nsACString& aData, JSContext *aCx,
+                              jsval &jsData);
 
   void SetReadyState(PRUint16 aNewReadyState);
 
   // if there are "strong event listeners" (see comment in nsWebSocket.cpp) or
   // outgoing not sent messages then this method keeps the object alive
   // when js doesn't have strong references to it.
   void UpdateMustKeepAlive();
   // ATTENTION, when calling this method the object can be released
   // (and possibly collected).
   void DontKeepAliveAnyMore();
 
-  nsCOMPtr<nsIWebSocketChannel> mWebSocketChannel;
+  nsCOMPtr<nsIWebSocketChannel> mChannel;
 
   nsRefPtr<nsDOMEventListenerWrapper> mOnOpenListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnErrorListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnMessageListener;
   nsRefPtr<nsDOMEventListenerWrapper> mOnCloseListener;
 
   // related to the WebSocket constructor steps
   nsString mOriginalURL;
@@ -151,18 +161,18 @@ protected:
 
   bool mKeepingAlive;
   bool mCheckMustKeepAlive;
   bool mTriggeredCloseEvent;
   bool mClosedCleanly;
   bool mDisconnected;
 
   nsCString mClientReason;
+  nsString  mServerReason;
   PRUint16  mClientReasonCode;
-  nsString  mServerReason;
   PRUint16  mServerReasonCode;
 
   nsCString mAsciiHost;  // hostname
   PRUint32  mPort;
   nsCString mResource; // [filepath[?query]]
   nsString  mUTF16Origin;
 
   nsCOMPtr<nsIURI> mURI;
@@ -171,16 +181,22 @@ protected:
   nsCString mEstablishedExtensions;
 
   PRUint16 mReadyState;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   PRUint32 mOutgoingBufferedAmount;
 
+  enum
+  {
+    WS_BINARY_TYPE_ARRAYBUFFER,
+    WS_BINARY_TYPE_BLOB,
+  } mBinaryType;
+
   // Web Socket owner information:
   // - the script file name, UTF8 encoded.
   // - source code line number where the Web Socket object was constructed.
   // - the ID of the inner window where the script lives. Note that this may not
   //   be the same as the Web Socket owner window.
   // These attributes are used for error reporting.
   nsCString mScriptFile;
   PRUint32 mScriptLine;
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -925,39 +925,16 @@ nsXMLHttpRequest::CreateResponseParsedJS
                     (jschar*)mResponseText.get(),
                     mResponseText.Length(), &mResultJSON)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
-nsresult
-nsXMLHttpRequest::CreateResponseArrayBuffer(JSContext *aCx)
-{
-  if (!aCx){
-    return NS_ERROR_FAILURE;
-  }
-
-  PRInt32 dataLen = mResponseBody.Length();
-  RootResultArrayBuffer();
-  mResultArrayBuffer = js_CreateArrayBuffer(aCx, dataLen);
-  if (!mResultArrayBuffer) {
-    return NS_ERROR_FAILURE;
-  }
-
-  if (dataLen > 0) {
-    JSObject *abuf = js::ArrayBuffer::getArrayBuffer(mResultArrayBuffer);
-    NS_ASSERTION(abuf, "What happened?");
-    memcpy(JS_GetArrayBufferData(abuf), mResponseBody.BeginReading(), dataLen);
-  }
-
-  return NS_OK;
-}
-
 /* attribute AString responseType; */
 NS_IMETHODIMP nsXMLHttpRequest::GetResponseType(nsAString& aResponseType)
 {
   switch (mResponseType) {
   case XML_HTTP_RESPONSE_TYPE_DEFAULT:
     aResponseType.Truncate();
     break;
   case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER:
@@ -1066,17 +1043,19 @@ NS_IMETHODIMP nsXMLHttpRequest::GetRespo
 
   case XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER:
   case XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER:
     if ((mResponseType == XML_HTTP_RESPONSE_TYPE_ARRAYBUFFER &&
          mState & XML_HTTP_REQUEST_DONE) ||
         (mResponseType == XML_HTTP_RESPONSE_TYPE_CHUNKED_ARRAYBUFFER &&
          mInLoadProgressEvent)) {
       if (!mResultArrayBuffer) {
-         rv = CreateResponseArrayBuffer(aCx);
+         RootResultArrayBuffer();
+         rv = nsContentUtils::CreateArrayBuffer(aCx, mResponseBody,
+                                                &mResultArrayBuffer);
          NS_ENSURE_SUCCESS(rv, rv);
       }
       *aResult = OBJECT_TO_JSVAL(mResultArrayBuffer);
     } else {
       *aResult = JSVAL_NULL;
     }
     break;
 
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -211,17 +211,16 @@ protected:
   nsresult AppendToResponseText(const char * aBuffer, PRUint32 aBufferLen);
   static NS_METHOD StreamReaderFunc(nsIInputStream* in,
                 void* closure,
                 const char* fromRawSegment,
                 PRUint32 toOffset,
                 PRUint32 count,
                 PRUint32 *writeCount);
   nsresult CreateResponseParsedJSON(JSContext* aCx);
-  nsresult CreateResponseArrayBuffer(JSContext* aCx);
   bool CreateResponseBlob(nsIRequest *request);
   // Change the state of the object with this. The broadcast argument
   // determines if the onreadystatechange listener should be called.
   nsresult ChangeState(PRUint32 aState, bool aBroadcast = true);
   already_AddRefed<nsILoadGroup> GetLoadGroup() const;
   nsIURI *GetBaseURI();
 
   nsresult RemoveAddEventListener(const nsAString& aType,