Bug 1180596 - Part 2 - make customizable settings of Presentation API. r=schien
authorLiang-Heng Chen <xeonchen@mozilla.com>
Thu, 30 Jul 2015 18:40:00 +0200
changeset 287309 be21dfb89dc7896dc55c01902745758dcdbb1f76
parent 287308 b18caf96c537efee8271b2e05bf94280c912238c
child 287310 135ffa347689f982bedee5acf92049a5820a7a62
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)
reviewersschien
bugs1180596
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 1180596 - Part 2 - make customizable settings of Presentation API. r=schien
b2g/app/b2g.js
b2g/chrome/content/settings.js
dom/presentation/PresentationDeviceManager.cpp
dom/presentation/PresentationDeviceManager.h
dom/presentation/provider/MulticastDNSDeviceProvider.cpp
dom/presentation/provider/MulticastDNSDeviceProvider.h
dom/presentation/provider/PresentationDeviceProviderModule.cpp
dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
modules/libpref/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1160,8 +1160,11 @@ pref("dom.serviceWorkers.enabled", false
 // Retain at most 10 processes' layers buffers
 pref("layers.compositor-lru-size", 10);
 
 // Enable Cardboard VR on mobile, assuming VR at all is enabled
 pref("dom.vr.cardboard.enabled", true);
 
 // In B2G by deafult any AudioChannelAgent is muted when created.
 pref("dom.audiochannel.mutedByDefault", true);
+
+// Default device name for Presentation API
+pref("dom.presentation.device.name", "Firefox OS");
--- a/b2g/chrome/content/settings.js
+++ b/b2g/chrome/content/settings.js
@@ -558,22 +558,30 @@ let settingsToObserve = {
   'debug.log-animations.enabled': {
     prefName: 'layers.offmainthreadcomposition.log-animations',
     defaultValue: false
   },
   'debug.paint-flashing.enabled': {
     prefName: 'nglayout.debug.paint_flashing',
     defaultValue: false
   },
+  // FIXME: Bug 1185806 - Provide a common device name setting.
+  // Borrow device name from developer's menu to avoid multiple name settings.
+  'devtools.discovery.device': {
+    prefName: 'dom.presentation.device.name',
+    defaultValue: 'Firefox OS'
+  },
   'devtools.eventlooplag.threshold': 100,
   'devtools.remote.wifi.visible': {
     resetToPref: true
   },
   'dom.mozApps.use_reviewer_certs': false,
   'dom.mozApps.signed_apps_installable_from': 'https://marketplace.firefox.com',
+  'dom.presentation.discovery.enabled': true,
+  'dom.presentation.discoverable': false,
   'dom.serviceWorkers.interception.enabled': true,
   'dom.serviceWorkers.testing.enabled': false,
   'gfx.layerscope.enabled': false,
   'layers.draw-borders': false,
   'layers.draw-tile-borders': false,
   'layers.dump': false,
   'layers.enable-tiles': true,
   'layers.effect.invert': false,
--- a/dom/presentation/PresentationDeviceManager.cpp
+++ b/dom/presentation/PresentationDeviceManager.cpp
@@ -31,16 +31,38 @@ PresentationDeviceManager::PresentationD
 
 PresentationDeviceManager::~PresentationDeviceManager()
 {
   UnloadDeviceProviders();
   mDevices.Clear();
 }
 
 void
+PresentationDeviceManager::Init()
+{
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  }
+
+  LoadDeviceProviders();
+}
+
+void
+PresentationDeviceManager::Shutdown()
+{
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+  }
+
+  UnloadDeviceProviders();
+}
+
+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) {
@@ -218,16 +240,18 @@ PresentationDeviceManager::OnSessionRequ
 
 // nsIObserver
 NS_IMETHODIMP
 PresentationDeviceManager::Observe(nsISupports *aSubject,
                                    const char *aTopic,
                                    const char16_t *aData)
 {
   if (!strcmp(aTopic, "profile-after-change")) {
-    LoadDeviceProviders();
+    Init();
+  } else if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    Shutdown();
   }
 
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/PresentationDeviceManager.h
+++ b/dom/presentation/PresentationDeviceManager.h
@@ -30,16 +30,20 @@ public:
   NS_DECL_NSIPRESENTATIONDEVICEEVENTLISTENER
   NS_DECL_NSIOBSERVER
 
   PresentationDeviceManager();
 
 private:
   virtual ~PresentationDeviceManager();
 
+  void Init();
+
+  void Shutdown();
+
   void LoadDeviceProviders();
 
   void UnloadDeviceProviders();
 
   void NotifyDeviceChange(nsIPresentationDevice* aDevice,
                           const char16_t* aType);
 
   nsCOMArray<nsIPresentationDeviceProvider> mProviders;
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -1,37 +1,54 @@
 /* -*- 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 "MulticastDNSDeviceProvider.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
 #include "nsAutoPtr.h"
 #include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
 #include "nsIPresentationDevice.h"
 #include "nsServiceManagerUtils.h"
 
+#define PREF_PRESENTATION_DISCOVERY "dom.presentation.discovery.enabled"
+#define PREF_PRESENTATION_DISCOVERABLE "dom.presentation.discoverable"
+#define PREF_PRESENTATION_DEVICE_NAME "dom.presentation.device.name"
+
+#define TCP_PRESENTATION_SERVER_CONTACT_ID \
+  "@mozilla.org/presentation-device/tcp-presentation-server;1"
+
+#define SERVICE_TYPE "_mozilla_papi._tcp."
+
 inline static PRLogModuleInfo*
 GetProviderLog()
 {
   static PRLogModuleInfo* log = PR_NewLogModule("MulticastDNSDeviceProvider");
   return log;
 }
 #undef LOG_I
 #define LOG_I(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
 #undef LOG_E
 #define LOG_E(...) MOZ_LOG(GetProviderLog(), mozilla::LogLevel::Error, (__VA_ARGS__))
 
-#define SERVICE_TYPE "_mozilla_papi._tcp."
-
 namespace mozilla {
 namespace dom {
 namespace presentation {
 
+static const char* kObservedPrefs[] = {
+  PREF_PRESENTATION_DISCOVERY,
+  PREF_PRESENTATION_DISCOVERABLE,
+  PREF_PRESENTATION_DEVICE_NAME,
+  nullptr
+};
+
 /**
  * This wrapper is used to break circular-reference problem.
  */
 class DNSServiceWrappedListener final
   : public nsIDNSServiceDiscoveryListener
   , public nsIDNSRegistrationListener
   , public nsIDNSServiceResolveListener
   , public nsITCPPresentationServerListener
@@ -63,17 +80,18 @@ NS_IMPL_ISUPPORTS(DNSServiceWrappedListe
                   nsIDNSServiceResolveListener,
                   nsITCPPresentationServerListener)
 
 NS_IMPL_ISUPPORTS(MulticastDNSDeviceProvider,
                   nsIPresentationDeviceProvider,
                   nsIDNSServiceDiscoveryListener,
                   nsIDNSRegistrationListener,
                   nsIDNSServiceResolveListener,
-                  nsITCPPresentationServerListener)
+                  nsITCPPresentationServerListener,
+                  nsIObserver)
 
 MulticastDNSDeviceProvider::~MulticastDNSDeviceProvider()
 {
   Uninit();
 }
 
 nsresult
 MulticastDNSDeviceProvider::Init()
