Bug 1069230 - Presentation API implementation. Part 4 - Establish session (sender) & available changes. r=smaug
authorSean Lin <selin@mozilla.com>
Mon, 30 Mar 2015 14:27:27 +0800
changeset 288284 fd03fa83f0ae19d3c886a25578ee502b7e6c48f6
parent 288283 ae8604b64e921e30229a9703516dbabf5a1bf534
child 288285 e89355a01d882643be92233106672996db4ed5eb
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1069230
milestone42.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 1069230 - Presentation API implementation. Part 4 - Establish session (sender) & available changes. r=smaug
dom/presentation/Presentation.cpp
dom/presentation/Presentation.h
dom/presentation/PresentationCallbacks.cpp
dom/presentation/PresentationCallbacks.h
dom/presentation/PresentationService.cpp
dom/presentation/PresentationService.h
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/interfaces/moz.build
dom/presentation/interfaces/nsIPresentationControlChannel.idl
dom/presentation/interfaces/nsIPresentationSessionTransport.idl
dom/presentation/moz.build
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -1,20 +1,25 @@
 /* -*- 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/PresentationAvailableEvent.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"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Presentation)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Presentation, DOMEventTargetHelper)
@@ -25,16 +30,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
   tmp->Shutdown();
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSession)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation)
+  NS_INTERFACE_MAP_ENTRY(nsIPresentationListener)
 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();
 }
@@ -48,26 +54,65 @@ Presentation::Presentation(nsPIDOMWindow
 Presentation::~Presentation()
 {
   Shutdown();
 }
 
 bool
 Presentation::Init()
 {
-  // TODO: Register listener for |mAvailable| changes.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return false;
+  }
+
+  nsresult rv = service->RegisterListener(this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
+    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
+  if (NS_WARN_IF(!deviceManager)) {
+    return false;
+  }
+  deviceManager->GetDeviceAvailable(&mAvailable);
+
+  // Check if a session instance is required now. The receiver requires a
+  // session instance is ready at beginning because the web content may access
+  // it right away; whereas the sender doesn't until |startSession| succeeds.
+  nsAutoString sessionId;
+  rv = service->GetExistentSessionIdAtLaunch(sessionId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  if (!sessionId.IsEmpty()) {
+    mSession = PresentationSession::Create(GetOwner(), sessionId,
+                                           PresentationSessionState::Disconnected);
+    if (NS_WARN_IF(!mSession)) {
+      return false;
+    }
+  }
 
   return true;
 }
 
 void Presentation::Shutdown()
 {
   mSession = nullptr;
 
-  // TODO: Unregister listener for |mAvailable| changes.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return;
+  }
+
+  nsresult rv = service->UnregisterListener(this);
+  NS_WARN_IF(NS_FAILED(rv));
 }
 
 /* virtual */ JSObject*
 Presentation::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationBinding::Wrap(aCx, this, aGivenProto);
 }
 
@@ -77,30 +122,99 @@ Presentation::StartSession(const nsAStri
                            ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (NS_WARN_IF(!global)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
+  // Get the origin.
+  nsAutoString origin;
+  nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  // TODO: Resolve/reject the promise.
+  // Ensure there's something to select.
+  if (NS_WARN_IF(!mAvailable)) {
+    promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return promise.forget();
+  }
+
+  // Ensure the URL is not empty.
+  if (NS_WARN_IF(aUrl.IsEmpty())) {
+    promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR);
+    return promise.forget();
+  }
+
+  // Generate an ID if it's not assigned.
+  nsAutoString id;
+  if (aId.WasPassed()) {
+    id = aId.Value();
+  } else {
+    nsCOMPtr<nsIUUIDGenerator> uuidgen =
+      do_GetService("@mozilla.org/uuid-generator;1");
+    if(NS_WARN_IF(!uuidgen)) {
+      promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+      return promise.forget();
+    }
+
+    nsID uuid;
+    uuidgen->GenerateUUIDInPlace(&uuid);
+    char buffer[NSID_LENGTH];
+    uuid.ToProvidedString(buffer);
+    CopyASCIItoUTF16(buffer, id);
+  }
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if(NS_WARN_IF(!service)) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return promise.forget();
+  }
+
+  nsCOMPtr<nsIPresentationServiceCallback> callback =
+    new PresentationRequesterCallback(GetOwner(), aUrl, id, promise);
+  rv = service->StartSession(aUrl, id, origin, callback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+  }
 
   return promise.forget();
 }
 
 already_AddRefed<PresentationSession>
 Presentation::GetSession() const
 {
   nsRefPtr<PresentationSession> session = mSession;
   return session.forget();
 }
 
 bool
 Presentation::CachedAvailable() const
 {
   return mAvailable;
 }
