Bug 1228474 - Support sending blob/arraybuffer for data channel. r=smaug
authorKershaw Chang <kechang@mozilla.com>
Sat, 17 Sep 2016 19:42:00 -0400
changeset 314367 9cb90011a89673ca47abbf4fd953490cfea37484
parent 314366 c3eb99e7b16d9a12b6624dc47af220bc0b9fd7b2
child 314368 672663f1a298ef69d17742a8da54c44f3b788aca
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1228474
milestone51.0a1
Bug 1228474 - Support sending blob/arraybuffer for data channel. r=smaug
dom/presentation/PresentationConnection.cpp
dom/presentation/PresentationConnection.h
dom/presentation/PresentationDataChannelSessionTransport.js
dom/presentation/PresentationService.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/PresentationTCPSessionTransport.cpp
dom/presentation/interfaces/nsIPresentationListener.idl
dom/presentation/interfaces/nsIPresentationService.idl
dom/presentation/interfaces/nsIPresentationSessionTransport.idl
dom/presentation/ipc/PPresentation.ipdl
dom/presentation/ipc/PresentationChild.cpp
dom/presentation/ipc/PresentationChild.h
dom/presentation/ipc/PresentationContentSessionInfo.cpp
dom/presentation/ipc/PresentationContentSessionInfo.h
dom/presentation/ipc/PresentationIPCService.cpp
dom/presentation/ipc/PresentationIPCService.h
dom/presentation/ipc/PresentationParent.cpp
dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html
dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
dom/webidl/PresentationConnection.webidl
--- a/dom/presentation/PresentationConnection.cpp
+++ b/dom/presentation/PresentationConnection.cpp
@@ -4,16 +4,17 @@
  * 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 "PresentationConnection.h"
 
 #include "ControllerConnectionCollection.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/File.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/PresentationConnectionClosedEvent.h"
 #include "mozilla/ErrorNames.h"
 #include "mozilla/DebugOnly.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationService.h"
@@ -49,16 +50,17 @@ PresentationConnection::PresentationConn
                                                const nsAString& aUrl,
                                                const uint8_t aRole,
                                                PresentationConnectionList* aList)
   : DOMEventTargetHelper(aWindow)
   , mId(aId)
   , mUrl(aUrl)
   , mState(PresentationConnectionState::Connecting)
   , mOwningConnectionList(aList)
