Bug 1301259 - Part2: Close the connected/connecting connection before starting reconnecting, r=smaug
authorKershaw Chang <kechang@mozilla.com>
Wed, 28 Sep 2016 23:35:00 +0200
changeset 315830 f8797c70734845bba0e918977e276f6b76e9769e
parent 315829 c572919bea3524d38e1a3364a4acba332910b84a
child 315831 f205f28cbcf3723aee0e302ea2ce7b71a00f4210
push id20634
push usercbook@mozilla.com
push dateFri, 30 Sep 2016 10:10:13 +0000
treeherderfx-team@afe79b010d13 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1301259
milestone52.0a1
Bug 1301259 - Part2: Close the connected/connecting connection before starting reconnecting, r=smaug
dom/presentation/PresentationCallbacks.cpp
dom/presentation/PresentationConnection.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/interfaces/nsIPresentationListener.idl
dom/presentation/ipc/PPresentation.ipdl
dom/presentation/ipc/PresentationBuilderParent.cpp
dom/presentation/ipc/PresentationBuilderParent.h
dom/presentation/ipc/PresentationChild.cpp
dom/presentation/ipc/PresentationChild.h
dom/presentation/ipc/PresentationIPCService.cpp
dom/presentation/ipc/PresentationIPCService.h
dom/presentation/ipc/PresentationParent.cpp
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -105,16 +105,20 @@ PresentationReconnectCallback::NotifySuc
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   nsresult rv = NS_OK;
   // We found a matched connection with the same window ID, URL, and
   // the session ID. Resolve the promise with this connection and dispatch
   // the event.
   if (mConnection) {
+    mConnection->NotifyStateChange(
+      mSessionId,
+      nsIPresentationSessionListener::STATE_CONNECTING,
+      NS_OK);
     mPromise->MaybeResolve(mConnection);
     rv = mRequest->DispatchConnectionAvailableEvent(mConnection);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   } else {
     // Use |PresentationRequesterCallback::NotifySuccess| to create a new
     // connection since we don't find one that can be reused.
@@ -137,16 +141,22 @@ PresentationReconnectCallback::NotifySuc
              service->BuildTransport(sessionId,
                                      nsIPresentationService::ROLE_CONTROLLER);
            }));
 }
 
 NS_IMETHODIMP
 PresentationReconnectCallback::NotifyError(nsresult aError)
 {
+  if (mConnection) {
+    mConnection->NotifyStateChange(
+      mSessionId,
+      nsIPresentationSessionListener::STATE_CLOSED,
+      aError);
+  }
   return PresentationRequesterCallback::NotifyError(aError);
 }
 
 NS_IMPL_ISUPPORTS(PresentationResponderLoadingCallback,
                   nsIWebProgressListener,
                   nsISupportsWeakReference)
 
 PresentationResponderLoadingCallback::PresentationResponderLoadingCallback(const nsAString& aSessionId)
--- a/dom/presentation/PresentationConnection.cpp
+++ b/dom/presentation/PresentationConnection.cpp
@@ -357,16 +357,21 @@ PresentationConnection::NotifyStateChang
   PRES_DEBUG("connection state change:id[%s], state[%x], reason[%x], role[%d]\n",
              NS_ConvertUTF16toUTF8(aSessionId).get(), aState,
              aReason, mRole);
 
   if (!aSessionId.Equals(mId)) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  // A terminated connection should always remain in terminated.
+  if (mState == PresentationConnectionState::Terminated) {
+    return NS_OK;
+  }
+
   PresentationConnectionState state;
   switch (aState) {
     case nsIPresentationSessionListener::STATE_CONNECTING:
       state = PresentationConnectionState::Connecting;
       break;
     case nsIPresentationSessionListener::STATE_CONNECTED:
       state = PresentationConnectionState::Connected;
       break;
@@ -519,27 +524,16 @@ PresentationConnection::DoReceiveMessage
     if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) {
       return NS_ERROR_FAILURE;
     }
   }
 
   return DispatchMessageEvent(jsData);
 }
 
