Bug 1080474 - Part 1 - device manager for Presentation API. r=fabrice.
authorShih-Chiang Chien <schien@mozilla.com>
Wed, 27 Aug 2014 10:28:03 +0800
changeset 250685 8107ed414207bf34020a392cfe857b50fbaf8079
parent 250684 b13ce81e70300e945df60750d33810c3dcdb4222
child 250686 83db4a079fd6840a160cb71619d99dfc305e58d2
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs1080474
milestone38.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 1080474 - Part 1 - device manager for Presentation API. r=fabrice.
b2g/installer/package-manifest.in
browser/installer/package-manifest.in
dom/moz.build
dom/presentation/PresentationDeviceManager.cpp
dom/presentation/PresentationDeviceManager.h
dom/presentation/PresentationSessionRequest.cpp
dom/presentation/PresentationSessionRequest.h
dom/presentation/interfaces/moz.build
dom/presentation/interfaces/nsIPresentationControlChannel.idl
dom/presentation/interfaces/nsIPresentationDevice.idl
dom/presentation/interfaces/nsIPresentationDeviceManager.idl
dom/presentation/interfaces/nsIPresentationDevicePrompt.idl
dom/presentation/interfaces/nsIPresentationDeviceProvider.idl
dom/presentation/interfaces/nsIPresentationSessionRequest.idl
dom/presentation/moz.build
dom/presentation/tests/xpcshell/test_presentation_device_manager.js
dom/presentation/tests/xpcshell/xpcshell.ini
layout/build/nsLayoutModule.cpp
mobile/android/installer/package-manifest.in
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -205,16 +205,17 @@
 #ifdef MOZ_WEBSPEECH
 @BINPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
 @BINPATH@/components/dom_time.xpt
 @BINPATH@/components/dom_engineeringmode.xpt
+@BINPATH@/components/dom_presentation.xpt
 @BINPATH@/components/downloads.xpt
 @BINPATH@/components/editor.xpt
 @BINPATH@/components/embed_base.xpt
 @BINPATH@/components/extensions.xpt
 @BINPATH@/components/exthandler.xpt
 @BINPATH@/components/exthelper.xpt
 @BINPATH@/components/fastfind.xpt
 @BINPATH@/components/feeds.xpt
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -232,16 +232,17 @@
 @RESPATH@/components/dom_workers.xpt
 @RESPATH@/components/dom_xbl.xpt
 @RESPATH@/components/dom_xpath.xpt
 @RESPATH@/components/dom_xul.xpt
 #ifdef MOZ_GAMEPAD
 @RESPATH@/components/dom_gamepad.xpt
 #endif
 @RESPATH@/components/dom_payment.xpt
+@RESPATH@/components/dom_presentation.xpt
 @RESPATH@/components/downloads.xpt
 @RESPATH@/components/editor.xpt
 @RESPATH@/components/embed_base.xpt
 @RESPATH@/components/extensions.xpt
 @RESPATH@/components/exthandler.xpt
 @RESPATH@/components/exthelper.xpt
 @RESPATH@/components/fastfind.xpt
 @RESPATH@/components/feeds.xpt
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -141,16 +141,18 @@ if CONFIG['MOZ_B2G']:
         'engineeringmode'
     ]
 
 if CONFIG['MOZ_B2G_BT_API_V2']:
     DIRS += ['bluetooth2']
 else:
     DIRS += ['bluetooth']
 