+  , mBinaryType(PresentationConnectionBinaryType::Arraybuffer)
 {
   MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
              aRole == nsIPresentationService::ROLE_RECEIVER);
   mRole = aRole;
 }
 
 /* virtual */ PresentationConnection::~PresentationConnection()
 {
@@ -164,36 +166,142 @@ PresentationConnection::GetUrl(nsAString
 }
 
 PresentationConnectionState
 PresentationConnection::State() const
 {
   return mState;
 }
 
+PresentationConnectionBinaryType
+PresentationConnection::BinaryType() const
+{
+  return mBinaryType;
+}
+
+void
+PresentationConnection::SetBinaryType(PresentationConnectionBinaryType aType)
+{
+  mBinaryType = aType;
+}
+
 void
 PresentationConnection::Send(const nsAString& aData,
                              ErrorResult& aRv)
 {
   // Sending is not allowed if the session is not connected.
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
-    aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send message due to an internal error."));
     return;
   }
 
   nsresult rv = service->SendSessionMessage(mId, mRole, aData);
   if(NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_OPERATION_ERR);
+    const uint32_t kMaxMessageLength = 256;
+    nsAutoString data(Substring(aData, 0, kMaxMessageLength));
+
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send message: \"") + data +
+      NS_LITERAL_STRING("\""));
+  }
+}
+
+void
+PresentationConnection::Send(Blob& aData,
+                             ErrorResult& aRv)
+{
+  if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send message due to an internal error."));
+    return;
+  }
+
+  nsresult rv = service->SendSessionBlob(mId, mRole, &aData);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send binary message for Blob message."));
+  }
+}
+
+void
+PresentationConnection::Send(const ArrayBuffer& aData,
+                             ErrorResult& aRv)
+{
+  if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send message due to an internal error."));
+    return;
+  }
+
+  aData.ComputeLengthAndData();
+
+  static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+  uint32_t length = aData.Length();
+  char* data = reinterpret_cast<char*>(aData.Data());
+  nsDependentCSubstring msgString(data, length);
+
+  nsresult rv = service->SendSessionBinaryMsg(mId, mRole, msgString);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send binary message for ArrayBuffer message."));
+  }
+}
+
+void
+PresentationConnection::Send(const ArrayBufferView& aData,
+                             ErrorResult& aRv)
+{
+  if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send message due to an internal error."));
+    return;
+  }
+
+  aData.ComputeLengthAndData();
+
+  static_assert(sizeof(*aData.Data()) == 1, "byte-sized data required");
+
+  uint32_t length = aData.Length();
+  char* data = reinterpret_cast<char*>(aData.Data());
+  nsDependentCSubstring msgString(data, length);
+
+  nsresult rv = service->SendSessionBinaryMsg(mId, mRole, msgString);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to send binary message for ArrayBufferView message."));
   }
 }
 
 void
 PresentationConnection::Close(ErrorResult& aRv)
 {
   // It only works when the state is CONNECTED or CONNECTING.
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected &&
@@ -347,41 +455,75 @@ PresentationConnection::ProcessStateChan
     default:
       MOZ_CRASH("Unknown presentation session state.");
       return NS_ERROR_INVALID_ARG;
   }
 }
 
 NS_IMETHODIMP
 PresentationConnection::NotifyMessage(const nsAString& aSessionId,
-                                      const nsACString& aData)
+                                      const nsACString& aData,
+                                      bool aIsBinary)
 {
   PRES_DEBUG("connection %s:id[%s], data[%s], role[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(aSessionId).get(),
              nsPromiseFlatCString(aData).get(), mRole);
 
   if (!aSessionId.Equals(mId)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // No message should be expected when the session is not connected.
   if (NS_WARN_IF(mState != PresentationConnectionState::Connected)) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
+  if (NS_WARN_IF(NS_FAILED(DoReceiveMessage(aData, aIsBinary)))) {
+    AsyncCloseConnectionWithErrorMsg(
+      NS_LITERAL_STRING("Unable to receive a message."));
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationConnection::DoReceiveMessage(const nsACString& aData, bool aIsBinary)
+{
   // Transform the data.
   AutoJSAPI jsapi;
   if (!jsapi.Init(GetOwner())) {
     return NS_ERROR_FAILURE;
   }
   JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> jsData(cx);
-  NS_ConvertUTF8toUTF16 utf16Data(aData);
-  if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) {
-    return NS_ERROR_FAILURE;
+
+  nsresult rv;
+  if (aIsBinary) {
+    if (mBinaryType == PresentationConnectionBinaryType::Blob) {
+      rv = nsContentUtils::CreateBlobBuffer(cx, GetOwner(), aData, &jsData);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+    } else if (mBinaryType == PresentationConnectionBinaryType::Arraybuffer) {
+      JS::Rooted<JSObject*> arrayBuf(cx);
+      rv = nsContentUtils::CreateArrayBuffer(cx, aData, arrayBuf.address());
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+      jsData.setObject(*arrayBuf);
+    } else {
+      NS_RUNTIMEABORT("Unknown binary type!");
+      return NS_ERROR_UNEXPECTED;
+    }
+  } else {
+    NS_ConvertUTF8toUTF16 utf16Data(aData);
+    if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) {
+      return NS_ERROR_FAILURE;
+    }
   }
 
   return DispatchMessageEvent(jsData);
 }
 
 NS_IMETHODIMP
 PresentationConnection::NotifyReplaced()
 {
@@ -391,33 +533,39 @@ PresentationConnection::NotifyReplaced()
   return NotifyStateChange(mId,
                            nsIPresentationSessionListener::STATE_CLOSED,
                            NS_OK);
 }
 
 nsresult
 PresentationConnection::DispatchConnectionClosedEvent(
   PresentationConnectionClosedReason aReason,
-  const nsAString& aMessage)
+  const nsAString& aMessage,
+  bool aDispatchNow)
 {
   if (mState != PresentationConnectionState::Closed) {
     MOZ_ASSERT(false, "The connection state should be closed.");
     return NS_ERROR_FAILURE;
   }
 
   PresentationConnectionClosedEventInit init;
   init.mReason = aReason;
   init.mMessage = aMessage;
 
   RefPtr<PresentationConnectionClosedEvent> closedEvent =
     PresentationConnectionClosedEvent::Constructor(this,
                                                    NS_LITERAL_STRING("close"),
                                                    init);
   closedEvent->SetTrusted(true);
 
+  if (aDispatchNow) {
+    bool ignore;
+    return DOMEventTargetHelper::DispatchEvent(closedEvent, &ignore);
+  }
+
   RefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(this, static_cast<Event*>(closedEvent));
   return asyncDispatcher->PostDOMEvent();
 }
 
 nsresult
 PresentationConnection::DispatchMessageEvent(JS::Handle<JS::Value> aData)
 {
@@ -574,8 +722,44 @@ PresentationConnection::RemoveFromLoadGr
   nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
   if (loadGroup) {
     mWeakLoadGroup = nullptr;
     return loadGroup->RemoveRequest(this, nullptr, NS_OK);
   }
 
   return NS_OK;
 }
+
+void
+PresentationConnection::AsyncCloseConnectionWithErrorMsg(const nsAString& aMessage)
+{
+  if (mState == PresentationConnectionState::Terminated) {
+    return;
+  }
+
+  nsString message = nsString(aMessage);
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableFunction([this, message]() -> void {
+      // Set |mState| to |PresentationConnectionState::Closed| here to avoid
+      // calling |ProcessStateChanged|.
+      mState = PresentationConnectionState::Closed;
+
+      // Make sure dispatching the event and closing the connection are invoked
+      // at the same time by setting |aDispatchNow| to true.
+      Unused << NS_WARN_IF(NS_FAILED(
+        DispatchConnectionClosedEvent(PresentationConnectionClosedReason::Error,
+                                      message,
+                                      true)));
+
+      nsCOMPtr<nsIPresentationService> service =
+        do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+      if(NS_WARN_IF(!service)) {
+        return;
+      }
+
+      Unused << NS_WARN_IF(NS_FAILED(
+        service->CloseSession(mId,
+                              mRole,
+                              nsIPresentationService::CLOSED_REASON_ERROR)));
+    });
+
+  Unused << NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(r)));
+}
--- a/dom/presentation/PresentationConnection.h
+++ b/dom/presentation/PresentationConnection.h
@@ -3,26 +3,28 @@
 /* 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 mozilla_dom_PresentationConnection_h
 #define mozilla_dom_PresentationConnection_h
 
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/TypedArray.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/dom/PresentationConnectionBinding.h"
 #include "mozilla/dom/PresentationConnectionClosedEventBinding.h"
 #include "nsIPresentationListener.h"
 #include "nsIRequest.h"
 #include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 
+class Blob;
 class PresentationConnectionList;
 
 class PresentationConnection final : public DOMEventTargetHelper
                                    , public nsIPresentationSessionListener
                                    , public nsIRequest
                                    , public SupportsWeakPtr<PresentationConnection>
 {
 public:
@@ -47,19 +49,32 @@ public:
 
   // WebIDL (public APIs)
   void GetId(nsAString& aId) const;
 
   void GetUrl(nsAString& aUrl) const;
 
   PresentationConnectionState State() const;
 
+  PresentationConnectionBinaryType BinaryType() const;
+
+  void SetBinaryType(PresentationConnectionBinaryType aType);
+
   void Send(const nsAString& aData,
             ErrorResult& aRv);
 
+  void Send(Blob& aData,
+            ErrorResult& aRv);
+
+  void Send(const ArrayBuffer& aData,
+            ErrorResult& aRv);
+
+  void Send(const ArrayBufferView& aData,
+            ErrorResult& aRv);
+
   void Close(ErrorResult& aRv);
 
   void Terminate(ErrorResult& aRv);
 
   bool
   Equals(uint64_t aWindowId, const nsAString& aId);
 
   IMPL_EVENT_HANDLER(connect);
@@ -78,30 +93,36 @@ private:
 
   bool Init();
 
   void Shutdown();
 
   nsresult ProcessStateChanged(nsresult aReason);
 
   nsresult DispatchConnectionClosedEvent(PresentationConnectionClosedReason aReason,
-                                         const nsAString& aMessage);
+                                         const nsAString& aMessage,
+                                         bool aDispatchNow = false);
 
   nsresult DispatchMessageEvent(JS::Handle<JS::Value> aData);
 
   nsresult ProcessConnectionWentAway();
 
   nsresult AddIntoLoadGroup();
 
   nsresult RemoveFromLoadGroup();
 
+  void AsyncCloseConnectionWithErrorMsg(const nsAString& aMessage);
+
+  nsresult DoReceiveMessage(const nsACString& aData, bool aIsBinary);
+
   nsString mId;
   nsString mUrl;
   uint8_t mRole;
   PresentationConnectionState mState;
   RefPtr<PresentationConnectionList> mOwningConnectionList;
-  nsWeakPtr mWeakLoadGroup;;
+  nsWeakPtr mWeakLoadGroup;
+  PresentationConnectionBinaryType mBinaryType;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationConnection_h
--- a/dom/presentation/PresentationDataChannelSessionTransport.js
+++ b/dom/presentation/PresentationDataChannelSessionTransport.js
@@ -22,17 +22,17 @@ const PRESENTATIONTRANSPORTBUILDER_CONTR
 
 function PresentationDataChannelDescription(aDataChannelSDP) {
   this._dataChannelSDP = JSON.stringify(aDataChannelSDP);
 }
 
 PresentationDataChannelDescription.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
   get type() {
-    return nsIPresentationChannelDescription.TYPE_DATACHANNEL;
+    return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
   },
   get tcpAddress() {
     return null;
   },
   get tcpPort() {
     return null;
   },
   get dataChannelSDP() {
@@ -89,16 +89,18 @@ PresentationTransportBuilder.prototype =
       case Ci.nsIPresentationService.ROLE_CONTROLLER:
         this._dataChannel = this._peerConnection.createDataChannel("presentationAPI");
         this._setDataChannel();
         break;
 
       case Ci.nsIPresentationService.ROLE_RECEIVER:
         this._peerConnection.ondatachannel = aEvent => {
           this._dataChannel = aEvent.channel;
+          // Ensure the binaryType of dataChannel is blob.
+          this._dataChannel.binaryType = "blob";
           this._setDataChannel();
         }
         break;
       default:
        throw Cr.NS_ERROR_ILLEGAL_VALUE;
     }
 
     // TODO bug 1228235 we should have a way to let device providers customize
@@ -129,17 +131,17 @@ PresentationTransportBuilder.prototype =
 
   _setDataChannel: function() {
     this._dataChannel.onopen = () => {
       log("data channel is open, notify the listener, role " + this._role);
 
       // Handoff the ownership of _peerConnection and _dataChannel to
       // _sessionTransport
       this._sessionTransport = new PresentationTransport();
-      this._sessionTransport.init(this._peerConnection, this._dataChannel);
+      this._sessionTransport.init(this._peerConnection, this._dataChannel, this._window);
       this._peerConnection = this._dataChannel = null;
 
       this._listener.onSessionTransport(this._sessionTransport);
       this._sessionTransport.callback.notifyTransportReady();
 
       this._cleanup(Cr.NS_OK);
     };
 
@@ -238,21 +240,22 @@ function PresentationTransport() {
   this._closeReason = Cr.NS_OK;
 }
 
 PresentationTransport.prototype = {
   classID: PRESENTATIONTRANSPORT_CID,
   contractID: PRESENTATIONTRANSPORT_CONTRACTID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport]),
 
-  init: function(aPeerConnection, aDataChannel) {
+  init: function(aPeerConnection, aDataChannel, aWindow) {
     log("initWithDataChannel");
     this._enableDataNotification = false;
     this._dataChannel = aDataChannel;
     this._peerConnection = aPeerConnection;
+    this._window = aWindow;
 
     this._dataChannel.onopen = () => {
       log("data channel reopen. Should never touch here");
     };
 
     this._dataChannel.onclose = () => {
       log("data channel onclose");
       if (this._callback) {
@@ -264,17 +267,17 @@ PresentationTransport.prototype = {
     this._dataChannel.onmessage = aEvent => {
       log("data channel onmessage " + aEvent.data);
 
       if (!this._enableDataNotification || !this._callback) {
         log("queue message");
         this._messageQueue.push(aEvent.data);
         return;
       }
-      this._callback.notifyData(aEvent.data);
+      this._doNotifyData(aEvent.data);
     };
 
     this._dataChannel.onerror = aError => {
       log("data channel onerror " + aError.name + ":" + aError.message);
       if (this._callback) {
         this._callback.notifyTransportClosed(Cr.NS_ERROR_FAILURE);
       }
       this._cleanup();
@@ -294,29 +297,46 @@ PresentationTransport.prototype = {
     this._callback = aCallback;
   },
 
   send: function(aData) {
     log("send " + aData);
     this._dataChannel.send(aData);
   },
 
+  sendBinaryMsg: function(aData) {
+    log("sendBinaryMsg");
+
+    let array = new Uint8Array(aData.length);
+    for (let i = 0; i < aData.length; i++) {
+      array[i] = aData.charCodeAt(i);
+    }
+
+    this._dataChannel.send(array);
+  },
+
+  sendBlob: function(aBlob) {
+    log("sendBlob");
+
+    this._dataChannel.send(aBlob);
+  },
+
   enableDataNotification: function() {
     log("enableDataNotification");
     if (this._enableDataNotification) {
       return;
     }
 
     if (!this._callback) {
       throw NS_ERROR_NOT_AVAILABLE;
     }
 
     this._enableDataNotification = true;
 
-    this._messageQueue.forEach(aData => this._callback.notifyData(aData));
+    this._messageQueue.forEach(aData => this._doNotifyData(aData));
     this._messageQueue = [];
   },
 
   close: function(aReason) {
     this._closeReason = aReason;
 
     this._dataChannel.close();
   },
@@ -325,13 +345,30 @@ PresentationTransport.prototype = {
     this._dataChannel = null;
 
     if (this._peerConnection) {
       this._peerConnection.close();
       this._peerConnection = null;
     }
     this._callback = null;
     this._messageQueue = [];
+    this._window = null;
+  },
+
+  _doNotifyData: function(aData) {
+    if (!this._callback) {
+      throw NS_ERROR_NOT_AVAILABLE;
+    }
+
+    if (aData instanceof this._window.Blob) {
+      let reader = new this._window.FileReader();
+      reader.addEventListener("load", (aEvent) => {
+        this._callback.notifyData(aEvent.target.result, true);
+      });
+      reader.readAsBinaryString(aData);
+    } else {
+      this._callback.notifyData(aData, false);
+    }
   },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationTransportBuilder,
                                                      PresentationTransport]);
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -772,16 +772,54 @@ PresentationService::SendSessionMessage(
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return info->Send(aData);
 }
 
 NS_IMETHODIMP
+PresentationService::SendSessionBinaryMsg(const nsAString& aSessionId,
+                                          uint8_t aRole,
+                                          const nsACString &aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aData.IsEmpty());
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+             aRole == nsIPresentationService::ROLE_RECEIVER);
+
+  RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->SendBinaryMsg(aData);
+}
+
+NS_IMETHODIMP
+PresentationService::SendSessionBlob(const nsAString& aSessionId,
+                                     uint8_t aRole,
+                                     nsIDOMBlob* aBlob)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+             aRole == nsIPresentationService::ROLE_RECEIVER);
+  MOZ_ASSERT(aBlob);
+
+  RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->SendBlob(aBlob);
+}
+
+NS_IMETHODIMP
 PresentationService::CloseSession(const nsAString& aSessionId,
                                   uint8_t aRole,
                                   uint8_t aClosedReason)
 {
   PRES_DEBUG("%s:id[%s], reason[%x], role[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(aSessionId).get(), aClosedReason, aRole);
 
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -280,16 +280,44 @@ PresentationSessionInfo::Send(const nsAS
   if (NS_WARN_IF(!mTransport)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return mTransport->Send(aData);
 }
 
 nsresult
+PresentationSessionInfo::SendBinaryMsg(const nsACString& aData)
+{
+  if (NS_WARN_IF(!IsSessionReady())) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mTransport->SendBinaryMsg(aData);
+}
+
+nsresult
+PresentationSessionInfo::SendBlob(nsIDOMBlob* aBlob)
+{
+  if (NS_WARN_IF(!IsSessionReady())) {
+    return NS_ERROR_DOM_INVALID_STATE_ERR;
+  }
+
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mTransport->SendBlob(aBlob);
+}
+
+nsresult
 PresentationSessionInfo::Close(nsresult aReason,
                                uint32_t aState)
 {
   if (NS_WARN_IF(!IsSessionReady())) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   // Do nothing if session is already terminated.
@@ -470,29 +498,29 @@ PresentationSessionInfo::NotifyTransport
     // Directly untrack the session info from the service.
     return UntrackFromService();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationSessionInfo::NotifyData(const nsACString& aData)
+PresentationSessionInfo::NotifyData(const nsACString& aData, bool aIsBinary)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!IsSessionReady())) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   if (NS_WARN_IF(!mListener)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  return mListener->NotifyMessage(mSessionId, aData);
+  return mListener->NotifyMessage(mSessionId, aData, aIsBinary);
 }
 
 // nsIPresentationSessionTransportBuilderListener
 NS_IMETHODIMP
 PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
 {
   PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -94,16 +94,20 @@ public:
     mControlChannel = aControlChannel;
     if (mControlChannel) {
       mControlChannel->SetListener(this);
     }
   }
 
   nsresult Send(const nsAString& aData);
 
+  nsresult SendBinaryMsg(const nsACString& aData);
+
+  nsresult SendBlob(nsIDOMBlob* aBlob);
+
   nsresult Close(nsresult aReason,
                  uint32_t aState);
 
   nsresult OnTerminate(nsIPresentationControlChannel* aControlChannel);
 
   nsresult ReplyError(nsresult aReason);
 
   virtual bool IsAccessible(base::ProcessId aProcessId);
--- a/dom/presentation/PresentationTCPSessionTransport.cpp
+++ b/dom/presentation/PresentationTCPSessionTransport.cpp
@@ -413,16 +413,28 @@ PresentationTCPSessionTransport::Send(co
   mMultiplexStream->AppendStream(stream);
 
   EnsureCopying();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+PresentationTCPSessionTransport::SendBinaryMsg(const nsACString& aData)
+{
+  return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
+
+NS_IMETHODIMP
+PresentationTCPSessionTransport::SendBlob(nsIDOMBlob* aBlob)
+{
+  return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+}
+
+NS_IMETHODIMP
 PresentationTCPSessionTransport::Close(nsresult aReason)
 {
   PRES_DEBUG("%s:reason[%x]\n", __func__, aReason);
 
   if (mReadyState == ReadyState::CLOSED || mReadyState == ReadyState::CLOSING) {
     return NS_OK;
   }
 
@@ -568,10 +580,10 @@ PresentationTCPSessionTransport::OnDataA
 
   nsCString data;
   nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Pass the incoming data to the listener.
-  return mCallback->NotifyData(data);
+  return mCallback->NotifyData(data, false);
 }
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -27,17 +27,18 @@ interface nsIPresentationSessionListener
   void notifyStateChange(in DOMString sessionId,
                          in unsigned short state,
                          in nsresult reason);
 
   /*
    * Called when receive messages.
    */
   void notifyMessage(in DOMString sessionId,
-                     in ACString data);
+                     in ACString data,
+                     in boolean isBinary);
 
   /*
    * Called when this listener is replaced by another one.
    */
   void notifyReplaced();
 };
 
 [scriptable, uuid(27f101d7-9ed1-429e-b4f8-43b00e8e111c)]
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -1,14 +1,15 @@
 /* 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 "nsISupports.idl"
 
+interface nsIDOMBlob;
 interface nsIDOMEventTarget;
 interface nsIInputStream;
 interface nsIPresentationAvailabilityListener;
 interface nsIPresentationRespondingListener;
 interface nsIPresentationSessionListener;
 interface nsIPresentationTransportBuilderConstructor;
 
 %{C++
@@ -91,16 +92,38 @@ interface nsIPresentationService : nsISu
    * @param role: Identify the function called by controller or receiver.
    * @param data: the message being sent out.
    */
   void sendSessionMessage(in DOMString sessionId,
 							            in uint8_t role,
                           in DOMString data);
 
   /*
+   * Send the binary message to the session.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   * @param role: Identify the function called by controller or receiver.
+   * @param data: the message being sent out.
+   */
+  void sendSessionBinaryMsg(in DOMString sessionId,
+                            in uint8_t role,
+                            in ACString data);
+
+  /*
+   * Send the blob to the session.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   * @param role: Identify the function called by controller or receiver.
+   * @param blob: The input blob to be sent.
+   */
+  void sendSessionBlob(in DOMString sessionId,
+                       in uint8_t role,
+                       in nsIDOMBlob blob);
+
+  /*
    * Close the session.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    */
   void closeSession(in DOMString sessionId,
                     in uint8_t role,
                     in uint8_t closedReason);
