Bug 1148307 - Part 4, use data channel in substitution for TCP session transport (in-process), r=smaug
authorJunior Hsu <juhsu@mozilla.com>
Mon, 11 Apr 2016 11:20:55 +0800
changeset 330656 e0a1ad6c114e2540b536f414d7d277a7aa683d1a
parent 330655 0db5c9b171c7e1dd40d0f8c8d6272578d9b75361
child 330657 ce7802ca68410504beb4c4d227423979a15b234c
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1148307
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1148307 - Part 4, use data channel in substitution for TCP session transport (in-process), r=smaug
dom/presentation/PresentationRequest.cpp
dom/presentation/PresentationService.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/PresentationTCPSessionTransport.cpp
dom/presentation/PresentationTCPSessionTransport.h
dom/presentation/interfaces/nsIPresentationListener.idl
dom/presentation/interfaces/nsIPresentationService.idl
dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
dom/presentation/ipc/PresentationIPCService.cpp
dom/presentation/ipc/PresentationParent.cpp
dom/presentation/moz.build
dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
dom/presentation/tests/mochitest/file_presentation_receiver.html
dom/presentation/tests/mochitest/mochitest.ini
dom/presentation/tests/mochitest/test_presentation_dc_receiver.html
dom/presentation/tests/mochitest/test_presentation_dc_sender.html
dom/presentation/tests/mochitest/test_presentation_receiver.html
dom/presentation/tests/mochitest/test_presentation_receiver_establish_connection_error.html
dom/presentation/tests/mochitest/test_presentation_receiver_establish_connection_timeout.html
dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
dom/presentation/tests/mochitest/test_presentation_sender.html
dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
dom/presentation/tests/mochitest/test_presentation_sender_establish_connection_error.html
dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html
dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html
dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html
dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
dom/webidl/PresentationConnection.webidl
modules/libpref/init/all.js
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -126,17 +126,17 @@ PresentationRequest::StartWithDevice(con
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if(NS_WARN_IF(!service)) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
     return promise.forget();
   }
 
   nsCOMPtr<nsIPresentationServiceCallback> callback =
     new PresentationRequesterCallback(this, mUrl, id, promise);
-  rv = service->StartSession(mUrl, id, origin, aDeviceId, callback);
+  rv = service->StartSession(mUrl, id, origin, aDeviceId, GetOwner()->WindowID(), callback);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -383,28 +383,36 @@ PresentationService::IsAppInstalled(nsIU
   return true;
 }
 
 NS_IMETHODIMP
 PresentationService::StartSession(const nsAString& aUrl,
                                   const nsAString& aSessionId,
                                   const nsAString& aOrigin,
                                   const nsAString& aDeviceId,
+                                  uint64_t aWindowId,
                                   nsIPresentationServiceCallback* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aCallback);
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
   // Create session info  and set the callback. The callback is called when the
   // request is finished.
   RefPtr<PresentationSessionInfo> info =
     new PresentationControllingInfo(aUrl, aSessionId, aCallback);
   mSessionInfo.Put(aSessionId, info);
 
