Bug 1258600 - Part2: Implement onconnect, onclose and onterminate event handlers, r=smaug
authorKershaw Chang <kechang@mozilla.com>
Mon, 30 May 2016 08:48:00 +0200
changeset 299642 3f3fef6d6d9c845a4de04eba6639cae3d5027c97
parent 299641 47a6b93fbd166149be25da3096dbb50175b834d2
child 299643 b7836822d08417b4134ae1a1f45a38d383971803
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1258600
milestone49.0a1
Bug 1258600 - Part2: Implement onconnect, onclose and onterminate event handlers, r=smaug
dom/base/nsGkAtomList.h
dom/presentation/PresentationConnection.cpp
dom/presentation/PresentationConnection.h
dom/presentation/PresentationService.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/interfaces/nsIPresentationListener.idl
dom/presentation/interfaces/nsIPresentationService.idl
dom/presentation/ipc/PPresentation.ipdl
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
dom/webidl/PresentationConnection.webidl
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -922,16 +922,17 @@ GK_ATOM(onstatechange, "onstatechange")
 GK_ATOM(onstatuschanged, "onstatuschanged")
 GK_ATOM(onstkcommand, "onstkcommand")
 GK_ATOM(onstksessionend, "onstksessionend")
 GK_ATOM(onstorage, "onstorage")
 GK_ATOM(onstorageareachanged, "onstorageareachanged")
 GK_ATOM(onsubmit, "onsubmit")
 GK_ATOM(onsuccess, "onsuccess")
 GK_ATOM(ontypechange, "ontypechange")
+GK_ATOM(onterminate, "onterminate")
 GK_ATOM(ontext, "ontext")
 GK_ATOM(ontoggle, "ontoggle")
 GK_ATOM(ontouchstart, "ontouchstart")
 GK_ATOM(ontouchend, "ontouchend")
 GK_ATOM(ontouchmove, "ontouchmove")
 GK_ATOM(ontouchcancel, "ontouchcancel")
 GK_ATOM(ontransitionend, "ontransitionend")
 GK_ATOM(onunderflow, "onunderflow")
--- a/dom/presentation/PresentationConnection.cpp
+++ b/dom/presentation/PresentationConnection.cpp
@@ -1,23 +1,27 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "PresentationConnection.h"
+
 #include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/MessageEvent.h"
 #include "mozilla/dom/MessageEventBinding.h"
+#include "mozilla/dom/PresentationConnectionClosedEvent.h"
+#include "mozilla/ErrorNames.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsStringStream.h"
-#include "PresentationConnection.h"
 #include "PresentationConnectionList.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationConnection)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationConnection, DOMEventTargetHelper)
@@ -29,16 +33,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwningConnectionList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(PresentationConnection, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(PresentationConnection, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationConnection)
   NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionListener)
+  NS_INTERFACE_MAP_ENTRY(nsIRequest)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 PresentationConnection::PresentationConnection(nsPIDOMWindowInner* aWindow,
                                                const nsAString& aId,
                                                const uint8_t aRole,
                                                PresentationConnectionList* aList)
   : DOMEventTargetHelper(aWindow)
   , mId(aId)
@@ -80,36 +85,54 @@ PresentationConnection::Init()
     return false;
   }
 
   nsresult rv = service->RegisterSessionListener(mId, mRole, this);
   if(NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
+  nsCOMPtr<nsILoadGroup> loadGroup;
+  GetLoadGroup(getter_AddRefs(loadGroup));
+  if(NS_WARN_IF(!loadGroup)) {
+    return false;
+  }
+
+  rv = loadGroup->AddRequest(this, nullptr);
+  if(NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  mWeakLoadGroup = do_GetWeakReference(loadGroup);
+
   return true;
 }
 
 void
 PresentationConnection::Shutdown()
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return;
   }
 
   nsresult rv = service->UnregisterSessionListener(mId, mRole);
   NS_WARN_IF(NS_FAILED(rv));