--- a/dom/presentation/interfaces/nsIPresentationSessionTransport.idl
+++ b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl
@@ -1,31 +1,32 @@
 /* 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 "nsISupports.idl"
 
+interface nsIDOMBlob;
 interface nsIInputStream;
 interface nsINetAddr;
 
 %{C++
 #define PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID \
   "@mozilla.org/presentation/presentationtcpsessiontransport;1"
 %}
 
 /*
  * The callback for session transport events.
  */
 [scriptable, uuid(9f158786-41a6-4a10-b29b-9497f25d4b67)]
 interface nsIPresentationSessionTransportCallback : nsISupports
 {
   void notifyTransportReady();
   void notifyTransportClosed(in nsresult reason);
-  void notifyData(in ACString data);
+  void notifyData(in ACString data, in boolean isBinary);
 };
 
 /*
  * App-to-App transport channel for the presentation session.
  */
 [scriptable, uuid(670b7e1b-65be-42b6-a596-be571907fa18)]
 interface nsIPresentationSessionTransport : nsISupports
 {
@@ -44,13 +45,25 @@ interface nsIPresentationSessionTranspor
 
   /*
    * Send message to the remote endpoint.
    * @param data The message to send.
    */
   void send(in DOMString data);
 
   /*
+   * Send the binary message to the remote endpoint.
+   * @param data: the message being sent out.
+   */
+  void sendBinaryMsg(in ACString data);
+
+  /*
+   * Send the blob to the remote endpoint.
+   * @param blob: The input blob to be sent.
+   */
+  void sendBlob(in nsIDOMBlob blob);
+
+  /*
    * Close this session transport.
    * @param reason The reason for closing this session transport.
    */
   void close(in nsresult reason);
 };
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -74,17 +74,17 @@ sync protocol PPresentation
   manages PPresentationBuilder;
   manages PPresentationRequest;
 
 child:
   async NotifyAvailableChange(bool aAvailable);
   async NotifySessionStateChange(nsString aSessionId,
                                  uint16_t aState,
                                  nsresult aReason);
