Bug 1069230 - Presentation API implementation. Part 6 - mozChromeEvent for app launch. r=fabrice r=smaug
authorSean Lin <selin@mozilla.com>
Mon, 30 Mar 2015 15:48:11 +0800
changeset 288286 3eb3f4dd3df854713ba641611b05ab300705f365
parent 288285 e89355a01d882643be92233106672996db4ed5eb
child 288287 da1c2259d1b6ced86d8e08a1acd8b8e7265d358e
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)
reviewersfabrice, smaug
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 6 - mozChromeEvent for app launch. r=fabrice r=smaug
b2g/components/B2GComponents.manifest
b2g/components/PresentationRequestUIGlue.js
b2g/components/moz.build
b2g/components/test/mochitest/mochitest.ini
b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
b2g/components/test/mochitest/test_presentation_request_ui_glue.html
b2g/installer/package-manifest.in
dom/presentation/PresentationService.cpp
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/interfaces/moz.build
dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl
--- a/b2g/components/B2GComponents.manifest
+++ b/b2g/components/B2GComponents.manifest
@@ -113,8 +113,11 @@ contract @mozilla.org/services/mobileid-
 # B2GAppMigrator.js
 component {7211ece0-b458-4635-9afc-f8d7f376ee95} B2GAppMigrator.js
 contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95}
 
 # B2GPresentationDevicePrompt.js
 component {4a300c26-e99b-4018-ab9b-c48cf9bc4de1} B2GPresentationDevicePrompt.js
 contract @mozilla.org/presentation-device/prompt;1 {4a300c26-e99b-4018-ab9b-c48cf9bc4de1}
 
