Bug 1069230 - Presentation API implementation. Part 5 - Establish session (receiver). r=smaug
authorSean Lin <selin@mozilla.com>
Mon, 30 Mar 2015 15:46:11 +0800
changeset 288285 e89355a01d882643be92233106672996db4ed5eb
parent 288284 fd03fa83f0ae19d3c886a25578ee502b7e6c48f6
child 288286 3eb3f4dd3df854713ba641611b05ab300705f365
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 5 - Establish session (receiver). r=smaug
dom/ipc/ContentChild.cpp
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/ipc/PresentationIPCService.cpp
dom/presentation/ipc/PresentationIPCService.h
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -167,16 +167,17 @@
 #include "mozilla/dom/mobileconnection/MobileConnectionChild.h"
 #include "mozilla/dom/mobilemessage/SmsChild.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 #include "mozilla/dom/PFileSystemRequestChild.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
 #include "mozilla/dom/bluetooth/PBluetoothChild.h"
 #include "mozilla/dom/PFMRadioChild.h"
 #include "mozilla/dom/PPresentationChild.h"
+#include "mozilla/dom/PresentationIPCService.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/PSpeechSynthesisChild.h"
 #endif
 
 #include "ProcessUtils.h"
 #include "StructuredCloneUtils.h"
@@ -1396,17 +1397,25 @@ ContentChild::DeallocPPresentationChild(
     delete aActor;
     return true;
 }
 
 bool
 ContentChild::RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe,
                                                      const nsString& aSessionId)
 {
-    // TODO Listen to |nsIWebProgressListener| state changes for this frame.
+    nsCOMPtr<nsIDocShell> docShell =
+        do_GetInterface(static_cast<TabChild*>(aIframe)->WebNavigation());
+    NS_WARN_IF(!docShell);
+
+    nsCOMPtr<nsIPresentationService> service =
+        do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+    NS_WARN_IF(!service);
+
+    NS_WARN_IF(NS_FAILED(static_cast<PresentationIPCService*>(service.get())->MonitorResponderLoading(aSessionId, docShell)));
 
     return true;
 }
 
 PCrashReporterChild*
 ContentChild::AllocPCrashReporterChild(const mozilla::dom::NativeThreadId& id,
                                        const uint32_t& processType)
 {
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -1,21 +1,30 @@
 /* -*- 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 "nsIDocShell.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIPresentationService.h"
+#include "nsIWebProgress.h"
+#include "nsServiceManagerUtils.h"
 #include "PresentationCallbacks.h"
 #include "PresentationSession.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+/*
+ * Implementation of PresentationRequesterCallback
+ */
+
 NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback)
 
 PresentationRequesterCallback::PresentationRequesterCallback(nsPIDOMWindow* aWindow,
                                                              const nsAString& aUrl,
                                                              const nsAString& aSessionId,
                                                              Promise* aPromise)
   : mWindow(aWindow)
   , mSessionId(aSessionId)
@@ -25,16 +34,17 @@ PresentationRequesterCallback::Presentat
   MOZ_ASSERT(mPromise);
   MOZ_ASSERT(!mSessionId.IsEmpty());
 }
 
 PresentationRequesterCallback::~PresentationRequesterCallback()
 {
 }
 
+// nsIPresentationServiceCallback
 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 =
@@ -51,8 +61,125 @@ PresentationRequesterCallback::NotifySuc
 NS_IMETHODIMP
 PresentationRequesterCallback::NotifyError(nsresult aError)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mPromise->MaybeReject(aError);
   return NS_OK;
 }
