Bug 1264513 - Part 1: IPresentationSessionTransportBuilder.idl changes - necessary refactory in in-proc data channel handling, r=smaug
authorJunior Hsu <juhsu@mozilla.com>
Fri, 03 Jun 2016 11:03:15 +0800
changeset 300430 25c1b11a4c39716ac1baef4373a9851a21a5eb6d
parent 300429 0cb38314f603d6a3f01fd90ee296b11df262b0cf
child 300431 9b7a469be51121e5bc8ada8038d0b3c46cdd0a46
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1264513
milestone49.0a1
Bug 1264513 - Part 1: IPresentationSessionTransportBuilder.idl changes - necessary refactory in in-proc data channel handling, r=smaug
dom/presentation/PresentationDataChannelSessionTransport.js
dom/presentation/PresentationService.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/PresentationTCPSessionTransport.cpp
dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
--- a/dom/presentation/PresentationDataChannelSessionTransport.js
+++ b/dom/presentation/PresentationDataChannelSessionTransport.js
@@ -44,50 +44,47 @@ function PresentationTransportBuilder() 
   log("PresentationTransportBuilder construct");
   this._isControlChannelNeeded = true;
 }
 
 PresentationTransportBuilder.prototype = {
   classID: PRESENTATIONTRANSPORTBUILDER_CID,
   contractID: PRESENTATIONTRANSPORTBUILDER_CONTRACTID,
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDataChannelSessionTransportBuilder,
-                                         Ci.nsIPresentationControlChannelListener,
                                          Ci.nsITimerCallback]),
 
