Bug 1205222 - [Presentation WebAPI] Add PresentationReceiver and navigator.presentation.receiver. r=smaug
authorSean Lin <selin@mozilla.com>
Tue, 22 Sep 2015 18:36:47 +0800
changeset 264084 54e486f7596fe26af80d26a05354a3f9f94182f0
parent 264083 1f327c67db57e5778b76dc6c4bde729fa28a5500
child 264085 b3870c695f4f0efcdfcdd31be9a1eb82ae57bfe7
push id29429
push usercbook@mozilla.com
push dateThu, 24 Sep 2015 10:05:08 +0000
treeherdermozilla-central@001942e4617b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1205222
milestone44.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 1205222 - [Presentation WebAPI] Add PresentationReceiver and navigator.presentation.receiver. r=smaug
dom/presentation/Presentation.cpp
dom/presentation/Presentation.h
dom/presentation/PresentationReceiver.cpp
dom/presentation/PresentationReceiver.h
dom/presentation/ipc/PresentationParent.cpp
dom/presentation/ipc/PresentationParent.h
dom/presentation/moz.build
dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
dom/presentation/tests/mochitest/file_presentation_receiver.html
dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
dom/presentation/tests/mochitest/test_presentation_receiver.html
dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
dom/webidl/Presentation.webidl
dom/webidl/PresentationReceiver.webidl
dom/webidl/moz.build
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -1,130 +1,81 @@
 /* -*- 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 "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/dom/PresentationBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "nsCycleCollectionParticipant.h"
-#include "nsIPresentationDeviceManager.h"
 #include "nsIPresentationService.h"
-#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "Presentation.h"
-#include "PresentationCallbacks.h"
-#include "PresentationSession.h"
+#include "PresentationReceiver.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(Presentation)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDefaultRequest)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessions)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingGetSessionPromises)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
-  tmp->Shutdown();
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDefaultRequest)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessions)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingGetSessionPromises)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(Presentation, DOMEventTargetHelper,
+                                   mDefaultRequest, mReceiver)
 
 NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation)
-  NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 /* static */ already_AddRefed<Presentation>
 Presentation::Create(nsPIDOMWindow* aWindow)
 {
   nsRefPtr<Presentation> presentation = new Presentation(aWindow);
   return NS_WARN_IF(!presentation->Init()) ? nullptr : presentation.forget();
 }
 
 Presentation::Presentation(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
 {
 }
 
 Presentation::~Presentation()
 {
-  Shutdown();
 }
 
 bool
 Presentation::Init()
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return false;
   }
 
   if (NS_WARN_IF(!GetOwner())) {
     return false;
   }
-  mWindowId = GetOwner()->WindowID();
 
-  // Check if a session instance is required now. A session may already be
-  // connecting before the web content gets loaded in a presenting browsing
-  // context (receiver).
+  // Check if a receiver instance is required now. A session may already be
+  // connecting before the web content gets loaded in a receiving browsing
+  // context.
   nsAutoString sessionId;
-  nsresult rv = service->GetExistentSessionIdAtLaunch(mWindowId, sessionId);
+  nsresult rv = service->GetExistentSessionIdAtLaunch(GetOwner()->WindowID(), sessionId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   if (!sessionId.IsEmpty()) {
-    rv = NotifySessionConnect(mWindowId, sessionId);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
+    mReceiver = PresentationReceiver::Create(GetOwner(), sessionId);
+    if (NS_WARN_IF(!mReceiver)) {
       return false;
     }
   }
 
-  // Register listener for incoming sessions.
-  rv = service->RegisterRespondingListener(mWindowId, this);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
-  }
-
   return true;
 }
 