+  // Only track the info when an actual window ID, which would never be 0, is
+  // provided (for an in-process sender page).
+  if (aWindowId != 0) {
+    mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
+    mRespondingWindowIds.Put(aSessionId, aWindowId);
+  }
+
   nsCOMPtr<nsIPresentationDeviceRequest> request =
     new PresentationDeviceRequest(aUrl, aSessionId, aOrigin);
 
   if (aDeviceId.IsVoid()) {
     // Pop up a prompt and ask user to select a device.
     nsCOMPtr<nsIPresentationDevicePrompt> prompt =
       do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
     if (NS_WARN_IF(!prompt)) {
@@ -609,39 +617,49 @@ PresentationService::NotifyReceiverReady
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Only track the responding info when an actual window ID, which would never
   // be 0, is provided (for an in-process receiver page).
   if (aWindowId != 0) {
-    mRespondingSessionIds.Put(aWindowId, new nsAutoString(aSessionId));
+    mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
     mRespondingWindowIds.Put(aSessionId, aWindowId);
   }
 
   return static_cast<PresentationPresentingInfo*>(info.get())->NotifyResponderReady();
 }
 
 NS_IMETHODIMP
 PresentationService::UntrackSessionInfo(const nsAString& aSessionId)
 {
   // Remove the session info.
   mSessionInfo.Remove(aSessionId);
 
   // Remove the in-process responding info if there's still any.
   uint64_t windowId = 0;
-  if(mRespondingWindowIds.Get(aSessionId, &windowId)) {
+  if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
     mRespondingWindowIds.Remove(aSessionId);
     mRespondingSessionIds.Remove(windowId);
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PresentationService::GetWindowIdBySessionId(const nsAString& aSessionId,
+                                            uint64_t* aWindowId)
+{
+  if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
+    return NS_OK;
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 bool
 PresentationService::IsSessionAccessible(const nsAString& aSessionId,
                                          base::ProcessId aProcessId)
 {
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (NS_WARN_IF(!info)) {
     return false;
   }
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -8,16 +8,17 @@
 #include "mozilla/dom/HTMLIFrameElementBinding.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/Function.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Move.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsContentUtils.h"
+#include "nsGlobalWindow.h"
 #include "nsIDocShell.h"
 #include "nsIFrameLoader.h"
 #include "nsIMutableArray.h"
 #include "nsINetAddr.h"
 #include "nsISocketTransport.h"
 #include "nsISupportsPrimitives.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
@@ -148,39 +149,35 @@ NS_IMPL_ISUPPORTS(TCPPresentationChannel
 
 NS_IMETHODIMP
 TCPPresentationChannelDescription::GetType(uint8_t* aRetVal)
 {
   if (NS_WARN_IF(!aRetVal)) {
     return NS_ERROR_INVALID_POINTER;
   }
 
-  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
-  // Only support TCP socket for now.
   *aRetVal = nsIPresentationChannelDescription::TYPE_TCP;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPPresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal)
 {
   if (NS_WARN_IF(!aRetVal)) {
     return NS_ERROR_INVALID_POINTER;
   }
 
   nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID);
   if (NS_WARN_IF(!array)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
-  // Ultimately we may use all the available addresses. DataChannel appears
-  // more robust upon handling ICE. And at the first stage Presentation API is
-  // only exposed on Firefox OS where the first IP appears enough for most
-  // scenarios.
+  // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
+  // into account. And at the first stage Presentation API is only exposed on
+  // Firefox OS where the first IP appears enough for most scenarios.
   nsCOMPtr<nsISupportsCString> address = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
   if (NS_WARN_IF(!address)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   address->SetData(mAddress);
 
   array->AppendElement(address, false);
   array.forget(aRetVal);
@@ -197,18 +194,16 @@ TCPPresentationChannelDescription::GetTc
 
   *aRetVal = mPort;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TCPPresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP)
 {
-  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
-  // Only support TCP socket for now.
   aDataChannelSDP.Truncate();
   return NS_OK;
 }
 
 /*
  * Implementation of PresentationSessionInfo
  */
 
@@ -236,16 +231,18 @@ PresentationSessionInfo::Shutdown(nsresu
 
   // Close the data transport channel if any.
   if (mTransport) {
     // |mIsTransportReady| will be unset once |NotifyTransportClosed| is called.
     NS_WARN_IF(NS_FAILED(mTransport->Close(aReason)));
   }
 
   mIsResponderReady = false;
+
+  mBuilder = nullptr;
 }
 
 nsresult
 PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
 {
   mListener = aListener;
 
   if (mListener) {
@@ -328,16 +325,32 @@ PresentationSessionInfo::UntrackFromServ
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   static_cast<PresentationService*>(service.get())->UntrackSessionInfo(mSessionId);
 
   return NS_OK;
 }
 
+nsPIDOMWindowInner*
+PresentationSessionInfo::GetWindow()
+{
+  nsCOMPtr<nsIPresentationService> service =
+  do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return nullptr;
+  }
+  uint64_t windowId = 0;
+  if (NS_WARN_IF(NS_FAILED(service->GetWindowIdBySessionId(mSessionId, &windowId)))) {
+    return nullptr;
+  }
+
+  return nsGlobalWindow::GetInnerWindowWithId(windowId)->AsInner();
+}
+
 /* virtual */ bool
 PresentationSessionInfo::IsAccessible(base::ProcessId aProcessId)
 {
   // No restriction by default.
   return true;
 }
 
 // nsIPresentationSessionTransportCallback
@@ -423,21 +436,20 @@ PresentationSessionInfo::OnSessionTransp
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationSessionInfo::OnError(nsresult reason)
 {
-  return NS_ERROR_NOT_IMPLEMENTED;
+  return ReplyError(reason);
 }
 
-
-/*
+/**
  * Implementation of PresentationControllingInfo
  *
  * During presentation session establishment, the sender expects the following
  * after trying to establish the control channel: (The order between step 3 and
  * 4 is not guaranteed.)
  * 1. |Init| is called to open a socket |mServerSocket| for data transport
  *    channel.
  * 2. |NotifyOpened| of |nsIPresentationControlChannelListener| is called to
@@ -522,21 +534,20 @@ PresentationControllingInfo::GetAddress(
   uint32_t count = 0;
   activeNetworkInfo->GetAddresses(&ips, &prefixes, &count);
   if (NS_WARN_IF(!count)) {
     NS_Free(prefixes);
     NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips);
     return NS_ERROR_FAILURE;
   }
 
-  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
-  // Ultimately we may use all the available addresses. DataChannel appears
-  // more robust upon handling ICE. And at the first stage Presentation API is
-  // only exposed on Firefox OS where the first IP appears enough for most
-  // scenarios.
+  // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
+  // into account. And at the first stage Presentation API is only exposed on
+  // Firefox OS where the first IP appears enough for most scenarios.
+
   nsAutoString ip;
   ip.Assign(ips[0]);
 
   // On Android platform, the IP address is retrieved from a callback function.
   // To make consistent code sequence, following function call is dispatched
   // into main thread instead of calling it directly.
   NS_DispatchToMainThread(
     NS_NewRunnableMethodWithArg<nsCString>(
@@ -628,17 +639,37 @@ PresentationControllingInfo::OnAnswer(ns
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationControllingInfo::NotifyOpened()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return GetAddress();
+
+  if (!Preferences::GetBool("dom.presentation.session_transport.data_channel.enable")) {
+    // Build TCP session transport
+    return GetAddress();
+  }
+
+  nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
+    do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1");
+
+  if (NS_WARN_IF(!builder)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mBuilder = builder;
+  mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
+
+  return builder->BuildDataChannelTransport(nsIPresentationSessionTransportBuilder::TYPE_SENDER,
+                                            GetWindow(),
+                                            mControlChannel,
+                                            this);
+
 }
 
 NS_IMETHODIMP
 PresentationControllingInfo::NotifyClosed(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Unset control channel here so it won't try to re-close it in potential
@@ -672,16 +703,17 @@ PresentationControllingInfo::OnSocketAcc
 
   // Initialize session transport builder and use |this| as the callback.
   nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
     do_CreateInstance(PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID);
   if (NS_WARN_IF(!builder)) {
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
+  mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
   return builder->BuildTCPSenderTransport(aTransport, this);
 }
 
 NS_IMETHODIMP
 PresentationControllingInfo::OnStopListening(nsIServerSocket* aServerSocket,
                                            nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -698,17 +730,17 @@ PresentationControllingInfo::OnStopListe
   }
 
   // It happens after the session is ready. Change the state to CLOSED.
   SetState(nsIPresentationSessionListener::STATE_CLOSED);
 
   return NS_OK;
 }
 
-/*
+/**
  * Implementation of PresentationPresentingInfo
  *
  * During presentation session establishment, the receiver expects the following
  * after trying to launch the app by notifying "presentation-launch-receiver":
  * (The order between step 2 and 3 is not guaranteed.)
  * 1. |Observe| of |nsIObserver| is called with "presentation-receiver-launched".
  *    Then start listen to document |STATE_TRANSFERRING| event.
  * 2. |NotifyResponderReady| is called to indicate the receiver page is ready
@@ -761,58 +793,107 @@ PresentationPresentingInfo::Shutdown(nsr
   mRequesterDescription = nullptr;
   mPromise = nullptr;
 }
 
 // nsIPresentationSessionTransportBuilderListener
 NS_IMETHODIMP
 PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
 {
-  PresentationSessionInfo::OnSessionTransport(transport);
+  nsresult rv = PresentationSessionInfo::OnSessionTransport(transport);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  // Prepare and send the answer.
-  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
-  // In the current implementation of |PresentationSessionTransport|,
-  // |GetSelfAddress| cannot return the real info when it's initialized via
-  // |InitWithChannelDescription|. Yet this deficiency only affects the channel
-  // description for the answer, which is not actually checked at requester side.
-  nsCOMPtr<nsINetAddr> selfAddr;
-  nsresult rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr));
-  NS_WARN_IF(NS_FAILED(rv));
+  // send answer for TCP session transport
+  if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
+    // Prepare and send the answer.
+    // In the current implementation of |PresentationSessionTransport|,
+    // |GetSelfAddress| cannot return the real info when it's initialized via
+    // |buildTCPReceiverTransport|. Yet this deficiency only affects the channel
+    // description for the answer, which is not actually checked at requester side.
+    nsCOMPtr<nsINetAddr> selfAddr;
+    rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr));
+    NS_WARN_IF(NS_FAILED(rv));
 
-  nsCString address;
-  uint16_t port = 0;
-  if (NS_SUCCEEDED(rv)) {
-    selfAddr->GetAddress(address);
-    selfAddr->GetPort(&port);
+    nsCString address;
+    uint16_t port = 0;
+    if (NS_SUCCEEDED(rv)) {
+      selfAddr->GetAddress(address);
+      selfAddr->GetPort(&port);
+    }
+    nsCOMPtr<nsIPresentationChannelDescription> description =
+      new TCPPresentationChannelDescription(address, port);
+
+    return mControlChannel->SendAnswer(description);
   }
-  nsCOMPtr<nsIPresentationChannelDescription> description =
-    new TCPPresentationChannelDescription(address, port);
 
-  return mControlChannel->SendAnswer(description);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::OnError(nsresult reason)
 {
   return PresentationSessionInfo::OnError(reason);
 }
 
 nsresult
 PresentationPresentingInfo::InitTransportAndSendAnswer()
 {
-  // Establish a data transport channel |mTransport| to the sender and use
-  // |this| as the callback.
-  nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
-    do_CreateInstance(PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID);
-  if (NS_WARN_IF(!builder)) {
-    return NS_ERROR_NOT_AVAILABLE;
+  uint8_t type = 0;
+  nsresult rv = mRequesterDescription->GetType(&type);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (type == nsIPresentationChannelDescription::TYPE_TCP) {
+    // Establish a data transport channel |mTransport| to the sender and use
+    // |this| as the callback.
+    nsCOMPtr<nsIPresentationTCPSessionTransportBuilder> builder =
+      do_CreateInstance(PRESENTATION_TCP_SESSION_TRANSPORT_CONTRACTID);
+    if (NS_WARN_IF(!builder)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    mBuilder = builder;
+    mTransportType = nsIPresentationChannelDescription::TYPE_TCP;
+    return builder->BuildTCPReceiverTransport(mRequesterDescription, this);
   }
 
-  return builder->BuildTCPReceiverTransport(mRequesterDescription, this);
+  if (type == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
+    nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder> builder =
+      do_CreateInstance("@mozilla.org/presentation/datachanneltransportbuilder;1");
+
+    if (NS_WARN_IF(!builder)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    mBuilder = builder;
+    mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
+    rv = builder->BuildDataChannelTransport(nsIPresentationSessionTransportBuilder::TYPE_RECEIVER,
+                                            GetWindow(),
+                                            mControlChannel,
+                                            this);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    // delegate |onOffer| to builder
+    nsCOMPtr<nsIPresentationControlChannelListener> listener(do_QueryInterface(builder));
+
+    if (NS_WARN_IF(!listener)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+
+    return listener->OnOffer(mRequesterDescription);
+  }
+
+  MOZ_ASSERT(false, "Unknown nsIPresentationChannelDescription type!");
+  return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
 PresentationPresentingInfo::UntrackFromService()
 {
   // Remove the OOP responding info (if it has never been used).
   if (mContentParent) {
     NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.get())->SendNotifyPresentationReceiverCleanUp(mSessionId));
@@ -933,17 +1014,17 @@ PresentationPresentingInfo::Notify(nsITi
 
   mTimer = nullptr;
   return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR);
 }
 
 // PromiseNativeHandler
 void
 PresentationPresentingInfo::ResolvedCallback(JSContext* aCx,
-                                            JS::Handle<JS::Value> aValue)
+                                             JS::Handle<JS::Value> aValue)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (NS_WARN_IF(!aValue.isObject())) {
     ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     return;
   }
 
@@ -997,17 +1078,17 @@ PresentationPresentingInfo::ResolvedCall
       ReplyError(NS_ERROR_DOM_OPERATION_ERR);
       return;
     }
   }
 }
 
 void
 PresentationPresentingInfo::RejectedCallback(JSContext* aCx,
-                                            JS::Handle<JS::Value> aValue)
+                                             JS::Handle<JS::Value> aValue)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_WARNING("Launching the receiver page has been rejected.");
 
   if (mTimer) {
     mTimer->Cancel();
     mTimer = nullptr;
   }
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -106,20 +106,17 @@ protected:
   {
     Shutdown(NS_OK);
   }
 
   virtual void Shutdown(nsresult aReason);
 
   nsresult ReplySuccess();
 
-  bool IsSessionReady()
-  {
-    return mIsResponderReady && mIsTransportReady;
-  }
+  virtual bool IsSessionReady() = 0;
 
   virtual nsresult UntrackFromService();
 
   void SetState(uint32_t aState)
   {
     if (mState == aState) {
       return;
     }
@@ -128,26 +125,32 @@ protected:
 
     // Notify session state change.
     if (mListener) {
       nsresult rv = mListener->NotifyStateChange(mSessionId, mState);
       NS_WARN_IF(NS_FAILED(rv));
     }
   }
 
+  // Should be nsIPresentationChannelDescription::TYPE_TCP/TYPE_DATACHANNEL
+  uint8_t mTransportType = 0;
+
+  nsPIDOMWindowInner* GetWindow();
+
   nsString mUrl;
   nsString mSessionId;
   bool mIsResponderReady;
   bool mIsTransportReady;
   uint32_t mState; // CONNECTED, CLOSED, TERMINATED
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationSessionListener> mListener;
   nsCOMPtr<nsIPresentationDevice> mDevice;
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
   nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
+  nsCOMPtr<nsIPresentationSessionTransportBuilder> mBuilder;
 };
 
 // Session info with controlling browsing context (sender side) behaviors.
 class PresentationControllingInfo final : public PresentationSessionInfo
                                         , public nsIServerSocketListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
@@ -172,16 +175,28 @@ private:
 
   void Shutdown(nsresult aReason) override;
 
   nsresult GetAddress();
 
   nsresult OnGetAddress(const nsACString& aAddress);
 
   nsCOMPtr<nsIServerSocket> mServerSocket;
+
+protected:
+  bool IsSessionReady() override
+  {
+    if (mTransportType == nsIPresentationChannelDescription::TYPE_TCP) {
+      return mIsResponderReady && mIsTransportReady;
+    } else if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
+      // Established RTCDataChannel implies responder is ready.
+      return mIsTransportReady;
+    }
+    return false;
+  }
 };
 
 // Session info with presenting browsing context (receiver side) behaviors.
 class PresentationPresentingInfo final : public PresentationSessionInfo
                                        , public PromiseNativeHandler
                                        , public nsITimerCallback
 {
 public:
@@ -231,14 +246,20 @@ private:
   RefPtr<PresentationResponderLoadingCallback> mLoadingCallback;
   nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsIPresentationChannelDescription> mRequesterDescription;
   RefPtr<Promise> mPromise;
 
   // The content parent communicating with the content process which the OOP
   // receiver page belongs to.
   nsCOMPtr<nsIContentParent> mContentParent;
+
+protected:
+  bool IsSessionReady() override
+  {
+    return mIsResponderReady && mIsTransportReady;
+  }
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationSessionInfo_h
--- a/dom/presentation/PresentationTCPSessionTransport.cpp
+++ b/dom/presentation/PresentationTCPSessionTransport.cpp
@@ -146,21 +146,19 @@ PresentationTCPSessionTransport::BuildTC
   }
 
   nsCOMPtr<nsIArray> serverHosts;
   rv = aDescription->GetTcpAddress(getter_AddRefs(serverHosts));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
-  // Ultimately we may use all the available addresses. DataChannel appears
-  // more robust upon handling ICE. And at the first stage Presentation API is
-  // only exposed on Firefox OS where the first IP appears enough for most
-  // scenarios.
+  // TODO bug 1228504 Take all IP addresses in PresentationChannelDescription
+  // into account. And at the first stage Presentation API is only exposed on
+  // Firefox OS where the first IP appears enough for most scenarios.
   nsCOMPtr<nsISupportsCString> supportStr = do_QueryElementAt(serverHosts, 0);
   if (NS_WARN_IF(!supportStr)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsAutoCString serverHost;
   supportStr->GetData(serverHost);
   if (serverHost.IsEmpty()) {
--- a/dom/presentation/PresentationTCPSessionTransport.h
+++ b/dom/presentation/PresentationTCPSessionTransport.h
@@ -28,21 +28,16 @@ namespace dom {
 
 /*
  * App-to-App transport channel for the presentation session. It's usually
  * initialized with an |InitWithSocketTransport| call if at the presenting sender
  * side; whereas it's initialized with an |InitWithChannelDescription| if at the
  * presenting receiver side. The lifetime is managed in either
  * |PresentationControllingInfo| (sender side) or |PresentationPresentingInfo|
  * (receiver side) in PresentationSessionInfo.cpp.
- *
- * TODO bug 1148307 Implement PresentationSessionTransport with DataChannel.
- * The implementation over the TCP channel is primarily used for the early stage
- * of Presentation API (without SSL) and should be migrated to DataChannel with
- * full support soon.
  */
 class PresentationTCPSessionTransport final : public nsIPresentationSessionTransport
                                             , public nsIPresentationTCPSessionTransportBuilder
                                             , public nsITransportEventSink
                                             , public nsIInputStreamCallback
                                             , public nsIStreamListener
 {
 public:
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -34,11 +34,11 @@ interface nsIPresentationSessionListener
 };
 
 [scriptable, uuid(27f101d7-9ed1-429e-b4f8-43b00e8e111c)]
 interface nsIPresentationRespondingListener : nsISupports
 {
   /*
    * Called when an incoming session connects.
    */
-  void notifySessionConnect(in uint64_t windowId,
+  void notifySessionConnect(in unsigned long long windowId,
                             in DOMString sessionId);
 };
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -28,37 +28,42 @@ interface nsIPresentationServiceCallback
   /*
    * Called when the operation fails.
    *
    * @param error: error message.
    */
   void notifyError(in nsresult error);
 };
 
-[scriptable, uuid(61864149-9838-4aa1-a21a-63eaf0b84a8c)]
+[scriptable, uuid(de42b741-5619-4650-b961-c2cebb572c95)]
 interface nsIPresentationService : nsISupports
 {
   /*
    * Start a new presentation session and display a prompt box which asks users
    * to select a device.
    *
    * @param url: The url of presenting page.
    * @param sessionId: An ID to identify presentation session.
    * @param origin: The url of requesting page.
    * @param deviceId: The specified device of handling this request, null string
                       for prompt device selection dialog.
+   * @param windowId: The inner window ID associated with the presentation
+   *                  session. (0 implies no window ID since no actual window
+   *                  uses 0 as its ID. Generally it's the case the window is
+   *                  located in different process from this service)
    * @param callback: Invoke the callback when the operation is completed.
    *                  NotifySuccess() is called with |id| if a session is
    *                  established successfully with the selected device.
    *                  Otherwise, NotifyError() is called with a error message.
    */
   void startSession(in DOMString url,
                     in DOMString sessionId,
                     in DOMString origin,
                     in DOMString deviceId,
+                    in unsigned long long windowId,
                     in nsIPresentationServiceCallback callback);
 
   /*
    * Send the message to the session.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param data: the message being sent out.
    */
@@ -109,45 +114,51 @@ interface nsIPresentationService : nsISu
   void unregisterSessionListener(in DOMString sessionId);
 
   /*
    * Register a responding listener. Must be called from the main thread.
    *
    * @param windowId: The window ID associated with the listener.
    * @param listener: The listener to register.
    */
-  void registerRespondingListener(in uint64_t windowId,
+  void registerRespondingListener(in unsigned long long windowId,
                                   in nsIPresentationRespondingListener listener);
 
   /*
    * Unregister a responding listener. Must be called from the main thread.
    * @param windowId: The window ID associated with the listener.
    */
-  void unregisterRespondingListener(in uint64_t windowId);
+  void unregisterRespondingListener(in unsigned long long windowId);
 
   /*
    * Check if the presentation instance has an existent session ID at launch.
    * An empty string is always returned at sender side. Whereas at receiver side
    * the associated session ID is returned if the window ID and URI are matched;
    * otherwise an empty string is returned.
    *
    * @param windowId: The inner window ID used to look up the session ID.
    */
-  DOMString getExistentSessionIdAtLaunch(in uint64_t windowId);
+  DOMString getExistentSessionIdAtLaunch(in unsigned long long windowId);
 
   /*
    * Notify the receiver page is ready for presentation use.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param windowId: The inner window ID associated with the presentation
    *                  session. (0 implies no window ID since no actual window
-   *                  uses 0 as its ID.)
+   *                  uses 0 as its ID. Generally it's the case the window is
+   *                  located in different process from this service)
    */
   void notifyReceiverReady(in DOMString sessionId,
-                           [optional] in uint64_t windowId);
+                           [optional] in unsigned long long windowId);
 
   /*
    * Untrack the relevant info about the presentation session if there's any.
    *
    * @param sessionId: An ID to identify presentation session.
    */
   void untrackSessionInfo(in DOMString sessionId);
+
+  /*
+   * The windowId for building RTCDataChannel session transport
+   */
+  unsigned long long getWindowIdBySessionId(in DOMString sessionId);
 };
--- a/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
+++ b/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
@@ -1,17 +1,17 @@
 /* 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 nsIPresentationChannelDescription;
 interface nsISocketTransport;
-interface nsIDOMWindow;
+interface mozIDOMWindow;
 interface nsIPresentationControlChannel;
 interface nsIPresentationSessionTransport;
 
 [scriptable, uuid(673f6de1-e253-41b8-9be8-b7ff161fa8dc)]
 interface nsIPresentationSessionTransportBuilderListener : nsISupports
 {
   // Should set |transport.callback| in |onSessionTransport|.
   void onSessionTransport(in nsIPresentationSessionTransport transport);
@@ -53,12 +53,12 @@ interface nsIPresentationDataChannelSess
   /**
    * The following creation function will trigger |listener.onSessionTransport|
    * if the session transport is successfully built, |listener.onError| if some
    * error occurs during creating session transport. The |notifyOpened| of
    * |aControlChannel| should be called before calling
    * |buildDataChannelTransport|.
    */
   void buildDataChannelTransport(in uint8_t aType,
-                                 in nsIDOMWindow aWindow,
+                                 in mozIDOMWindow aWindow,
                                  in nsIPresentationControlChannel aControlChannel,
                                  in nsIPresentationSessionTransportBuilderListener aListener);
 };
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -45,49 +45,55 @@ PresentationIPCService::~PresentationIPC
   sPresentationChild = nullptr;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(const nsAString& aUrl,
                                      const nsAString& aSessionId,
                                      const nsAString& aOrigin,
                                      const nsAString& aDeviceId,
+                                     uint64_t aWindowId,
                                      nsIPresentationServiceCallback* aCallback)
 {
-  return SendRequest(aCallback, StartSessionRequest(nsAutoString(aUrl),
-                                                    nsAutoString(aSessionId),
-                                                    nsAutoString(aOrigin),
-                                                    nsAutoString(aDeviceId)));
+  if (aWindowId != 0) {
+    mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
+    mRespondingWindowIds.Put(aSessionId, aWindowId);
+  }
+
+  return SendRequest(aCallback, StartSessionRequest(nsString(aUrl),
+                                                    nsString(aSessionId),
+                                                    nsString(aOrigin),
+                                                    nsString(aDeviceId)));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::SendSessionMessage(const nsAString& aSessionId,
                                            const nsAString& aData)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
   MOZ_ASSERT(!aData.IsEmpty());
 
-  return SendRequest(nullptr, SendSessionMessageRequest(nsAutoString(aSessionId),
-                                                        nsAutoString(aData)));
+  return SendRequest(nullptr, SendSessionMessageRequest(nsString(aSessionId),
+                                                        nsString(aData)));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::CloseSession(const nsAString& aSessionId)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
-  return SendRequest(nullptr, CloseSessionRequest(nsAutoString(aSessionId)));
+  return SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId)));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::TerminateSession(const nsAString& aSessionId)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
-  return SendRequest(nullptr, TerminateSessionRequest(nsAutoString(aSessionId)));
+  return SendRequest(nullptr, TerminateSessionRequest(nsString(aSessionId)));
 }
 
 nsresult
 PresentationIPCService::SendRequest(nsIPresentationServiceCallback* aCallback,
                                     const PresentationIPCRequest& aRequest)
 {
   if (sPresentationChild) {
     PresentationRequestChild* actor = new PresentationRequestChild(aCallback);
@@ -126,31 +132,31 @@ NS_IMETHODIMP
 PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId,
                                                 nsIPresentationSessionListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
   mSessionListeners.Put(aSessionId, aListener);
   if (sPresentationChild) {
-    NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsAutoString(aSessionId)));
+    NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsString(aSessionId)));
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::UnregisterSessionListener(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   UntrackSessionInfo(aSessionId);
 
   mSessionListeners.Remove(aSessionId);
   if (sPresentationChild) {
-    NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsAutoString(aSessionId)));
+    NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsString(aSessionId)));
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::RegisterRespondingListener(uint64_t aWindowId,
                                                    nsIPresentationRespondingListener* aListener)
 {
@@ -170,16 +176,26 @@ PresentationIPCService::UnregisterRespon
 
   mRespondingListeners.Remove(aWindowId);
   if (sPresentationChild) {
     NS_WARN_IF(!sPresentationChild->SendUnregisterRespondingHandler(aWindowId));
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId,
+                                               uint64_t* aWindowId)
+{
+  if (mRespondingWindowIds.Get(aSessionId, aWindowId)) {
+    return NS_OK;
+  }
+  return NS_ERROR_NOT_AVAILABLE;
+}
+
 nsresult
 PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId,
                                                  uint16_t aState)
 {
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
     return NS_OK;
   }