+# PresentationRequestUIGlue.js
+component {ccc8a839-0b64-422b-8a60-fb2af0e376d0} PresentationRequestUIGlue.js
+contract @mozilla.org/presentation/requestuiglue;1 {ccc8a839-0b64-422b-8a60-fb2af0e376d0}
new file mode 100644
--- /dev/null
+++ b/b2g/components/PresentationRequestUIGlue.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+"use strict"
+
+const { interfaces: Ci, utils: Cu, classes: Cc } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
+                                  "resource://gre/modules/SystemAppProxy.jsm");
+
+function PresentationRequestUIGlue() {
+  // This is to store the session ID / resolver binding.
+  // An example of the object literal is shown below:
+  //
+  // {
+  //   "sessionId1" : resolver1,
+  //   ...
+  // }
+  this._resolvers = {};
+
+  // Listen to the result for the opened iframe from front-end.
+  SystemAppProxy.addEventListener("mozPresentationContentEvent", aEvent => {
+    let detail = aEvent.detail;
+
+    if (detail.type != "presentation-receiver-launched") {
+      return;
+    }
+
+    let sessionId = detail.sessionId;
+    let resolver = this._resolvers[sessionId];
+    if (!resolver) {
+      return;
+    }
+
+    delete this._resolvers[sessionId];
+    resolver(detail.frame);
+  });
+}
+
+PresentationRequestUIGlue.prototype = {
+
+  sendRequest: function(aUrl, aSessionId) {
+    SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent",
+                                    { type: "presentation-launch-receiver",
+                                      url: aUrl,
+                                      id: aSessionId });
+
+    return new Promise(function(aResolve, aReject) {
+      this._resolvers[aSessionId] = aResolve;
+    }.bind(this));
+  },
+
+  classID: Components.ID("{ccc8a839-0b64-422b-8a60-fb2af0e376d0}"),
+
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationRequestUIGlue]);
--- a/b2g/components/moz.build
+++ b/b2g/components/moz.build
@@ -18,16 +18,17 @@ EXTRA_COMPONENTS += [
     'FxAccountsUIGlue.js',
     'HelperAppDialog.js',
     'InterAppCommUIGlue.js',
     'MailtoProtocolHandler.js',
     'MobileIdentityUIGlue.js',
     'OMAContentHandler.js',
     'PaymentGlue.js',
     'PaymentProviderStrategy.js',
+    'PresentationRequestUIGlue.js',
     'ProcessGlobal.js',
     'SmsProtocolHandler.js',
     'SystemMessageGlue.js',
     'TelProtocolHandler.js',
     'WebappsUpdateTimer.js',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'gonk':
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -2,18 +2,20 @@
 skip-if = toolkit != "gonk"
 support-files =
   permission_handler_chrome.js
   SandboxPromptTest.html
   filepicker_path_handler_chrome.js
   screenshot_helper.js
   systemapp_helper.js
   presentation_prompt_handler_chrome.js
+  presentation_ui_glue_handler_chrome.js
 
 [test_filepicker_path.html]
 [test_permission_deny.html]
 [test_permission_gum_remember.html]
 skip-if = true # Bug 1019572 - frequent timeouts
 [test_sandbox_permission.html]
 [test_screenshot.html]
 [test_systemapp.html]
 [test_presentation_device_prompt.html]
 [test_permission_visibilitychange.html]
+[test_presentation_request_ui_glue.html]
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm');
+
+const glue = Cc["@mozilla.org/presentation/requestuiglue;1"]
+             .createInstance(Ci.nsIPresentationRequestUIGlue);
+
+SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) {
+  if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') {
+    return;
+  }
+  sendAsyncMessage('presentation-launch-receiver', aEvent.detail);
+});
+
+addMessageListener('trigger-ui-glue', function(aData) {
+  var promise = glue.sendRequest(aData.url, aData.sessionId);
+  promise.then(function(aFrame){
+    sendAsyncMessage('iframe-resolved', aFrame);
+  });
+});
+
+addMessageListener('trigger-presentation-content-event', function(aData) {
+  var detail = {
+    type: 'presentation-receiver-launched',
+    sessionId: aData.sessionId,
+    frame: aData.frame
+  };
+  SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', detail);
+});
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Presentation Device Selection</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Test for Presentation UI Glue</a>
+<script type="application/javascript;version=1.8">
+
+'use strict';
+
+SimpleTest.waitForExplicitFinish();
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('presentation_ui_glue_handler_chrome.js'));
+
+var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+var url = 'http://example.com';
+var sessionId = 'sessionId';
+
+function testLaunchReceiver() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('presentation-launch-receiver', function launchReceiverHandler(aDetail) {
+      gScript.removeMessageListener('presentation-launch-receiver', launchReceiverHandler);
+      ok(true, "A presentation-launch-receiver mozPresentationChromeEvent should be received.");
+      is(aDetail.url, url, "Url should be the same.");
+      is(aDetail.id, sessionId, "Session ID should be the same.");
+
+      aResolve();
+    });
+
+    gScript.sendAsyncMessage('trigger-ui-glue',
+                             { url: url,
+                               sessionId : sessionId });
+  });
+}
+
+function testReceiverLaunched() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('iframe-resolved', function iframeResolvedHandler(aFrame) {
+      gScript.removeMessageListener('iframe-resolved', iframeResolvedHandler);
+      ok(true, "The promise should be resolved.");
+
+      aResolve();
+    });
+
+    var iframe = document.createElement('iframe');
+    iframe.setAttribute('remote', 'true');
+    iframe.setAttribute('mozbrowser', 'true');
+    iframe.setAttribute('src', 'http://example.com');
+    document.body.appendChild(iframe);
+
+    gScript.sendAsyncMessage('trigger-presentation-content-event',
+                             { sessionId : sessionId,
+                               frame: iframe });
+  });
+}
+
+function runTests() {
+  testLaunchReceiver()
+  .then(testReceiverLaunched)
+  .then(function() {
+    info('test finished, teardown');
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+}
+
+window.addEventListener('load', runTests);
+</script>
+</body>
+</html>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -955,16 +955,17 @@ bin/libfreebl_32int64_3.so
 @RESPATH@/components/B2GAboutRedirector.js
 @RESPATH@/components/FilePicker.js
 @RESPATH@/components/HelperAppDialog.js
 @RESPATH@/components/DownloadsUI.js
 @RESPATH@/components/InterAppCommUIGlue.js
 @RESPATH@/components/SystemMessageGlue.js
 @RESPATH@/components/B2GAppMigrator.js
 @RESPATH@/components/B2GPresentationDevicePrompt.js
+@RESPATH@/components/PresentationRequestUIGlue.js
 
 #ifndef MOZ_WIDGET_GONK
 @RESPATH@/components/SimulatorScreen.js
 #endif
 
 @RESPATH@/components/FxAccountsUIGlue.js
 @RESPATH@/components/services_fxaccounts.xpt
 
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -7,16 +7,17 @@
 #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 "nsIPresentationRequestUIGlue.h"
 #include "nsIPresentationSessionRequest.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "PresentationService.h"
 
 using namespace mozilla;
@@ -331,26 +332,30 @@ PresentationService::HandleSessionReques
     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)) {
+  nsCOMPtr<nsIPresentationRequestUIGlue> glue =
+    do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID);
+  if (NS_WARN_IF(!glue)) {
     ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
     return info->ReplyError(NS_ERROR_NOT_AVAILABLE);
   }
-  rv = obs->NotifyObservers(aRequest, "presentation-launch-receiver", nullptr);
+  nsCOMPtr<nsISupports> promise;
+  rv = glue->SendRequest(url, sessionId, getter_AddRefs(promise));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
     return info->ReplyError(rv);
   }