-void Presentation::Shutdown()
-{
-  mDefaultRequest = nullptr;
-  mSessions.Clear();
-  mPendingGetSessionPromises.Clear();
-
-  // Unregister listener for incoming sessions.
-  nsCOMPtr<nsIPresentationService> service =
-    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
-  if (NS_WARN_IF(!service)) {
-    return;
-  }
-
-  nsresult rv = service->UnregisterRespondingListener(mWindowId);
-  NS_WARN_IF(NS_FAILED(rv));
-}
-
-/* virtual */ void
-Presentation::DisconnectFromOwner()
-{
-  Shutdown();
-  DOMEventTargetHelper::DisconnectFromOwner();
-}
-
 /* virtual */ JSObject*
 Presentation::WrapObject(JSContext* aCx,
                          JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
@@ -135,86 +86,14 @@ Presentation::SetDefaultRequest(Presenta
 
 already_AddRefed<PresentationRequest>
 Presentation::GetDefaultRequest() const
 {
   nsRefPtr<PresentationRequest> request = mDefaultRequest;
   return request.forget();
 }
 
-already_AddRefed<Promise>
-Presentation::GetSession(ErrorResult& aRv)
-{
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
-  if (NS_WARN_IF(!global)) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  // If there's no existing session, leave the promise pending until a
-  // connecting request arrives from the controlling browsing context (sender).
-  // http://w3c.github.io/presentation-api/#dom-presentation-getsession
-  if (!mSessions.IsEmpty()) {
-    promise->MaybeResolve(mSessions[0]);
-  } else {
-    mPendingGetSessionPromises.AppendElement(promise);
-  }
-
-  return promise.forget();
-}
-
-already_AddRefed<Promise>
-Presentation::GetSessions(ErrorResult& aRv) const
+already_AddRefed<PresentationReceiver>
+Presentation::GetReceiver() const
 {
-  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
-  if (NS_WARN_IF(!global)) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
-    return nullptr;
-  }
-
-  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
-  promise->MaybeResolve(mSessions);
-  return promise.forget();
+  nsRefPtr<PresentationReceiver> receiver = mReceiver;
+  return receiver.forget();
 }
-
-NS_IMETHODIMP
-Presentation::NotifySessionConnect(uint64_t aWindowId,
-                                   const nsAString& aSessionId)
-{
-  if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  nsRefPtr<PresentationSession> session =
-    PresentationSession::Create(GetOwner(), aSessionId,
-                                PresentationSessionState::Disconnected);
-  if (NS_WARN_IF(!session)) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  mSessions.AppendElement(session);
-
-  // Resolve pending |GetSession| promises if any.
-  if (!mPendingGetSessionPromises.IsEmpty()) {
-    for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) {
-      mPendingGetSessionPromises[i]->MaybeResolve(session);
-    }
-    mPendingGetSessionPromises.Clear();
-  }
-
-  return DispatchSessionAvailableEvent();
-}
-
-nsresult
-Presentation::DispatchSessionAvailableEvent()
-{
-  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
-    new AsyncEventDispatcher(this, NS_LITERAL_STRING("sessionavailable"), false);
-  return asyncDispatcher->PostDOMEvent();
-}
--- a/dom/presentation/Presentation.h
+++ b/dom/presentation/Presentation.h
@@ -3,68 +3,50 @@
 /* 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/. */
 
 #ifndef mozilla_dom_Presentation_h
 #define mozilla_dom_Presentation_h
 
 #include "mozilla/DOMEventTargetHelper.h"
-#include "nsIPresentationListener.h"
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
+class PresentationReceiver;
 class PresentationRequest;
-class PresentationSession;
 
 class Presentation final : public DOMEventTargetHelper
-                         , public nsIPresentationRespondingListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation,
                                            DOMEventTargetHelper)
-  NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER
 
   static already_AddRefed<Presentation> Create(nsPIDOMWindow* aWindow);
 
-  virtual void DisconnectFromOwner() override;
-
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL (public APIs)
   void SetDefaultRequest(PresentationRequest* aRequest);
 
   already_AddRefed<PresentationRequest> GetDefaultRequest() const;
 
-  already_AddRefed<Promise> GetSession(ErrorResult& aRv);
-
-  already_AddRefed<Promise> GetSessions(ErrorResult& aRv) const;
-
-  IMPL_EVENT_HANDLER(sessionavailable);
+  already_AddRefed<PresentationReceiver> GetReceiver() const;
 
 private:
   explicit Presentation(nsPIDOMWindow* aWindow);
 
   ~Presentation();
 
   bool Init();
 