-NS_IMETHODIMP
-PresentationConnection::NotifyReplaced()
-{
-  PRES_DEBUG("connection %s:id[%s], role[%d]\n", __func__,
-             NS_ConvertUTF16toUTF8(mId).get(), mRole);
-
-  return NotifyStateChange(mId,
-                           nsIPresentationSessionListener::STATE_CLOSED,
-                           NS_OK);
-}
-
 nsresult
 PresentationConnection::DispatchConnectionClosedEvent(
   PresentationConnectionClosedReason aReason,
   const nsAString& aMessage,
   bool aDispatchNow)
 {
   if (mState != PresentationConnectionState::Closed) {
     MOZ_ASSERT(false, "The connection state should be closed.");
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -242,20 +242,16 @@ PresentationSessionInfo::Shutdown(nsresu
   mIsOnTerminating = false;
 
   ResetBuilder();
 }
 
 nsresult
 PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
 {
-  if (mListener && aListener) {
-    Unused << NS_WARN_IF(NS_FAILED(mListener->NotifyReplaced()));
-  }
-
   mListener = aListener;
 
   if (mListener) {
     // Enable data notification for the transport channel if it's available.
     if (mTransport) {
       nsresult rv = mTransport->EnableDataNotification();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
@@ -311,20 +307,16 @@ PresentationSessionInfo::SendBlob(nsIDOM
 
   return mTransport->SendBlob(aBlob);
 }
 
 nsresult
 PresentationSessionInfo::Close(nsresult aReason,
                                uint32_t aState)
 {
-  if (NS_WARN_IF(!IsSessionReady())) {
-    return NS_ERROR_DOM_INVALID_STATE_ERR;
-  }
-
   // Do nothing if session is already terminated.
   if (nsIPresentationSessionListener::STATE_TERMINATED == mState) {
     return NS_OK;
   }
 
   SetStateWithReason(aState, aReason);
 
   switch (aState) {
@@ -515,33 +507,32 @@ PresentationSessionInfo::NotifyData(cons
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return mListener->NotifyMessage(mSessionId, aData, aIsBinary);
 }
 
 // nsIPresentationSessionTransportBuilderListener
 NS_IMETHODIMP
-PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
+PresentationSessionInfo::OnSessionTransport(nsIPresentationSessionTransport* aTransport)
 {
   PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
 
   ResetBuilder();
 
   if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
     return NS_ERROR_FAILURE;
   }
 
-  // The session transport is managed by content process
-  if (!transport) {
-    return NS_OK;
+  if (NS_WARN_IF(!aTransport)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
-  mTransport = transport;
+  mTransport = aTransport;
 
   nsresult rv = mTransport->SetCallback(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (mListener) {
     mTransport->EnableDataNotification();
@@ -646,18 +637,16 @@ PresentationControllingInfo::Shutdown(ns
 {
   PresentationSessionInfo::Shutdown(aReason);
 
   // Close the server socket if any.
   if (mServerSocket) {
     Unused << NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
     mServerSocket = nullptr;
   }
-
-  mIsReconnecting = false;
 }
 
 nsresult
 PresentationControllingInfo::GetAddress()
 {
 #if defined(MOZ_WIDGET_GONK)
   nsCOMPtr<nsINetworkManager> networkManager =
     do_GetService("@mozilla.org/network/manager;1");
@@ -837,23 +826,22 @@ PresentationControllingInfo::NotifyConne
 
 NS_IMETHODIMP
 PresentationControllingInfo::NotifyReconnected()
 {
   PRES_DEBUG("%s:id[%s], role[%d]\n", __func__,
              NS_ConvertUTF16toUTF8(mSessionId).get(), mRole);
 
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mReconnectCallback);
 
   if (NS_WARN_IF(mState != nsIPresentationSessionListener::STATE_CONNECTING)) {
     return NS_ERROR_FAILURE;
   }
 
-  return mReconnectCallback->NotifySuccess(GetUrl());
+  return NotifyReconnectResult(NS_OK);
 }
 
 nsresult
 PresentationControllingInfo::BuildTransport()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mState != nsIPresentationSessionListener::STATE_CONNECTING) {
@@ -931,18 +919,33 @@ PresentationControllingInfo::NotifyDisco
 
   if (NS_WARN_IF(NS_FAILED(aReason) || !mIsResponderReady)) {
     // The presentation session instance may already exist.
     // Change the state to CLOSED if it is not terminated.
     if (nsIPresentationSessionListener::STATE_TERMINATED != mState) {
       SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
     }
 
-    // Reply error for an abnormal close.
-    return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    // If |aReason| is NS_OK, it implies that the user closes the connection
+    // before becomming connected. No need to call |ReplyError| in this case.
+    if (NS_FAILED(aReason)) {
+      if (mIsReconnecting) {
+        NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
+      }
+      // Reply error for an abnormal close.
+      return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+    }
+    Shutdown(aReason);
+  }
+
+  // This is the case for reconnecting a connection which is in
+  // connecting state and |mTransport| is not ready.
+  if (mDoReconnectAfterClose && !mTransport) {
+    mDoReconnectAfterClose = false;
+    return Reconnect(mReconnectCallback);
   }
 
   return NS_OK;
 }
 
 // nsIServerSocketListener
 NS_IMETHODIMP
 PresentationControllingInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
@@ -1017,60 +1020,72 @@ PresentationControllingInfo::OnStopListe
  * 5. Once both step 3 and 4 are done, the rest is to build a new data
  *    transport channel by following the same steps as starting a
  *    new session.
  */
 
 nsresult
 PresentationControllingInfo::Reconnect(nsIPresentationServiceCallback* aCallback)
 {
+  PRES_DEBUG("%s:id[%s], role[%d], state[%d]\n", __func__,
+             NS_ConvertUTF16toUTF8(mSessionId).get(), mRole, mState);
+
   if (!aCallback) {
     return NS_ERROR_INVALID_ARG;
   }
 
   mReconnectCallback = aCallback;
 
   if (NS_WARN_IF(mState == nsIPresentationSessionListener::STATE_TERMINATED)) {
-    return mReconnectCallback->NotifyError(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return NotifyReconnectResult(NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 
-  SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTING, NS_OK);
+  // If |mState| is not CLOSED, we have to close the connection before
+  // reconnecting. The process to reconnect will be continued after
+  // |NotifyDisconnected| or |NotifyTransportClosed| is invoked.
+  if (mState == nsIPresentationSessionListener::STATE_CONNECTING ||
+      mState == nsIPresentationSessionListener::STATE_CONNECTED) {
+    mDoReconnectAfterClose = true;
+    return Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
+  }
+
+  // Make sure |mState| is closed at this point.
+  MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
+
+  mState = nsIPresentationSessionListener::STATE_CONNECTING;
+  mIsReconnecting = true;
 
   nsresult rv = NS_OK;
   if (!mControlChannel) {
     nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
     rv = mDevice->EstablishControlChannel(getter_AddRefs(ctrlChannel));
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+      return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
     }
 
     rv = Init(ctrlChannel);
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+      return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
     }
-
-    mIsReconnecting = true;
   } else {
     return ContinueReconnect();
   }
 
   return NS_OK;
 }
 
 nsresult
 PresentationControllingInfo::ContinueReconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mControlChannel);
-  MOZ_ASSERT(mReconnectCallback);
 
   mIsReconnecting = false;
-  if (NS_WARN_IF(NS_FAILED(mControlChannel->Reconnect(mSessionId, GetUrl()))) &&
-      mReconnectCallback) {
-    return mReconnectCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
+  if (NS_WARN_IF(NS_FAILED(mControlChannel->Reconnect(mSessionId, GetUrl())))) {
+    return NotifyReconnectResult(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
 // nsIListNetworkAddressesListener
 NS_IMETHODIMP
 PresentationControllingInfo::OnListedNetworkAddresses(const char** aAddressArray,
@@ -1110,16 +1125,62 @@ PresentationControllingInfo::OnListNetwo
     NewRunnableMethod<nsCString>(
       this,
       &PresentationControllingInfo::OnGetAddress,
       "127.0.0.1"));
 
   return NS_OK;
 }
 
+nsresult
+PresentationControllingInfo::NotifyReconnectResult(nsresult aStatus)
+{
+  if (!mReconnectCallback) {
+    MOZ_ASSERT(false, "mReconnectCallback can not be null here.");
+    return NS_ERROR_FAILURE;
+  }
+
+  mIsReconnecting = false;
+  nsCOMPtr<nsIPresentationServiceCallback> callback =
+    mReconnectCallback.forget();
+  if (NS_FAILED(aStatus)) {
+    return callback->NotifyError(aStatus);
+  }
+
+  return callback->NotifySuccess(GetUrl());
+}
+
+// nsIPresentationSessionTransportCallback
+NS_IMETHODIMP
+PresentationControllingInfo::NotifyTransportReady()
+{
+  return PresentationSessionInfo::NotifyTransportReady();
+}
+
+NS_IMETHODIMP
+PresentationControllingInfo::NotifyTransportClosed(nsresult aReason)
+{
+  if (!mDoReconnectAfterClose) {
+    return PresentationSessionInfo::NotifyTransportClosed(aReason);;
+  }
+
+  MOZ_ASSERT(mState == nsIPresentationSessionListener::STATE_CLOSED);
+
+  mTransport = nullptr;
+  mIsTransportReady = false;
+  mDoReconnectAfterClose = false;
+  return Reconnect(mReconnectCallback);
+}
+
+NS_IMETHODIMP
+PresentationControllingInfo::NotifyData(const nsACString& aData, bool aIsBinary)
+{
+  return PresentationSessionInfo::NotifyData(aData, aIsBinary);
+}
+
 /**
  * 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.
@@ -1177,27 +1238,27 @@ PresentationPresentingInfo::Shutdown(nsr
   mRequesterDescription = nullptr;
   mPendingCandidates.Clear();
   mPromise = nullptr;
   mHasFlushPendingEvents = false;
 }
 
 // nsIPresentationSessionTransportBuilderListener
 NS_IMETHODIMP
-PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* transport)
+PresentationPresentingInfo::OnSessionTransport(nsIPresentationSessionTransport* aTransport)
 {
-  nsresult rv = PresentationSessionInfo::OnSessionTransport(transport);
+  nsresult rv = PresentationSessionInfo::OnSessionTransport(aTransport);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // The session transport is managed by content process
-  if (!transport) {
-    return NS_OK;
+  if (NS_WARN_IF(!aTransport)) {
+    return NS_ERROR_INVALID_ARG;
   }
 
   // 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
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -187,16 +187,17 @@ class PresentationControllingInfo final 
                                         , public nsIServerSocketListener
                                         , public nsIListNetworkAddressesListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
   NS_DECL_NSISERVERSOCKETLISTENER
   NS_DECL_NSILISTNETWORKADDRESSESLISTENER
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK
 
   PresentationControllingInfo(const nsAString& aUrl,
                               const nsAString& aSessionId)
     : PresentationSessionInfo(aUrl,
                               aSessionId,
                               nsIPresentationService::ROLE_CONTROLLER)
   {}
 
@@ -215,19 +216,22 @@ private:
   void Shutdown(nsresult aReason) override;
 
   nsresult GetAddress();
 
   nsresult OnGetAddress(const nsACString& aAddress);
 
   nsresult ContinueReconnect();
 
+  nsresult NotifyReconnectResult(nsresult aStatus);
+
   nsCOMPtr<nsIServerSocket> mServerSocket;
   nsCOMPtr<nsIPresentationServiceCallback> mReconnectCallback;
   bool mIsReconnecting = false;
+  bool mDoReconnectAfterClose = false;
 };
 
 // Session info with presenting browsing context (receiver side) behaviors.
 class PresentationPresentingInfo final : public PresentationSessionInfo
                                        , public PromiseNativeHandler
                                        , public nsITimerCallback
 {
 public:
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -29,21 +29,16 @@ interface nsIPresentationSessionListener
                          in nsresult reason);
 
   /*
    * Called when receive messages.
    */
   void notifyMessage(in DOMString sessionId,
                      in ACString data,
                      in boolean isBinary);
-
-  /*
-   * Called when this listener is replaced by another one.
-   */
-  void notifyReplaced();
 };
 
 [scriptable, uuid(27f101d7-9ed1-429e-b4f8-43b00e8e111c)]
 interface nsIPresentationRespondingListener : nsISupports
 {
   /*
    * Called when an incoming session connects.
    */
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -76,16 +76,19 @@ sync protocol PPresentation
 
 child:
   async NotifyAvailableChange(bool aAvailable);
   async NotifySessionStateChange(nsString aSessionId,
                                  uint16_t aState,
                                  nsresult aReason);
   async NotifyMessage(nsString aSessionId, nsCString aData, bool aIsBinary);
   async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId);
+  async NotifyCloseSessionTransport(nsString aSessionId,
+                                    uint8_t aRole,
+                                    nsresult aReason);
 
   async PPresentationBuilder(nsString aSessionId, uint8_t aRole);
 
 parent:
   async __delete__();
 
   async RegisterAvailabilityHandler();
   async UnregisterAvailabilityHandler();
--- a/dom/presentation/ipc/PresentationBuilderParent.cpp
+++ b/dom/presentation/ipc/PresentationBuilderParent.cpp
@@ -6,16 +6,108 @@
 
 #include "DCPresentationChannelDescription.h"
 #include "PresentationBuilderParent.h"
 #include "PresentationSessionInfo.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+
+class PresentationSessionTransportIPC final :
+  public nsIPresentationSessionTransport
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORT
+
+  PresentationSessionTransportIPC(PresentationParent* aParent,
+                                  const nsAString& aSessionId,
+                                  uint8_t aRole)
+    : mParent(aParent)
+    , mSessionId(aSessionId)
+    , mRole(aRole)
+  {
+    MOZ_ASSERT(mParent);
+  }
+
+private:
+  virtual ~PresentationSessionTransportIPC() = default;
+
+  RefPtr<PresentationParent> mParent;
+  nsString mSessionId;
+  uint8_t mRole;
+};
+
+NS_IMPL_ISUPPORTS(PresentationSessionTransportIPC,
+                  nsIPresentationSessionTransport)
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::GetCallback(
+                           nsIPresentationSessionTransportCallback** aCallback)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::SetCallback(
+                            nsIPresentationSessionTransportCallback* aCallback)
+{
+  if (aCallback) {
+    aCallback->NotifyTransportReady();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::GetSelfAddress(nsINetAddr** aSelfAddress)
+{
+  MOZ_ASSERT(false, "Not expected.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::EnableDataNotification()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::Send(const nsAString& aData)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::SendBinaryMsg(const nsACString& aData)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::SendBlob(nsIDOMBlob* aBlob)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionTransportIPC::Close(nsresult aReason)
+{
+  if (NS_WARN_IF(!mParent->SendNotifyCloseSessionTransport(mSessionId,
+                                                           mRole,
+                                                           aReason))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+} // anonymous namespace
+
 NS_IMPL_ISUPPORTS(PresentationBuilderParent,
                   nsIPresentationSessionTransportBuilder,
                   nsIPresentationDataChannelSessionTransportBuilder)
 
 PresentationBuilderParent::PresentationBuilderParent(PresentationParent* aParent)
   : mParent(aParent)
 {
   MOZ_COUNT_CTOR(PresentationBuilderParent);
@@ -34,21 +126,25 @@ NS_IMETHODIMP
 PresentationBuilderParent::BuildDataChannelTransport(
                       uint8_t aRole,
                       mozIDOMWindow* aWindow, /* unused */
                       nsIPresentationSessionTransportBuilderListener* aListener)
 {
   mBuilderListener = aListener;
 
   RefPtr<PresentationSessionInfo> info = static_cast<PresentationSessionInfo*>(aListener);
+  nsAutoString sessionId(info->GetSessionId());
   if (NS_WARN_IF(!mParent->SendPPresentationBuilderConstructor(this,
-                                                               nsString(info->GetSessionId()),
+                                                               sessionId,
                                                                aRole))) {
     return NS_ERROR_FAILURE;
   }
+  mIPCSessionTransport = new PresentationSessionTransportIPC(mParent,
+                                                             sessionId,
+                                                             aRole);
   mNeedDestroyActor = true;
   mParent = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationBuilderParent::OnIceCandidate(const nsAString& aCandidate)
 {
@@ -145,27 +241,20 @@ PresentationBuilderParent::RecvClose(con
   }
   return true;
 }
 
 // Delegate to nsIPresentationSessionTransportBuilderListener
 bool
 PresentationBuilderParent::RecvOnSessionTransport()
 {
-  // To avoid releasing |this| in this method
-  NS_DispatchToMainThread(NS_NewRunnableFunction([this]() -> void {
-    Unused <<
-      NS_WARN_IF(!mBuilderListener ||
-                 NS_FAILED(mBuilderListener->OnSessionTransport(nullptr)));
-  }));
-
-  nsCOMPtr<nsIPresentationSessionTransportCallback>
-    callback(do_QueryInterface(mBuilderListener));
-
-  callback->NotifyTransportReady();
+  RefPtr<PresentationBuilderParent> kungFuDeathGrip = this;
+  Unused <<
+    NS_WARN_IF(!mBuilderListener ||
+               NS_FAILED(mBuilderListener->OnSessionTransport(mIPCSessionTransport)));
   return true;
 }
 
 bool
 PresentationBuilderParent::RecvOnSessionTransportError(const nsresult& aReason)
 {
   if (NS_WARN_IF(!mBuilderListener ||
                  NS_FAILED(mBuilderListener->OnError(aReason)))) {
--- a/dom/presentation/ipc/PresentationBuilderParent.h
+++ b/dom/presentation/ipc/PresentationBuilderParent.h
@@ -38,14 +38,15 @@ public:
 
   virtual bool RecvOnSessionTransportError(const nsresult& aReason) override;
 
 private:
   virtual ~PresentationBuilderParent();
   bool mNeedDestroyActor = false;
   RefPtr<PresentationParent> mParent;
   nsCOMPtr<nsIPresentationSessionTransportBuilderListener> mBuilderListener;
+  nsCOMPtr<nsIPresentationSessionTransport> mIPCSessionTransport;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationBuilderParent_h__
--- a/dom/presentation/ipc/PresentationChild.cpp
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -128,16 +128,28 @@ PresentationChild::RecvNotifySessionConn
                                             const nsString& aSessionId)
 {
   if (mService) {
     Unused << NS_WARN_IF(NS_FAILED(mService->NotifySessionConnect(aWindowId, aSessionId)));
   }
   return true;
 }
 
+bool
+PresentationChild::RecvNotifyCloseSessionTransport(const nsString& aSessionId,
+                                                   const uint8_t& aRole,
+                                                   const nsresult& aReason)
+{
+  if (mService) {
+    Unused << NS_WARN_IF(NS_FAILED(
+      mService->CloseContentSessionTransport(aSessionId, aRole, aReason)));
+  }
+  return true;
+}
+
 /*
  * Implementation of PresentationRequestChild
  */
 
 PresentationRequestChild::PresentationRequestChild(nsIPresentationServiceCallback* aCallback)
   : mActorDestroyed(false)
   , mCallback(aCallback)
 {
--- a/dom/presentation/ipc/PresentationChild.h
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -54,16 +54,21 @@ public:
   RecvNotifyMessage(const nsString& aSessionId,
                     const nsCString& aData,
                     const bool& aIsBinary) override;
 
   virtual bool
   RecvNotifySessionConnect(const uint64_t& aWindowId,
                            const nsString& aSessionId) override;
 
+  virtual bool
+  RecvNotifyCloseSessionTransport(const nsString& aSessionId,
+                                  const uint8_t& aRole,
+                                  const nsresult& aReason) override;
+
 private:
   virtual ~PresentationChild();
 
   bool mActorDestroyed = false;
   RefPtr<PresentationIPCService> mService;
 };
 
 class PresentationRequestChild final : public PPresentationRequestChild
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -263,17 +263,16 @@ PresentationIPCService::RegisterSessionL
                                                 uint8_t aRole,
                                                 nsIPresentationSessionListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (mSessionListeners.Get(aSessionId, getter_AddRefs(listener))) {
-    Unused << NS_WARN_IF(NS_FAILED(listener->NotifyReplaced()));
     mSessionListeners.Put(aSessionId, aListener);
     return NS_OK;
   }
 
   mSessionListeners.Put(aSessionId, aListener);
   if (sPresentationChild) {
     Unused <<
       NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(
@@ -503,8 +502,22 @@ nsresult
 PresentationIPCService::MonitorResponderLoading(const nsAString& aSessionId,
                                                 nsIDocShell* aDocShell)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mCallback = new PresentationResponderLoadingCallback(aSessionId);
   return mCallback->Init(aDocShell);
 }
+
+nsresult
+PresentationIPCService::CloseContentSessionTransport(const nsString& aSessionId,
+                                                     uint8_t aRole,
+                                                     nsresult aReason)
+{
+  RefPtr<PresentationContentSessionInfo> info =
+    GetSessionInfo(aSessionId, aRole);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->Close(aReason);
+}
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -48,16 +48,20 @@ public:
 
   nsresult MonitorResponderLoading(const nsAString& aSessionId,
                                    nsIDocShell* aDocShell);
 
   nsresult NotifySessionTransport(const nsString& aSessionId,
                                   const uint8_t& aRole,
                                   nsIPresentationSessionTransport* transport);
 
+  nsresult CloseContentSessionTransport(const nsString& aSessionId,
+                                        uint8_t aRole,
+                                        nsresult aReason);
+
 private:
   virtual ~PresentationIPCService();
   nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
                        const PresentationIPCRequest& aRequest);
 
   nsTObserverArray<nsCOMPtr<nsIPresentationAvailabilityListener> > mAvailabilityListeners;
   nsRefPtrHashtable<nsStringHashKey,
                     nsIPresentationSessionListener> mSessionListeners;
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -301,24 +301,16 @@ PresentationParent::NotifyStateChange(co
                                                aState,
                                                aReason))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationParent::NotifyReplaced()
-{
-  // Do nothing here, since |PresentationIPCService::RegisterSessionListener|
-  // already dealt with this in content process.
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 PresentationParent::NotifyMessage(const nsAString& aSessionId,
                                   const nsACString& aData,
                                   bool aIsBinary)
 {
   if (NS_WARN_IF(mActorDestroyed ||
                  !SendNotifyMessage(nsString(aSessionId),
                                     nsCString(aData),
                                     aIsBinary))) {