Bug 1115480 - Part 2: Implement mDNS device provider. r=schien
authorLiang-Heng Chen <xeonchen@mozilla.com>
Wed, 20 May 2015 23:06:00 -0400
changeset 246924 7ada5429c80deeedcefbd2925355d3f6c80e1bc6
parent 246923 dda118cb0bcc60bcc66ee42f4bae5d5338f46d33
child 246925 ce7b42bbf7173dc2a81a73d9791ef754f6afa750
push id15945
push userryanvm@gmail.com
push dateWed, 03 Jun 2015 12:58:01 +0000
treeherderb2g-inbound@ce7b42bbf717 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersschien
bugs1115480
milestone41.0a1
Bug 1115480 - Part 2: Implement mDNS device provider. r=schien
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/provider/moz.build
dom/presentation/tests/xpcshell/test_multicast_dns_device_provider.js
dom/presentation/tests/xpcshell/xpcshell.ini
--- a/dom/presentation/PresentationDeviceManager.cpp
+++ b/dom/presentation/PresentationDeviceManager.cpp
@@ -17,17 +17,18 @@
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_ISUPPORTS(PresentationDeviceManager,
                   nsIPresentationDeviceManager,
                   nsIPresentationDeviceListener,
                   nsIPresentationDeviceEventListener,
-                  nsIObserver)
+                  nsIObserver,
+                  nsISupportsWeakReference)
 
 PresentationDeviceManager::PresentationDeviceManager()
 {
 }
 
 PresentationDeviceManager::~PresentationDeviceManager()
 {
   UnloadDeviceProviders();
--- a/dom/presentation/PresentationDeviceManager.h
+++ b/dom/presentation/PresentationDeviceManager.h
@@ -7,24 +7,26 @@
 #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"
+#include "nsWeakReference.h"
 
 namespace mozilla {
 namespace dom {
 
 class PresentationDeviceManager final : public nsIPresentationDeviceManager
                                       , public nsIPresentationDeviceListener
                                       , public nsIPresentationDeviceEventListener
                                       , public nsIObserver
+                                      , public nsSupportsWeakReference
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPRESENTATIONDEVICEMANAGER
   NS_DECL_NSIPRESENTATIONDEVICELISTENER
   NS_DECL_NSIPRESENTATIONDEVICEEVENTLISTENER
   NS_DECL_NSIOBSERVER
 
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.cpp
@@ -0,0 +1,493 @@
+/* -*- 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 "nsAutoPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIPresentationDevice.h"
+#include "nsServiceManagerUtils.h"
+
+inline static PRLogModuleInfo*
+GetProviderLog()
+{
+  static PRLogModuleInfo* log = PR_NewLogModule("MulticastDNSDeviceProvider");
+  return log;
+}
+#undef LOG_I
+#define LOG_I(...) PR_LOG(GetProviderLog(), PR_LOG_NOTICE, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) PR_LOG(GetProviderLog(), PR_LOG_ERROR, (__VA_ARGS__))
+
+#define SERVICE_TYPE "_mozilla_papi._tcp."
+
+namespace mozilla {
+namespace dom {
+namespace presentation {
+
+/**
+ * This wrapper is used to break circular-reference problem.
+ */
+class DNSServiceWrappedListener final
+  : public nsIDNSServiceDiscoveryListener
+  , public nsIDNSRegistrationListener
+  , public nsIDNSServiceResolveListener
+  , public nsITCPPresentationServerListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_FORWARD_SAFE_NSIDNSSERVICEDISCOVERYLISTENER(mListener)
+  NS_FORWARD_SAFE_NSIDNSREGISTRATIONLISTENER(mListener)
+  NS_FORWARD_SAFE_NSIDNSSERVICERESOLVELISTENER(mListener)
+  NS_FORWARD_SAFE_NSITCPPRESENTATIONSERVERLISTENER(mListener)
+
+  explicit DNSServiceWrappedListener() = default;
+
+  nsresult SetListener(MulticastDNSDeviceProvider* aListener)
+  {
+    mListener = aListener;
+    return NS_OK;
+  }
+
+private:
+  virtual ~DNSServiceWrappedListener() = default;
+
+  MulticastDNSDeviceProvider* mListener = nullptr;
+};
+
+NS_IMPL_ISUPPORTS(DNSServiceWrappedListener,
+                  nsIDNSServiceDiscoveryListener,
+                  nsIDNSRegistrationListener,
+                  nsIDNSServiceResolveListener,
+                  nsITCPPresentationServerListener)
+
+NS_IMPL_ISUPPORTS(MulticastDNSDeviceProvider,
+                  nsIPresentationDeviceProvider,
+                  nsIDNSServiceDiscoveryListener,
+                  nsIDNSRegistrationListener,
+                  nsIDNSServiceResolveListener,
+                  nsITCPPresentationServerListener)
+
+MulticastDNSDeviceProvider::~MulticastDNSDeviceProvider()
+{
+  Uninit();
+}
+
+nsresult
+MulticastDNSDeviceProvider::Init()
+{
+  if (mInitialized) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  mMulticastDNS = do_GetService(DNSSERVICEDISCOVERY_CONTRACT_ID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  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);
+  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)))) {
+    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)))) {
+    return rv;
+  }
+
+  mInitialized = true;
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::Uninit()
+{
+  if (!mInitialized) {
+    return NS_OK;
+  }
+
+  if (mPresentationServer) {
+    mPresentationServer->Close();
+    mPresentationServer = nullptr;
+  }
+
+  if (mDiscoveryRequest) {
+    mDiscoveryRequest->Cancel(NS_OK);
+    mDiscoveryRequest = nullptr;
+  }
+  if (mRegisterRequest) {
+    mRegisterRequest->Cancel(NS_OK);
+    mRegisterRequest = nullptr;
+  }
+  mMulticastDNS = nullptr;
+
+  if (mWrappedListener) {
+    mWrappedListener->SetListener(nullptr);
+    mWrappedListener = nullptr;
+  }
+
+  mInitialized = false;
+  return NS_OK;
+}
+
+nsresult
+MulticastDNSDeviceProvider::RegisterService(uint32_t aPort)
+{
+  LOG_I("RegisterService: %d", aPort);
+
+  nsresult rv;
+
+  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))))) {
+    return rv;
+  }
+  if (NS_WARN_IF(NS_FAILED(rv = serviceInfo->SetPort(aPort)))) {
+    return rv;
+  }
+
+  if (mRegisterRequest) {
+    mRegisterRequest->Cancel(NS_OK);
+    mRegisterRequest = nullptr;
+  }
+  return mMulticastDNS->RegisterService(serviceInfo, mWrappedListener, getter_AddRefs(mRegisterRequest));
+}
+
+// nsIPresentationDeviceProvider
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::GetListener(nsIPresentationDeviceListener** aListener)
+{
+  if (NS_WARN_IF(!aListener)) {
+    return NS_ERROR_INVALID_POINTER;
+  }
+
+  nsCOMPtr<nsIPresentationDeviceListener> listener = do_QueryReferent(mDeviceListener);
+  listener.forget(aListener);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::SetListener(nsIPresentationDeviceListener* aListener)
+{
+  mDeviceListener = do_GetWeakReference(aListener);
+
+  nsresult rv;
+  if (mDeviceListener) {
+    if (NS_WARN_IF(NS_FAILED(rv = Init()))) {
+      return rv;
+    }
+  } else {
+    if (NS_WARN_IF(NS_FAILED(rv = Uninit()))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::ForceDiscovery()
+{
+  LOG_I("ForceDiscovery");
+  MOZ_ASSERT(mInitialized);
+  MOZ_ASSERT(mMulticastDNS);
+
+  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;
+}
+
+// nsIDNSServiceDiscoveryListener
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnDiscoveryStarted(const nsACString& aServiceType)
+{
+  LOG_I("OnDiscoveryStarted");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnDiscoveryStopped(const nsACString& aServiceType)
+{
+  LOG_I("OnDiscoveryStopped");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnServiceFound(nsIDNSServiceInfo* aServiceInfo)
+{
+  if (NS_WARN_IF(!aServiceInfo)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv ;
+
+  nsAutoCString serviceName;
+  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(serviceName)))) {
+    return rv;
+  }
+
+  LOG_I("OnServiceFound: %s", serviceName.get());
+
+  if (mRegisteredName == serviceName) {
+    LOG_I("ignore self");
+    return NS_OK;
+  }
+
+  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)))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnServiceLost(nsIDNSServiceInfo* aServiceInfo)
+{
+  if (NS_WARN_IF(!aServiceInfo)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv;
+
+  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)))) {
+    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)
+{
+  LOG_E("OnStartDiscoveryFailed: %d", aErrorCode);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnStopDiscoveryFailed(const nsACString& aServiceType, int32_t aErrorCode)
+{
+  LOG_E("OnStopDiscoveryFailed: %d", aErrorCode);
+  return NS_OK;
+}
+
+// nsIDNSRegistrationListener
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnServiceRegistered(nsIDNSServiceInfo* aServiceInfo)
+{
+  if (NS_WARN_IF(!aServiceInfo)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  nsresult rv;
+
+  nsAutoCString name;
+  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(name)))) {
+    return rv;
+  }
+
+  LOG_I("OnServiceRegistered (%s)",  name.get());
+  mRegisteredName = name;
+
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->SetId(name)))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnServiceUnregistered(nsIDNSServiceInfo* aServiceInfo)
+{
+  LOG_I("OnServiceUnregistered");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnRegistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+{
+  LOG_E("OnRegistrationFailed: %d", aErrorCode);
+
+  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)))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnUnregistrationFailed(nsIDNSServiceInfo* aServiceInfo, int32_t aErrorCode)
+{
+  LOG_E("OnUnregistrationFailed: %d", aErrorCode);
+  return NS_OK;
+}
+
+// nsIDNSServiceResolveListener
+NS_IMETHODIMP
+MulticastDNSDeviceProvider::OnServiceResolved(nsIDNSServiceInfo* aServiceInfo)
+{
+  if (NS_WARN_IF(!aServiceInfo)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsresult rv;
+
+  nsAutoCString serviceName;
+  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceName(serviceName)))) {
+    return rv;
+  }
+
+  LOG_I("OnServiceResolved: %s", serviceName.get());
+
+  nsCOMPtr<nsIPresentationDevice> device;
+  nsCOMPtr<nsIPresentationDeviceListener> listener;
+  GetListener(getter_AddRefs(listener));
+
+  if (NS_SUCCEEDED(mPresentationServer->GetTCPDevice(serviceName,
+                                                     getter_AddRefs(device)))) {
+    NS_WARN_IF(NS_FAILED(mPresentationServer->RemoveTCPDevice(serviceName)));
+    if (listener) {
+      NS_WARN_IF(NS_FAILED(listener->RemoveDevice(device)));
+    }
+  }
+
+  nsAutoCString host;
+  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetHost(host)))) {
+    return rv;
+  }
+
+  uint16_t port;
+  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetPort(&port)))) {
+    return rv;
+  }
+
+  nsAutoCString serviceType;
+  if (NS_WARN_IF(NS_FAILED(rv = aServiceInfo->GetServiceType(serviceType)))) {
+    return rv;
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->CreateTCPDevice(serviceName,
+                                                                     serviceName,
+                                                                     serviceType,
+                                                                     host,
+                                                                     port,
+                                                                     getter_AddRefs(device))))) {
+    return rv;
+  }
+
+  if (listener) {
+    listener->AddDevice(device);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+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;
+  }
+
+  nsresult rv;
+
+  if (NS_FAILED(aReason)) {
+    if (NS_WARN_IF(NS_FAILED(rv = mPresentationServer->Init(EmptyCString(), 0)))) {
+      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)))) {
+      return rv;
+    }
+  }
+
+  return NS_OK;
+}
+
+} // namespace presentation
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/MulticastDNSDeviceProvider.h
@@ -0,0 +1,64 @@
+/* -*- 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_presentation_provider_MulticastDNSDeviceProvider_h
+#define mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
+
+#include "nsCOMPtr.h"
+#include "nsICancelable.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsIPresentationDeviceProvider.h"
+#include "nsITCPPresentationServer.h"
+#include "nsRefPtr.h"
+#include "nsString.h"
+#include "nsWeakPtr.h"
+
+namespace mozilla {
+namespace dom {
+namespace presentation {
+
+class DNSServiceWrappedListener;
+class MulticastDNSService;
+
+class MulticastDNSDeviceProvider final
+  : public nsIPresentationDeviceProvider
+  , public nsIDNSServiceDiscoveryListener
+  , public nsIDNSRegistrationListener
+  , public nsIDNSServiceResolveListener
+  , public nsITCPPresentationServerListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIPRESENTATIONDEVICEPROVIDER
+  NS_DECL_NSIDNSSERVICEDISCOVERYLISTENER
+  NS_DECL_NSIDNSREGISTRATIONLISTENER
+  NS_DECL_NSIDNSSERVICERESOLVELISTENER
+  NS_DECL_NSITCPPRESENTATIONSERVERLISTENER
+
+  explicit MulticastDNSDeviceProvider() = default;
+  nsresult Init();
+  nsresult Uninit();
+
+private:
+  virtual ~MulticastDNSDeviceProvider();
+  nsresult RegisterService(uint32_t aPort);
+
+  bool mInitialized = false;
+  nsWeakPtr mDeviceListener;
+  nsCOMPtr<nsITCPPresentationServer> mPresentationServer;
+  nsCOMPtr<nsIDNSServiceDiscovery> mMulticastDNS;
+  nsRefPtr<DNSServiceWrappedListener> mWrappedListener;
+
+  nsCOMPtr<nsICancelable> mDiscoveryRequest;
+  nsCOMPtr<nsICancelable> mRegisterRequest;
+
+  nsCString mRegisteredName;
+};
+
+} // namespace presentation
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_presentation_provider_MulticastDNSDeviceProvider_h
new file mode 100644
--- /dev/null
+++ b/dom/presentation/provider/PresentationDeviceProviderModule.cpp
@@ -0,0 +1,44 @@
+/* -*- 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/ModuleUtils.h"
+
+#define MULTICAST_DNS_PROVIDER_CID \
+  {0x814f947a, 0x52f7, 0x41c9, \
+    { 0x94, 0xa1, 0x36, 0x84, 0x79, 0x72, 0x84, 0xac }}
+
+#define MULTICAST_DNS_PROVIDER_CONTRACT_ID "@mozilla.org/presentation-device/multicastdns-provider;1"
+
+using mozilla::dom::presentation::MulticastDNSDeviceProvider;
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(MulticastDNSDeviceProvider)
+NS_DEFINE_NAMED_CID(MULTICAST_DNS_PROVIDER_CID);
+
+static const mozilla::Module::CIDEntry kPresentationDeviceProviderCIDs[] = {
+  { &kMULTICAST_DNS_PROVIDER_CID, false, nullptr, MulticastDNSDeviceProviderConstructor },
+  { nullptr }
+};
+
+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_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,
+  kPresentationDeviceProviderContracts,
+  kPresentationDeviceProviderCategories
+};
+
+NSMODULE_DEFN(PresentationDeviceProviderModule) = &kPresentationDeviceProviderModule;
--- a/dom/presentation/provider/moz.build
+++ b/dom/presentation/provider/moz.build
@@ -3,8 +3,18 @@
 # 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/.
 
 EXTRA_COMPONENTS += [
     'BuiltinProviders.manifest',
     'TCPPresentationServer.js'
 ]
+
+UNIFIED_SOURCES += [
+    'MulticastDNSDeviceProvider.cpp',
+    'PresentationDeviceProviderModule.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_multicast_dns_device_provider.js
@@ -0,0 +1,255 @@
+/* -*- 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/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";
+
+let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+function MockFactory(aClass) {
+  this._cls = aClass;
+}
+MockFactory.prototype = {
+  createInstance: function(aOuter, aIID) {
+    if (aOuter) {
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    }
+    switch(typeof(this._cls)) {
+      case "function":
+        return new this._cls().QueryInterface(aIID);
+      case "object":
+        return this._cls.QueryInterface(aIID);
+      default:
+        return null;
+    }
+  },
+  lockFactory: function(aLock) {
+    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+  },
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
+};
+
+function ContractHook(aContractID, aClass) {
+  this._contractID = aContractID;
+  this.classID = Cc[UUID_CONTRACT_ID].getService(Ci.nsIUUIDGenerator).generateUUID();
+  this._newFactory = new MockFactory(aClass);
+
+  if (!this.hookedMap.has(this._contractID)) {
+    this.hookedMap.set(this._contractID, new Array());
+  }
+
+  this.init();
+}
+
+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);
+
+    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);
+    }
+  },
+
+  unregister: function() {
+    var classID, factory;
+
+    try {
+      classID = registrar.contractIDToCID(this._contractID);
+      factory = Cm.getClassObject(Cc[this._contractID], Ci.nsIFactory);
+    } catch (ex) {
+      classID = "";
+      factory = null;
+    }
+
+    if (factory) {
+      registrar.unregisterFactory(classID, factory);
+    }
+
+    return { classID: classID, factory: factory };
+  }
+};
+
+function MockDNSServiceInfo() {}
+MockDNSServiceInfo.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDNSServiceInfo]),
+
+  set host(aHost) {
+    this._host = aHost;
+  },
+
+  get host() {
+    return this._host;
+  },
+
+  set port(aPort) {
+    this._port = aPort;
+  },
+
+  get port() {
+    return this._port;
+  },
+
+  set serviceName(aServiceName) {
+    this._serviceName = aServiceName;
+  },
+
+  get serviceName() {
+    return this._serviceName;
+  },
+
+  set serviceType(aServiceType) {
+    this._serviceType = aServiceType;
+  },
+
+  get serviceType() {
+    return this._serviceType;
+  },
+
+  set domainName(aDomainName) {
+    this._domainName = aDomainName;
+  },
+
+  get domainName() {
+    return this._domainName;
+  },
+
+  set attributes(aAttributes) {
+    this._attributes = aAttributes;
+  },
+
+  get attributes() {
+    return this._attributes;
+  }
+};
+
+function TestPresentationDeviceListener() {}
+TestPresentationDeviceListener.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDeviceListener]),
+
+  addDevice: function(device) {},
+  removeDevice: function(device) {},
+  updateDevice: function(device) {}
+};
+
+function createDevice(host, port, serviceName, serviceType, domainName, attributes) {
+  let device = new MockDNSServiceInfo();
+  device.host = host || "";
+  device.port = port || 0;
+  device.serviceName = serviceName || "";
+  device.serviceType = serviceType || "";
+  device.domainName = domainName || "";
+  device.attributes = attributes || null;
+  return device;
+}
+
+function registerService() {
+  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);
+
+  Assert.equal(mockObj.serviceRegistered, 0);
+  let provider = Cc[PROVIDER_CONTRACT_ID].createInstance(Ci.nsIPresentationDeviceProvider);
+  Assert.equal(mockObj.serviceRegistered, 0);
+  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);
+  provider.listener = null;
+  Assert.equal(mockObj.serviceUnregistered, 1);
+
+  run_next_test();
+}
+
+function addDevice() {
+  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);
+  provider.forceDiscovery();
+  Assert.equal(listener.devices.length, 1);
+
+  provider.listener = null;
+
+  run_next_test();
+}
+
+function run_test() {
+  let infoHook = new ContractHook(INFO_CONTRACT_ID, MockDNSServiceInfo);
+
+  add_test(registerService);
+  add_test(addDevice);
+
+  run_next_test();
+}
--- a/dom/presentation/tests/xpcshell/xpcshell.ini
+++ b/dom/presentation/tests/xpcshell/xpcshell.ini
@@ -1,6 +1,5 @@
 [DEFAULT]
-head =
-tail =
 
+[test_multicast_dns_device_provider.js]
 [test_presentation_device_manager.js]
 [test_tcp_control_channel.js]