@@ -92,94 +110,134 @@ MulticastDNSDeviceProvider::Init()
   mWrappedListener = new DNSServiceWrappedListener();
   if (NS_WARN_IF(!mWrappedListener)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (NS_WARN_IF(NS_FAILED(rv = mWrappedListener->SetListener(this)))) {
     return rv;
   }
 
-  mPresentationServer = do_CreateInstance("@mozilla.org/presentation-device/tcp-presentation-server;1", &rv);
+  mPresentationServer = do_CreateInstance(TCP_PRESENTATION_SERVER_CONTACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  if (NS_WARN_IF(NS_FAILED(mPresentationServer->SetListener(mWrappedListener)))) {
-    return rv;
-  }
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
+
+  Preferences::AddStrongObservers(this, kObservedPrefs);
+
+  mDiscoveryEnabled = Preferences::GetBool(PREF_PRESENTATION_DISCOVERY);
+  mDiscoverable = Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE);
+  mServiceName = Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME);
+
+  if (mDiscoveryEnabled && NS_WARN_IF(NS_FAILED(rv = ForceDiscovery()))) {
     return rv;
   }
 
-  uint16_t port = 0;
-  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&port)))) {
-    return rv;
-  }
-
-  if (NS_WARN_IF(NS_FAILED(rv = RegisterService(port)))) {
+  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
     return rv;
   }
 
   mInitialized = true;
   return NS_OK;
 }
 
 nsresult
 MulticastDNSDeviceProvider::Uninit()
 {
   if (!mInitialized) {
     return NS_OK;
   }
 
-  if (mPresentationServer) {
-    mPresentationServer->Close();
-    mPresentationServer = nullptr;
-  }
+  Preferences::RemoveObservers(this, kObservedPrefs);
 
-  if (mDiscoveryRequest) {
-    mDiscoveryRequest->Cancel(NS_OK);
-    mDiscoveryRequest = nullptr;
-  }
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(NS_OK);
-    mRegisterRequest = nullptr;
-  }
+  StopDiscovery(NS_OK);
+  UnregisterService(NS_OK);
+
   mMulticastDNS = nullptr;
 
   if (mWrappedListener) {
     mWrappedListener->SetListener(nullptr);
     mWrappedListener = nullptr;
   }
 
   mInitialized = false;
   return NS_OK;
 }
 
 nsresult