-  void Shutdown();
-
-  nsresult DispatchSessionAvailableEvent();
-
-  // Store the inner window ID for |UnregisterRespondingListener| call in
-  // |Shutdown| since the inner window may not exist at that moment.
-  uint64_t mWindowId;
-
   nsRefPtr<PresentationRequest> mDefaultRequest;
-  nsTArray<nsRefPtr<PresentationSession>> mSessions;
-  nsTArray<nsRefPtr<Promise>> mPendingGetSessionPromises;
+  nsRefPtr<PresentationReceiver> mReceiver;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Presentation_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationReceiver.cpp
@@ -0,0 +1,194 @@
+/* -*- 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 "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/PresentationReceiverBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsIPresentationService.h"
+#include "nsServiceManagerUtils.h"
+#include "PresentationReceiver.h"
+#include "PresentationSession.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationReceiver)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessions)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingGetSessionPromises)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+  tmp->Shutdown();
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessions)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingGetSessionPromises)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ADDREF_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(PresentationReceiver, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationReceiver)
+  NS_INTERFACE_MAP_ENTRY(nsIPresentationRespondingListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+/* static */ already_AddRefed<PresentationReceiver>
+PresentationReceiver::Create(nsPIDOMWindow* aWindow,
+                             const nsAString& aSessionId)
+{
+  nsRefPtr<PresentationReceiver> receiver = new PresentationReceiver(aWindow);
+  return NS_WARN_IF(!receiver->Init(aSessionId)) ? nullptr : receiver.forget();
+}
+
+PresentationReceiver::PresentationReceiver(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+}
+
+PresentationReceiver::~PresentationReceiver()
+{
+  Shutdown();
+}
+
+bool
+PresentationReceiver::Init(const nsAString& aSessionId)
+{
+  if (NS_WARN_IF(!GetOwner())) {
+    return false;
+  }
+  mWindowId = GetOwner()->WindowID();
+
+  if (!aSessionId.IsEmpty()) {
+    nsresult rv = NotifySessionConnect(mWindowId, aSessionId);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+    }
+  }
+
+  // Register listener for incoming sessions.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return false;
+  }
+
+  nsresult rv = service->RegisterRespondingListener(mWindowId, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  return true;
+}
+
+void PresentationReceiver::Shutdown()
+{
+  mSessions.Clear();
+  mPendingGetSessionPromises.Clear();
+
+  // Unregister listener for incoming sessions.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return;
+  }
+
+  nsresult rv = service->UnregisterRespondingListener(mWindowId);
+  NS_WARN_IF(NS_FAILED(rv));
+}
+
+/* virtual */ void
+PresentationReceiver::DisconnectFromOwner()
+{
+  Shutdown();
+  DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+/* virtual */ JSObject*
+PresentationReceiver::WrapObject(JSContext* aCx,
+                                 JS::Handle<JSObject*> aGivenProto)
+{
+  return PresentationReceiverBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<Promise>
+PresentationReceiver::GetSession(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  // If there's no existing session, leave the promise pending until a
+  // connecting request arrives from the controlling browsing context (sender).
+  // http://w3c.github.io/presentation-api/#dom-presentation-getsession
+  if (!mSessions.IsEmpty()) {
+    promise->MaybeResolve(mSessions[0]);
+  } else {
+    mPendingGetSessionPromises.AppendElement(promise);
+  }
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+PresentationReceiver::GetSessions(ErrorResult& aRv) const
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
+  if (NS_WARN_IF(!global)) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  promise->MaybeResolve(mSessions);
+  return promise.forget();
+}
+
+NS_IMETHODIMP
+PresentationReceiver::NotifySessionConnect(uint64_t aWindowId,
+                                           const nsAString& aSessionId)
+{
+  if (NS_WARN_IF(aWindowId != GetOwner()->WindowID())) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsRefPtr<PresentationSession> session =
+    PresentationSession::Create(GetOwner(), aSessionId,
+                                PresentationSessionState::Disconnected);
+  if (NS_WARN_IF(!session)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  mSessions.AppendElement(session);
+
+  // Resolve pending |GetSession| promises if any.
+  if (!mPendingGetSessionPromises.IsEmpty()) {
+    for(uint32_t i = 0; i < mPendingGetSessionPromises.Length(); i++) {
+      mPendingGetSessionPromises[i]->MaybeResolve(session);
+    }
+    mPendingGetSessionPromises.Clear();
+  }
+
+  return DispatchSessionAvailableEvent();
+}
+
+nsresult
+PresentationReceiver::DispatchSessionAvailableEvent()
+{
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, NS_LITERAL_STRING("sessionavailable"), false);
+  return asyncDispatcher->PostDOMEvent();
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationReceiver.h
@@ -0,0 +1,65 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PresentationReceiver_h
+#define mozilla_dom_PresentationReceiver_h
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "nsIPresentationListener.h"
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+class PresentationSession;
+
+class PresentationReceiver final : public DOMEventTargetHelper
+                                 , public nsIPresentationRespondingListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationReceiver,
+                                           DOMEventTargetHelper)
+  NS_DECL_NSIPRESENTATIONRESPONDINGLISTENER
+
+  static already_AddRefed<PresentationReceiver> Create(nsPIDOMWindow* aWindow,
+                                                       const nsAString& aSessionId);
+
+  virtual void DisconnectFromOwner() override;
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  // WebIDL (public APIs)
+  already_AddRefed<Promise> GetSession(ErrorResult& aRv);
+
+  already_AddRefed<Promise> GetSessions(ErrorResult& aRv) const;
+
+  IMPL_EVENT_HANDLER(sessionavailable);
+
+private:
+  explicit PresentationReceiver(nsPIDOMWindow* aWindow);
+
+  ~PresentationReceiver();
+
+  bool Init(const nsAString& aSessionId);
+
+  void Shutdown();
+
+  nsresult DispatchSessionAvailableEvent();
+
+  // Store the inner window ID for |UnregisterRespondingListener| call in
+  // |Shutdown| since the inner window may not exist at that moment.
+  uint64_t mWindowId;
+
+  nsTArray<nsRefPtr<PresentationSession>> mSessions;
+  nsTArray<nsRefPtr<Promise>> mPendingGetSessionPromises;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationReceiver_h
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -45,16 +45,21 @@ PresentationParent::ActorDestroy(ActorDe
 {
   mActorDestroyed = true;
 
   for (uint32_t i = 0; i < mSessionIds.Length(); i++) {
     NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(mSessionIds[i])));
   }
   mSessionIds.Clear();
 
+  for (uint32_t i = 0; i < mWindowIds.Length(); i++) {
+    NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(mWindowIds[i])));
+  }
+  mWindowIds.Clear();
+
   mService->UnregisterAvailabilityListener(this);
   mService = nullptr;
 }
 
 bool
 PresentationParent::RecvPPresentationRequestConstructor(
   PPresentationRequestParent* aActor,
   const PresentationIPCRequest& aRequest)