-  async NotifyMessage(nsString aSessionId, nsCString aData);
+  async NotifyMessage(nsString aSessionId, nsCString aData, bool aIsBinary);
   async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId);
 
   async PPresentationBuilder(nsString aSessionId, uint8_t aRole);
 
 parent:
   async __delete__();
 
   async RegisterAvailabilityHandler();
--- a/dom/presentation/ipc/PresentationChild.cpp
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -107,20 +107,23 @@ PresentationChild::RecvNotifySessionStat
                                                                       aState,
                                                                       aReason)));
   }
   return true;
 }
 
 bool
 PresentationChild::RecvNotifyMessage(const nsString& aSessionId,
-                                     const nsCString& aData)
+                                     const nsCString& aData,
+                                     const bool& aIsBinary)
 {
   if (mService) {
-    Unused << NS_WARN_IF(NS_FAILED(mService->NotifyMessage(aSessionId, aData)));
+    Unused << NS_WARN_IF(NS_FAILED(mService->NotifyMessage(aSessionId,
+                                                           aData,
+                                                           aIsBinary)));
   }
   return true;
 }
 
 bool
 PresentationChild::RecvNotifySessionConnect(const uint64_t& aWindowId,
                                             const nsString& aSessionId)
 {
--- a/dom/presentation/ipc/PresentationChild.h
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -47,17 +47,18 @@ public:
 
   virtual bool
   RecvNotifySessionStateChange(const nsString& aSessionId,
                                const uint16_t& aState,
                                const nsresult& aReason) override;
 
   virtual bool
   RecvNotifyMessage(const nsString& aSessionId,
-                    const nsCString& aData) override;
+                    const nsCString& aData,
+                    const bool& aIsBinary) override;
 
   virtual bool
   RecvNotifySessionConnect(const uint64_t& aWindowId,
                            const nsString& aSessionId) override;
 
 private:
   virtual ~PresentationChild();
 