-MulticastDNSDeviceProvider::RegisterService(uint32_t aPort)
+MulticastDNSDeviceProvider::RegisterService()
 {
-  LOG_I("RegisterService: %d", aPort);
+  LOG_I("RegisterService: %s (%d)", mServiceName.get(), mDiscoverable);
+
+  if (!mDiscoverable) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(!mRegisterRequest);
 
   nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetListener(mWrappedListener)))) {
+    return rv;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
+    return rv;
+  }
+  uint16_t servicePort;
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&servicePort)))) {
+    return rv;
+  }
 
-  nsCOMPtr<nsIDNSServiceInfo> serviceInfo = do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
+  /**
+   * Register the presentation control channel server as an mDNS service.
+   */
+  nsCOMPtr<nsIDNSServiceInfo> serviceInfo =
+    do_CreateInstance(DNSSERVICEINFO_CONTRACT_ID, &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType(NS_LITERAL_CSTRING(SERVICE_TYPE))))) {
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceType(
+      NS_LITERAL_CSTRING(SERVICE_TYPE))))) {
     return rv;
   }
-  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(aPort)))) {
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetServiceName(mServiceName)))) {
+    return rv;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(servicePort)))) {
     return rv;
   }
 
+  return mMulticastDNS->RegisterService(serviceInfo,
+                                        mWrappedListener,
+                                        getter_AddRefs(mRegisterRequest));
+}
+
+nsresult
+MulticastDNSDeviceProvider::UnregisterService(nsresult aReason)
+{
   if (mRegisterRequest) {
-    mRegisterRequest->Cancel(NS_OK);
+    mRegisterRequest->Cancel(aReason);
     mRegisterRequest = nullptr;
   }
-  return mMulticastDNS->RegisterService(serviceInfo, mWrappedListener, getter_AddRefs(mRegisterRequest));
+
+  if (mPresentationServer) {
+    mPresentationServer->SetListener(nullptr);
+    mPresentationServer->Close();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::StopDiscovery(nsresult aReason)
+{
+  if (mDiscoveryRequest) {
+    mDiscoveryRequest->Cancel(aReason);
+    mDiscoveryRequest = nullptr;
+  }
+
+  return NS_OK;
 }
 
 // nsIPresentationDeviceProvider
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::GetListener(nsIPresentationDeviceListener** aListener)
 {
   if (NS_WARN_IF(!aListener)) {
     return NS_ERROR_INVALID_POINTER;
@@ -208,26 +266,27 @@ MulticastDNSDeviceProvider::SetListener(
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::ForceDiscovery()
 {
-  LOG_I("ForceDiscovery");
-  MOZ_ASSERT(mInitialized);
+  LOG_I("ForceDiscovery (%d)", mDiscoveryEnabled);
+
+  if (!mDiscoveryEnabled) {
+    return NS_OK;
+  }
+
   MOZ_ASSERT(mMulticastDNS);
 
+  StopDiscovery(NS_OK);
+
   nsresult rv;
-
-  if (mDiscoveryRequest) {
-    mDiscoveryRequest->Cancel(NS_OK);
-    mDiscoveryRequest = nullptr;
-  }
   if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->StartDiscovery(
       NS_LITERAL_CSTRING(SERVICE_TYPE),
       mWrappedListener,
       getter_AddRefs(mDiscoveryRequest))))) {
     return rv;
   }
 
   return NS_OK;
@@ -272,17 +331,18 @@ MulticastDNSDeviceProvider::OnServiceFou
   nsCOMPtr<nsIPresentationDevice> device;
   if (NS_SUCCEEDED(mPresentationServer->GetTCPDevice(serviceName,
                                                      getter_AddRefs(device)))) {
     LOG_I("device exists");
     return NS_OK;
   }
 
   if (mMulticastDNS) {
-    if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(aServiceInfo, mWrappedListener)))) {
+    if (NS_WARN_IF(NS_FAILED(rv = mMulticastDNS->ResolveService(
+        aServiceInfo, mWrappedListener)))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -297,40 +357,43 @@ MulticastDNSDeviceProvider::OnServiceLos
   nsAutoCString serviceName;
   if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(serviceName)))) {
     return rv;
   }
 
   LOG_I("OnServiceLost: %s", serviceName.get());
 
   nsCOMPtr<nsIPresentationDevice> device;