+DIRS += ['presentation']
+
 TEST_DIRS += [
     'tests',
     'imptests',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'cocoa', 'windows', 'android', 'qt'):
     TEST_DIRS += ['plugins/test']
 
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationDeviceManager.cpp
@@ -0,0 +1,231 @@
+/* -*- 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 "PresentationDeviceManager.h"
+
+#include "mozilla/Services.h"
+#include "MainThreadUtils.h"
+#include "nsCategoryCache.h"
+#include "nsCOMPtr.h"
+#include "nsIMutableArray.h"
+#include "nsIObserverService.h"
+#include "nsXULAppAPI.h"
+#include "PresentationSessionRequest.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(PresentationDeviceManager,
+                  nsIPresentationDeviceManager,
+                  nsIPresentationDeviceListener,
+                  nsIPresentationDeviceEventListener,
+                  nsIObserver)
+
+PresentationDeviceManager::PresentationDeviceManager()
+{
+}
+
+PresentationDeviceManager::~PresentationDeviceManager()
+{
+  UnloadDeviceProviders();
+  mDevices.Clear();
+}
+
+void
+PresentationDeviceManager::LoadDeviceProviders()
+{
+  MOZ_ASSERT(mProviders.IsEmpty());
+
+  nsCategoryCache<nsIPresentationDeviceProvider> providerCache(PRESENTATION_DEVICE_PROVIDER_CATEGORY);
+  providerCache.GetEntries(mProviders);
+
+  for (uint32_t i = 0; i < mProviders.Length(); ++i) {
+    mProviders[i]->SetListener(this);
+  }
+}
+
+void
+PresentationDeviceManager::UnloadDeviceProviders()
+{
+  for (uint32_t i = 0; i < mProviders.Length(); ++i) {
+    mProviders[i]->SetListener(nullptr);
+  }
+
+  mProviders.Clear();
+}
+
+void
+PresentationDeviceManager::NotifyDeviceChange(nsIPresentationDevice* aDevice,
+                                              const char16_t* aType)
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(aDevice,
+                         PRESENTATION_DEVICE_CHANGE_TOPIC,
+                         aType);
+  }
+}
+
+// nsIPresentationDeviceManager
+NS_IMETHODIMP
+PresentationDeviceManager::ForceDiscovery()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  for (uint32_t i = 0; i < mProviders.Length(); ++i) {
+    mProviders[i]->ForceDiscovery();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceManager::AddDeviceProvider(nsIPresentationDeviceProvider* aProvider)
+{
+  NS_ENSURE_ARG(aProvider);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(mProviders.Contains(aProvider))) {
+    return NS_OK;
+  }
+
+  mProviders.AppendElement(aProvider);
+  aProvider->SetListener(this);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceManager::RemoveDeviceProvider(nsIPresentationDeviceProvider* aProvider)
+{
+  NS_ENSURE_ARG(aProvider);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!mProviders.RemoveElement(aProvider))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  aProvider->SetListener(nullptr);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceManager::GetDeviceAvailable(bool* aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  *aRetVal = !mDevices.IsEmpty();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceManager::GetAvailableDevices(nsIArray** aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIMutableArray> devices = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  for (uint32_t i = 0; i < mDevices.Length(); ++i) {
+    devices->AppendElement(mDevices[i], false);
+  }
+
+  devices.forget(aRetVal);
+
+  return NS_OK;
+}
+
+// nsIPresentationDeviceListener
+NS_IMETHODIMP
+PresentationDeviceManager::AddDevice(nsIPresentationDevice* aDevice)
+{
+  NS_ENSURE_ARG(aDevice);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(mDevices.Contains(aDevice))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mDevices.AppendElement(aDevice);
+  aDevice->SetListener(this);
+
+  NotifyDeviceChange(aDevice, MOZ_UTF16("add"));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceManager::RemoveDevice(nsIPresentationDevice* aDevice)
+{
+  NS_ENSURE_ARG(aDevice);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  int32_t index = mDevices.IndexOf(aDevice);
+  if (NS_WARN_IF(index < 0)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mDevices[index]->SetListener(nullptr);
+  mDevices.RemoveElementAt(index);
+
+  NotifyDeviceChange(aDevice, MOZ_UTF16("remove"));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationDeviceManager::UpdateDevice(nsIPresentationDevice* aDevice)
+{
+  NS_ENSURE_ARG(aDevice);
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (NS_WARN_IF(!mDevices.Contains(aDevice))) {
+    return NS_ERROR_FAILURE;
+  }
+
+  NotifyDeviceChange(aDevice, MOZ_UTF16("update"));
+
+  return NS_OK;
+}
+
+// nsIPresentationDeviceListener
+NS_IMETHODIMP
+PresentationDeviceManager::OnSessionRequest(nsIPresentationDevice* aDevice,
+                                            const nsAString& aUrl,
+                                            const nsAString& aPresentationId,
+                                            nsIPresentationControlChannel* aControlChannel)
+{
+  NS_ENSURE_ARG(aDevice);
+  NS_ENSURE_ARG(aControlChannel);
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+
+  nsRefPtr<PresentationSessionRequest> request =
+    new PresentationSessionRequest(aDevice, aUrl, aPresentationId, aControlChannel);
+  obs->NotifyObservers(request,
+                       PRESENTATION_SESSION_REQUEST_TOPIC,
+                       nullptr);
+
+  return NS_OK;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+PresentationDeviceManager::Observe(nsISupports *aSubject,
+                                   const char *aTopic,
+                                   const char16_t *aData)
+{
+  if (!strcmp(aTopic, "profile-after-change")) {
+    LoadDeviceProviders();
+  }
+
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationDeviceManager.h
@@ -0,0 +1,49 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PresentationDeviceManager_h__
+#define mozilla_dom_PresentationDeviceManager_h__
+
+#include "nsIObserver.h"
+#include "nsIPresentationDevice.h"
+#include "nsIPresentationDeviceManager.h"
+#include "nsIPresentationDeviceProvider.h"
+#include "nsCOMArray.h"
+
+namespace mozilla {
+namespace dom {
+
+class PresentationDeviceManager MOZ_FINAL : public nsIPresentationDeviceManager
+                                          , public nsIPresentationDeviceListener
+                                          , public nsIPresentationDeviceEventListener
+                                          , public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONDEVICEMANAGER
+  NS_DECL_NSIPRESENTATIONDEVICELISTENER
+  NS_DECL_NSIPRESENTATIONDEVICEEVENTLISTENER
+  NS_DECL_NSIOBSERVER
+
+  PresentationDeviceManager();
+
+private:
+  virtual ~PresentationDeviceManager();
+
+  void LoadDeviceProviders();
+
+  void UnloadDeviceProviders();
+
+  void NotifyDeviceChange(nsIPresentationDevice* aDevice,
+                          const char16_t* aType);
+
+  nsCOMArray<nsIPresentationDeviceProvider> mProviders;
+  nsCOMArray<nsIPresentationDevice> mDevices;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PresentationDeviceManager_h__ */
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionRequest.cpp
@@ -0,0 +1,71 @@
+/* -*- 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 "PresentationSessionRequest.h"
+#include "nsIPresentationControlChannel.h"
+#include "nsIPresentationDevice.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ISUPPORTS(PresentationSessionRequest, nsIPresentationSessionRequest)
+
+PresentationSessionRequest::PresentationSessionRequest(nsIPresentationDevice* aDevice,
+                                                       const nsAString& aUrl,
+                                                       const nsAString& aPresentationId,
+                                                       nsIPresentationControlChannel* aControlChannel)
+  : mUrl(aUrl)
+  , mPresentationId(aPresentationId)
+  , mDevice(aDevice)
+  , mControlChannel(aControlChannel)
+{
+}
+
+PresentationSessionRequest::~PresentationSessionRequest()
+{
+}
+
+// nsIPresentationSessionRequest
+
+NS_IMETHODIMP
+PresentationSessionRequest::GetDevice(nsIPresentationDevice** aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+
+  nsCOMPtr<nsIPresentationDevice> device = mDevice;
+  device.forget(aRetVal);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionRequest::GetUrl(nsAString& aRetVal)
+{
+  aRetVal = mUrl;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionRequest::GetPresentationId(nsAString& aRetVal)
+{
+  aRetVal = mPresentationId;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationSessionRequest::GetControlChannel(nsIPresentationControlChannel** aRetVal)
+{
+  NS_ENSURE_ARG_POINTER(aRetVal);
+
+  nsCOMPtr<nsIPresentationControlChannel> controlChannel = mControlChannel;
+  controlChannel.forget(aRetVal);
+
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/PresentationSessionRequest.h
@@ -0,0 +1,40 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_PresentationSessionRequest_h__
+#define mozilla_dom_PresentationSessionRequest_h__
+
+#include "nsIPresentationSessionRequest.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace dom {
+
+class PresentationSessionRequest MOZ_FINAL : public nsIPresentationSessionRequest
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONSESSIONREQUEST
+
+  PresentationSessionRequest(nsIPresentationDevice* aDevice,
+                             const nsAString& aUrl,
+                             const nsAString& aPresentationId,
+                             nsIPresentationControlChannel* aControlChannel);
+
+private:
+  virtual ~PresentationSessionRequest();
+
+  nsString mUrl;
+  nsString mPresentationId;
+  nsCOMPtr<nsIPresentationDevice> mDevice;
+  nsCOMPtr<nsIPresentationControlChannel> mControlChannel;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* mozilla_dom_PresentationSessionRequest_h__ */
+
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+    'nsIPresentationControlChannel.idl',
+    'nsIPresentationDevice.idl',
+    'nsIPresentationDeviceManager.idl',
+    'nsIPresentationDevicePrompt.idl',
+    'nsIPresentationDeviceProvider.idl',
+    'nsIPresentationSessionRequest.idl',
+]
+
+XPIDL_MODULE = 'dom_presentation'
+
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl
@@ -0,0 +1,91 @@
+/* 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 nsIArray;
+interface nsIInputStream;
+
+[scriptable, uuid(ae318e05-2a4e-4f85-95c0-e8b191ad812c)]
+interface nsIPresentationChannelDescription: nsISupports
+{
+  const unsigned short TYPE_TCP = 1;
+  const unsigned short TYPE_DATACHANNEL = 2;
+
+  // Type of transport channel.
+  readonly attribute uint8_t type;
+
+  // Addresses for TCP channel.
+  // Should only be used while type == TYPE_TCP.
+  readonly attribute nsIArray tcpAddress;
+
+  // Port number for TCP channel.
+  // Should only be used while type == TYPE_TCP.
+  readonly attribute uint16_t tcpPort;
+
+  // SDP for Data Channel.
+  // Should only be used while type == TYPE_DATACHANNEL.
+  readonly attribute DOMString dataChannelSDP;
+};
+
+/*
+ * The callbacks for events on control channel.
+ */
+[scriptable, uuid(d0cdc638-a9d5-4bcd-838c-3aed7c3f2a6b)]
+interface nsIPresentationControlChannelListener: nsISupports
+{
+  /*
+   * Callback for receiving offer from remote endpoint.
+   * @param offer The received offer.
+   */
+  void onOffer(in nsIPresentationChannelDescription offer);
+
+  /*
+   * Callback for receiving answer from remote endpoint.
+   * @param answer The received answer.
+   */
+  void onAnswer(in nsIPresentationChannelDescription answer);
+
+  /*
+   * The callback for notifying channel opened.
+   */
+  void notifyOpened();
+
+  /*
+   * The callback for notifying channel closed.
+   * @param reason The reason of channel close, NS_OK represents normal close.
+   */
+  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)]
+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
+   * on remote endpoint.
+   * @param offer The offer to send.
+   */
+  void sendOffer(in nsIPresentationChannelDescription offer);
+
+  /*
+   * Send answer to remote endpiont. |onAnswer| should
+   * be invoked on remote endpoint.
+   * @param answer The answer to send.
+   */
+  void sendAnswer(in nsIPresentationChannelDescription answer);
+
+  /*
+   * Close the transport channel.
+   */
+  void close();
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationDevice.idl
@@ -0,0 +1,55 @@
+/* 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 nsIPresentationControlChannel;
+interface nsIPresentationDevice;
+
+/*
+ * Event callbacks from remote presentation device.
+ */
+[scriptable, uuid(81984458-b9d1-4731-a26a-ba62ab339aac)]
+interface nsIPresentationDeviceEventListener : nsISupports
+{
+  /*
+   * Callback while the remote device is requesting to start a presentation session.
+   * @param url The URL requested to open by remote device.
+   * @param presentationId The Id for representing this session.
+   * @param controlChannel The control channel for this session.
+   */
+  void onSessionRequest(in nsIPresentationDevice device,
+                        in DOMString url,
+                        in DOMString presentationId,
+                        in nsIPresentationControlChannel controlChannel);
+};
+
+/*
+ * Remote device.
+ */
+[scriptable, uuid(7fac99d4-9b19-4b8d-b5cd-5da8adbe58f1)]
+interface nsIPresentationDevice : nsISupports
+{
+  // The unique Id for the device. UUID is recommanded.
+  readonly attribute AUTF8String id;
+
+  // The human-readable name of this device.
+  readonly attribute AUTF8String name;
+
+  //TODO expose more info in order to fulfill UX spec
+  // The category of this device, could be "wifi", "bluetooth", "hdmi", etc.
+  readonly attribute AUTF8String type;
+
+  // The listener for handling remote session request.
+  attribute nsIPresentationDeviceEventListener listener;
+
+  /*
+   * Establish a control channel to this device.
+   * @param url The URL requested to open by remote device.
+   * @param presentationId The Id for representing this session.
+   * @return The control channel for this session.
+   */
+  nsIPresentationControlChannel establishControlChannel(in DOMString url,
+                                                        in DOMString presentationId);
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationDeviceManager.idl
@@ -0,0 +1,47 @@
+/* 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 nsIArray;
+interface nsIPresentationDeviceProvider;
+
+%{C++
+#define PRESENTATION_DEVICE_MANAGER_CONTRACTID "@mozilla.org/presentation-device/manager;1"
+#define PRESENTATION_DEVICE_CHANGE_TOPIC "presentation-device-change"
+%}
+
+/*
+ * Manager for the device availablility. User can observe "presentation-device-change"
+ * for any update of the available devices.
+ */
+[scriptable, uuid(beb61db5-3d5f-454f-a15a-dbfa0337c569)]
+interface nsIPresentationDeviceManager : nsISupports
+{
+  // true if there is any device available.
+  readonly attribute boolean deviceAvailable;
+
+  /*
+   * Register a device provider manually.
+   * @param provider The device provider to add.
+   */
+  void addDeviceProvider(in nsIPresentationDeviceProvider provider);
+
+  /*
+   * Unregister a device provider manually.
+   * @param provider The device provider to remove.
+   */
+  void removeDeviceProvider(in nsIPresentationDeviceProvider provider);
+
+  /*
+   * Force all registered device providers to update device information.
+   */
+  void forceDiscovery();
+
+  /*
+   * Retrieve all available devices, return a list of nsIPresentationDevice.
+   */
+  nsIArray getAvailableDevices();
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationDevicePrompt.idl
@@ -0,0 +1,48 @@
+/* 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 nsIPresentationDevice;
+
+%{C++
+#define PRESENTATION_DEVICE_PROMPT_CONTRACTID "@mozilla.org/presentation-device/prompt;1"
+%}
+
+/*
+ * The information and callbacks for device selection
+ */
+[scriptable, uuid(b2aa7f6a-9448-446a-bba4-9c29638b0ed4)]
+interface nsIPresentationDeviceRequest : nsISupports
+{
+  // The origin which initiate the request.
+  readonly attribute DOMString origin;
+
+  // The URL to be opened after selection.
+  readonly attribute DOMString requestURL;
+
+  /*
+   * Callback after selecting a device
+   * @param device The selected device.
+   */
+  void select(in nsIPresentationDevice device);
+
+  /*
+    Callback after selection failed or canceled by user.
+   */
+  void cancel();
+};
+
+/*
+ * UI prompt for device selection.
+ */
+[scriptable, uuid(ac1a7e44-de86-454f-a9f1-276de2539831)]
+interface nsIPresentationDevicePrompt : nsISupports
+{
+  /*
+   * Request a device selection.
+   * @param request The information and callbacks of this selection request.
+   */
+  void promptDeviceSelection(in nsIPresentationDeviceRequest request);
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationDeviceProvider.idl
@@ -0,0 +1,38 @@
+/* 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 nsIPresentationDevice;
+
+%{C++
+#define PRESENTATION_DEVICE_PROVIDER_CATEGORY "presentation-device-provider"
+%}
+
+/*
+ * The callbacks for any device updates.
+ */
+[scriptable, uuid(7f9f0514-d957-485a-90e8-57cc3acbf15b)]
+interface nsIPresentationDeviceListener: nsISupports
+{
+  void addDevice(in nsIPresentationDevice device);
+  void removeDevice(in nsIPresentationDevice device);
+  void updateDevice(in nsIPresentationDevice device);
+};
+
+/*
+ * Device provider for any device protocol, can be registered as default
+ * providers by adding its contractID to category "presentation-device-provider".
+ */
+[scriptable, uuid(3db2578a-0f50-44ad-b01b-28427b71b7bf)]
+interface nsIPresentationDeviceProvider: nsISupports
+{
+  // The listener for handling any device update.
+  attribute nsIPresentationDeviceListener listener;
+
+  /*
+   * Force to update device information.
+   */
+  void forceDiscovery();
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/interfaces/nsIPresentationSessionRequest.idl
@@ -0,0 +1,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/. */
+
+#include "nsISupports.idl"
+
+interface nsIPresentationDevice;
+interface nsIPresentationControlChannel;
+
+%{C++
+#define PRESENTATION_SESSION_REQUEST_TOPIC "presentation-session-request"
+%}
+
+/*
+ * The event of a device requesting for a presentation session. User can
+ * monitor the session request on every device by observing "presentation-sesion-request".
+ */
+[scriptable, uuid(d808a084-d0f8-455a-a8df-5879e05a755b)]
+interface nsIPresentationSessionRequest: nsISupports
+{
+  // The device which requesting the presentation session.
+  readonly attribute nsIPresentationDevice device;
+
+  // The URL requested to open by remote device.
+  readonly attribute DOMString url;
+
+  // The Id for representing this session.
+  readonly attribute DOMString presentationId;
+
+  // The control channel for this session.
+  readonly attribute nsIPresentationControlChannel controlChannel;
+};
new file mode 100644
--- /dev/null
+++ b/dom/presentation/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += ['interfaces']
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
+
+EXPORTS.mozilla.dom.presentation += [
+    'PresentationDeviceManager.h',
+]
+
+SOURCES += [
+    'PresentationDeviceManager.cpp',
+    'PresentationSessionRequest.cpp',
+]
+
+FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/xpcshell/test_presentation_device_manager.js
@@ -0,0 +1,192 @@
+/* -*- 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;
+
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
+Cu.import('resource://gre/modules/Services.jsm');
+
+const manager = Cc['@mozilla.org/presentation-device/manager;1']
+                  .getService(Ci.nsIPresentationDeviceManager);
+
+function TestPresentationDevice() {}
+
+
+function TestPresentationControlChannel() {}
+
+TestPresentationControlChannel.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]),
+  sendOffer: function(offer) {},
+  sendAnswer: function(answer) {},
+  close: function() {},
+  set listener(listener) {},
+  get listener() {},
+};
+
+var testProvider = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceProvider]),
+
+  forceDiscovery: function() {
+  },
+  set listener(listener) {
+  },
+  get listener() {
+  },
+};
+
+var testDevice = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]),
+  id: 'id',
+  name: 'name',
+  type: 'type',
+  establishControlChannel: function(url, presentationId) {
+    return null;
+  },
+  set listener(listener) {
+    this._listener = listener;
+  },
+  get listener() {
+    return this._listener;
+  },
+
+  simulateSessionRequest: function(url, presentationId, controlChannel) {
+    this._listener.onSessionRequest(this, url, presentationId, controlChannel);
+  },
+};
+
+function addProvider() {
+  Object.defineProperty(testProvider, 'listener', {
+    configurable: true,
+    set: function(listener) {
+      Assert.strictEqual(listener, manager, 'listener setter is invoked by PresentationDeviceManager');
+      delete testProvider.listener;
+      run_next_test();
+    },
+  });
+  manager.addDeviceProvider(testProvider);
+}
+
+function forceDiscovery() {
+  testProvider.forceDiscovery = function() {
+    testProvider.forceDiscovery = function() {};
+    Assert.ok(true, 'forceDiscovery is invoked by PresentationDeviceManager');
+    run_next_test();
+  };
+  manager.forceDiscovery();
+}
+
+function addDevice() {
+  Services.obs.addObserver(function observer(subject, topic, data) {
+    Services.obs.removeObserver(observer, topic);
+
+    let updatedDevice = subject.QueryInterface(Ci.nsIPresentationDevice);
+    Assert.equal(updatedDevice.id, testDevice.id, 'expected device id');
+    Assert.equal(updatedDevice.name, testDevice.name, 'expected device name');
+    Assert.equal(updatedDevice.type, testDevice.type, 'expected device type');
+    Assert.equal(data, 'add', 'expected update type');
+
+    Assert.ok(manager.deviceAvailable, 'device is available');
+
+    let devices = manager.getAvailableDevices();
+    Assert.equal(devices.length, 1, 'expect 1 available device');
+
+    let device = devices.queryElementAt(0, Ci.nsIPresentationDevice);
+    Assert.equal(device.id, testDevice.id, 'expected device id');
+    Assert.equal(device.name, testDevice.name, 'expected device name');
+    Assert.equal(device.type, testDevice.type, 'expected device type');
+
+    run_next_test();
+  }, 'presentation-device-change', false);
+  manager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(testDevice);
+}
+
+function updateDevice() {
+  Services.obs.addObserver(function observer(subject, topic, data) {
+    Services.obs.removeObserver(observer, topic);
+
+    let updatedDevice = subject.QueryInterface(Ci.nsIPresentationDevice);
+    Assert.equal(updatedDevice.id, testDevice.id, 'expected device id');
+    Assert.equal(updatedDevice.name, testDevice.name, 'expected device name');
+    Assert.equal(updatedDevice.type, testDevice.type, 'expected device type');
+    Assert.equal(data, 'update', 'expected update type');
+
+    Assert.ok(manager.deviceAvailable, 'device is available');
+
+    let devices = manager.getAvailableDevices();
+    Assert.equal(devices.length, 1, 'expect 1 available device');
+
+    let device = devices.queryElementAt(0, Ci.nsIPresentationDevice);
+    Assert.equal(device.id, testDevice.id, 'expected device id');
+    Assert.equal(device.name, testDevice.name, 'expected name after device update');
+    Assert.equal(device.type, testDevice.type, 'expected device type');
+
+    run_next_test();
+  }, 'presentation-device-change', false);
+  testDevice.name = 'updated-name';
+  manager.QueryInterface(Ci.nsIPresentationDeviceListener).updateDevice(testDevice);
+}
+
+function sessionRequest() {
+  let testUrl = 'http://www.example.org/';
+  let testPresentationId = 'test-presentation-id';
+  let testControlChannel = new TestPresentationControlChannel();
+  Services.obs.addObserver(function observer(subject, topic, data) {
+    Services.obs.removeObserver(observer, topic);
+
+    let request = subject.QueryInterface(Ci.nsIPresentationSessionRequest);
+
+    Assert.equal(request.device.id, testDevice.id, 'expected device');
+    Assert.equal(request.url, testUrl, 'expected requesting URL');
+    Assert.equal(request.presentationId, testPresentationId, 'expected presentation Id');
+
+    run_next_test();
+  }, 'presentation-session-request', false);
+  testDevice.simulateSessionRequest(testUrl, testPresentationId, testControlChannel);
+}
+
+function removeDevice() {
+  Services.obs.addObserver(function observer(subject, topic, data) {
+    Services.obs.removeObserver(observer, topic);
+
+    let updatedDevice = subject.QueryInterface(Ci.nsIPresentationDevice);
+    Assert.equal(updatedDevice.id, testDevice.id, 'expected device id');
+    Assert.equal(updatedDevice.name, testDevice.name, 'expected device name');
+    Assert.equal(updatedDevice.type, testDevice.type, 'expected device type');
+    Assert.equal(data, 'remove', 'expected update type');
+
+    Assert.ok(!manager.deviceAvailable, 'device is not available');
+
+    let devices = manager.getAvailableDevices();
+    Assert.equal(devices.length, 0, 'expect 0 available device');
+
+    run_next_test();
+  }, 'presentation-device-change', false);
+  manager.QueryInterface(Ci.nsIPresentationDeviceListener).removeDevice(testDevice);
+}
+
+function removeProvider() {
+  Object.defineProperty(testProvider, 'listener', {
+    configurable: true,
+    set: function(listener) {
+      Assert.strictEqual(listener, null, 'unsetListener is invoked by PresentationDeviceManager');
+      delete testProvider.listener;
+      run_next_test();
+    },
+  });
+  manager.removeDeviceProvider(testProvider);
+}
+
+add_test(addProvider);
+add_test(forceDiscovery);
+add_test(addDevice);
+add_test(updateDevice);
+add_test(sessionRequest);
+add_test(removeDevice);
+add_test(removeProvider);
+
+function run_test() {
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/xpcshell/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_presentation_device_manager.js]
--- a/layout/build/nsLayoutModule.cpp
+++ b/layout/build/nsLayoutModule.cpp
@@ -243,16 +243,18 @@ static void Shutdown();
 
 #ifdef MOZ_WIDGET_GONK
 #include "GonkGPSGeolocationProvider.h"
 #endif
 #include "MediaManager.h"
 
 #include "GMPService.h"
 
+#include "mozilla/dom/presentation/PresentationDeviceManager.h"
+
 using namespace mozilla;
 using namespace mozilla::dom;
 using mozilla::dom::alarm::AlarmHalService;
 using mozilla::dom::power::PowerManagerService;
 using mozilla::dom::quota::QuotaManager;
 using mozilla::dom::workers::ServiceWorkerManager;
 using mozilla::dom::workers::WorkerDebuggerManager;
 using mozilla::dom::TCPSocketChild;
@@ -266,16 +268,21 @@ using mozilla::gmp::GeckoMediaPluginServ
 // Transformiix
 /* 5d5d92cd-6bf8-11d9-bf4a-000a95dc234c */
 #define TRANSFORMIIX_NODESET_CID \
 { 0x5d5d92cd, 0x6bf8, 0x11d9, { 0xbf, 0x4a, 0x0, 0x0a, 0x95, 0xdc, 0x23, 0x4c } }
 
 #define TRANSFORMIIX_NODESET_CONTRACTID \
 "@mozilla.org/transformiix-nodeset;1"
 
+// PresentationDeviceManager
+/* e1e79dec-4085-4994-ac5b-744b016697e6 */
+#define PRESENTATION_DEVICE_MANAGER_CID \
+{ 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } }
+
 // Factory Constructor
 NS_GENERIC_FACTORY_CONSTRUCTOR(txMozillaXSLTProcessor)
 NS_GENERIC_FACTORY_CONSTRUCTOR(XPathEvaluator)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(txNodeSetAdaptor, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsDOMSerializer)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsXMLHttpRequest, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDOMFileReader, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFormData)
@@ -364,16 +371,17 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR
                                          NS_CreateTelephonyService)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIVoicemailService,
                                          NS_CreateVoicemailService)
 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeTVService,
                                          TVServiceFactory::CreateFakeTVService)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVTunerData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVChannelData)
 NS_GENERIC_FACTORY_CONSTRUCTOR(TVProgramData)
+NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationDeviceManager)
 
 //-----------------------------------------------------------------------------
 
 static bool gInitialized = false;
 
 // Perform our one-time intialization for this module
 
 // static
@@ -809,16 +817,18 @@ NS_DEFINE_NAMED_CID(FAKE_TV_SERVICE_CID)
 NS_DEFINE_NAMED_CID(TV_TUNER_DATA_CID);
 NS_DEFINE_NAMED_CID(TV_CHANNEL_DATA_CID);
 NS_DEFINE_NAMED_CID(TV_PROGRAM_DATA_CID);
 
 NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID);
 
 NS_DEFINE_NAMED_CID(NS_MOBILE_CONNECTION_SERVICE_CID);
 
+NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID);
+
 static nsresult
 CreateWindowCommandTableConstructor(nsISupports *aOuter,
                                     REFNSIID aIID, void **aResult)
 {
   nsresult rv;
   nsCOMPtr<nsIControllerCommandTable> commandTable =
       do_CreateInstance(NS_CONTROLLERCOMMANDTABLE_CONTRACTID, &rv);
   if (NS_FAILED(rv)) return rv;
@@ -1097,16 +1107,17 @@ static const mozilla::Module::CIDEntry k
 #endif
   { &kTELEPHONY_SERVICE_CID, false, nullptr, nsITelephonyServiceConstructor },
   { &kNS_MOBILE_CONNECTION_SERVICE_CID, false, NULL, nsIMobileConnectionServiceConstructor },
   { &kNS_VOICEMAIL_SERVICE_CID, false, nullptr, nsIVoicemailServiceConstructor },
   { &kFAKE_TV_SERVICE_CID, false, nullptr, FakeTVServiceConstructor },
   { &kTV_TUNER_DATA_CID, false, nullptr, TVTunerDataConstructor },
   { &kTV_CHANNEL_DATA_CID, false, nullptr, TVChannelDataConstructor },
   { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor },
+  { &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor },
   { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kLayoutContracts[] = {
   XPCONNECT_CONTRACTS
   { "@mozilla.org/layout/xul-boxobject;1", &kNS_BOXOBJECT_CID },
 #ifdef MOZ_XUL
   { "@mozilla.org/layout/xul-boxobject-listbox;1", &kNS_LISTBOXOBJECT_CID },
@@ -1256,16 +1267,17 @@ static const mozilla::Module::ContractID
   { TELEPHONY_SERVICE_CONTRACTID, &kTELEPHONY_SERVICE_CID },
   { FAKE_TV_SERVICE_CONTRACTID, &kFAKE_TV_SERVICE_CID },
   { TV_TUNER_DATA_CONTRACTID, &kTV_TUNER_DATA_CID },
   { TV_CHANNEL_DATA_CONTRACTID, &kTV_CHANNEL_DATA_CID },
   { TV_PROGRAM_DATA_CONTRACTID, &kTV_PROGRAM_DATA_CID },
   { "@mozilla.org/gecko-media-plugin-service;1",  &kGECKO_MEDIA_PLUGIN_SERVICE_CID },
   { NS_MOBILE_CONNECTION_SERVICE_CONTRACTID, &kNS_MOBILE_CONNECTION_SERVICE_CID },
   { NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID },
+  { PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kLayoutCategories[] = {
   XPCONNECT_CATEGORIES
   { "content-policy", NS_DATADOCUMENTCONTENTPOLICY_CONTRACTID, NS_DATADOCUMENTCONTENTPOLICY_CONTRACTID },
   { "content-policy", NS_NODATAPROTOCOLCONTENTPOLICY_CONTRACTID, NS_NODATAPROTOCOLCONTENTPOLICY_CONTRACTID },
   { "content-policy", "CSPService", CSPSERVICE_CONTRACTID },
@@ -1279,16 +1291,17 @@ static const mozilla::Module::CategoryEn
 #endif
   CONTENTDLF_CATEGORIES
 #ifdef MOZ_WIDGET_GONK
   { "profile-after-change", "Gonk System Worker Manager", SYSTEMWORKERMANAGER_CONTRACTID },
 #endif
 #ifdef MOZ_B2G_BT
   { "profile-after-change", "Bluetooth Service", BLUETOOTHSERVICE_CONTRACTID },
 #endif
+  { "profile-after-change", "PresentationDeviceManager", PRESENTATION_DEVICE_MANAGER_CONTRACTID },
   { nullptr }
 };
 
 static void
 LayoutModuleDtor()
 {
   Shutdown();
   nsContentUtils::XPCOMShutdown();
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -170,16 +170,17 @@
 @BINPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @BINPATH@/components/dom_xbl.xpt
 @BINPATH@/components/dom_xpath.xpt
 @BINPATH@/components/dom_xul.xpt
 #ifdef MOZ_GAMEPAD
 @BINPATH@/components/dom_gamepad.xpt
 #endif
+@BINPATH@/components/dom_presentation.xpt
 @BINPATH@/components/downloads.xpt
 @BINPATH@/components/editor.xpt
 @BINPATH@/components/embed_base.xpt
 @BINPATH@/components/extensions.xpt
 @BINPATH@/components/exthandler.xpt
 @BINPATH@/components/exthelper.xpt
 @BINPATH@/components/fastfind.xpt
 @BINPATH@/components/feeds.xpt