@@ -245,33 +261,33 @@ PresentationIPCService::NotifyReceiverRe
   MOZ_ASSERT(NS_IsMainThread());
 
   // No actual window uses 0 as its ID.
   if (NS_WARN_IF(aWindowId == 0)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Track the responding info for an OOP receiver page.
-  mRespondingSessionIds.Put(aWindowId, new nsAutoString(aSessionId));
+  mRespondingSessionIds.Put(aWindowId, new nsString(aSessionId));
   mRespondingWindowIds.Put(aSessionId, aWindowId);
 
-  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsAutoString(aSessionId)));
+  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsString(aSessionId)));
 
   // Release mCallback after using aSessionId
   // because aSessionId is held by mCallback.
   mCallback = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::UntrackSessionInfo(const nsAString& aSessionId)
 {
   // Remove the OOP responding info (if it has never been used).
   uint64_t windowId = 0;
-  if(mRespondingWindowIds.Get(aSessionId, &windowId)) {
+  if (mRespondingWindowIds.Get(aSessionId, &windowId)) {
     mRespondingWindowIds.Remove(aSessionId);
     mRespondingSessionIds.Remove(windowId);
   }
 
   return NS_OK;
 }
 
 void
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -181,48 +181,50 @@ PresentationParent::NotifyAvailableChang
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyStateChange(const nsAString& aSessionId,
                                       uint16_t aState)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifySessionStateChange(nsAutoString(aSessionId), aState))) {
+                 !SendNotifySessionStateChange(nsString(aSessionId), aState))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyMessage(const nsAString& aSessionId,
                                   const nsACString& aData)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifyMessage(nsAutoString(aSessionId), nsAutoCString(aData)))) {
+                 !SendNotifyMessage(nsString(aSessionId), nsCString(aData)))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifySessionConnect(uint64_t aWindowId,
                                          const nsAString& aSessionId)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifySessionConnect(aWindowId, nsAutoString(aSessionId)))) {
+                 !SendNotifySessionConnect(aWindowId, nsString(aSessionId)))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 bool
 PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId)
 {
   MOZ_ASSERT(mService);
+
+  // Set window ID to 0 since the window is from content process.
   NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, 0)));
   return true;
 }
 
 /*
  * Implementation of PresentationRequestParent
  */
 