-  buildDataChannelTransport: function(aRole, aWindow, aControlChannel, aListener) {
-    if (!aRole || !aWindow || !aControlChannel || !aListener) {
+  buildDataChannelTransport: function(aRole, aWindow, aListener) {
+    if (!aRole || !aWindow || !aListener) {
       log("buildDataChannelTransport with illegal parameters");
       throw Cr.NS_ERROR_ILLEGAL_VALUE;
     }
 
     if (this._window) {
       log("buildDataChannelTransport has started.");
       throw Cr.NS_ERROR_UNEXPECTED;
     }
 
     log("buildDataChannelTransport with role " + aRole);
     this._role = aRole;
     this._window = aWindow;
-    this._controlChannel = aControlChannel.QueryInterface(Ci.nsIPresentationControlChannel);
-    this._controlChannel.listener = this;
     this._listener = aListener.QueryInterface(Ci.nsIPresentationSessionTransportBuilderListener);
 
     // TODO bug 1227053 set iceServers from |nsIPresentationDevice|
     this._peerConnection = new this._window.RTCPeerConnection();
 
-    // |this._controlChannel == null| will throw since the control channel is
+    // |this._listener == null| will throw since the control channel is
     // abnormally closed.
     this._peerConnection.onicecandidate = aEvent => aEvent.candidate &&
-      this._controlChannel.sendIceCandidate(JSON.stringify(aEvent.candidate));
+      this._listener.sendIceCandidate(JSON.stringify(aEvent.candidate));
 
     this._peerConnection.onnegotiationneeded = () => {
       log("onnegotiationneeded with role " + this._role);
       this._peerConnection.createOffer()
           .then(aOffer => this._peerConnection.setLocalDescription(aOffer))
-          .then(() => this._controlChannel
+          .then(() => this._listener
                           .sendOffer(new PresentationDataChannelDescription(this._peerConnection.localDescription)))
           .catch(e => this._reportError(e));
     }
 
     switch (this._role) {
       case Ci.nsIPresentationService.ROLE_CONTROLLER:
         this._dataChannel = this._peerConnection.createDataChannel("presentationAPI");
         this._setDataChannel();
@@ -164,21 +161,16 @@ PresentationTransportBuilder.prototype =
     if (this._peerConnection) {
       this._peerConnection.close();
       this._peerConnection = null;
     }
 
     this._role = null;
     this._window = null;
 
-    if (this._controlChannel) {
-      this._controlChannel.close(aReason);
-      this._controlChannel = null;
-    }
-
     this._listener = null;
     this._sessionTransport = null;
 
     if (this._timer) {
       this._timer.cancel();
       this._timer = null;
     }
   },
@@ -197,17 +189,17 @@ PresentationTransportBuilder.prototype =
                         .RTCSessionDescription(JSON.parse(aOffer.dataChannelSDP));
 
     this._peerConnection.setRemoteDescription(offer)
         .then(() => this._peerConnection.signalingState == "stable" ||
                       this._peerConnection.createAnswer())
         .then(aAnswer => this._peerConnection.setLocalDescription(aAnswer))
         .then(() => {
           this._isControlChannelNeeded = false;
-          this._controlChannel
+          this._listener
               .sendAnswer(new PresentationDataChannelDescription(this._peerConnection.localDescription))
         }).catch(e => this._reportError(e));
   },
 
   onAnswer: function(aAnswer) {
     if (this._role !== Ci.nsIPresentationService.ROLE_CONTROLLER ||
           this._sessionTransport) {
       log("onAnswer status error");
@@ -224,29 +216,24 @@ PresentationTransportBuilder.prototype =
   },
 
   onIceCandidate: function(aCandidate) {
     log("onIceCandidate: " + aCandidate + " with role " + this._role);
     let candidate = new this._window.RTCIceCandidate(JSON.parse(aCandidate));
     this._peerConnection.addIceCandidate(candidate).catch(e => this._reportError(e));
   },
 
-  notifyOpened: function() {
-    log("notifyOpened, should be opened beforehand");
-  },
-
   notifyClosed: function(aReason) {
     log("notifyClosed reason: " + aReason);
 
     if (aReason != Cr.NS_OK) {
       this._cleanup(aReason);
     } else if (this._isControlChannelNeeded) {
       this._cleanup(Cr.NS_ERROR_FAILURE);
     }
-    this._controlChannel = null;
   },
 };
 
 function PresentationTransport() {
   this._messageQueue = [];
   this._closeReason = Cr.NS_OK;
 }
 
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -448,17 +448,17 @@ PresentationService::StartSession(const 
   nsCOMPtr<nsISimpleEnumerator> enumerator;
   rv = devices->Enumerate(getter_AddRefs(enumerator));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   NS_ConvertUTF16toUTF8 utf8DeviceId(aDeviceId);
   bool hasMore;
-  while(NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore){
+  while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) {
     nsCOMPtr<nsISupports> isupports;
     rv = enumerator->GetNext(getter_AddRefs(isupports));
 
     nsCOMPtr<nsIPresentationDevice> device(do_QueryInterface(isupports));
     MOZ_ASSERT(device);
 
     nsAutoCString id;
     if (NS_SUCCEEDED(device->GetId(id)) && id.Equals(utf8DeviceId)) {
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -428,16 +428,40 @@ PresentationSessionInfo::OnSessionTransp
 }
 
 NS_IMETHODIMP
 PresentationSessionInfo::OnError(nsresult reason)
 {
   return ReplyError(reason);
 }
 
+NS_IMETHODIMP
+PresentationSessionInfo::SendOffer(nsIPresentationChannelDescription* aOffer)
+{
+  return mControlChannel->SendOffer(aOffer);
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::SendAnswer(nsIPresentationChannelDescription* aAnswer)
+{
+  return mControlChannel->SendAnswer(aAnswer);
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::SendIceCandidate(const nsAString& candidate)
+{
+  return mControlChannel->SendIceCandidate(candidate);
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::Close(nsresult reason)
+{
+  return mControlChannel->Close(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.
@@ -575,18 +599,28 @@ PresentationControllingInfo::GetAddress(
 #endif
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationControllingInfo::OnIceCandidate(const nsAString& aCandidate)
 {
-  MOZ_ASSERT(false, "Should not receive ICE candidates.");
-  return NS_ERROR_FAILURE;
+  if (mTransportType != nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
+    return NS_ERROR_FAILURE;
+  }
+
+  if (NS_WARN_IF(!mBuilder)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
+    builder = do_QueryInterface(mBuilder);
+
+  return builder->OnIceCandidate(aCandidate);
 }
 
 nsresult
 PresentationControllingInfo::OnGetAddress(const nsACString& aAddress)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // Prepare and send the offer.
@@ -607,16 +641,26 @@ PresentationControllingInfo::OnOffer(nsI
 {
   MOZ_ASSERT(false, "Sender side should not receive offer.");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 PresentationControllingInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
 {
+  if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
+
+    if (NS_WARN_IF(!mBuilder)) {
+      return NS_ERROR_FAILURE;
+    }
+    nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
+      builder = do_QueryInterface(mBuilder);
+    return builder->OnAnswer(aDescription);
+  }
+
   mIsResponderReady = true;
 
   // Close the control channel since it's no longer needed.
   nsresult rv = mControlChannel->Close(NS_OK);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
@@ -646,26 +690,35 @@ PresentationControllingInfo::NotifyOpene
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   mBuilder = builder;
   mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
 
   return builder->BuildDataChannelTransport(nsIPresentationService::ROLE_CONTROLLER,
                                             GetWindow(),
-                                            mControlChannel,
                                             this);
 
 }
 
 NS_IMETHODIMP
 PresentationControllingInfo::NotifyClosed(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
+    if (NS_WARN_IF(!mBuilder)) {
+      return NS_ERROR_FAILURE;
+    }
+    nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
+      builder = do_QueryInterface(mBuilder);
+
+    NS_WARN_IF(NS_FAILED(builder->NotifyClosed(aReason)));
+  }
+
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
     // The presentation session instance may already exist.
     // Change the state to TERMINATED since it never succeeds.
     SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, aReason);
@@ -818,22 +871,16 @@ PresentationPresentingInfo::OnSessionTra
       new TCPPresentationChannelDescription(address, port);
 
     return mControlChannel->SendAnswer(description);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
-PresentationPresentingInfo::OnError(nsresult reason)
-{
-  return PresentationSessionInfo::OnError(reason);
-}
-
 nsresult
 PresentationPresentingInfo::InitTransportAndSendAnswer()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   uint8_t type = 0;
   nsresult rv = mRequesterDescription->GetType(&type);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -864,30 +911,23 @@ PresentationPresentingInfo::InitTranspor
     if (NS_WARN_IF(!builder)) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     mBuilder = builder;
     mTransportType = nsIPresentationChannelDescription::TYPE_DATACHANNEL;
     rv = builder->BuildDataChannelTransport(nsIPresentationService::ROLE_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);
+    return builder->OnOffer(mRequesterDescription);
   }
 
   MOZ_ASSERT(false, "Unknown nsIPresentationChannelDescription type!");
   return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
 PresentationPresentingInfo::UntrackFromService()
@@ -966,32 +1006,48 @@ PresentationPresentingInfo::OnAnswer(nsI
 {
   MOZ_ASSERT(false, "Receiver side should not receive answer.");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::OnIceCandidate(const nsAString& aCandidate)
 {
-  MOZ_ASSERT(false, "Should not receive ICE candidates.");
-  return NS_ERROR_FAILURE;
+  if (NS_WARN_IF(!mBuilder)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
+    builder = do_QueryInterface(mBuilder);
+
+  return builder->OnIceCandidate(aCandidate);
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::NotifyOpened()
 {
   // Do nothing.
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationPresentingInfo::NotifyClosed(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (mTransportType == nsIPresentationChannelDescription::TYPE_DATACHANNEL) {
+    if (NS_WARN_IF(!mBuilder)) {
+      return NS_ERROR_FAILURE;
+    }
+    nsCOMPtr<nsIPresentationDataChannelSessionTransportBuilder>
+      builder = do_QueryInterface(mBuilder);
+
+    NS_WARN_IF(NS_FAILED(builder->NotifyClosed(aReason)));
+  }
+
   // Unset control channel here so it won't try to re-close it in potential
   // subsequent |Shutdown| calls.
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason))) {
     // The presentation session instance may already exist.
     // Change the state to TERMINATED since it never succeeds.
     SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, aReason);
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -203,17 +203,16 @@ protected:
 // Session info with presenting browsing context (receiver side) behaviors.
 class PresentationPresentingInfo final : public PresentationSessionInfo
                                        , public PromiseNativeHandler
                                        , public nsITimerCallback
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
-  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTBUILDERLISTENER
   NS_DECL_NSITIMERCALLBACK
 
   PresentationPresentingInfo(const nsAString& aUrl,
                              const nsAString& aSessionId,
                              nsIPresentationDevice* aDevice)
     : PresentationSessionInfo(aUrl,
                               aSessionId,
                               nsIPresentationService::ROLE_RECEIVER)
@@ -221,16 +220,18 @@ public:
     MOZ_ASSERT(aDevice);
     SetDevice(aDevice);
   }
 
   nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
 
   nsresult NotifyResponderReady();
 
+  NS_IMETHODIMP OnSessionTransport(nsIPresentationSessionTransport* transport) override;
+
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void SetPromise(Promise* aPromise)
   {
     mPromise = aPromise;
     mPromise->AppendNativeHandler(this);
--- a/dom/presentation/PresentationTCPSessionTransport.cpp
+++ b/dom/presentation/PresentationTCPSessionTransport.cpp
@@ -63,22 +63,23 @@ NS_IMPL_CYCLE_COLLECTION(PresentationTCP
                          mInputStreamPump, mInputStreamScriptable,
                          mMultiplexStream, mMultiplexStreamCopier, mCallback)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PresentationTCPSessionTransport)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PresentationTCPSessionTransport)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PresentationTCPSessionTransport)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPresentationSessionTransport)
+  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
   NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransport)
-  NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
+  NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionTransportBuilder)
   NS_INTERFACE_MAP_ENTRY(nsIPresentationTCPSessionTransportBuilder)
-  NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
-  NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
+  NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
 NS_INTERFACE_MAP_END
 
 PresentationTCPSessionTransport::PresentationTCPSessionTransport()
   : mReadyState(ReadyState::CLOSED)
   , mAsyncCopierActive(false)
   , mCloseStatus(NS_OK)
   , mDataNotificationEnabled(false)
 {
--- a/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
+++ b/dom/presentation/interfaces/nsIPresentationSessionTransportBuilder.idl
@@ -10,18 +10,22 @@ 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);
+  void onError(in nsresult reason);
 
-  void onError(in nsresult reason);
+  void sendOffer(in nsIPresentationChannelDescription offer);
+  void sendAnswer(in nsIPresentationChannelDescription answer);
+  void sendIceCandidate(in DOMString candidate);
+  void close(in nsresult reason);
 };
 
 [scriptable, uuid(2fdbe67d-80f9-48dc-8237-5bef8fa19801)]
 interface nsIPresentationSessionTransportBuilder : nsISupports
 {
 };
 
 /**
@@ -50,13 +54,18 @@ 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,
+  void buildDataChannelTransport(in uint8_t aRole,
                                  in mozIDOMWindow aWindow,
-                                 in nsIPresentationControlChannel aControlChannel,
                                  in nsIPresentationSessionTransportBuilderListener aListener);
+
+  // Bug 1275150 - Merge TCP builder with the following APIs
+  void onOffer(in nsIPresentationChannelDescription offer);
+  void onAnswer(in nsIPresentationChannelDescription answer);
+  void onIceCandidate(in DOMString candidate);
+  void notifyClosed(in nsresult reason);
 };
--- a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
+++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js
@@ -251,17 +251,17 @@ const mockedSessionTransport = {
     };
 
     setTimeout(()=>{
       this._listener.onSessionTransport(this);
       this._listener = null;
     }, 0);
   },
   // in-process case
-  buildDataChannelTransport: function(role, window, controlChannel, listener) {
+  buildDataChannelTransport: function(role, window, listener) {
     this._listener = listener;
     this._role = role;
 
     var hasNavigator = window ? (typeof window.navigator != "undefined") : false;
     sendAsyncMessage('check-navigator', hasNavigator);
 
     setTimeout(()=>{
       this._listener.onSessionTransport(this);
--- a/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
+++ b/dom/presentation/tests/mochitest/test_presentation_datachannel_sessiontransport.html
@@ -21,56 +21,24 @@
 SimpleTest.waitForExplicitFinish();
 
 const loadingTimeoutPref = "presentation.receiver.loading.timeout";
 
 var clientBuilder;
 var serverBuilder;
 var clientTransport;
 var serverTransport;
-var clientControlChannel;
-var serverControlChannel;
 
 const clientMessage = "Client Message";
 const serverMessage = "Server Message";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 const { Services } = Cu.import("resource://gre/modules/Services.jsm");
 
-function TestControlChannel() {
-  this._listener = null;
-}
-
-TestControlChannel.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
-  set listener(aListener) {
-    this._listener = aListener;
-  },
-  get listener() {
-    return this._listener;
-  },
-  sendOffer: function(aOffer) {
-    setTimeout(()=>this._remote.listener.onOffer(aOffer), 0);
-  },
-  sendAnswer: function(aAnswer) {
-    setTimeout(()=>this._remote.listener.onAnswer(aAnswer), 0);
-  },
-  sendIceCandidate: function(aCandidate) {
-    setTimeout(()=>this._remote.listener.onIceCandidate(aCandidate), 0);
-  },
-  close: function(aReason) {
-    setTimeout(()=>this._listener.notifyClosed(aReason), 0);
-    setTimeout(()=>this._remote.listener.notifyClosed(aReason), 0);
-  },
-  set remote(aRemote) {
-    this._remote = aRemote;
-  },
-};
-
 var isClientReady = false;
 var isServerReady = false;
 var isClientClosed = false;
 var isServerClosed = false;
 
 var gResolve;
 var gReject;
 
@@ -126,57 +94,93 @@ const clientListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
   onSessionTransport: function(aTransport) {
     info("Client Transport is built.");
     clientTransport = aTransport;
     clientTransport.callback = clientCallback;
   },
   onError: function(aError)  {
     ok(false, "client's builder reports error " + aError);
-  }
+  },
+  sendOffer: function(aOffer) {
+    setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0);
+  },
+  sendAnswer: function(aAnswer) {
+    setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0);
+  },
+  sendIceCandidate: function(aCandidate) {
+    setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0);
+  },
+  close: function(aReason) {
+    setTimeout(()=>this._localBuilder.notifyClosed(aReason), 0);
+    setTimeout(()=>this._remoteBuilder.notifyClosed(aReason), 0);
+  },
+  set remoteBuilder(aRemoteBuilder) {
+    this._remoteBuilder = aRemoteBuilder;
+  },
+  set localBuilder(aLocalBuilder) {
+    this._localBuilder = aLocalBuilder;
+  },
 }
 
 const serverListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportBuilderListener]),
   onSessionTransport: function(aTransport) {
     info("Server Transport is built.");
     serverTransport = aTransport;
     serverTransport.callback = serverCallback;
     serverTransport.enableDataNotification();
   },
   onError: function(aError)  {
     ok(false, "server's builder reports error " + aError);
-  }
+  },
+  sendOffer: function(aOffer) {
+    setTimeout(()=>this._remoteBuilder.onOffer(aOffer), 0);
+  },
+  sendAnswer: function(aAnswer) {
+    setTimeout(()=>this._remoteBuilder.onAnswer(aAnswer), 0);
+  },
+  sendIceCandidate: function(aCandidate) {
+    setTimeout(()=>this._remoteBuilder.onIceCandidate(aCandidate), 0);
+  },
+  close: function(aReason) {
+    setTimeout(()=>this._localBuilder.notifyClosed(aReason), 0);
+    setTimeout(()=>this._remoteBuilder.notifyClosed(aReason), 0);
+  },
+  set remoteBuilder(aRemoteBuilder) {
+    this._remoteBuilder = aRemoteBuilder;
+  },
+  set localBuilder(aLocalBuilder) {
+    this._localBuilder = aLocalBuilder;
+  },
 }
 
 function testBuilder() {
   return new Promise(function(aResolve, aReject) {
     gResolve = aResolve;
     gReject = aReject;
 
-    clientControlChannel = new TestControlChannel();
-    serverControlChannel = new TestControlChannel();
-    clientControlChannel.remote = serverControlChannel;
-    serverControlChannel.remote = clientControlChannel;
-
     clientBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"]
                       .createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder);
     serverBuilder = Cc["@mozilla.org/presentation/datachanneltransportbuilder;1"]
                       .createInstance(Ci.nsIPresentationDataChannelSessionTransportBuilder);
 
+    clientListener.localBuilder = clientBuilder;
+    clientListener.remoteBuilder = serverBuilder;
+    serverListener.localBuilder = serverBuilder;
+    serverListener.remoteBuilder = clientBuilder;
+
     clientBuilder
       .buildDataChannelTransport(Ci.nsIPresentationService.ROLE_CONTROLLER,
                                  window,
-                                 clientControlChannel,
                                  clientListener);
 
     serverBuilder
       .buildDataChannelTransport(Ci.nsIPresentationService.ROLE_RECEIVER,
                                  window,
-                                 serverControlChannel,
                                  serverListener);
   });
 }
 
 function testClientSendMessage() {
   return new Promise(function(aResolve, aReject) {
     info("client sends message");
     gResolve = aResolve;