+
+  nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakLoadGroup);
+  if (loadGroup) {
+    loadGroup->RemoveRequest(this, nullptr, NS_OK);
+    mWeakLoadGroup = nullptr;
+  }
 }
 
 /* virtual */ void
 PresentationConnection::DisconnectFromOwner()
 {
-  Shutdown();
+  NS_WARN_IF(NS_FAILED(ProcessConnectionWentAway()));
   DOMEventTargetHelper::DisconnectFromOwner();
 }
 
 /* virtual */ JSObject*
 PresentationConnection::WrapObject(JSContext* aCx,
                                    JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationConnectionBinding::Wrap(aCx, this, aGivenProto);
@@ -177,17 +200,18 @@ PresentationConnection::Terminate(ErrorR
     return;
   }
 
   NS_WARN_IF(NS_FAILED(service->TerminateSession(mId, mRole)));
 }
 
 NS_IMETHODIMP
 PresentationConnection::NotifyStateChange(const nsAString& aSessionId,
-                                          uint16_t aState)
+                                          uint16_t aState,
+                                          nsresult aReason)
 {
   if (!aSessionId.Equals(mId)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   PresentationConnectionState state;
   switch (aState) {
     case nsIPresentationSessionListener::STATE_CONNECTING:
@@ -205,45 +229,82 @@ PresentationConnection::NotifyStateChang
     default:
       NS_WARNING("Unknown presentation session state.");
       return NS_ERROR_INVALID_ARG;
   }
 
   if (mState == state) {
     return NS_OK;
   }
-
   mState = state;
 
-  // Unregister session listener if the session is no longer connected.
-  if (mState == PresentationConnectionState::Terminated) {
-    nsCOMPtr<nsIPresentationService> service =
-      do_GetService(PRESENTATION_SERVICE_CONTRACTID);
-    if (NS_WARN_IF(!service)) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-
-    nsresult rv = service->UnregisterSessionListener(mId, mRole);
-    if(NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
-  nsresult rv = DispatchStateChangeEvent();
+  nsresult rv = ProcessStateChanged(aReason);
   if(NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
   }
 
   if (mOwningConnectionList) {
     mOwningConnectionList->NotifyStateChange(aSessionId, this);
   }
 
   return NS_OK;
 }
 
+nsresult
+PresentationConnection::ProcessStateChanged(nsresult aReason)
+{
+  switch (mState) {
+    case PresentationConnectionState::Connected: {
+      RefPtr<AsyncEventDispatcher> asyncDispatcher =
+        new AsyncEventDispatcher(this, NS_LITERAL_STRING("connect"), false);
+      return asyncDispatcher->PostDOMEvent();
+    }
+    case PresentationConnectionState::Closed: {
+      PresentationConnectionClosedReason reason =
+        PresentationConnectionClosedReason::Closed;
+
+      nsString errorMsg;
+      if (NS_FAILED(aReason)) {
+        reason = PresentationConnectionClosedReason::Error;
+        nsCString name, message;
+
+        // If aReason is not a DOM error, use error name as message.
+        if (NS_FAILED(NS_GetNameAndMessageForDOMNSResult(aReason,
+                                                         name,
+                                                         message))) {
+          mozilla::GetErrorName(aReason, message);
+          message.InsertLiteral("Internal error: ", 0);
+        }
+        CopyUTF8toUTF16(message, errorMsg);
+      }
+
+      return DispatchConnectionClosedEvent(reason, errorMsg);
+    }
+    case PresentationConnectionState::Terminated: {
+      nsCOMPtr<nsIPresentationService> service =
+        do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+      if (NS_WARN_IF(!service)) {
+        return NS_ERROR_NOT_AVAILABLE;
+      }
+
+      nsresult rv = service->UnregisterSessionListener(mId, mRole);
+      if(NS_WARN_IF(NS_FAILED(rv))) {
+        return rv;
+      }
+
+      RefPtr<AsyncEventDispatcher> asyncDispatcher =
+        new AsyncEventDispatcher(this, NS_LITERAL_STRING("terminate"), false);
+      return asyncDispatcher->PostDOMEvent();
+    }
+    default:
+      MOZ_CRASH("Unknown presentation session state.");
+      return NS_ERROR_INVALID_ARG;
+  }
+}
+
 NS_IMETHODIMP
 PresentationConnection::NotifyMessage(const nsAString& aSessionId,
                                       const nsACString& aData)
 {
   if (!aSessionId.Equals(mId)) {
     return NS_ERROR_INVALID_ARG;
   }
 
@@ -263,20 +324,37 @@ PresentationConnection::NotifyMessage(co
   if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) {
     return NS_ERROR_FAILURE;
   }
 
   return DispatchMessageEvent(jsData);
 }
 
 nsresult
-PresentationConnection::DispatchStateChangeEvent()
+PresentationConnection::DispatchConnectionClosedEvent(
+  PresentationConnectionClosedReason aReason,
+  const nsAString& aMessage)
 {
+  if (mState != PresentationConnectionState::Closed) {
+    MOZ_ASSERT(false, "The connection state should be closed.");
+    return NS_ERROR_FAILURE;
+  }
+
+  PresentationConnectionClosedEventInit init;
+  init.mReason = aReason;
+  init.mMessage = aMessage;
+
+  RefPtr<PresentationConnectionClosedEvent> closedEvent =
+    PresentationConnectionClosedEvent::Constructor(this,
+                                                   NS_LITERAL_STRING("close"),
+                                                   init);
+  closedEvent->SetTrusted(true);
+
   RefPtr<AsyncEventDispatcher> asyncDispatcher =
-    new AsyncEventDispatcher(this, NS_LITERAL_STRING("statechange"), false);
+    new AsyncEventDispatcher(this, static_cast<Event*>(closedEvent));
   return asyncDispatcher->PostDOMEvent();
 }
 
 nsresult
 PresentationConnection::DispatchMessageEvent(JS::Handle<JS::Value> aData)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (NS_WARN_IF(!global)) {
@@ -298,8 +376,102 @@ PresentationConnection::DispatchMessageE
                                  false, false, aData, origin,
                                  EmptyString(), nullptr, nullptr);
   messageEvent->SetTrusted(true);
 
   RefPtr<AsyncEventDispatcher> asyncDispatcher =
     new AsyncEventDispatcher(this, static_cast<Event*>(messageEvent));
   return asyncDispatcher->PostDOMEvent();
 }
+
+nsresult
+PresentationConnection::ProcessConnectionWentAway()
+{
+  if (mState != PresentationConnectionState::Connected &&
+      mState != PresentationConnectionState::Connecting) {
+    // If the state is not connected or connecting, do not need to
+    // close the session.
+    return NS_OK;
+  }
+
+  mState = PresentationConnectionState::Terminated;
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return service->CloseSession(
+    mId, mRole, nsIPresentationService::CLOSED_REASON_WENTAWAY);
+}
+
+NS_IMETHODIMP
+PresentationConnection::GetName(nsACString &aResult)
+{
+  aResult.AssignLiteral("about:presentation-connection");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationConnection::IsPending(bool* aRetval)
+{
+  *aRetval = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationConnection::GetStatus(nsresult* aStatus)
+{
+  *aStatus = NS_OK;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationConnection::Cancel(nsresult aStatus)
+{
+  nsCOMPtr<nsIRunnable> event =
+    NewRunnableMethod(this, &PresentationConnection::ProcessConnectionWentAway);
+  return NS_DispatchToCurrentThread(event);
+}
+NS_IMETHODIMP
+PresentationConnection::Suspend(void)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+NS_IMETHODIMP
+PresentationConnection::Resume(void)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+PresentationConnection::GetLoadGroup(nsILoadGroup** aLoadGroup)
+{
+  *aLoadGroup = nullptr;
+
+  nsCOMPtr<nsIDocument> doc = GetOwner() ? GetOwner()->GetExtantDoc() : nullptr;
+  if (!doc) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aLoadGroup = doc->GetDocumentLoadGroup().take();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationConnection::SetLoadGroup(nsILoadGroup * aLoadGroup)
+{
+  return NS_ERROR_UNEXPECTED;
+}
+
+NS_IMETHODIMP
+PresentationConnection::GetLoadFlags(nsLoadFlags* aLoadFlags)
+{
+  *aLoadFlags = nsIRequest::LOAD_BACKGROUND;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationConnection::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+  return NS_OK;
+}
--- a/dom/presentation/PresentationConnection.h
+++ b/dom/presentation/PresentationConnection.h
@@ -4,31 +4,36 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_PresentationConnection_h
 #define mozilla_dom_PresentationConnection_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/PresentationConnectionBinding.h"
+#include "mozilla/dom/PresentationConnectionClosedEventBinding.h"
 #include "nsIPresentationListener.h"
+#include "nsIRequest.h"
+#include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 
 class PresentationConnectionList;
 
 class PresentationConnection final : public DOMEventTargetHelper
                                    , public nsIPresentationSessionListener
+                                   , public nsIRequest
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationConnection,
                                            DOMEventTargetHelper)
   NS_DECL_NSIPRESENTATIONSESSIONLISTENER
+  NS_DECL_NSIREQUEST
 
   static already_AddRefed<PresentationConnection>
   Create(nsPIDOMWindowInner* aWindow,
          const nsAString& aId,
          const uint8_t aRole,
          PresentationConnectionList* aList = nullptr);
 
   virtual void DisconnectFromOwner() override;
@@ -43,37 +48,45 @@ public:
 
   void Send(const nsAString& aData,
             ErrorResult& aRv);
 
   void Close(ErrorResult& aRv);
 
   void Terminate(ErrorResult& aRv);
 
-  IMPL_EVENT_HANDLER(statechange);
+  IMPL_EVENT_HANDLER(connect);
+  IMPL_EVENT_HANDLER(close);
+  IMPL_EVENT_HANDLER(terminate);
   IMPL_EVENT_HANDLER(message);
 
 private:
   PresentationConnection(nsPIDOMWindowInner* aWindow,
                          const nsAString& aId,
                          const uint8_t aRole,
                          PresentationConnectionList* aList);
 
   ~PresentationConnection();
 
   bool Init();
 
   void Shutdown();
 
-  nsresult DispatchStateChangeEvent();
+  nsresult ProcessStateChanged(nsresult aReason);
+
+  nsresult DispatchConnectionClosedEvent(PresentationConnectionClosedReason aReason,
+                                         const nsAString& aMessage);
 
   nsresult DispatchMessageEvent(JS::Handle<JS::Value> aData);
 
+  nsresult ProcessConnectionWentAway();
+
   nsString mId;
   uint8_t mRole;
   PresentationConnectionState mState;
   RefPtr<PresentationConnectionList> mOwningConnectionList;
+  nsWeakPtr mWeakLoadGroup;;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationConnection_h
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -506,28 +506,36 @@ PresentationService::SendSessionMessage(
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return info->Send(aData);
 }
 
 NS_IMETHODIMP
 PresentationService::CloseSession(const nsAString& aSessionId,
-                                  uint8_t aRole)
+                                  uint8_t aRole,
+                                  uint8_t aClosedReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aSessionId.IsEmpty());
   MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
              aRole == nsIPresentationService::ROLE_RECEIVER);
 
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  if (aClosedReason == nsIPresentationService::CLOSED_REASON_WENTAWAY) {
+    UntrackSessionInfo(aSessionId, aRole);
+    // Remove nsIPresentationSessionListener since we don't want to dispatch
+    // PresentationConnectionClosedEvent if the page is went away.
+    info->SetListener(nullptr);
+  }
+
   return info->Close(NS_OK, nsIPresentationSessionListener::STATE_CLOSED);
 }
 
 NS_IMETHODIMP
 PresentationService::TerminateSession(const nsAString& aSessionId,
                                       uint8_t aRole)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -577,17 +585,18 @@ PresentationService::RegisterSessionList
 
   RefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId, aRole);
   if (NS_WARN_IF(!info)) {
     // Notify the listener of TERMINATED since no correspondent session info is
     // available possibly due to establishment failure. This would be useful at
     // the receiver side, since a presentation session is created at beginning
     // and here is the place to realize the underlying establishment fails.
     nsresult rv = aListener->NotifyStateChange(aSessionId,
-                                               nsIPresentationSessionListener::STATE_TERMINATED);
+                                               nsIPresentationSessionListener::STATE_TERMINATED,
+                                               NS_ERROR_NOT_AVAILABLE);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return info->SetListener(aListener);
 }
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -251,17 +251,17 @@ PresentationSessionInfo::SetListener(nsI
       nsresult rv = mTransport->EnableDataNotification();
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     // The transport might become ready, or might become un-ready again, before
     // the listener has registered. So notify the listener of the state change.
-    return mListener->NotifyStateChange(mSessionId, mState);
+    return mListener->NotifyStateChange(mSessionId, mState, mReason);
   }
 
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::Send(const nsAString& aData)
 {
@@ -279,26 +279,26 @@ PresentationSessionInfo::Send(const nsAS
 nsresult
 PresentationSessionInfo::Close(nsresult aReason,
                                uint32_t aState)
 {
   if (NS_WARN_IF(!IsSessionReady())) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
-  SetState(aState);
+  SetStateWithReason(aState, aReason);
 
   Shutdown(aReason);
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::ReplySuccess()
 {
-  SetState(nsIPresentationSessionListener::STATE_CONNECTED);
+  SetStateWithReason(nsIPresentationSessionListener::STATE_CONNECTED, NS_OK);
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::ReplyError(nsresult aError)
 {
   Shutdown(aError);
 
@@ -375,17 +375,17 @@ PresentationSessionInfo::NotifyTransport
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above.
   mIsTransportReady = false;
 
   if (mState == nsIPresentationSessionListener::STATE_CONNECTED) {
     // The transport channel is closed unexpectedly (not caused by a |Close| call).
-    SetState(nsIPresentationSessionListener::STATE_CLOSED);
+    SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aReason);
   }
 
   Shutdown(aReason);
 
   if (mState == nsIPresentationSessionListener::STATE_TERMINATED) {
     // Directly untrack the session info from the service.
     return UntrackFromService();
   }
@@ -663,17 +663,17 @@ PresentationControllingInfo::NotifyClose
 
   // 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.
-    SetState(nsIPresentationSessionListener::STATE_TERMINATED);
+    SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, aReason);
 
     // Reply error for an abnormal close.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
@@ -714,17 +714,17 @@ PresentationControllingInfo::OnStopListe
   Shutdown(aStatus);
 
   if (NS_WARN_IF(!IsSessionReady())) {
     // It happens before the session is ready. Reply the callback.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   // It happens after the session is ready. Change the state to CLOSED.
-  SetState(nsIPresentationSessionListener::STATE_CLOSED);
+  SetStateWithReason(nsIPresentationSessionListener::STATE_CLOSED, aStatus);
 
   return NS_OK;
 }
 
 /**
  * Implementation of PresentationPresentingInfo
  *
  * During presentation session establishment, the receiver expects the following
@@ -989,17 +989,17 @@ PresentationPresentingInfo::NotifyClosed
 
   // 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.
-    SetState(nsIPresentationSessionListener::STATE_TERMINATED);
+    SetStateWithReason(nsIPresentationSessionListener::STATE_TERMINATED, aReason);
 
     // Reply error for an abnormal close.
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
   return NS_OK;
 }
 
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -39,16 +39,17 @@ public:
   PresentationSessionInfo(const nsAString& aUrl,
                           const nsAString& aSessionId,
                           const uint8_t aRole)
     : mUrl(aUrl)
     , mSessionId(aSessionId)
     , mIsResponderReady(false)
     , mIsTransportReady(false)
     , mState(nsIPresentationSessionListener::STATE_CONNECTING)
+    , mReason(NS_OK)
   {
     MOZ_ASSERT(!mUrl.IsEmpty());
     MOZ_ASSERT(!mSessionId.IsEmpty());
     MOZ_ASSERT(aRole == nsIPresentationService::ROLE_CONTROLLER ||
                aRole == nsIPresentationService::ROLE_RECEIVER);
     mRole = aRole;
   }
 
@@ -112,27 +113,28 @@ protected:
   virtual void Shutdown(nsresult aReason);
 
   nsresult ReplySuccess();
 
   virtual bool IsSessionReady() = 0;
 
   virtual nsresult UntrackFromService();
 
-  void SetState(uint32_t aState)
+  void SetStateWithReason(uint32_t aState, nsresult aReason)
   {
     if (mState == aState) {
       return;
     }
 
     mState = aState;
+    mReason = aReason;
 
     // Notify session state change.
     if (mListener) {
-      nsresult rv = mListener->NotifyStateChange(mSessionId, mState);
+      nsresult rv = mListener->NotifyStateChange(mSessionId, mState, aReason);
       NS_WARN_IF(NS_FAILED(rv));
     }
   }
 
   // Should be nsIPresentationChannelDescription::TYPE_TCP/TYPE_DATACHANNEL
   uint8_t mTransportType = 0;
 
   nsPIDOMWindowInner* GetWindow();
@@ -140,16 +142,17 @@ protected:
   nsString mUrl;
   nsString mSessionId;
   // mRole should be nsIPresentationService::ROLE_CONTROLLER
   //              or nsIPresentationService::ROLE_RECEIVER.
   uint8_t mRole;
   bool mIsResponderReady;
   bool mIsTransportReady;
   uint32_t mState; // CONNECTED, CLOSED, TERMINATED
+  nsresult mReason;
   nsCOMPtr<nsIPresentationSessionListener> mListener;
   nsCOMPtr<nsIPresentationDevice> mDevice;
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
   nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
   nsCOMPtr<nsIPresentationSessionTransportBuilder> mBuilder;
 };
 
 // Session info with controlling browsing context (sender side) behaviors.
--- a/dom/presentation/interfaces/nsIPresentationListener.idl
+++ b/dom/presentation/interfaces/nsIPresentationListener.idl
@@ -20,17 +20,18 @@ interface nsIPresentationSessionListener
   const unsigned short STATE_CONNECTED = 1;
   const unsigned short STATE_CLOSED = 2;
   const unsigned short STATE_TERMINATED = 3;
 
   /*
    * Called when session state changes.
    */
   void notifyStateChange(in DOMString sessionId,
-                         in unsigned short state);
+                         in unsigned short state,
+                         in nsresult reason);
 
   /*
    * Called when receive messages.
    */
   void notifyMessage(in DOMString sessionId,
                      in ACString data);
 };
 
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -34,16 +34,20 @@ interface nsIPresentationServiceCallback
 };
 
 [scriptable, uuid(de42b741-5619-4650-b961-c2cebb572c95)]
 interface nsIPresentationService : nsISupports
 {
   const unsigned short ROLE_CONTROLLER = 0x1;
   const unsigned short ROLE_RECEIVER = 0x2;
 
+  const unsigned short CLOSED_REASON_ERROR = 0x1;
+  const unsigned short CLOSED_REASON_CLOSED = 0x2;
+  const unsigned short CLOSED_REASON_WENTAWAY = 0x3;
+
   /*
    * 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
@@ -77,17 +81,18 @@ interface nsIPresentationService : nsISu
 
   /*
    * Close the session.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    */
   void closeSession(in DOMString sessionId,
-                    in uint8_t role);
+                    in uint8_t role,
+                    in uint8_t closedReason);
 
   /*
    * Terminate the session.
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    */
   void terminateSession(in DOMString sessionId,
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -27,16 +27,17 @@ struct SendSessionMessageRequest
   uint8_t role;
   nsString data;
 };
 
 struct CloseSessionRequest
 {
   nsString sessionId;
   uint8_t role;
+  uint8_t closedReason;
 };
 
 struct TerminateSessionRequest
 {
   nsString sessionId;
   uint8_t role;
 };
 