--- a/dom/presentation/ipc/PresentationContentSessionInfo.cpp
+++ b/dom/presentation/ipc/PresentationContentSessionInfo.cpp
@@ -31,16 +31,36 @@ PresentationContentSessionInfo::Send(con
   if (!mTransport) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return mTransport->Send(aData);
 }
 
 nsresult
+PresentationContentSessionInfo::SendBinaryMsg(const nsACString& aData)
+{
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mTransport->SendBinaryMsg(aData);
+}
+
+nsresult
+PresentationContentSessionInfo::SendBlob(nsIDOMBlob* aBlob)
+{
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return mTransport->SendBlob(aBlob);
+}
+
+nsresult
 PresentationContentSessionInfo::Close(nsresult aReason)
 {
   if (!mTransport) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return mTransport->Close(aReason);
 }
@@ -66,23 +86,24 @@ PresentationContentSessionInfo::NotifyTr
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   return static_cast<PresentationIPCService*>(service.get())->
            NotifyTransportClosed(mSessionId, mRole, aReason);
 }
 
 NS_IMETHODIMP
-PresentationContentSessionInfo::NotifyData(const nsACString& aData)
+PresentationContentSessionInfo::NotifyData(const nsACString& aData,
+                                           bool aIsBinary)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   return static_cast<PresentationIPCService*>(service.get())->
-           NotifyMessage(mSessionId, aData);
+           NotifyMessage(mSessionId, aData, aIsBinary);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/ipc/PresentationContentSessionInfo.h
+++ b/dom/presentation/ipc/PresentationContentSessionInfo.h
@@ -37,16 +37,20 @@ public:
                aRole == nsIPresentationService::ROLE_RECEIVER);
     MOZ_ASSERT(aTransport);
   }
 
   nsresult Init();
 
   nsresult Send(const nsAString& aData);
 