-  if (NS_FAILED(mPresentationServer->GetTCPDevice(serviceName, getter_AddRefs(device)))) {
+  if (NS_FAILED(mPresentationServer->GetTCPDevice(serviceName,
+                                                  getter_AddRefs(device)))) {
     return NS_OK; // ignore non-existing device;
   }
 
   NS_WARN_IF(NS_FAILED(mPresentationServer->RemoveTCPDevice(serviceName)));
 
   nsCOMPtr<nsIPresentationDeviceListener> listener;
   GetListener(getter_AddRefs(listener));
   if (listener) {
     listener->RemoveDevice(device);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnStartDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnStartDiscoveryFailed(const nsACString& aServiceType,
+                                                   int32_t aErrorCode)
 {
   LOG_E("OnStartDiscoveryFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType,
+                                                  int32_t aErrorCode)
 {
   LOG_E("OnStopDiscoveryFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 // nsIDNSRegistrationListener
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo)
@@ -358,38 +421,37 @@ MulticastDNSDeviceProvider::OnServiceReg
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
 {
   LOG_I("OnServiceUnregistered");
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo,
+                                                 int32_t aErrorCode)
 {
   LOG_E("OnRegistrationFailed: %d", aErrorCode);
 
+  mRegisterRequest = nullptr;
+
   nsresult rv;
 
   if (aErrorCode == nsIDNSRegistrationListener::ERROR_SERVICE_NOT_RUNNING) {
-    uint16_t port = 0;
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&port)))) {
-      return rv;
-    }
-
-    if (NS_WARN_IF(NS_FAILED(rv = RegisterService(port)))) {
+    if (NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo,
+                                                   int32_t aErrorCode)
 {
   LOG_E("OnUnregistrationFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 // nsIDNSServiceResolveListener
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
@@ -446,48 +508,108 @@ MulticastDNSDeviceProvider::OnServiceRes
   if (listener) {
     listener->AddDevice(device);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+MulticastDNSDeviceProvider::OnResolveFailed(nsIDNSServiceInfo* aServiceInfo,
+                                            int32_t aErrorCode)
 {
   LOG_E("OnResolveFailed: %d", aErrorCode);
   return NS_OK;
 }
 
 // nsITCPPresentationServerListener
 NS_IMETHODIMP
 MulticastDNSDeviceProvider::OnClose(nsresult aReason)
 {
   LOG_I("OnClose: %x", aReason);
 
-  if (mRegisterRequest) {
-    mRegisterRequest->Cancel(aReason);
-    mRegisterRequest = nullptr;
-  }
+  UnregisterService(aReason);
 
   nsresult rv;
 
-  if (NS_FAILED(aReason)) {
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
-      return rv;
+  if (mDiscoveryEnabled && NS_WARN_IF(NS_FAILED(rv = ForceDiscovery()))) {
+    return rv;
+  }
+
+  if (mDiscoverable && NS_WARN_IF(NS_FAILED(rv = RegisterService()))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+// nsIObserver
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::Observe(nsISupports* aSubject,
+                                    const char* aTopic,
+                                    const char16_t* aData)
+{
+  NS_ConvertUTF16toUTF8 data(aData);
+  LOG_I("Observe: topic = %s, data = %s", aTopic, data.get());
+
+  if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
+    if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERY)) {
+      OnDiscoveryChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERY));
+    } else if (data.EqualsLiteral(PREF_PRESENTATION_DISCOVERABLE)) {
+      OnDiscoverableChanged(Preferences::GetBool(PREF_PRESENTATION_DISCOVERABLE));
+    } else if (data.EqualsLiteral(PREF_PRESENTATION_DEVICE_NAME)) {
+      OnServiceNameChanged(Preferences::GetCString(PREF_PRESENTATION_DEVICE_NAME));
     }
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::OnDiscoveryChanged(bool aEnabled)
+{
+  LOG_I("DiscoveryEnabled = %d\n", aEnabled);
 
-    uint16_t port = 0;
-    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->GetPort(&port)))) {
-      return rv;
-    }
+  mDiscoveryEnabled = aEnabled;
+
+  if (mDiscoveryEnabled) {
+    return ForceDiscovery();
+  }
+
+  return StopDiscovery(NS_OK);
+}
+
+nsresult
+MulticastDNSDeviceProvider::OnDiscoverableChanged(bool aEnabled)
+{
+  LOG_I("Discoverable = %d\n", aEnabled);
+
+  mDiscoverable = aEnabled;
 
-    if (NS_WARN_IF(NS_FAILED(rv = RegisterService(port)))) {
-      return rv;
-    }
+  if (mDiscoverable) {
+    return RegisterService();
+  }
+
+  return UnregisterService(NS_OK);
+}
+
+nsresult
+MulticastDNSDeviceProvider::OnServiceNameChanged(const nsCString& aServiceName)
+{
+  LOG_I("serviceName = %s\n", aServiceName.get());
+
+  mServiceName = aServiceName;
+
+  nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(NS_OK)))) {
+    return rv;
+  }
+
+  if (mDiscoverable) {
+    return RegisterService();
   }
 
   return NS_OK;
 }
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/provider/MulticastDNSDeviceProvider.h
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
 #define mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
 
 #include "nsCOMPtr.h"
 #include "nsICancelable.h"
 #include "nsIDNSServiceDiscovery.h"