@@ -50,17 +51,19 @@ union PresentationIPCRequest
 
 sync protocol PPresentation
 {
   manager PContent;
   manages PPresentationRequest;
 
 child:
   async NotifyAvailableChange(bool aAvailable);
-  async NotifySessionStateChange(nsString aSessionId, uint16_t aState);
+  async NotifySessionStateChange(nsString aSessionId,
+                                 uint16_t aState,
+                                 nsresult aReason);
   async NotifyMessage(nsString aSessionId, nsCString aData);
   async NotifySessionConnect(uint64_t aWindowId, nsString aSessionId);
 
 parent:
   async __delete__();
 
   async RegisterAvailabilityHandler();
   async UnregisterAvailabilityHandler();
--- a/dom/presentation/ipc/PresentationChild.cpp
+++ b/dom/presentation/ipc/PresentationChild.cpp
@@ -63,20 +63,23 @@ PresentationChild::RecvNotifyAvailableCh
   if (mService) {
     NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailable)));
   }
   return true;
 }
 
 bool
 PresentationChild::RecvNotifySessionStateChange(const nsString& aSessionId,
-                                                const uint16_t& aState)
+                                                const uint16_t& aState,
+                                                const nsresult& aReason)
 {
   if (mService) {
-    NS_WARN_IF(NS_FAILED(mService->NotifySessionStateChange(aSessionId, aState)));
+    NS_WARN_IF(NS_FAILED(mService->NotifySessionStateChange(aSessionId,
+                                                            aState,
+                                                            aReason)));
   }
   return true;
 }
 
 bool
 PresentationChild::RecvNotifyMessage(const nsString& aSessionId,
                                      const nsCString& aData)
 {
--- a/dom/presentation/ipc/PresentationChild.h
+++ b/dom/presentation/ipc/PresentationChild.h
@@ -31,17 +31,18 @@ public:
   virtual bool
   DeallocPPresentationRequestChild(PPresentationRequestChild* aActor) override;
 
   virtual bool
   RecvNotifyAvailableChange(const bool& aAvailable) override;
 
   virtual bool
   RecvNotifySessionStateChange(const nsString& aSessionId,
-                               const uint16_t& aState) override;
+                               const uint16_t& aState,
+                               const nsresult& aReason) override;
 
   virtual bool
   RecvNotifyMessage(const nsString& aSessionId,
                     const nsCString& aData) override;
 
   virtual bool
   RecvNotifySessionConnect(const uint64_t& aWindowId,
                            const nsString& aSessionId) override;
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -76,21 +76,24 @@ PresentationIPCService::SendSessionMessa
 
   return SendRequest(nullptr, SendSessionMessageRequest(nsString(aSessionId),
                                                         aRole,
                                                         nsString(aData)));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::CloseSession(const nsAString& aSessionId,
-                                     uint8_t aRole)
+                                     uint8_t aRole,
+                                     uint8_t aClosedReason)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
-  return SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId), aRole));
+  return SendRequest(nullptr, CloseSessionRequest(nsString(aSessionId),
+                                                  aRole,
+                                                  aClosedReason));
 }
 
 NS_IMETHODIMP
 PresentationIPCService::TerminateSession(const nsAString& aSessionId,
                                          uint8_t aRole)
 {
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
@@ -193,24 +196,25 @@ NS_IMETHODIMP
 PresentationIPCService::GetWindowIdBySessionId(const nsAString& aSessionId,
                                                uint64_t* aWindowId)
 {
   return GetWindowIdBySessionIdInternal(aSessionId, aWindowId);
 }
 
 nsresult
 PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId,
-                                                 uint16_t aState)
+                                                 uint16_t aState,
+                                                 nsresult aReason)
 {
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
     return NS_OK;
   }
 