@@ -144,24 +149,27 @@ PresentationParent::RecvUnregisterSessio
   NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(aSessionId)));
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvRegisterRespondingHandler(const uint64_t& aWindowId)
 {
   MOZ_ASSERT(mService);
+
+  mWindowIds.AppendElement(aWindowId);
   NS_WARN_IF(NS_FAILED(mService->RegisterRespondingListener(aWindowId, this)));
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvUnregisterRespondingHandler(const uint64_t& aWindowId)
 {
   MOZ_ASSERT(mService);
+  mWindowIds.RemoveElement(aWindowId);
   NS_WARN_IF(NS_FAILED(mService->UnregisterRespondingListener(aWindowId)));
   return true;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyAvailableChange(bool aAvailable)
 {
   if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) {
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -59,16 +59,17 @@ public:
   virtual bool RecvNotifyReceiverReady(const nsString& aSessionId) override;
 
 private:
   virtual ~PresentationParent();
 
   bool mActorDestroyed;
   nsCOMPtr<nsIPresentationService> mService;
   nsTArray<nsString> mSessionIds;
+  nsTArray<uint64_t> mWindowIds;
 };
 
 class PresentationRequestParent final : public PPresentationRequestParent
                                       , public nsIPresentationServiceCallback
 {
   friend class PresentationParent;
 
 public:
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -12,31 +12,33 @@ MOCHITEST_MANIFESTS += ['tests/mochitest
 EXPORTS.mozilla.dom += [
     'ipc/PresentationChild.h',
     'ipc/PresentationIPCService.h',
     'ipc/PresentationParent.h',
     'Presentation.h',
     'PresentationAvailability.h',
     'PresentationCallbacks.h',
     'PresentationDeviceManager.h',
+    'PresentationReceiver.h',
     'PresentationRequest.h',
     'PresentationService.h',
     'PresentationSession.h',
     'PresentationSessionInfo.h',
     'PresentationSessionTransport.h',
 ]
 
 UNIFIED_SOURCES += [
     'ipc/PresentationChild.cpp',
     'ipc/PresentationIPCService.cpp',
     'ipc/PresentationParent.cpp',
     'Presentation.cpp',
     'PresentationAvailability.cpp',
     'PresentationCallbacks.cpp',
     'PresentationDeviceManager.cpp',
+    'PresentationReceiver.cpp',
     'PresentationRequest.cpp',
     'PresentationService.cpp',
     'PresentationSession.cpp',
     'PresentationSessionInfo.cpp',
     'PresentationSessionRequest.cpp',
     'PresentationSessionTransport.cpp',
 ]
 
--- a/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
@@ -24,28 +24,18 @@ function info(msg) {
 
 function finish() {
   alert('DONE');
 }
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available in OOP pages.");
-
-    navigator.presentation.getSessions().then(
-      function(aSessions) {
-        is(aSessions.length, 0, "Non-receiving OOP pages shouldn't get a predefined presentation session instance.");
-        aResolve();
-      },
-      function(aError) {
-        ok(false, "Error occurred when getting sessions: " + aError);
-        teardown();
-        aReject();
-      }
-    );
+    ok(!navigator.presentation.receiver, "Non-receiving OOP pages shouldn't get a presentation receiver instance.");
+    aResolve();
   });
 }
 
 testSessionAvailable().
 then(finish);
 
 </script>
 </body>
--- a/dom/presentation/tests/mochitest/file_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html
@@ -30,18 +30,19 @@ function finish() {
   window.parent.postMessage('DONE', '*');
 }
 
 var session;
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available.");
+    ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
-    navigator.presentation.getSession().then(
+    navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
         is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
         aResolve();
       },
       function(aError) {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html
@@ -30,18 +30,19 @@ function finish() {
   alert('DONE');
 }
 
 var session;
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available.");
+    ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
-    navigator.presentation.getSession().then(
+    navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
         is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
         aResolve();
       },
       function(aError) {
--- a/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
+++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html
@@ -30,18 +30,19 @@ function finish() {
   window.parent.postMessage('DONE', '*');
 }
 
 var session;
 
 function testSessionAvailable() {
   return new Promise(function(aResolve, aReject) {
     ok(navigator.presentation, "navigator.presentation should be available.");
+    ok(navigator.presentation.receiver, "navigator.presentation.receiver should be available.");
 
-    navigator.presentation.getSession().then(
+    navigator.presentation.receiver.getSession().then(
       function(aSession) {
         session = aSession;
 
         ok(session.id, "Session ID should be set: " + session.id);
         is(session.state, "disconnected", "Session state at receiver side should be disconnected by default.");
         aResolve();
       },
       function(aError) {
--- a/dom/presentation/tests/mochitest/test_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver.html
@@ -88,28 +88,18 @@ function setup() {
 
 function testIncomingSessionRequest() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
       gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
       info("Trying to launch receiver page.");
 
       ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
-
-      navigator.presentation.getSessions().then(
-        function(aSessions) {
-          is(aSessions.length, 0, "Non-receiving in-process pages shouldn't get a predefined presentation session instance.");
-          aResolve();
-        },
-        function(aError) {
-          ok(false, "Error occurred when getting sessions: " + aError);
-          teardown();
-          aReject();
-        }
-      );
+      ok(!navigator.presentation.receiver, "Non-receiving in-process pages shouldn't get a presentation receiver instance.");
+      aResolve();
     });
 
     gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
   });
 }
 
 function teardown() {
   gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
--- a/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_default_request.html
@@ -76,16 +76,18 @@ function testStartSession() {
     });
 
     gScript.addMessageListener('data-transport-initialized', function dataTransportInitializedHandler() {
       gScript.removeMessageListener('data-transport-initialized', dataTransportInitializedHandler);
       info("Data transport channel is initialized.");
       gScript.sendAsyncMessage('trigger-incoming-answer');
     });
 
+    ok(!navigator.presentation.receiver, "Sender shouldn't get a presentation receiver instance.");
+
     navigator.presentation.defaultRequest.onsessionconnect = function(aEvent) {
       navigator.presentation.defaultRequest.onsessionconnect = null;
       session = aEvent.session;
       ok(session, "|sessionconnect| event is fired with a session.");
       ok(session.id, "Session ID should be set.");
       is(session.state, "connected", "Session state at sender side should be connected by default.");
       aResolve();
     };
--- a/dom/webidl/Presentation.webidl
+++ b/dom/webidl/Presentation.webidl
@@ -13,31 +13,14 @@ interface Presentation : EventTarget {
   * controller's behalf, it MUST start a presentation session using the default
   * presentation request (as if the controller had called |defaultRequest.start()|).
   *
   * Only used by controlling browsing context (senders).
   */
   attribute PresentationRequest? defaultRequest;
 
   /*
-   * Get the first connected presentation session in a presenting browsing
-   * context.
-   *
-   * Only used by presenting browsing context (receivers).
+   * This should be available on the receiving browsing context in order to
+   * access the controlling browsing context and communicate with them.
    */
-  [Throws]
-  Promise<PresentationSession> getSession();
-
-  /*
-   * Get all connected presentation sessions in a presenting browsing context.
-   *
-   * Only used by presenting browsing context (receivers).
-   */
-  [Throws]
-  Promise<sequence<PresentationSession>> getSessions();
-
-  /*
-   * It is called when an incoming session is connecting.
-   *
-   * Only used by presenting browsing context (receivers).
-   */
-  attribute EventHandler onsessionavailable;
+  [SameObject]
+  readonly attribute PresentationReceiver? receiver;
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PresentationReceiver.webidl
@@ -0,0 +1,27 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[Pref="dom.presentation.enabled",
+ CheckAnyPermissions="presentation"]
+interface PresentationReceiver : EventTarget {
+  /*
+   * Get the first connected presentation session in a receiving browsing
+   * context.
+   */
+  [Throws]
+  Promise<PresentationSession> getSession();
+
+  /*
+   * Get all connected presentation sessions in a receiving browsing context.
+   */
+  [Throws]
+  Promise<sequence<PresentationSession>> getSessions();
+
+  /*
+   * It is called when an incoming session is connecting.
+   */
+  attribute EventHandler onsessionavailable;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -368,16 +368,17 @@ WEBIDL_FILES = [
     'PluginArray.webidl',
     'PointerEvent.webidl',
     'PopupBoxObject.webidl',
     'Position.webidl',
     'PositionError.webidl',
     'Presentation.webidl',
     'PresentationAvailability.webidl',
     'PresentationDeviceInfoManager.webidl',
+    'PresentationReceiver.webidl',
     'PresentationRequest.webidl',
     'PresentationSession.webidl',
     'ProcessingInstruction.webidl',
     'ProfileTimelineMarker.webidl',
     'Promise.webidl',
     'PromiseDebugging.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',