+  nsCOMPtr<Promise> realPromise = do_QueryInterface(promise);
+  static_cast<PresentationResponderInfo*>(info.get())->SetPromise(realPromise);
 
   return NS_OK;
 }
 
 void
 PresentationService::NotifyAvailableChange(bool aIsAvailable)
 {
   nsTObserverArray<nsCOMPtr<nsIPresentationListener> >::ForwardIterator iter(mListeners);
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -1,21 +1,21 @@
 /* -*- 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/HTMLIFrameElementBinding.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;
@@ -335,36 +335,26 @@ PresentationRequesterInfo::OnStopListeni
  *    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_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.
+  nsresult rv;
   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))) {
@@ -374,27 +364,23 @@ PresentationResponderInfo::Init(nsIPrese
   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;
+  mPromise = 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);
@@ -480,78 +466,95 @@ PresentationResponderInfo::NotifyClosed(
   if (NS_WARN_IF(NS_FAILED(aReason))) {
     // 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);
 }
+
+// PromiseNativeHandler
+void
+PresentationResponderInfo::ResolvedCallback(JSContext* aCx,
+                                            JS::Handle<JS::Value> aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!aValue.isObject())) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  JS::Rooted<JSObject*> obj(aCx, &aValue.toObject());
+  if (NS_WARN_IF(!obj)) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  // Start to listen to document state change event |STATE_TRANSFERRING|.
+  HTMLIFrameElement* frame = nullptr;
+  nsresult rv = UNWRAP_OBJECT(HTMLIFrameElement, obj, frame);
+  if (NS_WARN_IF(!frame)) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  nsCOMPtr<nsIFrameLoaderOwner> owner = do_QueryInterface((nsIFrameLoaderOwner*) frame);
+  if (NS_WARN_IF(!owner)) {
+    ReplyError(NS_ERROR_NOT_AVAILABLE);
+    return;
+  }
+
+  nsCOMPtr<nsIFrameLoader> frameLoader;
+  rv = owner->GetFrameLoader(getter_AddRefs(frameLoader));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    ReplyError(rv);
+    return;
+  }
+
+  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))) {
+      ReplyError(rv);
+      return;
+    }
+
+    mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
+    rv = mLoadingCallback->Init(docShell);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ReplyError(rv);
+      return;
+    }
+  }
+}
+
+void
+PresentationResponderInfo::RejectedCallback(JSContext* aCx,
+                                            JS::Handle<JS::Value> aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_WARNING("The receiver page fails to become ready before timeout.");
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  ReplyError(NS_ERROR_DOM_ABORT_ERR);
+}
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -2,19 +2,20 @@
 /* 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_PresentationSessionInfo_h
 #define mozilla_dom_PresentationSessionInfo_h
 
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseNativeHandler.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"
@@ -144,50 +145,60 @@ private:
 
   void Shutdown(nsresult aReason) override;
 
   nsCOMPtr<nsIServerSocket> mServerSocket;
 };
 
 // Session info with receiver side behaviors.
 class PresentationResponderInfo final : public PresentationSessionInfo
-                                      , public nsIObserver
+                                      , public PromiseNativeHandler
                                       , 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);
   }
 
   nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
 
   nsresult NotifyResponderReady();
 
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
+
+  void SetPromise(Promise* aPromise)
+  {
+    mPromise = aPromise;
+    mPromise->AppendNativeHandler(this);
+  }
+
 private:
   ~PresentationResponderInfo()
   {
     Shutdown(NS_OK);
   }
 
   void Shutdown(nsresult aReason) override;
 
   nsresult InitTransportAndSendAnswer();
 
   nsRefPtr<PresentationResponderLoadingCallback> mLoadingCallback;
   nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsIPresentationChannelDescription> mRequesterDescription;
+  nsRefPtr<Promise> mPromise;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationSessionInfo_h
--- a/dom/presentation/interfaces/moz.build
+++ b/dom/presentation/interfaces/moz.build
@@ -6,16 +6,17 @@
 
 XPIDL_SOURCES += [
     'nsIPresentationControlChannel.idl',
     'nsIPresentationDevice.idl',
     'nsIPresentationDeviceManager.idl',
     'nsIPresentationDevicePrompt.idl',
     'nsIPresentationDeviceProvider.idl',
     'nsIPresentationListener.idl',
+    'nsIPresentationRequestUIGlue.idl',
     'nsIPresentationService.idl',
     'nsIPresentationSessionRequest.idl',
     'nsIPresentationSessionTransport.idl',
     'nsITCPPresentationServer.idl',
 ]
 
 XPIDL_MODULE = 'dom_presentation'
 
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl
@@ -0,0 +1,25 @@
+/* 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"
+
+%{C++
+#define PRESENTATION_REQUEST_UI_GLUE_CONTRACTID \
+  "@mozilla.org/presentation/requestuiglue;1"
+%}
+
+[scriptable, uuid(faa45119-6fb5-496c-aa4c-f740177a38b5)]
+interface nsIPresentationRequestUIGlue : nsISupports
+{
+  /*
+   * This method is called to open the responding app/page when a presentation
+   * request comes in at receiver side.
+   *
+   * @param url       The url of the request.
+   * @param sessionId The session ID of the request.
+   *
+   * @return A promise that resolves to the opening frame.
+   */
+  nsISupports sendRequest(in DOMString url, in DOMString sessionId);
+};