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 340645 3f3fef6d6d9c845a4de04eba6639cae3d5027c97
parent 340644 47a6b93fbd166149be25da3096dbb50175b834d2
child 340646 b7836822d08417b4134ae1a1f45a38d383971803
push id1183
push userraliiev@mozilla.com
push dateMon, 05 Sep 2016 20:01:49 +0000
treeherdermozilla-release@3148731bed45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1258600
milestone49.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 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".
    *