+
+/*
+ * Implementation of PresentationRequesterCallback
+ */
+
+NS_IMPL_ISUPPORTS(PresentationResponderLoadingCallback,
+                  nsIWebProgressListener,
+                  nsISupportsWeakReference)
+
+PresentationResponderLoadingCallback::PresentationResponderLoadingCallback(const nsAString& aSessionId)
+  : mSessionId(aSessionId)
+{
+}
+
+PresentationResponderLoadingCallback::~PresentationResponderLoadingCallback()
+{
+  if (mProgress) {
+    mProgress->RemoveProgressListener(this);
+    mProgress = nullptr;
+  }
+}
+
+nsresult
+PresentationResponderLoadingCallback::Init(nsIDocShell* aDocShell)
+{
+  mProgress = do_GetInterface(aDocShell);
+  if (NS_WARN_IF(!mProgress)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
+  nsresult rv = aDocShell->GetBusyFlags(&busyFlags);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  if ((busyFlags & nsIDocShell::BUSY_FLAGS_NONE) ||
+      (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
+    // The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
+    // has already been fired), so the page is ready for presentation use.
+    return NotifyReceiverReady();
+  }
+
+  // Start to listen to document state change event |STATE_TRANSFERRING|.
+  return mProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
+}
+
+nsresult
+PresentationResponderLoadingCallback::NotifyReceiverReady()
+{
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return service->NotifyReceiverReady(mSessionId);
+}
+
+// nsIWebProgressListener
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnStateChange(nsIWebProgress* aWebProgress,
+                                                    nsIRequest* aRequest,
+                                                    uint32_t aStateFlags,
+                                                    nsresult aStatus)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
+    mProgress->RemoveProgressListener(this);
+
+    return NotifyReceiverReady();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnProgressChange(nsIWebProgress* aWebProgress,
+                                                       nsIRequest* aRequest,
+                                                       int32_t aCurSelfProgress,
+                                                       int32_t aMaxSelfProgress,
+                                                       int32_t aCurTotalProgress,
+                                                       int32_t aMaxTotalProgress)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnLocationChange(nsIWebProgress* aWebProgress,
+                                                       nsIRequest* aRequest,
+                                                       nsIURI* aURI,
+                                                       uint32_t aFlags)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnStatusChange(nsIWebProgress* aWebProgress,
+                                                     nsIRequest* aRequest,
+                                                     nsresult aStatus,
+                                                     const char16_t* aMessage)
+{
+  // Do nothing.
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationResponderLoadingCallback::OnSecurityChange(nsIWebProgress* aWebProgress,
+                                                       nsIRequest* aRequest,
+                                                       uint32_t state)
+{
+  // Do nothing.
+  return NS_OK;
+}
--- a/dom/presentation/PresentationCallbacks.h
+++ b/dom/presentation/PresentationCallbacks.h
@@ -5,18 +5,22 @@
  * 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 "nsIWebProgressListener.h"
 #include "nsString.h"
+#include "nsWeakReference.h"
 
+class nsIDocShell;
+class nsIWebProgress;
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Promise;
 
 class PresentationRequesterCallback final : public nsIPresentationServiceCallback
@@ -33,12 +37,32 @@ public:
 private:
   ~PresentationRequesterCallback();
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsString mSessionId;
   nsRefPtr<Promise> mPromise;
 };
 
+class PresentationResponderLoadingCallback final : public nsIWebProgressListener
+                                                 , public nsSupportsWeakReference
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIWEBPROGRESSLISTENER
+
+  explicit PresentationResponderLoadingCallback(const nsAString& aSessionId);
+
+  nsresult Init(nsIDocShell* aDocShell);
+
+private:
+  ~PresentationResponderLoadingCallback();
+
+  nsresult NotifyReceiverReady();
+
+  nsString mSessionId;
+  nsCOMPtr<nsIWebProgress> mProgress;
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationCallbacks_h
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -1,20 +1,24 @@
 /* -*- 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 "mozIApplication.h"
+#include "nsIAppsService.h"
 #include "nsIObserverService.h"
 #include "nsIPresentationControlChannel.h"
 #include "nsIPresentationDeviceManager.h"
 #include "nsIPresentationDevicePrompt.h"
 #include "nsIPresentationListener.h"
+#include "nsIPresentationSessionRequest.h"
+#include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "PresentationService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -162,16 +166,20 @@ PresentationService::Init()
   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;
   }
+  rv = obs->AddObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC, false);
+  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;
   }
 
   rv = deviceManager->GetDeviceAvailable(&mIsAvailable);
@@ -183,16 +191,23 @@ PresentationService::Observe(nsISupports
                              const char* aTopic,
                              const char16_t* aData)
 {
   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, PRESENTATION_SESSION_REQUEST_TOPIC)) {
+    nsCOMPtr<nsIPresentationSessionRequest> request(do_QueryInterface(aSubject));
+    if (NS_WARN_IF(!request)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    return HandleSessionRequest(request);
   } 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;
@@ -205,16 +220,17 @@ PresentationService::HandleShutdown()
 
   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);
+    obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC);
   }
 }
 
 nsresult
 PresentationService::HandleDeviceChange()
 {
   nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
     do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
@@ -231,26 +247,151 @@ PresentationService::HandleDeviceChange(
   if (isAvailable != mIsAvailable) {
     mIsAvailable = isAvailable;
     NotifyAvailableChange(mIsAvailable);
   }
 
   return NS_OK;
 }
 
+nsresult
+PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aRequest)
+{
+  nsCOMPtr<nsIPresentationControlChannel> ctrlChannel;
+  nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel));
+  if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) {
+    return rv;
+  }
+
+  nsAutoString url;
+  rv = aRequest->GetUrl(url);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+  nsAutoString sessionId;
+  rv = aRequest->GetPresentationId(sessionId);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+  nsCOMPtr<nsIPresentationDevice> device;
+  rv = aRequest->GetDevice(getter_AddRefs(device));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  // Verify the existence of the app if necessary.
+  nsCOMPtr<nsIURI> uri;
+  rv = NS_NewURI(getter_AddRefs(uri), url);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_BAD_URI);
+    return rv;
+  }
+
+  bool isApp;
+  rv = uri->SchemeIs("app", &isApp);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return rv;
+  }
+
+  if (NS_WARN_IF(isApp && !IsAppInstalled(uri))) {
+    ctrlChannel->Close(NS_ERROR_DOM_NOT_FOUND_ERR);
+    return NS_OK;
+  }
+#endif
+
+  // Make sure the service is not handling another session request.
+  if (NS_WARN_IF(!mRespondingSessionId.IsEmpty())) {
+    ctrlChannel->Close(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR);
+    return rv;
+  }
+
+  // Set |mRespondingSessionId| to indicate the service is handling a session
+  // request. Then a session instance will be prepared while instantiating
+  // |navigator.presentation| at receiver side. This variable will be reset when
+  // registering the session listener.
+  mRespondingSessionId = sessionId;
+
+  // Create or reuse session info.
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(sessionId);
+  if (NS_WARN_IF(info)) {
+    // TODO Update here after session resumption becomes supported.
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    mRespondingSessionId.Truncate();
+    return NS_ERROR_DOM_ABORT_ERR;
+  }
+
+  info = new PresentationResponderInfo(url, sessionId, device);
+  rv = info->Init(ctrlChannel);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    mRespondingSessionId.Truncate();
+    return rv;
+  }
+
+  mSessionInfo.Put(sessionId, info);
+
+  // Notify the receiver to launch.
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return info->ReplyError(NS_ERROR_NOT_AVAILABLE);
+  }
+  rv = obs->NotifyObservers(aRequest, "presentation-launch-receiver", nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
+    return info->ReplyError(rv);
+  }
+
+  return NS_OK;
+}
+
 void
 PresentationService::NotifyAvailableChange(bool aIsAvailable)
 {
   nsTObserverArray<nsCOMPtr<nsIPresentationListener> >::ForwardIterator iter(mListeners);
   while (iter.HasMore()) {
     nsCOMPtr<nsIPresentationListener> listener = iter.GetNext();
     NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aIsAvailable)));
   }
 }
 
+bool
+PresentationService::IsAppInstalled(nsIURI* aUri)
+{
+  nsAutoCString prePath;
+  nsresult rv = aUri->GetPrePath(prePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return false;
+  }
+
+  nsAutoString manifestUrl;
+  AppendUTF8toUTF16(prePath, manifestUrl);
+  manifestUrl.AppendLiteral("/manifest.webapp");
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!appsService)) {
+    return false;
+  }
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByManifestURL(manifestUrl, getter_AddRefs(app));
+  if (NS_WARN_IF(!app)) {
+    return false;
+  }
+
+  return true;
+}
+
 NS_IMETHODIMP
 PresentationService::StartSession(const nsAString& aUrl,
                                   const nsAString& aSessionId,
                                   const nsAString& aOrigin,
                                   nsIPresentationServiceCallback* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aCallback);
@@ -326,18 +467,31 @@ PresentationService::UnregisterListener(
 
 NS_IMETHODIMP
 PresentationService::RegisterSessionListener(const nsAString& aSessionId,
                                              nsIPresentationSessionListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
+  if (mRespondingSessionId.Equals(aSessionId)) {
+    mRespondingSessionId.Truncate();
+  }
+
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   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);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   return info->SetListener(aListener);
 }
 
 NS_IMETHODIMP
 PresentationService::UnregisterSessionListener(const nsAString& aSessionId)
@@ -349,27 +503,29 @@ PresentationService::UnregisterSessionLi
     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.
-
+  aSessionId = mRespondingSessionId;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::NotifyReceiverReady(const nsAString& aSessionId)
 {
-  // TODO: Notify the correspondent session info.
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
 
-  return NS_OK;
+  return static_cast<PresentationResponderInfo*>(info.get())->NotifyResponderReady();
 }
 
 already_AddRefed<nsIPresentationService>
 NS_CreatePresentationService()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIPresentationService> service;
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -8,16 +8,19 @@
 #define mozilla_dom_PresentationService_h
 
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTObserverArray.h"
 #include "PresentationSessionInfo.h"
 
+class nsIPresentationSessionRequest;
+class nsIURI;
+
 namespace mozilla {
 namespace dom {
 
 class PresentationService final : public nsIPresentationService
                                 , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
@@ -33,26 +36,33 @@ public:
     nsRefPtr<PresentationSessionInfo> info;
     return mSessionInfo.Get(aSessionId, getter_AddRefs(info)) ?
            info.forget() : nullptr;
   }
 
   void
   RemoveSessionInfo(const nsAString& aSessionId)
   {
+    if (mRespondingSessionId.Equals(aSessionId)) {
+      mRespondingSessionId.Truncate();
+    }
+
     mSessionInfo.Remove(aSessionId);
   }
 
 private:
   ~PresentationService();
   void HandleShutdown();
   nsresult HandleDeviceChange();
+  nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
   void NotifyAvailableChange(bool aIsAvailable);
+  bool IsAppInstalled(nsIURI* aUri);
 
   bool mIsAvailable;
+  nsString mRespondingSessionId;
   nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfo;
   nsTObserverArray<nsCOMPtr<nsIPresentationListener>> mListeners;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -1,21 +1,29 @@
 /* -*- 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/ContentParent.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "nsIDocShell.h"
+#include "nsIFrameLoader.h"
+#include "nsIObserverService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "PresentationService.h"
 #include "PresentationSessionInfo.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::services;
 
 /*
  * Implementation of PresentationSessionInfo
  */
 
 NS_IMPL_ISUPPORTS(PresentationSessionInfo,
                   nsIPresentationSessionTransportCallback,
                   nsIPresentationControlChannelListener);
@@ -224,16 +232,18 @@ PresentationRequesterInfo::OnOffer(nsIPr
 {
   MOZ_ASSERT(false, "Sender side should not receive offer.");
   return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 PresentationRequesterInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
 {
+  mIsResponderReady = true;
+
   // 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).
@@ -306,26 +316,148 @@ PresentationRequesterInfo::OnStopListeni
                                         nsIPresentationSessionListener::STATE_DISCONNECTED);
   }
 
   return NS_OK;
 }
 
 /*
  * Implementation of PresentationResponderInfo
+ *
+ * During presentation session establishment, the receiver expects the following
+ * after trying to launch the app by notifying "presentation-launch-receiver":
+ * (The order between step 2 and 3 is not guaranteed.)
+ * 1. |Observe| of |nsIObserver| is called with "presentation-receiver-launched".
+ *    Then start listen to document |STATE_TRANSFERRING| event.
+ * 2. |NotifyResponderReady| is called to indicate the receiver page is ready
+ *    for presentation use.
+ * 3. |OnOffer| of |nsIPresentationControlChannelListener| is called.
+ * 4. Once both step 2 and 3 are done, establish the data transport channel and
+ *    send the answer. (The control channel will be closed by the sender once it
+ *    receives the answer.)
+ * 5. |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is
+ *    called. The presentation session is ready to use, so notify the listener
+ *    of CONNECTED state.
  */
 
-NS_IMPL_ISUPPORTS_INHERITED0(PresentationResponderInfo,
-                             PresentationSessionInfo)
+NS_IMPL_ISUPPORTS_INHERITED(PresentationResponderInfo,
+                            PresentationSessionInfo,
+                            nsIObserver,
+                            nsITimerCallback)
+
+nsresult
+PresentationResponderInfo::Init(nsIPresentationControlChannel* aControlChannel)
+{
+  PresentationSessionInfo::Init(aControlChannel);
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (NS_WARN_IF(!obs)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsresult rv = obs->AddObserver(this, "presentation-receiver-launched", false);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // Add a timer to prevent waiting indefinitely in case the receiver page fails
+  // to become ready.
+  int32_t timeout =
+    Preferences::GetInt("presentation.receiver.loading.timeout", 10000);
+  mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+PresentationResponderInfo::Shutdown(nsresult aReason)
+{
+  PresentationSessionInfo::Shutdown(aReason);
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, "presentation-receiver-launched");
+  }
+
+  if (mTimer) {
+    mTimer->Cancel();
+  }
+
+  mLoadingCallback = nullptr;
+  mRequesterDescription = nullptr;
+}
+
+nsresult
+PresentationResponderInfo::InitTransportAndSendAnswer()
+{
+  // Establish a data transport channel |mTransport| to the sender and use
+  // |this| as the callback.
+  mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID);
+  if (NS_WARN_IF(!mTransport)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsresult rv = mTransport->InitWithChannelDescription(mRequesterDescription, this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // TODO Prepare and send the answer.
+
+  return NS_OK;
+ }
+
+nsresult
+PresentationResponderInfo::NotifyResponderReady()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  mIsResponderReady = true;
+
+  // Initialize |mTransport| and send the answer to the sender if sender's
+  // description is already offered.
+  if (mRequesterDescription) {
+    nsresult rv = InitTransportAndSendAnswer();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return ReplyError(rv);
+    }
+  }
+
+  return NS_OK;
+}
 
 // nsIPresentationControlChannelListener
 NS_IMETHODIMP
 PresentationResponderInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
 {
-  // TODO Initialize |mTransport| and use |this| as the callback.
+  if (NS_WARN_IF(!aDescription)) {
+    return ReplyError(NS_ERROR_INVALID_ARG);
+  }
+
+  mRequesterDescription = aDescription;
+
+  // Initialize |mTransport| and send the answer to the sender if the receiver
+  // page is ready for presentation use.
+  if (mIsResponderReady) {
+    nsresult rv = InitTransportAndSendAnswer();
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return ReplyError(rv);
+    }
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationResponderInfo::OnAnswer(nsIPresentationChannelDescription* aDescription)
 {
   MOZ_ASSERT(false, "Receiver side should not receive answer.");
   return NS_ERROR_FAILURE;
@@ -341,13 +473,85 @@ PresentationResponderInfo::NotifyOpened(
 NS_IMETHODIMP
 PresentationResponderInfo::NotifyClosed(nsresult aReason)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   SetControlChannel(nullptr);
 
   if (NS_WARN_IF(NS_FAILED(aReason))) {
-    // TODO Notify session failure.
+    // Reply error for an abnormal close.
+    return ReplyError(aReason);
   }
 
   return NS_OK;
 }
+
+// nsIObserver
+NS_IMETHODIMP
+PresentationResponderInfo::Observe(nsISupports* aSubject,
+                                   const char* aTopic,
+                                   const char16_t* aData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // The receiver has launched.
+  if (!strcmp(aTopic, "presentation-receiver-launched")) {
+    // Ignore irrelevant notifications.
+    if (!mSessionId.Equals(aData)) {
+      return NS_OK;
+    }
+
+    // Remove the observer.
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+    if (obs) {
+      obs->RemoveObserver(this, "presentation-receiver-launched");
+    }
+
+    // Start to listen to document state change event |STATE_TRANSFERRING|.
+    nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface(aSubject);
+    if (NS_WARN_IF(!owner)) {
+      return ReplyError(NS_ERROR_NOT_AVAILABLE);
+    }
+
+    nsCOMPtr<nsIFrameLoader> frameLoader;
+    nsresult rv = owner->GetFrameLoader(getter_AddRefs(frameLoader));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return ReplyError(rv);
+    }
+
+    nsRefPtr<TabParent> tabParent = TabParent::GetFrom(frameLoader);
+    if (tabParent) {
+      // OOP frame
+      nsCOMPtr<nsIContentParent> cp = tabParent->Manager();
+      NS_WARN_IF(!static_cast<ContentParent*>(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId));
+    } else {
+      // In-process frame
+      nsCOMPtr<nsIDocShell> docShell;
+      rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return ReplyError(rv);
+      }
+
+      mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
+      rv = mLoadingCallback->Init(docShell);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return ReplyError(rv);
+      }
+    }
+
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "Unexpected topic for PresentationResponderInfo.");
+  return NS_ERROR_UNEXPECTED;
+}
+
+// nsITimerCallback
+NS_IMETHODIMP
+PresentationResponderInfo::Notify(nsITimer* aTimer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_WARNING("The receiver page fails to become ready before timeout.");
+
+  mTimer = nullptr;
+  return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR);
+}
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -4,23 +4,26 @@
  * 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 "nsIObserver.h"
 #include "nsIPresentationControlChannel.h"
 #include "nsIPresentationDevice.h"
 #include "nsIPresentationListener.h"
 #include "nsIPresentationService.h"
 #include "nsIPresentationSessionTransport.h"
 #include "nsIServerSocket.h"