@@ -246,18 +248,20 @@ PresentationRequestParent::ActorDestroy(
   mActorDestroyed = true;
   mService = nullptr;
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest)
 {
   MOZ_ASSERT(mService);
+
+  // Set window ID to 0 since the window is from content process.
   return mService->StartSession(aRequest.url(), aRequest.sessionId(),
-                                aRequest.origin(), aRequest.deviceId(), this);
+                                aRequest.origin(), aRequest.deviceId(), 0, this);
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
 {
   MOZ_ASSERT(mService);
 
   // Validate the accessibility (primarily for receiver side) so that a
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -60,14 +60,18 @@ EXTRA_JS_MODULES += [
     'PresentationDeviceInfoManager.jsm',
 ]
 
 IPDL_SOURCES += [
     'ipc/PPresentation.ipdl',
     'ipc/PPresentationRequest.ipdl'
 ]
 
+LOCAL_INCLUDES += [
+    '../base'
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -1,16 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 'use strict';
 
 const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/Timer.jsm');
 
 function registerMockedFactory(contractId, mockedClassId, mockedFactory) {
   var originalClassId, originalFactory;
 
   var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
   if (!registrar.isCIDRegistered(mockedClassId)) {
     try {
       originalClassId = registrar.contractIDToCID(contractId);
@@ -44,17 +46,22 @@ const sessionId = 'test-session-id';
 const address = Cc["@mozilla.org/supports-cstring;1"]
                   .createInstance(Ci.nsISupportsCString);
 address.data = "127.0.0.1";
 const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
 addresses.appendElement(address, false);
 
 const mockedChannelDescription = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]),
-  type: 1,
+  get type() {
+    if (Services.prefs.getBoolPref("dom.presentation.session_transport.data_channel.enable")) {
+      return Ci.nsIPresentationChannelDescription.TYPE_DATACHANNEL;
+    }
+    return Ci.nsIPresentationChannelDescription.TYPE_TCP;
+  },
   tcpAddress: addresses,
   tcpPort: 1234,
 };
 
 const mockedServerSocket = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIServerSocket,
                                          Ci.nsIFactory]),
   createInstance: function(aOuter, aIID) {
@@ -194,16 +201,18 @@ const mockedDevicePrompt = {
   simulateCancel: function() {
     this._request.cancel();
   }
 };
 
 const mockedSessionTransport = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport,
                                          Ci.nsIPresentationTCPSessionTransportBuilder,
+                                         Ci.nsIPresentationDataChannelSessionTransportBuilder,
+                                         Ci.nsIPresentationControlChannelListener,
                                          Ci.nsIFactory]),
   createInstance: function(aOuter, aIID) {
     if (aOuter) {
       throw Components.results.NS_ERROR_NO_AGGREGATION;
     }
     return this.QueryInterface(aIID);
   },
   set callback(callback) {
@@ -215,35 +224,53 @@ const mockedSessionTransport = {
   get selfAddress() {
     return this._selfAddress;
   },
   buildTCPSenderTransport: function(transport, listener) {
     sendAsyncMessage('data-transport-initialized');
     this._listener = listener;
     this._type = Ci.nsIPresentationSessionTransportBuilder.TYPE_SENDER;
 
-    this._listener.onSessionTransport(this);
-    this._listener = null;
-
-    this.simulateTransportReady();
+    setTimeout(()=>{
+      this._listener.onSessionTransport(this);
+      this._listener = null;
+      this.simulateTransportReady();
+    }, 0);
   },
   buildTCPReceiverTransport: function(description, listener) {
     this._listener = listener;
     this._type = Ci.nsIPresentationSessionTransportBuilder.TYPE_RECEIVER;
 
     var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress;
     this._selfAddress = {
       QueryInterface: XPCOMUtils.generateQI([Ci.nsINetAddr]),
       address: (addresses.length > 0) ?
                 addresses.queryElementAt(0, Ci.nsISupportsCString).data : "",
       port: description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpPort,
     };
 
-    this._listener.onSessionTransport(this);
-    this._listener = null;
+    setTimeout(()=>{
+      this._listener.onSessionTransport(this);
+      this._listener = null;
+    }, 0);
+  },
+  // in-process case
+  buildDataChannelTransport: function(type, window, controlChannel, listener) {
+    dump("build data channel transport\n");
+    this._listener = listener;
+    this._type = type;
+
+    var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
+    sendAsyncMessage('check-navigator', hasNavigator);
+
+    setTimeout(()=>{
+      this._listener.onSessionTransport(this);
+      this._listener = null;
+      this.simulateTransportReady();
+    }, 0);
   },
   enableDataNotification: function() {
     sendAsyncMessage('data-transport-notification-enabled');
   },
   send: function(data) {
     sendAsyncMessage('message-sent', data);
   },
   close: function(reason) {
@@ -251,16 +278,20 @@ const mockedSessionTransport = {
     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);
   },
+  onOffer: function(aOffer) {
+  },
+  onAnswer: function(aAnswer) {
+  }
 };
 
 const mockedNetworkInfo = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]),
   getAddresses: function(ips, prefixLengths) {
     ips.value = ["127.0.0.1"];
     prefixLengths.value = [0];
     return 1;
@@ -306,16 +337,19 @@ originalFactoryData.push(registerMockedF
                                                uuidGenerator.generateUUID(),
                                                mockedDevicePrompt));
 originalFactoryData.push(registerMockedFactory("@mozilla.org/network/server-socket;1",
                                                uuidGenerator.generateUUID(),
                                                mockedServerSocket));
 originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/presentationtcpsessiontransport;1",
                                                uuidGenerator.generateUUID(),
                                                mockedSessionTransport));
+originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/datachanneltransportbuilder;1",
+                                               uuidGenerator.generateUUID(),
+                                               mockedSessionTransport));
 originalFactoryData.push(registerMockedFactory("@mozilla.org/network/manager;1",
                                                uuidGenerator.generateUUID(),
                                                mockedNetworkManager));
 originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/requestuiglue;1",
                                                uuidGenerator.generateUUID(),
                                                mockedRequestUIGlue));
 
 function tearDown() {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html
@@ -72,17 +72,17 @@ function testIncomingMessage() {
 
     connection.addEventListener('message', function messageHandler(aEvent) {
       connection.removeEventListener('message', messageHandler);
       is(aEvent.data, incomingMessage, "An incoming message should be received.");
       aResolve();
     });
 
     command({ name: 'trigger-incoming-message',
-    	      data: incomingMessage });
+    	        data: incomingMessage });
   });
 }
 
 function testTerminateConnection() {
   return new Promise(function(aResolve, aReject) {
     connection.onstatechange = function() {
       connection.onstatechange = null;
       is(connection.state, "terminated", "Connection should be terminated.");
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -4,28 +4,33 @@ support-files =
   PresentationSessionChromeScript.js
   file_presentation_receiver.html
   file_presentation_receiver_oop.html
   file_presentation_non_receiver_oop.html
   file_presentation_receiver_establish_connection_error.html
   file_presentation_receiver_inner_iframe_oop.html
   file_presentation_non_receiver_inner_iframe_oop.html
 
+
+[test_presentation_dc_sender.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
+[test_presentation_dc_receiver.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_device_info.html]
 [test_presentation_device_info_permission.html]
-[test_presentation_sender_disconnect.html]
-skip-if = toolkit == 'android' # Bug 1129785
-[test_presentation_sender_establish_connection_error.html]
-skip-if = toolkit == 'android' # Bug 1129785
-[test_presentation_sender.html]
-skip-if = toolkit == 'android' # Bug 1129785
-[test_presentation_sender_default_request.html]
-skip-if = toolkit == 'android' # Bug 1129785
 [test_presentation_sender_startWithDevice.html]
 skip-if = toolkit == 'android' # Bug 1129785
-[test_presentation_receiver_establish_connection_error.html]
+[test_presentation_tcp_sender_disconnect.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_sender_establish_connection_error.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_sender.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_sender_default_request.html]
+skip-if = toolkit == 'android' # Bug 1129785
+[test_presentation_tcp_receiver_establish_connection_error.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android' || os == 'mac' || os == 'win' || buildapp == 'mulet') # Bug 1129785, Bug 1204709
-[test_presentation_receiver_establish_connection_timeout.html]
+[test_presentation_tcp_receiver_establish_connection_timeout.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
-[test_presentation_receiver.html]
+[test_presentation_tcp_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
-[test_presentation_receiver_oop.html]
+[test_presentation_tcp_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_receiver.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for B2G PresentationConnection API at receiver side</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for B2G PresentationConnection API at receiver side</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="application/javascript">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver.html');
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('src', receiverUrl);
+
+    // This event is triggered when the iframe calls "postMessage".
+    window.addEventListener('message', function listener(aEvent) {
+      var message = aEvent.data;
+      if (/^OK /.exec(message)) {
+        ok(true, "Message from iframe: " + message);
+      } else if (/^KO /.exec(message)) {
+        ok(false, "Message from iframe: " + message);
+      } else if (/^INFO /.exec(message)) {
+        info("Message from iframe: " + message);
+      } else if (/^COMMAND /.exec(message)) {
+        var command = JSON.parse(message.replace(/^COMMAND /, ''));
+        gScript.sendAsyncMessage(command.name, command.data);
+      } else if (/^DONE$/.exec(message)) {
+        ok(true, "Messaging from iframe complete.");
+        window.removeEventListener('message', listener);
+
+        teardown();
+      }
+    }, false);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(iframe);
+
+      aResolve(iframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    gScript.addMessageListener('offer-received', function offerReceivedHandler() {
+      gScript.removeMessageListener('offer-received', offerReceivedHandler);
+      info("An offer is received.");
+    });
+
+    gScript.addMessageListener('answer-sent', function answerSentHandler(aIsValid) {
+      gScript.removeMessageListener('answer-sent', answerSentHandler);
+      ok(aIsValid, "A valid answer is sent.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");
+    });
+
+    gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+      gScript.removeMessageListener('check-navigator', checknavigatorHandler);
+      ok(aSuccess, "buildDataChannel get correct window object");
+    });
+
+    gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+      gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+      info("Data notification is enabled for data transport channel.");
+    });
+
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      is(aReason, SpecialPowers.Cr.NS_OK, "The data transport should be closed normally.");
+    });
+
+    aResolve();
+  });
+}
+
+function testIncomingSessionRequest() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+      gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+      info("Trying to launch receiver page.");
+
+      ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
+      ok(!navigator.presentation.receiver, "Non-receiving in-process pages shouldn't get a presentation receiver instance.");
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequest);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: 'presentation-device-manage', allow: false, context: document},
+  {type: 'presentation', allow: true, context: document},
+], function() {
+  SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+                                      ["dom.presentation.session_transport.data_channel.enable", true]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
@@ -0,0 +1,208 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for B2G Presentation API at sender side</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148307">Test for B2G Presentation API at sender side</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var request;
+var connection;
+
+function testSetup() {
+  return new Promise(function(aResolve, aReject) {
+    request = new PresentationRequest("http://example.com");
+
+    request.getAvailability().then(
+      function(aAvailability) {
+        aAvailability.onchange = function() {
+          aAvailability.onchange = null;
+          ok(aAvailability.value, "Device should be available.");
+          aResolve();
+        }
+      },
+      function(aError) {
+        ok(false, "Error occurred when getting availability: " + aError);
+        teardown();
+        aReject();
+      }
+    );
+
+    gScript.sendAsyncMessage('trigger-device-add');
+  });
+}
+
+function testStartConnection() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('device-prompt', function devicePromptHandler() {
+      gScript.removeMessageListener('device-prompt', devicePromptHandler);
+      info("Device prompt is triggered.");
+      gScript.sendAsyncMessage('trigger-device-prompt-select');
+    });
+
+    gScript.addMessageListener('control-channel-established', function controlChannelEstablishedHandler() {
+      gScript.removeMessageListener('control-channel-established', controlChannelEstablishedHandler);
+      info("A control channel is established.");
+      gScript.sendAsyncMessage('trigger-control-channel-open');
+    });
+
+    gScript.addMessageListener('control-channel-opened', function controlChannelOpenedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-opened', controlChannelOpenedHandler);
+      info("The control channel is opened.");
+    });
+
+    gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+      gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+      info("The control channel is closed. " + aReason);
+    });
+
+    gScript.addMessageListener('check-navigator', function checknavigatorHandler(aSuccess) {
+      gScript.removeMessageListener('check-navigator', checknavigatorHandler);
+      ok(aSuccess, "buildDataChannel get correct window object");
+    });
+
+    gScript.addMessageListener('offer-sent', function offerSentHandler(aIsValid) {
+      gScript.removeMessageListener('offer-sent', offerSentHandler);
+      ok(aIsValid, "A valid offer is sent out.");
+      gScript.sendAsyncMessage('trigger-incoming-transport');
+    });
+
+    gScript.addMessageListener('answer-received', function answerReceivedHandler() {
+      gScript.removeMessageListener('answer-received', answerReceivedHandler);
+      info("An answer is received.");
+    });
+
+    gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
+      gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
+      info("Data transport channel is initialized.");
+      gScript.sendAsyncMessage('trigger-incoming-answer');
+    });
+
+    gScript.addMessageListener('data-transport-notification-enabled', function dataTransportNotificationEnabledHandler() {
+      gScript.removeMessageListener('data-transport-notification-enabled', dataTransportNotificationEnabledHandler);
+      info("Data notification is enabled for data transport channel.");
+    });
+
+    var connectionFromEvent;
+    request.onconnectionavailable = function(aEvent) {
+      request.onconnectionavailable = null;
+      connectionFromEvent = aEvent.connection;
+      ok(connectionFromEvent, "|connectionavailable| event is fired with a connection.");
+
+      if (connection) {
+        is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+        aResolve();
+      }
+    };
+
+    request.start().then(
+      function(aConnection) {
+        connection = aConnection;
+        ok(connection, "Connection should be available.");
+        ok(connection.id, "Connection ID should be set.");
+        is(connection.state, "connected", "Connection state at sender side should be connected by default.");
+
+        if (connectionFromEvent) {
+          is(connection, connectionFromEvent, "The connection from promise and the one from |connectionavailable| event should be the same.");
+          aResolve();
+        }
+      },
+      function(aError) {
+        ok(false, "Error occurred when establishing a connection: " + aError);
+        teardown();
+        aReject();
+      }
+    );
+  });
+}
+
+function testSend() {
+  return new Promise(function(aResolve, aReject) {
+    const outgoingMessage = "test outgoing message";
+
+    gScript.addMessageListener('message-sent', function messageSentHandler(aMessage) {
+      gScript.removeMessageListener('message-sent', messageSentHandler);
+      is(aMessage, outgoingMessage, "The message is sent out.");
+      aResolve();
+    });
+
+    connection.send(outgoingMessage);
+  });
+}
+
+function testIncomingMessage() {
+  return new Promise(function(aResolve, aReject) {
+    const incomingMessage = "test incoming message";
+
+    connection.addEventListener('message', function messageHandler(aEvent) {
+      connection.removeEventListener('message', messageHandler);
+      is(aEvent.data, incomingMessage, "An incoming message should be received.");
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-incoming-message', incomingMessage);
+  });
+}
+
+function testTerminateConnection() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('data-transport-closed', function dataTransportClosedHandler(aReason) {
+      gScript.removeMessageListener('data-transport-closed', dataTransportClosedHandler);
+      info("The data transport is closed. " + aReason);
+    });
+
+    connection.onstatechange = function() {
+      connection.onstatechange = null;
+      is(connection.state, "terminated", "Connection should be terminated.");
+      aResolve();
+    };
+
+    connection.terminate();
+  });
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    info('teardown-complete');
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  ok(window.PresentationRequest, "PresentationRequest should be available.");
+
+  testSetup().
+  then(testStartConnection).
+  then(testSend).
+  then(testIncomingMessage).
+  then(testTerminateConnection).
+  then(teardown);
+}
+
+SimpleTest.expectAssertions(0, 5);
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: 'presentation-device-manage', allow: false, context: document},
+  {type: 'presentation', allow: true, context: document},
+], function() {
+  SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
+                                      ["dom.presentation.session_transport.data_channel.enable", true]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
@@ -151,17 +151,16 @@ function runTests() {
   ok(window.PresentationRequest, "PresentationRequest should be available.");
 
   testSetup().
   then(testStartConnectionWithDevice).
   then(testStartConnectionWithDeviceNotFoundError).
   then(teardown);
 }
 
-//SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: true, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
                                       ["dom.presentation.test.enabled", true],
                                       ["dom.presentation.test.stage", 0]]},