+  nsresult SendBinaryMsg(const nsACString& aData);
+
+  nsresult SendBlob(nsIDOMBlob* aBlob);
+
   nsresult Close(nsresult aReason);
 
 private:
   virtual ~PresentationContentSessionInfo() {}
 
   nsString mSessionId;
   uint8_t mRole;
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -97,16 +97,56 @@ PresentationIPCService::SendSessionMessa
   }
 
   return SendRequest(nullptr, SendSessionMessageRequest(nsString(aSessionId),
                                                         aRole,
                                                         nsString(aData)));
 }
 
 NS_IMETHODIMP
+PresentationIPCService::SendSessionBinaryMsg(const nsAString& aSessionId,
+                                             uint8_t aRole,
+                                             const nsACString &aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aData.IsEmpty());
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+             aRole == nsIPresentationService::ROLE_RECEIVER);
+
+  RefPtr<PresentationContentSessionInfo> info;
+  // data channel session transport is maintained by content process
+  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+    return info->SendBinaryMsg(aData);
+  }
+
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::SendSessionBlob(const nsAString& aSessionId,
+                                        uint8_t aRole,
+                                        nsIDOMBlob* aBlob)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aSessionId.IsEmpty());
+  MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
+             aRole == nsIPresentationService::ROLE_RECEIVER);
+  MOZ_ASSERT(aBlob);
+
+  RefPtr<PresentationContentSessionInfo> info;
+  // data channel session transport is maintained by content process
+  if (mSessionInfos.Get(aSessionId, getter_AddRefs(info))) {
+    return info->SendBlob(aBlob);
+  }
+
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
+NS_IMETHODIMP
 PresentationIPCService::CloseSession(const nsAString& aSessionId,
                                      uint8_t aRole,
                                      uint8_t aClosedReason)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
   nsresult rv = SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId),
                                                          aRole,
@@ -327,24 +367,25 @@ PresentationIPCService::NotifySessionSta
   }
 
   return listener->NotifyStateChange(aSessionId, aState, aReason);
 }
 
 // Only used for OOP RTCDataChannel session transport case.
 nsresult
 PresentationIPCService::NotifyMessage(const nsAString& aSessionId,
-                                      const nsACString& aData)
+                                      const nsACString& aData,
+                                      const bool& aIsBinary)
 {
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
     return NS_OK;
   }
 
-  return listener->NotifyMessage(aSessionId, aData);
+  return listener->NotifyMessage(aSessionId, aData, aIsBinary);
 }
 
 // Only used for OOP RTCDataChannel session transport case.
 nsresult
 PresentationIPCService::NotifyTransportClosed(const nsAString& aSessionId,
                                               uint8_t aRole,
                                               nsresult aReason)
 {
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -32,17 +32,18 @@ public:
 
   nsresult NotifyAvailableChange(bool aAvailable);
 
   nsresult NotifySessionStateChange(const nsAString& aSessionId,
                                     uint16_t aState,
                                     nsresult aReason);
 
   nsresult NotifyMessage(const nsAString& aSessionId,
-                         const nsACString& aData);
+                         const nsACString& aData,
+                         const bool& aIsBinary);
 
   nsresult NotifySessionConnect(uint64_t aWindowId,
                                 const nsAString& aSessionId);
 
   void NotifyPresentationChildDestroyed();
 
   nsresult MonitorResponderLoading(const nsAString& aSessionId,
                                    nsIDocShell* aDocShell);
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -310,20 +310,23 @@ PresentationParent::NotifyReplaced()
 {
   // Do nothing here, since |PresentationIPCService::RegisterSessionListener|
   // already dealt with this in content process.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyMessage(const nsAString& aSessionId,
-                                  const nsACString& aData)
+                                  const nsACString& aData,
+                                  bool aIsBinary)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifyMessage(nsString(aSessionId), nsCString(aData)))) {
+                 !SendNotifyMessage(nsString(aSessionId),
+                                    nsCString(aData),
+                                    aIsBinary))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifySessionConnect(uint64_t aWindowId,
                                          const nsAString& aSessionId)
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -291,17 +291,17 @@ const mockedSessionTransport = {
   close: function(reason) {
     sendAsyncMessage('data-transport-closed', reason);
     this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
   },
   simulateTransportReady: function() {
     this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
   },
   simulateIncomingMessage: function(message) {
-    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message);
+    this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message, false);
   },
   onOffer: function(aOffer) {
   },
   onAnswer: function(aAnswer) {
   }
 };
 
 const mockedNetworkInfo = {
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript1UA.js
@@ -72,16 +72,28 @@ const mockControlChannelOfSender = {
     mockControlChannelOfReceiver.disconnect();
   },
   terminate: function(presentationId) {
     sendAsyncMessage('sender-terminate');
   },
   reconnect: function(presentationId, url) {
     sendAsyncMessage('start-reconnect', url);
   },
+  sendIceCandidate: function(candidate) {
+    mockControlChannelOfReceiver.notifyIceCandidate(candidate);
+  },
+  notifyIceCandidate: function(candidate) {
+    if (!this._listener) {
+      return;
+    }
+
+    this._listener
+        .QueryInterface(Ci.nsIPresentationControlChannelListener)
+        .onIceCandidate(candidate);
+  },
 };
 
 // control channel of receiver
 const mockControlChannelOfReceiver = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
   set listener(listener) {
     // PresentationPresentingInfo::SetControlChannel
     if (listener) {
@@ -125,17 +137,29 @@ const mockControlChannelOfReceiver = {
     }
 
     this._listener
         .QueryInterface(Ci.nsIPresentationControlChannelListener)
         .notifyDisconnected(reason);
     sendAsyncMessage('control-channel-receiver-closed', reason);
   },
   terminate: function(presentaionId) {
-  }
+  },
+  sendIceCandidate: function(candidate) {
+    mockControlChannelOfReceiver.notifyIceCandidate(candidate);
+  },
+  notifyIceCandidate: function(candidate) {
+    if (!this._listener) {
+      return;
+    }
+
+    this._listener
+        .QueryInterface(Ci.nsIPresentationControlChannelListener)
+        .onIceCandidate(candidate);
+  },
 };
 
 const mockDevice = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
   id:   'id',
   name: 'name',
   type: 'type',
   establishControlChannel: function(url, presentationId) {
--- a/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionFrameScript.js
@@ -129,17 +129,17 @@ function loadPrivilegedScriptTest() {
       sendMessage('data-transport-closed', reason);
       this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason);
       this._callback = null;
     },
     simulateTransportReady: function() {
       this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady();
     },
     simulateIncomingMessage: function(message) {
-      this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message);
+      this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message, false);
     },
     onOffer: function(aOffer) {
       this._listener.sendAnswer(mockedChannelDescription);
       this._onSessionTransport();
     },
     onAnswer: function(aAnswer) {
       this._onSessionTransport();
     },