+#include "nsIObserver.h"
 #include "nsIPresentationDeviceProvider.h"
 #include "nsITCPPresentationServer.h"
 #include "mozilla/nsRefPtr.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
 
 namespace mozilla {
 namespace dom {
@@ -23,42 +24,53 @@ class DNSServiceWrappedListener;
 class MulticastDNSService;
 
 class MulticastDNSDeviceProvider final
   : public nsIPresentationDeviceProvider
   , public nsIDNSServiceDiscoveryListener
   , public nsIDNSRegistrationListener
   , public nsIDNSServiceResolveListener
   , public nsITCPPresentationServerListener
+  , public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONDEVICEPROVIDER
   NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER
   NS_DECL_NSIDNSREGISTRATIONLISTENER
   NS_DECL_NSIDNSSERVICERESOLVELISTENER
   NS_DECL_NSITCPPRESENTATIONSERVERLISTENER
+  NS_DECL_NSIOBSERVER
 
   explicit MulticastDNSDeviceProvider() = default;
   nsresult Init();
   nsresult Uninit();
 
 private:
   virtual ~MulticastDNSDeviceProvider();
-  nsresult RegisterService(uint32_t aPort);
+  nsresult RegisterService();
+  nsresult UnregisterService(nsresult aReason);
+  nsresult StopDiscovery(nsresult aReason);
+
+  nsresult OnDiscoveryChanged(bool aEnabled);
+  nsresult OnDiscoverableChanged(bool aEnabled);
+  nsresult OnServiceNameChanged(const nsCString& aServiceName);
 
   bool mInitialized = false;
   nsWeakPtr mDeviceListener;
   nsCOMPtr<nsITCPPresentationServer> mPresentationServer;
   nsCOMPtr<nsIDNSServiceDiscovery> mMulticastDNS;
   nsRefPtr<DNSServiceWrappedListener> mWrappedListener;
 
   nsCOMPtr<nsICancelable> mDiscoveryRequest;
   nsCOMPtr<nsICancelable> mRegisterRequest;
 
+  bool mDiscoveryEnabled = false;
+  bool mDiscoverable = false;
+  nsCString mServiceName;
   nsCString mRegisteredName;
 };
 
 } // namespace presentation
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
--- a/dom/presentation/provider/PresentationDeviceProviderModule.cpp
+++ b/dom/presentation/provider/PresentationDeviceProviderModule.cpp
@@ -23,17 +23,17 @@ static const mozilla::Module::CIDEntry k
 };
 
 static const mozilla::Module::ContractIDEntry kPresentationDeviceProviderContracts[] = {
   { MULTICAST_DNS_PROVIDER_CONTRACT_ID, &kMULTICAST_DNS_PROVIDER_CID },
   { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kPresentationDeviceProviderCategories[] = {
-#if defined(MOZ_WIDGET_ANDROID) // || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
+#if defined(MOZ_WIDGET_ANDROID) || (defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16)
   { PRESENTATION_DEVICE_PROVIDER_CATEGORY, "MulticastDNSDeviceProvider", MULTICAST_DNS_PROVIDER_CONTRACT_ID },
 #endif
   { nullptr }
 };
 
 static const mozilla::Module kPresentationDeviceProviderModule = {
   mozilla::Module::kVersion,
   kPresentationDeviceProviderCIDs,
--- a/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
+++ b/dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
@@ -1,23 +1,27 @@
 /* -*- 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, manager: Cm, results: Cr, utils: Cu } = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const INFO_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1";
 const PROVIDER_CONTRACT_ID = "@mozilla.org/presentation-device/multicastdns-provider;1";
 const SD_CONTRACT_ID = "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1";
 const UUID_CONTRACT_ID = "@mozilla.org/uuid-generator;1";
 
+const PREF_DISCOVERY = "dom.presentation.discovery.enabled";
+const PREF_DISCOVERABLE = "dom.presentation.discoverable";
+
 let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
 
 function MockFactory(aClass) {
   this._cls = aClass;
 }
 MockFactory.prototype = {
   createInstance: function(aOuter, aIID) {
     if (aOuter) {
@@ -53,31 +57,37 @@ function ContractHook(aContractID, aClas
 ContractHook.prototype = {
   hookedMap: new Map(), // remember only the most original factory.
 
   init: function() {
     this.reset();
 
     let oldContract = this.unregister();
     this.hookedMap.get(this._contractID).push(oldContract);
-    registrar.registerFactory(this.classID, "", this._contractID, this._newFactory);
+    registrar.registerFactory(this.classID,
+                              "",
+                              this._contractID,
+                              this._newFactory);
 
     do_register_cleanup(() => { this.cleanup.apply(this); });
   },
 
   reset: function() {},
 
   cleanup: function() {
     this.reset();
 
     this.unregister();
     let prevContract = this.hookedMap.get(this._contractID).pop();
 
     if (prevContract.factory) {
-      registrar.registerFactory(prevContract.classID, "", this._contractID, prevContract.factory);
+      registrar.registerFactory(prevContract.classID,
+                                "",
+                                this._contractID,
+                                prevContract.factory);
     }
   },
 
   unregister: function() {
     var classID, factory;
 
     try {
       classID = registrar.contractIDToCID(this._contractID);
@@ -164,92 +174,375 @@ function createDevice(host, port, servic
   device.serviceName = serviceName || "";
   device.serviceType = serviceType || "";
   device.domainName = domainName || "";
   device.attributes = attributes || null;
   return device;
 }
 
 function registerService() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {},
+    registerService: function(serviceInfo, listener) {
+      this.serviceRegistered++;
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {
+          this.serviceUnregistered++;
+        }.bind(this)
+      };
+    },
+    resolveService: function(serviceInfo, listener) {},
+    serviceRegistered: 0,
+    serviceUnregistered: 0
+  };
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+
+  Assert.equal(mockObj.serviceRegistered, 0);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Register
+  provider.listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+  };
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Unregister
+  provider.listener = null;
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 1);
+
+  run_next_test();
+}
+
+function noRegisterService() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {},
+    registerService: function(serviceInfo, listener) {
+      Assert.ok(false, "should not register service if not discoverable");
+    },
+    resolveService: function(serviceInfo, listener) {},
+  };
+
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+
+  // Try register
+  provider.listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+  };
+  provider.listener = null;
+
+  run_next_test();
+}
+
+function registerServiceDynamically() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {},
     registerService: function(serviceInfo, listener) {
       this.serviceRegistered++;
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {
           this.serviceUnregistered++;
         }.bind(this)
-      }
+      };
     },
     resolveService: function(serviceInfo, listener) {},
     serviceRegistered: 0,
     serviceUnregistered: 0
   };
   let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
 
   Assert.equal(mockObj.serviceRegistered, 0);
-  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   Assert.equal(mockObj.serviceRegistered, 0);
+
+  // Try Register
   provider.listener = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, Ci.nsISupportsWeakReference]),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
     addDevice: function(device) {},
     removeDevice: function(device) {},
     updateDevice: function(device) {},
   };
+  Assert.equal(mockObj.serviceRegistered, 0);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Enable registration
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
   Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 0);
 
-  Assert.equal(mockObj.serviceUnregistered, 0);
+  // Disable registration
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, false);
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 1);
+
+  // Try unregister
   provider.listener = null;
+  Assert.equal(mockObj.serviceRegistered, 1);
   Assert.equal(mockObj.serviceUnregistered, 1);
 
   run_next_test();
 }
 
 function addDevice() {
-  let mockDevice = createDevice("device.local", 12345, "service.name", "_mozilla_papi._tcp");
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
   let mockObj = {
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
     startDiscovery: function(serviceType, listener) {
       listener.onDiscoveryStarted(serviceType);
-      listener.onServiceFound(createDevice("", 0, mockDevice.serviceName, mockDevice.serviceType));
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
       return {
         QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
         cancel: function() {}
-      }
+      };
     },
     registerService: function(serviceInfo, listener) {},
     resolveService: function(serviceInfo, listener) {
       Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
       Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
-      listener.onServiceResolved(createDevice(mockDevice.host, mockDevice.port, mockDevice.serviceName, mockDevice.serviceType));
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) { this.devices.push(device); },
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+    devices: []
+  };
+  Assert.equal(listener.devices.length, 0);
+
+  // Start discovery
+  provider.listener = listener;
+  Assert.equal(listener.devices.length, 1);
+
+  // Force discovery again
+  provider.forceDiscovery();
+  Assert.equal(listener.devices.length, 1);
+
+  provider.listener = null;
+  Assert.equal(listener.devices.length, 1);
+
+  run_next_test();
+}
+
+function noAddDevice() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+
+  let mockDevice = createDevice("device.local", 12345, "service.name", "_mozilla_papi._tcp");
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      Assert.ok(false, "shouldn't perform any device discovery");
+    },
+    registerService: function(serviceInfo, listener) {},
+    resolveService: function(serviceInfo, listener) {
     }
   };
   let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
 
   let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
   let listener = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener, Ci.nsISupportsWeakReference]),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) {},
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+  };
+  provider.listener = listener;
+  provider.forceDiscovery();
+  provider.listener = null;
+
+  run_next_test();
+}
+
+function addDeviceDynamically() {
+  Services.prefs.setBoolPref(PREF_DISCOVERY, false);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {},
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    }
+  };
+
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  let listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
     addDevice: function(device) { this.devices.push(device); },
     removeDevice: function(device) {},
     updateDevice: function(device) {},
     devices: []
   };
   provider.listener = listener;
+  Assert.equal(listener.devices.length, 0);
 
-  Assert.equal(listener.devices.length, 0);
+  // Enable discovery
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+  Assert.equal(listener.devices.length, 1);
+
+  // Try discovery again
   provider.forceDiscovery();
   Assert.equal(listener.devices.length, 1);
 
   provider.listener = null;
 
   run_next_test();
 }
 
+function serverClosed() {
+  Services.prefs.setBoolPref(PREF_DISCOVERABLE, true);
+  Services.prefs.setBoolPref(PREF_DISCOVERY, true);
+
+  let mockDevice = createDevice("device.local",
+                                12345,
+                                "service.name",
+                                "_mozilla_papi._tcp");
+
+  let mockObj = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceDiscovery]),
+    startDiscovery: function(serviceType, listener) {
+      listener.onDiscoveryStarted(serviceType);
+      listener.onServiceFound(createDevice("",
+                                           0,
+                                           mockDevice.serviceName,
+                                           mockDevice.serviceType));
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {}
+      };
+    },
+    registerService: function(serviceInfo, listener) {
+      this.serviceRegistered++;
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
+        cancel: function() {
+          this.serviceUnregistered++;
+        }.bind(this)
+      };
+    },
+    resolveService: function(serviceInfo, listener) {
+      Assert.equal(serviceInfo.serviceName, mockDevice.serviceName);
+      Assert.equal(serviceInfo.serviceType, mockDevice.serviceType);
+      listener.onServiceResolved(createDevice(mockDevice.host,
+                                              mockDevice.port,
+                                              mockDevice.serviceName,
+                                              mockDevice.serviceType));
+    },
+    serviceRegistered: 0,
+    serviceUnregistered: 0
+  };
+  let contractHook = new ContractHook(SD_CONTRACT_ID, mockObj);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+
+  Assert.equal(mockObj.serviceRegistered, 0);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+
+  // Register
+  let listener = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener,
+                                           Ci.nsISupportsWeakReference]),
+    addDevice: function(device) { this.devices.push(device); },
+    removeDevice: function(device) {},
+    updateDevice: function(device) {},
+    devices: []
+  };
+  Assert.equal(listener.devices.length, 0);
+
+  provider.listener = listener;
+  Assert.equal(mockObj.serviceRegistered, 1);
+  Assert.equal(mockObj.serviceUnregistered, 0);
+  Assert.equal(listener.devices.length, 1);
+
+  let serverListener = provider.QueryInterface(Ci.nsITCPPresentationServerListener);
+  serverListener.onClose(Cr.NS_ERROR_UNEXPECTED);
+
+  Assert.equal(mockObj.serviceRegistered, 2);
+  Assert.equal(mockObj.serviceUnregistered, 1);
+  Assert.equal(listener.devices.length, 2);
+
+  // Unregister
+  provider.listener = null;
+  Assert.equal(mockObj.serviceRegistered, 2);
+  Assert.equal(mockObj.serviceUnregistered, 2);
+  Assert.equal(listener.devices.length, 2);
+
+  run_next_test();
+}
+
 function run_test() {
   let infoHook = new ContractHook(INFO_CONTRACT_ID, MockDNSServiceInfo);
 
+  do_register_cleanup(() => {
+    Services.prefs.clearUserPref(PREF_DISCOVERY);
+    Services.prefs.clearUserPref(PREF_DISCOVERABLE);
+  });
+
   add_test(registerService);
+  add_test(noRegisterService);
+  add_test(registerServiceDynamically);
   add_test(addDevice);
+  add_test(noAddDevice);
+  add_test(addDeviceDynamically);
+  add_test(serverClosed);
 
   run_next_test();
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4873,16 +4873,18 @@ pref("dom.udpsocket.enabled", false);
 pref("dom.messageChannel.enabled", true);
 
 // Disable before keyboard events and after keyboard events by default.
 pref("dom.beforeAfterKeyboardEvent.enabled", false);
 
 // Presentation API
 pref("dom.presentation.enabled", false);
 pref("dom.presentation.tcp_server.debug", false);
+pref("dom.presentation.discovery.enabled", true);
+pref("dom.presentation.discoverable", false);
 
 #ifdef XP_MACOSX
 // Use raw ICU instead of CoreServices API in Unicode collation
 pref("intl.collation.mac.use_icu", true);
 
 // Enable NSTextInput protocol for use with IMEs that have not
 // been updated to use the NSTextInputClient protocol.
 pref("intl.ime.nstextinput.enable", false);