+
+NS_IMETHODIMP
+Presentation::NotifyAvailableChange(bool aAvailable)
+{
+  mAvailable = aAvailable;
+
+  PresentationAvailableEventInit init;
+  init.mAvailable = mAvailable;
+  nsRefPtr<PresentationAvailableEvent> event =
+    PresentationAvailableEvent::Constructor(this,
+                                            NS_LITERAL_STRING("availablechange"),
+                                            init);
+  event->SetTrusted(true);
+
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, event);
+  return asyncDispatcher->PostDOMEvent();
+}
--- a/dom/presentation/Presentation.h
+++ b/dom/presentation/Presentation.h
@@ -3,29 +3,32 @@
 /* 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 PresentationSession;
 
 class Presentation final : public DOMEventTargetHelper
+                         , public nsIPresentationListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation,
                                            DOMEventTargetHelper)
+  NS_DECL_NSIPRESENTATIONLISTENER
 
   static already_AddRefed<Presentation> Create(nsPIDOMWindow* aWindow);
   virtual JSObject*
     WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL (public APIs)
   already_AddRefed<Promise> StartSession(const nsAString& aUrl,
                                          const Optional<nsAString>& aId,
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -0,0 +1,58 @@
+/* -*- 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/dom/Promise.h"
+#include "PresentationCallbacks.h"
+#include "PresentationSession.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
+
+PresentationRequesterCallback::PresentationRequesterCallback(nsPIDOMWindow* aWindow,
+                                                             const nsAString& aUrl,
+                                                             const nsAString& aSessionId,
+                                                             Promise* aPromise)
+  : mWindow(aWindow)
+  , mSessionId(aSessionId)
+  , mPromise(aPromise)
+{
+  MOZ_ASSERT(mWindow);
+  MOZ_ASSERT(mPromise);
+  MOZ_ASSERT(!mSessionId.IsEmpty());
+}
+
+PresentationRequesterCallback::~PresentationRequesterCallback()
+{
+}
+
+NS_IMETHODIMP
+PresentationRequesterCallback::NotifySuccess()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // At the sender side, this function must get called after the transport
+  // channel is ready. So we simply set the session state as connected.
+  nsRefPtr<PresentationSession> session =
+    PresentationSession::Create(mWindow, mSessionId, PresentationSessionState::Connected);
+  if (!session) {
+    mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+    return NS_OK;
+  }
+
+  mPromise->MaybeResolve(session);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterCallback::NotifyError(nsresult aError)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mPromise->MaybeReject(aError);
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationCallbacks.h
@@ -0,0 +1,44 @@
+/* -*- 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_PresentationCallbacks_h
+#define mozilla_dom_PresentationCallbacks_h
+
+#include "mozilla/nsRefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsIPresentationService.h"
+#include "nsString.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+class PresentationRequesterCallback final : public nsIPresentationServiceCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSERVICECALLBACK
+
+  PresentationRequesterCallback(nsPIDOMWindow* aWindow,
+                                const nsAString& aUrl,
+                                const nsAString& aSessionId,
+                                Promise* aPromise);
+
+private:
+  ~PresentationRequesterCallback();
+
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsString mSessionId;
+  nsRefPtr<Promise> mPromise;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PresentationCallbacks_h
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -1,56 +1,242 @@
 /* -*- Mode: C++; 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/. */
 
 #include "ipc/PresentationIPCService.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
+#include "nsIPresentationControlChannel.h"
+#include "nsIPresentationDeviceManager.h"
+#include "nsIPresentationDevicePrompt.h"
 #include "nsIPresentationListener.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "PresentationService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+namespace mozilla {