--- a/dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html
+++ b/dom/presentation/tests/mochitest/file_presentation_1ua_receiver.html
@@ -31,16 +31,37 @@ function command(name, data) {
   alert('COMMAND ' + JSON.stringify({name: name, data: data}));
 }
 
 function finish() {
   alert('DONE');
 }
 
 var connection;
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
+const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
+const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
+TYPED_DATA_ARRAY.set(DATA_ARRAY);
+
+function is_same_buffer(recv_data, expect_data) {
+  let recv_dataview = new Uint8Array(recv_data);
+  let expected_dataview = new Uint8Array(expect_data);
+
+  if (recv_dataview.length !== expected_dataview.length) {
+    return false;
+  }
+
+  for (let i = 0; i < recv_dataview.length; i++) {
+    if (recv_dataview[i] != expected_dataview[i]) {
+      info('discover byte differenct at ' + i);
+      return false;
+    }
+  }
+  return true;
+}
 
 function testConnectionAvailable() {
   return new Promise(function(aResolve, aReject) {
     info('Receiver: --- testConnectionAvailable ---');
     ok(navigator.presentation, "Receiver: navigator.presentation should be available.");
     ok(navigator.presentation.receiver, "Receiver: navigator.presentation.receiver should be available.");
 
     navigator.presentation.receiver.connectionList
@@ -100,16 +121,29 @@ function testSendMessage() {
       if (message.type === 'message-from-receiver-received') {
         window.removeEventListener('hashchange', hashchangeHandler);
         aResolve();
       }
     });
   });
 }
 
+function testIncomingBlobMessage() {
+  return new Promise(function(aResolve, aReject) {
+    info('Receiver: --- testIncomingBlobMessage ---');
+    connection.send('testIncomingBlobMessage');
+    connection.addEventListener('message', function messageHandler(evt) {
+      connection.removeEventListener('message', messageHandler);
+      let recvData= String.fromCharCode.apply(null, new Uint8Array(evt.data));
+      is(recvData, "Hello World", 'expected same string data');
+      aResolve();
+    });
+  });
+}
+
 function testConnectionClosed() {
   return new Promise(function(aResolve, aReject) {
     info('Receiver: --- testConnectionClosed ---');
     connection.onclose = function() {
       connection.onclose = null;
       is(connection.state, "closed", "Receiver: Connection should be closed.");
       command('forward-command', JSON.stringify({ name: 'receiver-closed' }));
       aResolve();
@@ -125,23 +159,56 @@ function testReconnectConnection() {
     connection.onconnect = function() {
       connection.onconnect = null;
       ok(true, "The connection is reconnected.")
       aResolve();
     };
   });
 }
 
+function testIncomingArrayBuffer() {
+  return new Promise(function(aResolve, aReject) {
+    info('Receiver: --- testIncomingArrayBuffer ---');
+    connection.binaryType = "blob";
+    connection.send('testIncomingArrayBuffer');
+    connection.addEventListener('message', function messageHandler(evt) {
+      connection.removeEventListener('message', messageHandler);
+      var fileReader = new FileReader();
+      fileReader.onload = function() {
+        ok(is_same_buffer(DATA_ARRAY_BUFFER, this.result), "expected same buffer data");
+        aResolve();
+      };
+      fileReader.readAsArrayBuffer(evt.data);
+    });
+  });
+}
+
+function testIncomingArrayBufferView() {
+  return new Promise(function(aResolve, aReject) {
+    info('Receiver: --- testIncomingArrayBufferView ---');
+    connection.binaryType = "arraybuffer";
+    connection.send('testIncomingArrayBufferView');
+    connection.addEventListener('message', function messageHandler(evt) {
+      connection.removeEventListener('message', messageHandler);
+      ok(is_same_buffer(evt.data, TYPED_DATA_ARRAY), "expected same buffer data");
+      aResolve();
+    });
+  });
+}
+
 function runTests() {
   testConnectionAvailable()
   .then(testConnectionReady)
   .then(testIncomingMessage)
   .then(testSendMessage)
+  .then(testIncomingBlobMessage)
   .then(testConnectionClosed)
   .then(testReconnectConnection)
+  .then(testIncomingArrayBuffer)
+  .then(testIncomingArrayBufferView)
   .then(testConnectionClosed);
 }
 
 runTests();
 
 </script>
   </body>
 </html>
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
@@ -10,16 +10,20 @@ function debug(str) {
 }
 
 var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript1UA.js'));
 var receiverUrl = SimpleTest.getTestFileURL('file_presentation_1ua_receiver.html');
 var request;
 var connection;
 var receiverIframe;
 var presentationId;
+const DATA_ARRAY = [0, 255, 254, 0, 1, 2, 3, 0, 255, 255, 254, 0];
+const DATA_ARRAY_BUFFER = new ArrayBuffer(DATA_ARRAY.length);
+const TYPED_DATA_ARRAY = new Uint8Array(DATA_ARRAY_BUFFER);
+TYPED_DATA_ARRAY.set(DATA_ARRAY);
 
 function postMessageToIframe(aType) {
   receiverIframe.src = receiverUrl + "#" +
                        encodeURIComponent(JSON.stringify({ type: aType }));
 }
 
 function setup() {
 
@@ -154,16 +158,56 @@ function testIncomingMessage() {
       is(msg, "msg-receiver-to-sender", "Sender: Sender should receive message from Receiver");
       postMessageToIframe('message-from-receiver-received');
       aResolve();
     });
     postMessageToIframe('trigger-message-from-receiver');
   });
 }
 
