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 246926 7ada5429c80deeedcefbd2925355d3f6c80e1bc6
parent 246925 dda118cb0bcc60bcc66ee42f4bae5d5338f46d33
child 246927 ce7b42bbf7173dc2a81a73d9791ef754f6afa750
push id28846
push userryanvm@gmail.com
push dateWed, 03 Jun 2015 19:51:00 +0000
treeherdermozilla-central@e5ee2c56963c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersschien
bugs1115480
milestone41.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 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]