+#include "nsITimer.h"
 #include "nsString.h"
+#include "PresentationCallbacks.h"
 
 namespace mozilla {
 namespace dom {
 
 class PresentationSessionInfo : public nsIPresentationSessionTransportCallback
                               , public nsIPresentationControlChannelListener
 {
 public:
@@ -141,33 +144,50 @@ private:
 
   void Shutdown(nsresult aReason) override;
 
   nsCOMPtr<nsIServerSocket> mServerSocket;
 };
 
 // Session info with receiver side behaviors.
 class PresentationResponderInfo final : public PresentationSessionInfo
+                                      , public nsIObserver
+                                      , public nsITimerCallback
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSITIMERCALLBACK
 
   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.
+  nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
+
+  nsresult NotifyResponderReady();
 
 private:
-  ~PresentationResponderInfo() {}
+  ~PresentationResponderInfo()
+  {
+    Shutdown(NS_OK);
+  }
+
+  void Shutdown(nsresult aReason) override;
+
+  nsresult InitTransportAndSendAnswer();
+
+  nsRefPtr<PresentationResponderLoadingCallback> mLoadingCallback;
+  nsCOMPtr<nsITimer> mTimer;
+  nsCOMPtr<nsIPresentationChannelDescription> mRequesterDescription;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationSessionInfo_h
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -3,16 +3,17 @@
 /* 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/ContentChild.h"
 #include "mozilla/dom/PPresentation.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "nsIPresentationListener.h"
+#include "PresentationCallbacks.h"
 #include "PresentationChild.h"
 #include "PresentationIPCService.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace {
 
@@ -193,8 +194,16 @@ PresentationIPCService::NotifyReceiverRe
   return NS_OK;
 }
 
 void
 PresentationIPCService::NotifyPresentationChildDestroyed()
 {
   sPresentationChild = nullptr;
 }
+
+nsresult
+PresentationIPCService::MonitorResponderLoading(const nsAString& aSessionId,
+                                                nsIDocShell* aDocShell)
+{
+  mCallback = new PresentationResponderLoadingCallback(aSessionId);
+  return mCallback->Init(aDocShell);
+}
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -6,43 +6,51 @@
 
 #ifndef mozilla_dom_PresentationIPCService_h
 #define mozilla_dom_PresentationIPCService_h
 
 #include "nsIPresentationService.h"
 #include "nsRefPtrHashtable.h"
 #include "nsTObserverArray.h"
 
+class nsIDocShell;
+
 namespace mozilla {
 namespace dom {
 
 class PresentationRequest;
+class PresentationResponderLoadingCallback;
 
 class PresentationIPCService final : public nsIPresentationService
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONSERVICE
 
   PresentationIPCService();
 
   nsresult NotifyAvailableChange(bool aAvailable);
 
   nsresult NotifySessionStateChange(const nsAString& aSessionId,
                                     uint16_t aState);
 
-  nsresult NotifyMessage(const nsAString& aSessionId, const nsACString& aData);
+  nsresult NotifyMessage(const nsAString& aSessionId,
+                         const nsACString& aData);
 
   void NotifyPresentationChildDestroyed();
 
+  nsresult MonitorResponderLoading(const nsAString& aSessionId,
+                                   nsIDocShell* aDocShell);
+
 private:
   virtual ~PresentationIPCService();
   nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
                        const PresentationRequest& aRequest);
 
   nsTObserverArray<nsCOMPtr<nsIPresentationListener> > mListeners;
   nsRefPtrHashtable<nsStringHashKey, nsIPresentationSessionListener> mSessionListeners;
+  nsRefPtr<PresentationResponderLoadingCallback> mCallback;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationIPCService_h