+function testSendBlobMessage() {
+  return new Promise(function(aResolve, aReject) {
+    info('Sender: --- testSendBlobMessage ---');
+    connection.addEventListener('message', function messageHandler(evt) {
+      connection.removeEventListener('message', messageHandler);
+      let msg = evt.data;
+      is(msg, "testIncomingBlobMessage", "Sender: Sender should receive message from Receiver");
+      let blob = new Blob(["Hello World"], {type : 'text/plain'});
+      connection.send(blob);
+      aResolve();
+    });
+  });
+}
+
+function testSendArrayBuffer() {
+  return new Promise(function(aResolve, aReject) {
+    info('Sender: --- testSendArrayBuffer ---');
+    connection.addEventListener('message', function messageHandler(evt) {
+      connection.removeEventListener('message', messageHandler);
+      let msg = evt.data;
+      is(msg, "testIncomingArrayBuffer", "Sender: Sender should receive message from Receiver");
+      connection.send(DATA_ARRAY_BUFFER);
+      aResolve();
+    });
+  });
+}
+
+function testSendArrayBufferView() {
+  return new Promise(function(aResolve, aReject) {
+    info('Sender: --- testSendArrayBufferView ---');
+    connection.addEventListener('message', function messageHandler(evt) {
+      connection.removeEventListener('message', messageHandler);
+      let msg = evt.data;
+      is(msg, "testIncomingArrayBufferView", "Sender: Sender should receive message from Receiver");
+      connection.send(TYPED_DATA_ARRAY);
+      aResolve();
+    });
+  });
+}
+
 function testCloseConnection() {
   info('Sender: --- testCloseConnection ---');
   // Test terminate immediate after close.
   function controlChannelEstablishedHandler()
   {
     gScript.removeMessageListener('control-channel-established',
                                   controlChannelEstablishedHandler);
     ok(false, "terminate after close should do nothing");
@@ -264,31 +308,35 @@ function teardown() {
   gScript.sendAsyncMessage('teardown');
 }
 
 function runTests() {
   setup().then(testCreateRequest)
          .then(testStartConnection)
          .then(testSendMessage)
          .then(testIncomingMessage)
+         .then(testSendBlobMessage)
          .then(testCloseConnection)
          .then(testReconnect)
+         .then(testSendArrayBuffer)
+         .then(testSendArrayBufferView)
          .then(testCloseConnection)
          .then(testTerminateAfterClose)
          .then(teardown);
 }
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout('Test for guarantee not firing async event');
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: "browser", allow: true, context: document},
 ], () => {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
                                       /* Mocked TCP session transport builder in the test */
-                                      ["dom.presentation.session_transport.data_channel.enable", false],
+                                      ["dom.presentation.session_transport.data_channel.enable", true],
                                       ["dom.presentation.controller.enabled", true],
                                       ["dom.presentation.receiver.enabled", true],
                                       ["dom.presentation.test.enabled", true],
                                       ["dom.presentation.test.stage", 0],
-                                      ["dom.mozBrowserFramesEnabled", true]]},
+                                      ["dom.mozBrowserFramesEnabled", true],
+                                      ["media.navigator.permission.disabled", true]]},
                             runTests);
 });
--- a/dom/webidl/PresentationConnection.webidl
+++ b/dom/webidl/PresentationConnection.webidl
@@ -15,16 +15,22 @@ enum PresentationConnectionState
   // Existing presentation, but the communication channel is inactive.
   "closed",
 
   // The presentation is nonexistent anymore. It could be terminated manually,
   // or either controlling or receiving browsing context is no longer available.
   "terminated"
 };
 
+enum PresentationConnectionBinaryType
+{
+  "blob",
+  "arraybuffer"
+};
+
 [Pref="dom.presentation.enabled"]
 interface PresentationConnection : EventTarget {
   /*
    * Unique id for all existing connections.
    */
   [Constant]
   readonly attribute DOMString id;
 
@@ -36,30 +42,37 @@ interface PresentationConnection : Event
   /*
    * @value "connected", "closed", or "terminated".
    */
   readonly attribute PresentationConnectionState state;
 
   attribute EventHandler onconnect;
   attribute EventHandler onclose;
   attribute EventHandler onterminate;
+  attribute PresentationConnectionBinaryType binaryType;
 
   /*
    * After a communication channel has been established between the controlling
    * and receiving context, this function is called to send message out, and the
    * event handler "onmessage" will be invoked at the remote side.
    *
    * This function only works when the state is "connected".
-   *
-   * TODO bug 1228474 Implement PresentationSessionTransport with DataChannel to
-   * support other binary types.
    */
   [Throws]
   void send(DOMString data);
 
+  [Throws]
+  void send(Blob data);
+
+  [Throws]
+  void send(ArrayBuffer data);
+
+  [Throws]
+  void send(ArrayBufferView data);
+
   /*
    * It is triggered when receiving messages.
    */
   attribute EventHandler onmessage;
 
   /*
    * Both the controlling and receiving browsing context can close the
    * connection. Then the connection state should turn into "closed".