-  return listener->NotifyStateChange(aSessionId, aState);
+  return listener->NotifyStateChange(aSessionId, aState, aReason);
 }
 
 nsresult
 PresentationIPCService::NotifyMessage(const nsAString& aSessionId,
                                       const nsACString& aData)
 {
   nsCOMPtr<nsIPresentationSessionListener> listener;
   if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) {
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -26,17 +26,18 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationIPCService();
 
   nsresult NotifyAvailableChange(bool aAvailable);
 
   nsresult NotifySessionStateChange(const nsAString& aSessionId,
-                                    uint16_t aState);
+                                    uint16_t aState,
+                                    nsresult aReason);
 
   nsresult NotifyMessage(const nsAString& aSessionId,
                          const nsACString& aData);
 
   nsresult NotifySessionConnect(uint64_t aWindowId,
                                 const nsAString& aSessionId);
 
   void NotifyPresentationChildDestroyed();
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -195,20 +195,23 @@ PresentationParent::NotifyAvailableChang
   if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyStateChange(const nsAString& aSessionId,
-                                      uint16_t aState)
+                                      uint16_t aState,
+                                      nsresult aReason)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifySessionStateChange(nsString(aSessionId), aState))) {
+                 !SendNotifySessionStateChange(nsString(aSessionId),
+                                               aState,
+                                               aReason))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyMessage(const nsAString& aSessionId,
                                   const nsACString& aData)