+namespace dom {
+
+/*
+ * Implementation of PresentationDeviceRequest
+ */
+
+class PresentationDeviceRequest final : public nsIPresentationDeviceRequest
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONDEVICEREQUEST
+
+  PresentationDeviceRequest(const nsAString& aRequestUrl,
+                            const nsAString& aId,
+                            const nsAString& aOrigin);
+
+private:
+  virtual ~PresentationDeviceRequest();
+
+  nsString mRequestUrl;
+  nsString mId;
+  nsString mOrigin;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest)
+
+PresentationDeviceRequest::PresentationDeviceRequest(const nsAString& aRequestUrl,
+                                                     const nsAString& aId,
+                                                     const nsAString& aOrigin)
+  : mRequestUrl(aRequestUrl)
+  , mId(aId)
+  , mOrigin(aOrigin)
+{
+  MOZ_ASSERT(!mRequestUrl.IsEmpty());
+  MOZ_ASSERT(!mId.IsEmpty());
+  MOZ_ASSERT(!mOrigin.IsEmpty());
+}
+
+PresentationDeviceRequest::~PresentationDeviceRequest()
+{
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::GetOrigin(nsAString& aOrigin)
+{
+  aOrigin = mOrigin;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::GetRequestURL(nsAString& aRequestUrl)
+{
+  aRequestUrl = mRequestUrl;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aDevice);
+
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  // Update device in the session info.
+  nsRefPtr<PresentationSessionInfo> info =
+    static_cast<PresentationService*>(service.get())->GetSessionInfo(mId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  info->SetDevice(aDevice);
+
+  // Establish a control channel. If we failed to do so, the callback is called
+  // with an error message.
+  nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
+  nsresult rv = aDevice->EstablishControlChannel(mRequestUrl, mId, getter_AddRefs(ctrlChannel));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return info->ReplyError(NS_ERROR_DOM_NETWORK_ERR);
+  }
+
+  // Initialize the session info with the control channel.
+  rv = info->Init(ctrlChannel);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return info->ReplyError(NS_ERROR_DOM_NETWORK_ERR);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceRequest::Cancel()
+{
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<PresentationSessionInfo> info =
+    static_cast<PresentationService*>(service.get())->GetSessionInfo(mId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return info->ReplyError(NS_ERROR_DOM_PROP_ACCESS_DENIED);
+}
+
+/*
+ * Implementation of PresentationService
+ */
+
 NS_IMPL_ISUPPORTS(PresentationService, nsIPresentationService, nsIObserver)
 
 PresentationService::PresentationService()
+  : mIsAvailable(false)
 {
 }
 
 PresentationService::~PresentationService()
 {
+  HandleShutdown();
 }
 
 bool
 PresentationService::Init()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (NS_WARN_IF(!obs)) {
     return false;
   }
 
-  // TODO: Add observers and get available devices.
+  nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+  rv = obs->AddObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC, false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
 
-  return true;
+  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
+    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
+  if (NS_WARN_IF(!deviceManager)) {
+    return false;
+  }
+
+  rv = deviceManager->GetDeviceAvailable(&mIsAvailable);
+  return !NS_WARN_IF(NS_FAILED(rv));
 }
 
 NS_IMETHODIMP
 PresentationService::Observe(nsISupports* aSubject,
                              const char* aTopic,
                              const char16_t* aData)
 {
-  // TODO: Handle device availability changes can call |NotifyAvailableChange|.
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    HandleShutdown();
+    return NS_OK;
+  } else if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) {
+    return HandleDeviceChange();
+  } else if (!strcmp(aTopic, "profile-after-change")) {
+    // It's expected since we add and entry to |kLayoutCategories| in
+    // |nsLayoutModule.cpp| to launch this service earlier.
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "Unexpected topic for PresentationService");
+  return NS_ERROR_UNEXPECTED;
+}
+
+void
+PresentationService::HandleShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mListeners.Clear();
+  mSessionInfo.Clear();
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+    obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC);
+  }
+}
+
+nsresult
+PresentationService::HandleDeviceChange()
+{
+  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
+    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
+  if (NS_WARN_IF(!deviceManager)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  bool isAvailable;
+  nsresult rv = deviceManager->GetDeviceAvailable(&isAvailable);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if (isAvailable != mIsAvailable) {
+    mIsAvailable = isAvailable;
+    NotifyAvailableChange(mIsAvailable);
+  }
 
   return NS_OK;
 }
 
 void
 PresentationService::NotifyAvailableChange(bool aIsAvailable)
 {
   nsTObserverArray<nsCOMPtr<nsIPresentationListener> >::ForwardIterator iter(mListeners);
@@ -65,17 +251,34 @@ PresentationService::StartSession(const 
                                   const nsAString& aSessionId,
                                   const nsAString& aOrigin,
                                   nsIPresentationServiceCallback* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aCallback);
   MOZ_ASSERT(!aSessionId.IsEmpty());
 
-  // TODO: Reply the callback.
+  // Create session info  and set the callback. The callback is called when the
+  // request is finished.
+  nsRefPtr<PresentationRequesterInfo> info =
+    new PresentationRequesterInfo(aUrl, aSessionId, aCallback);
+  mSessionInfo.Put(aSessionId, info);
+
+  // Pop up a prompt and ask user to select a device.
+  nsCOMPtr<nsIPresentationDevicePrompt> prompt =
+    do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID);
+  if (NS_WARN_IF(!prompt)) {
+    return info->ReplyError(NS_ERROR_DOM_ABORT_ERR);
+  }
+  nsCOMPtr<nsIPresentationDeviceRequest> request =
+    new PresentationDeviceRequest(aUrl, aSessionId, aOrigin);
+  nsresult rv = prompt->PromptDeviceSelection(request);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return info->ReplyError(NS_ERROR_DOM_ABORT_ERR);
+  }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::SendSessionMessage(const nsAString& aSessionId,
                                         nsIInputStream* aStream)
 {
@@ -123,33 +326,32 @@ PresentationService::UnregisterListener(
 
 NS_IMETHODIMP
 PresentationService::RegisterSessionListener(const nsAString& aSessionId,
                                              nsIPresentationSessionListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
-  PresentationSessionInfo* info = mSessionInfo.Get(aSessionId);
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  info->SetListener(aListener);
-  return NS_OK;
+  return info->SetListener(aListener);
 }
 
 NS_IMETHODIMP
 PresentationService::UnregisterSessionListener(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  PresentationSessionInfo* info = mSessionInfo.Get(aSessionId);
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (info) {
-    info->SetListener(nullptr);
+    return info->SetListener(nullptr);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::GetExistentSessionIdAtLaunch(nsAString& aSessionId)
 {
   // TODO: Return the value based on it's a sender or a receiver.
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -2,19 +2,19 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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_PresentationService_h
 #define mozilla_dom_PresentationService_h
 
-#include "nsClassHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
+#include "nsRefPtrHashtable.h"
 #include "nsTObserverArray.h"
 #include "PresentationSessionInfo.h"
 
 namespace mozilla {
 namespace dom {
 
 class PresentationService final : public nsIPresentationService
                                 , public nsIObserver
@@ -22,20 +22,37 @@ class PresentationService final : public
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationService();
   bool Init();
 
+  already_AddRefed<PresentationSessionInfo>
+  GetSessionInfo(const nsAString& aSessionId)
+  {
+    nsRefPtr<PresentationSessionInfo> info;
+    return mSessionInfo.Get(aSessionId, getter_AddRefs(info)) ?
+           info.forget() : nullptr;
+  }
+
+  void
+  RemoveSessionInfo(const nsAString& aSessionId)
+  {
+    mSessionInfo.Remove(aSessionId);
+  }
+
 private:
   ~PresentationService();
+  void HandleShutdown();
+  nsresult HandleDeviceChange();
   void NotifyAvailableChange(bool aIsAvailable);
 
-  nsClassHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfo;
+  bool mIsAvailable;
+  nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfo;
   nsTObserverArray<nsCOMPtr<nsIPresentationListener>> mListeners;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -0,0 +1,353 @@
+/* -*- 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 "nsServiceManagerUtils.h"
+#include "nsThreadUtils.h"
+#include "PresentationService.h"
+#include "PresentationSessionInfo.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+/*
+ * Implementation of PresentationSessionInfo
+ */
+
+NS_IMPL_ISUPPORTS(PresentationSessionInfo,
+                  nsIPresentationSessionTransportCallback,
+                  nsIPresentationControlChannelListener);
+
+/* virtual */ nsresult
+PresentationSessionInfo::Init(nsIPresentationControlChannel* aControlChannel)
+{
+  SetControlChannel(aControlChannel);
+  return NS_OK;
+}
+
+/* virtual */ void
+PresentationSessionInfo::Shutdown(nsresult aReason)
+{
+  // Close the control channel if any.
+  if (mControlChannel) {
+    mControlChannel->SetListener(nullptr);
+    NS_WARN_IF(NS_FAILED(mControlChannel->Close(aReason)));
+    mControlChannel = nullptr;
+  }
+
+  // Close the data transport channel if any.
+  if (mTransport) {
+    mTransport->SetCallback(nullptr);
+    NS_WARN_IF(NS_FAILED(mTransport->Close(aReason)));
+    mTransport = nullptr;
+  }
+
+  mIsResponderReady = false;
+  mIsTransportReady = false;
+}
+
+nsresult
+PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener)
+{
+  mListener = aListener;
+
+  if (mListener) {
+    // The transport might become ready, or might become un-ready again, before
+    // the listener has registered. So notify the listener of the state change.
+    uint16_t state = IsSessionReady() ?
+                     nsIPresentationSessionListener::STATE_CONNECTED :
+                     nsIPresentationSessionListener::STATE_DISCONNECTED;
+    return mListener->NotifyStateChange(mSessionId, state);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::Send(nsIInputStream* aData)
+{
+  // TODO Send data to |mTransport|.
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::Close(nsresult aReason)
+{
+  // TODO Close |mTransport|.
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::ReplySuccess()
+{
+  if (mListener) {
+    // Notify session state change.
+    nsresult rv = mListener->NotifyStateChange(mSessionId,
+                                               nsIPresentationSessionListener::STATE_CONNECTED);
+    NS_WARN_IF(NS_FAILED(rv));
+  }
+
+  if (mCallback) {
+    NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess()));
+    SetCallback(nullptr);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+PresentationSessionInfo::ReplyError(nsresult aError)
+{
+  Shutdown(aError);
+
+  if (mCallback) {
+    NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aError)));
+    SetCallback(nullptr);
+  }
+
+  // Remove itself since it never succeeds.
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  static_cast<PresentationService*>(service.get())->RemoveSessionInfo(mSessionId);
+
+  return NS_OK;
+}
+
+// nsIPresentationSessionTransportCallback
+NS_IMETHODIMP
+PresentationSessionInfo::NotifyTransportReady()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mIsTransportReady = true;
+
+  // At sender side, session might not be ready at this point (waiting for
+  // receiver's answer). Yet at receiver side, session must be ready at this
+  // point since the data transport channel is created after the receiver page
+  // is ready for presentation use.
+  if (IsSessionReady()) {
+    return ReplySuccess();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::NotifyTransportClosed(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mTransport = nullptr;
+
+  if (!IsSessionReady()) {
+    // It happens before the session is ready. Reply the callback.
+    return ReplyError(aReason);
+  }
+
+  Shutdown(aReason);
+
+  if (mListener) {
+    // It happens after the session is ready. Notify session state change.
+    uint16_t state = (NS_WARN_IF(NS_FAILED(aReason))) ?
+                     nsIPresentationSessionListener::STATE_DISCONNECTED :
+                     nsIPresentationSessionListener::STATE_TERMINATED;
+    return mListener->NotifyStateChange(mSessionId, state);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionInfo::NotifyData(const nsACString& aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // TODO Notify the listener.
+
+  return NS_OK;
+}
+
+/*
+ * Implementation of PresentationRequesterInfo
+ *
+ * During presentation session establishment, the sender expects the following
+ * after trying to establish the control channel: (The order between step 2 and
+ * 3 is not guaranteed.)
+ * 1. |Init| is called to open a socket |mServerSocket| for data transport
+ *    channel and send the offer to the receiver via the control channel.
+ * 2.1 |OnSocketAccepted| of |nsIServerSocketListener| is called to indicate the
+ *     data transport channel is connected. Then initialize |mTransport|.
+ * 2.2 |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
+ *     called.
+ * 3. |OnAnswer| of |nsIPresentationControlChannelListener| is called to
+ *    indicate the receiver is ready. Close the control channel since it's no
+ *    longer needed.
+ * 4. Once both step 2 and 3 are done, the presentation session is ready to use.
+ *    So notify the listener of CONNECTED state.
+ */
+
+NS_IMPL_ISUPPORTS_INHERITED(PresentationRequesterInfo,
+                            PresentationSessionInfo,
+                            nsIServerSocketListener)
+
+nsresult
+PresentationRequesterInfo::Init(nsIPresentationControlChannel* aControlChannel)
+{
+  PresentationSessionInfo::Init(aControlChannel);
+
+  // TODO Initialize |mServerSocket|, use |this| as the listener, and prepare to
+  // send offer.
+
+  return NS_OK;
+}
+
+void
+PresentationRequesterInfo::Shutdown(nsresult aReason)
+{
+  PresentationSessionInfo::Shutdown(aReason);
+
+  // Close the server socket if any.
+  if (mServerSocket) {
+    NS_WARN_IF(NS_FAILED(mServerSocket->Close()));
+    mServerSocket = nullptr;
+  }
+}
+
+// nsIPresentationControlChannelListener
+NS_IMETHODIMP
+PresentationRequesterInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
+{
+  MOZ_ASSERT(false, "Sender side should not receive offer.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
+{
+  // Close the control channel since it's no longer needed.
+  nsresult rv = mControlChannel->Close(NS_OK);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return ReplyError(rv);
+  }
+
+  // Session might not be ready at this moment (waiting for the establishment of
+  // the data transport channel).
+  if (IsSessionReady()){
+    return ReplySuccess();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::NotifyOpened()
+{
+  // Do nothing and wait for receiver to be ready.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::NotifyClosed(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  SetControlChannel(nullptr);
+
+  if (NS_WARN_IF(NS_FAILED(aReason))) {
+    // Reply error for an abnormal close.
+    return ReplyError(aReason);
+  }
+
+  return NS_OK;
+}
+
+// nsIServerSocketListener
+NS_IMETHODIMP
+PresentationRequesterInfo::OnSocketAccepted(nsIServerSocket* aServerSocket,
+                                            nsISocketTransport* aTransport)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Initialize |mTransport| and use |this| as the callback.
+  mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID);
+  if (NS_WARN_IF(!mTransport)) {
+    return ReplyError(NS_ERROR_NOT_AVAILABLE);
+  }
+
+  nsresult rv = mTransport->InitWithSocketTransport(aTransport, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket,
+                                           nsresult aStatus)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  Shutdown(aStatus);
+
+  if (!IsSessionReady()) {
+    // It happens before the session is ready. Reply the callback.
+    return ReplyError(aStatus);
+  }
+
+  // It happens after the session is ready. Notify session state change.
+  if (mListener) {
+    return mListener->NotifyStateChange(mSessionId,
+                                        nsIPresentationSessionListener::STATE_DISCONNECTED);
+  }
+
+  return NS_OK;
+}
+
+/*
+ * Implementation of PresentationResponderInfo
+ */
+
+NS_IMPL_ISUPPORTS_INHERITED0(PresentationResponderInfo,
+                             PresentationSessionInfo)
+
+// nsIPresentationControlChannelListener
+NS_IMETHODIMP
+PresentationResponderInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
+{
+  // TODO Initialize |mTransport| and use |this| as the callback.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
+{
+  MOZ_ASSERT(false, "Receiver side should not receive answer.");
+  return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+PresentationResponderInfo::NotifyOpened()
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderInfo::NotifyClosed(nsresult aReason)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  SetControlChannel(nullptr);
+
+  if (NS_WARN_IF(NS_FAILED(aReason))) {
+    // TODO Notify session failure.
+  }
+
+  return NS_OK;
+}
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -4,60 +4,170 @@
  * 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_PresentationSessionInfo_h
 #define mozilla_dom_PresentationSessionInfo_h
 
 #include "mozilla/nsRefPtr.h"
 #include "nsCOMPtr.h"
+#include "nsIPresentationControlChannel.h"
+#include "nsIPresentationDevice.h"
 #include "nsIPresentationListener.h"
 #include "nsIPresentationService.h"
+#include "nsIPresentationSessionTransport.h"
+#include "nsIServerSocket.h"
 #include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 
-class PresentationSessionInfo
+class PresentationSessionInfo : public nsIPresentationSessionTransportCallback
+                              , public nsIPresentationControlChannelListener
 {
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK
+
   PresentationSessionInfo(const nsAString& aUrl,
                           const nsAString& aSessionId,
                           nsIPresentationServiceCallback* aCallback)
     : mUrl(aUrl)
     , mSessionId(aSessionId)
+    , mIsResponderReady(false)
+    , mIsTransportReady(false)
     , mCallback(aCallback)
   {
     MOZ_ASSERT(!mUrl.IsEmpty());
     MOZ_ASSERT(!mSessionId.IsEmpty());
   }
 
+  virtual nsresult Init(nsIPresentationControlChannel* aControlChannel);
+
   const nsAString& GetUrl() const
   {
     return mUrl;
   }
 
   const nsAString& GetSessionId() const
   {
     return mSessionId;
   }
 
   void SetCallback(nsIPresentationServiceCallback* aCallback)
   {
     mCallback = aCallback;
   }
 
-  void SetListener(nsIPresentationSessionListener* aListener)
+  nsresult SetListener(nsIPresentationSessionListener* aListener);
+
+  void SetDevice(nsIPresentationDevice* aDevice)
+  {
+    mDevice = aDevice;
+  }
+
+  already_AddRefed<nsIPresentationDevice> GetDevice() const
+  {
+    nsCOMPtr<nsIPresentationDevice> device = mDevice;
+    return device.forget();
+  }
+
+  void SetControlChannel(nsIPresentationControlChannel* aControlChannel)
   {
-    mListener = aListener;
+    if (mControlChannel) {
+      mControlChannel->SetListener(nullptr);
+    }
+
+    mControlChannel = aControlChannel;
+    if (mControlChannel) {
+      mControlChannel->SetListener(this);
+    }
+  }
+
+  nsresult Send(nsIInputStream* aData);
+
+  nsresult Close(nsresult aReason);
+
+  nsresult ReplyError(nsresult aReason);
+
+protected:
+  virtual ~PresentationSessionInfo()
+  {
+    Shutdown(NS_OK);
+  }
+
+  virtual void Shutdown(nsresult aReason);
+
+  nsresult ReplySuccess();
+
+  bool IsSessionReady()
+  {
+    return mIsResponderReady && mIsTransportReady;
   }
 
-private:
   nsString mUrl;
   nsString mSessionId;
+  bool mIsResponderReady;
+  bool mIsTransportReady;
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationSessionListener> mListener;
+  nsCOMPtr<nsIPresentationDevice> mDevice;
+  nsCOMPtr<nsIPresentationSessionTransport> mTransport;
+  nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
+};
+
+// Session info with sender side behaviors.
+class PresentationRequesterInfo final : public PresentationSessionInfo
+                                      , public nsIServerSocketListener
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
+  NS_DECL_NSISERVERSOCKETLISTENER
+
+  PresentationRequesterInfo(const nsAString& aUrl,
+                            const nsAString& aSessionId,
+                            nsIPresentationServiceCallback* aCallback)
+    : PresentationSessionInfo(aUrl, aSessionId, aCallback)
+  {
+    MOZ_ASSERT(mCallback);
+  }
+
+  nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
+
+private:
+  ~PresentationRequesterInfo()
+  {
+    Shutdown(NS_OK);
+  }
+
+  void Shutdown(nsresult aReason) override;
+
+  nsCOMPtr<nsIServerSocket> mServerSocket;
+};
+
+// Session info with receiver side behaviors.
+class PresentationResponderInfo final : public PresentationSessionInfo
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
+
+  PresentationResponderInfo(const nsAString& aUrl,
+                            const nsAString& aSessionId,
+                            nsIPresentationDevice* aDevice)
+    : PresentationSessionInfo(aUrl, aSessionId, nullptr)
+  {
+    MOZ_ASSERT(aDevice);
+
+    SetDevice(aDevice);
+  }
+
+  // TODO May need to inherit more interfaces to handle some observed changes.
+
+private:
+  ~PresentationResponderInfo() {}
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationSessionInfo_h
--- a/dom/presentation/interfaces/moz.build
+++ b/dom/presentation/interfaces/moz.build
@@ -8,13 +8,14 @@ XPIDL_SOURCES += [
     'nsIPresentationControlChannel.idl',
     'nsIPresentationDevice.idl',
     'nsIPresentationDeviceManager.idl',
     'nsIPresentationDevicePrompt.idl',
     'nsIPresentationDeviceProvider.idl',
     'nsIPresentationListener.idl',
     'nsIPresentationService.idl',
     'nsIPresentationSessionRequest.idl',
+    'nsIPresentationSessionTransport.idl',
     'nsITCPPresentationServer.idl',
 ]
 
 XPIDL_MODULE = 'dom_presentation'
 
--- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -58,17 +58,17 @@ interface nsIPresentationControlChannelL
    */
   void notifyClosed(in nsresult reason);
 };
 
 /*
  * The control channel for establishing RTCPeerConnection for a presentation
  * session. SDP Offer/Answer will be exchanged through this interface.
  */
-[scriptable, uuid(6bff04b9-8e79-466f-9446-f969de646fd3)]
+[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)]
 interface nsIPresentationControlChannel: nsISupports
 {
   // The listener for handling events of this control channel.
   // All the events should be pending until listener is assigned.
   attribute nsIPresentationControlChannelListener listener;
 
   /*
    * Send offer to remote endpiont. |onOffer| should be invoked
@@ -82,12 +82,19 @@ interface nsIPresentationControlChannel:
    * Send answer to remote endpiont. |onAnswer| should
    * be invoked on remote endpoint.
    * @param answer The answer to send.
    * @throws  NS_ERROR_FAILURE on failure
    */
   void sendAnswer(in nsIPresentationChannelDescription answer);
 
   /*
-   * Close the transport channel.
+   * Notify the app-to-app connection is fully established. (Only used at the
+   * receiver side.)
    */
-  void close();
+  void sendReceiverReady();
+
+  /*
+   * Close the transport channel.
+   * @param reason The reason of channel close; NS_OK represents normal.
+   */
+  void close(in nsresult reason);
 };
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl
@@ -0,0 +1,69 @@
+/* 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 "nsISupports.idl"
+
+interface nsIInputStream;
+interface nsINetAddr;
+interface nsIPresentationChannelDescription;
+interface nsISocketTransport;
+
+%{C++
+#define PRESENTATION_SESSION_TRANSPORT_CID \
+  { 0xc9d023f4, 0x6228, 0x4c07, \
+    { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } }
+#define PRESENTATION_SESSION_TRANSPORT_CONTRACTID \
+  "@mozilla.org/presentation/presentationsessiontransport;1"
+%}
+
+/*
+ * The callback for session transport events.
+ */
+[scriptable, uuid(9f158786-41a6-4a10-b29b-9497f25d4b67)]
+interface nsIPresentationSessionTransportCallback : nsISupports
+{
+  void notifyTransportReady();
+  void notifyTransportClosed(in nsresult reason);
+  void notifyData(in ACString data);
+};
+
+/*
+ * App-to-App transport channel for the presentation session.
+ */
+[scriptable, uuid(5a9fb9e9-b846-4c49-ad57-20ed88457295)]
+interface nsIPresentationSessionTransport : nsISupports
+{
+  attribute nsIPresentationSessionTransportCallback callback;
+  readonly attribute nsINetAddr selfAddress;
+
+  /*
+   * Initialize the transport channel with an existent socket transport. (This
+   * is primarily used at the sender side.)
+   * @param transport The socket transport.
+   * @param callback The callback for followup notifications.
+   */
+  void initWithSocketTransport(in nsISocketTransport transport,
+                               in nsIPresentationSessionTransportCallback callback);
+
+  /*
+   * Initialize the transport channel with the channel description. (This is
+   * primarily used at the receiver side.)
+   * @param description The channel description.
+   * @param callback The callback for followup notifications.
+   */
+  void initWithChannelDescription(in nsIPresentationChannelDescription description,
+                                  in nsIPresentationSessionTransportCallback callback);
+
+  /*
+   * Send message to the remote endpoint.
+   * @param data The message to send.
+   */
+  void send(in nsIInputStream data);
+
+  /*
+   * Close this session transport.
+   * @param reason The reason for closing this session transport.
+   */
+  void close(in nsresult reason);
+};
--- a/dom/presentation/moz.build
+++ b/dom/presentation/moz.build
@@ -9,30 +9,33 @@ DIRS += ['interfaces', 'provider']
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
 
 EXPORTS.mozilla.dom += [
     'ipc/PresentationChild.h',
     'ipc/PresentationIPCService.h',
     'ipc/PresentationParent.h',
     'Presentation.h',
+    'PresentationCallbacks.h',
     'PresentationDeviceManager.h',
     'PresentationService.h',
     'PresentationSession.h',
     'PresentationSessionInfo.h',
 ]
 
 UNIFIED_SOURCES += [
     'ipc/PresentationChild.cpp',
     'ipc/PresentationIPCService.cpp',
     'ipc/PresentationParent.cpp',
     'Presentation.cpp',
+    'PresentationCallbacks.cpp',
     'PresentationDeviceManager.cpp',
     'PresentationService.cpp',
     'PresentationSession.cpp',
+    'PresentationSessionInfo.cpp',
     'PresentationSessionRequest.cpp',
 ]
 
 EXTRA_COMPONENTS += [
     'PresentationDeviceInfoManager.js',
     'PresentationDeviceInfoManager.manifest',
 ]