rename from dom/presentation/tests/mochitest/test_presentation_receiver.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html
--- a/dom/presentation/tests/mochitest/test_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver.html
@@ -111,23 +111,21 @@ function teardown() {
   gScript.sendAsyncMessage('teardown');
 }
 
 function runTests() {
   setup().
   then(testIncomingSessionRequest);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0]]},
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_receiver_establish_connection_error.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html
--- a/dom/presentation/tests/mochitest/test_presentation_receiver_establish_connection_error.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_error.html
@@ -86,23 +86,21 @@ function teardown() {
   gScript.sendAsyncMessage('teardown');
 }
 
 function runTests() {
   setup().
   then(testIncomingSessionRequest);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0]]},
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_receiver_establish_connection_timeout.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html
--- a/dom/presentation/tests/mochitest/test_presentation_receiver_establish_connection_timeout.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_timeout.html
@@ -60,24 +60,22 @@ function teardown() {
 }
 
 function runTests() {
   setup().
   then(testIncomingSessionRequestReceiverLaunchTimeout).
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0],
+                                      ["dom.presentation.session_transport.data_channel.enable", false],
                                       ["presentation.receiver.loading.timeout", 10]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html
--- a/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_oop.html
@@ -152,26 +152,24 @@ function teardown() {
   gScript.sendAsyncMessage('teardown');
 }
 
 function runTests() {
   setup().
   then(testIncomingSessionRequest);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
   {type: 'browser', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0],
+                                      ["dom.presentation.session_transport.data_channel.enable", false],
                                       ["dom.mozBrowserFramesEnabled", true],
                                       ["dom.ipc.browser_frames.oop_by_default", true]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_sender.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
--- a/dom/presentation/tests/mochitest/test_presentation_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
@@ -181,23 +181,21 @@ function runTests() {
   testSetup().
   then(testStartConnection).
   then(testSend).
   then(testIncomingMessage).
   then(testTerminateConnection).
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0]]},
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
--- a/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
@@ -129,23 +129,21 @@ function runTests() {
   ok(navigator.presentation, "navigator.presentation should be available.");
 
   testSetup().
   then(testStartConnection).
   then(testTerminateConnection).
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0]]},
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
--- a/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
@@ -135,23 +135,21 @@ function runTests() {
   ok(window.PresentationRequest, "PresentationRequest should be available.");
 
   testSetup().
   then(testStartConnection).
   then(testDisconnection).
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0]]},
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
rename from dom/presentation/tests/mochitest/test_presentation_sender_establish_connection_error.html
rename to dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
--- a/dom/presentation/tests/mochitest/test_presentation_sender_establish_connection_error.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
@@ -339,23 +339,21 @@ function runTests() {
   then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportInit).
   then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportInit).
   then(testStartConnectionUnexpectedControlChannelCloseBeforeDataTransportReady).
   then(testStartConnectionUnexpectedControlChannelCloseNoReasonBeforeDataTransportReady).
   then(testStartConnectionUnexpectedDataTransportClose).
   then(teardown);
 }
 