@@ -306,17 +309,19 @@ PresentationRequestParent::DoRequest(con
 
   // Validate the accessibility (primarily for receiver side) so that a
   // compromised child process can't fake the ID.
   if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
                   IsSessionAccessible(aRequest.sessionId(), aRequest.role(), OtherPid()))) {
     return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
   }
 
-  nsresult rv = mService->CloseSession(aRequest.sessionId(), aRequest.role());
+  nsresult rv = mService->CloseSession(aRequest.sessionId(),
+                                       aRequest.role(),
+                                       aRequest.closedReason());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NotifyError(rv);
   }
   return NotifySuccess();
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const TerminateSessionRequest& aRequest)
--- a/dom/webidl/PresentationConnection.webidl
+++ b/dom/webidl/PresentationConnection.webidl
@@ -29,20 +29,19 @@ interface PresentationConnection : Event
   [Constant]
   readonly attribute DOMString id;
 
   /*
    * @value "connected", "closed", or "terminated".
    */
   readonly attribute PresentationConnectionState state;
 
-  /*
-   * It is called when connection state changes.
-   */
-  attribute EventHandler onstatechange;
+  attribute EventHandler onconnect;
+  attribute EventHandler onclose;
+  attribute EventHandler onterminate;
 
   /*
    * 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".
    *