-SimpleTest.expectAssertions(0, 5);
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPermissions([
   {type: 'presentation-device-manage', allow: false, context: document},
   {type: 'presentation', allow: true, context: document},
 ], function() {
   SpecialPowers.pushPrefEnv({ 'set': [["dom.presentation.enabled", true],
-                                      ["dom.presentation.test.enabled", true],
-                                      ["dom.presentation.test.stage", 0]]},
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
                             runTests);
 });
 
 </script>
 </body>
 </html>
--- a/dom/webidl/PresentationConnection.webidl
+++ b/dom/webidl/PresentationConnection.webidl
@@ -38,17 +38,17 @@ interface PresentationConnection : Event
 
   /*
    * 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 1148307 Implement PresentationSessionTransport with DataChannel to
+   * TODO bug 1228474 Implement PresentationSessionTransport with DataChannel to
    * support other binary types.
    */
   [Throws]
   void send(DOMString data);
 
   /*
    * It is triggered when receiving messages.
    */
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5063,16 +5063,17 @@ pref("dom.udpsocket.enabled", false);
 pref("dom.beforeAfterKeyboardEvent.enabled", false);
 
 // Presentation API
 pref("dom.presentation.enabled", false);
 pref("dom.presentation.tcp_server.debug", false);
 pref("dom.presentation.discovery.enabled", false);
 pref("dom.presentation.discovery.timeout_ms", 10000);
 pref("dom.presentation.discoverable", false);
+pref("dom.presentation.session_transport.data_channel.enable", true);
 
 #ifdef XP_MACOSX
 #if !defined(RELEASE_BUILD) || defined(DEBUG)
 // In non-release builds we crash by default on insecure text input (when a
 // password editor has focus but secure event input isn't enabled).  The
 // following pref, when turned on, disables this behavior.  See bug 1188425.
 pref("intl.allow-insecure-text-input", false);
 #endif