Merge b2g-inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 03 Jun 2015 15:50:54 -0400
changeset 269620 e5ee2c56963c52ea1bdf9758c07c63d20e6d4707
parent 269527 196d99aabc27b355fb7e1c03f841cf0d4945db6f (current diff)
parent 269615 d18c1d87f63c90272f99f0946db9c3c2dfe80483 (diff)
child 269635 c57eb7431f80dc27d0b78f11e97b5c6493d9e714
push id2540
push userwcosta@mozilla.com
push dateWed, 03 Jun 2015 20:55:41 +0000
reviewersmerge
milestone41.0a1
Merge b2g-inbound to m-c. a=merge
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,19 +10,19 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,19 +10,19 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,18 +14,18 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="aac9cc4bb94cf720baf8f7ee419b4d76ac86b1ac"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="10b3daf0093db94c64e78a72ac43a93b68976087"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,18 +12,18 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,19 +10,19 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,19 +10,19 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,18 +14,18 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="aac9cc4bb94cf720baf8f7ee419b4d76ac86b1ac"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="10b3daf0093db94c64e78a72ac43a93b68976087"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform_bionic" path="bionic" remote="b2g" revision="e2b3733ba3fa5e3f404e983d2e4142b1f6b1b846"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,19 +10,19 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "477b5672811ed970a7476fe6f67dba546a302dce", 
+        "git_revision": "45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "5bab67b888d1491d1718cf1a7349bcef3114cbab", 
+    "revision": "936633804b786ab99d2afc97311dc5cedeade648", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,18 +12,18 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,19 +10,19 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="477b5672811ed970a7476fe6f67dba546a302dce"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="45dc6306cf502a4f00ae9f8bd8293a8a3a37c07b"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="fffc68521ebb1501d6b015c6d1c4a17a04fdb2e2"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5deaf27fd266316f27e68206cc3be0e6f47ded54"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="5c487ecd9afbcea1d507b9e4dfd09b3a8787fa65"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="ffc05a232799fe8fcb3e47b7440b52b1fb4244c0"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -276,16 +276,17 @@
 @RESPATH@/components/necko_about.xpt
 @RESPATH@/components/necko_cache.xpt
 @RESPATH@/components/necko_cache2.xpt
 @RESPATH@/components/necko_cookie.xpt
 @RESPATH@/components/necko_dns.xpt
 @RESPATH@/components/necko_file.xpt
 @RESPATH@/components/necko_ftp.xpt
 @RESPATH@/components/necko_http.xpt
+@RESPATH@/components/necko_mdns.xpt
 @RESPATH@/components/necko_res.xpt
 @RESPATH@/components/necko_socket.xpt
 @RESPATH@/components/necko_strconv.xpt
 @RESPATH@/components/necko_viewsource.xpt
 @RESPATH@/components/necko_websocket.xpt
 @RESPATH@/components/necko_wifi.xpt
 @RESPATH@/components/necko_wyciwyg.xpt
 #ifdef MOZ_RTSP
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -281,16 +281,17 @@
 @RESPATH@/components/necko_about.xpt
 @RESPATH@/components/necko_cache.xpt
 @RESPATH@/components/necko_cache2.xpt
 @RESPATH@/components/necko_cookie.xpt
 @RESPATH@/components/necko_dns.xpt
 @RESPATH@/components/necko_file.xpt
 @RESPATH@/components/necko_ftp.xpt
 @RESPATH@/components/necko_http.xpt
+@RESPATH@/components/necko_mdns.xpt
 @RESPATH@/components/necko_res.xpt
 @RESPATH@/components/necko_socket.xpt
 @RESPATH@/components/necko_strconv.xpt
 @RESPATH@/components/necko_viewsource.xpt
 @RESPATH@/components/necko_websocket.xpt
 #ifdef NECKO_WIFI
 @RESPATH@/components/necko_wifi.xpt
 #endif
--- a/config/system-headers
+++ b/config/system-headers
@@ -390,16 +390,19 @@ descrip.h
 Devices.h
 Dialogs.h
 direct.h
 dirent.h
 DiskInit.h
 dlfcn.h
 dlgs.h
 dl.h
+#ifdef MOZ_WIDGET_GONK
+dns_sd.h
+#endif
 docobj.h
 dos/dosextens.h
 dos.h
 Drag.h
 DriverServices.h
 DriverSynchronization.h
 DropInPanel.h
 dvidef.h
--- a/configure.in
+++ b/configure.in
@@ -222,17 +222,17 @@ if test -n "$gonkdir" ; then
         MOZ_B2G_CAMERA=1
         MOZ_OMX_DECODER=1
         AC_SUBST(MOZ_OMX_DECODER)
         MOZ_RTSP=1
         MOZ_FMP4=1
         MOZ_SECUREELEMENT=1
         ;;
     17|18)
-        GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include"
+        GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include -I$gonkdir/external/mdnsresponder/mDNSShared"
         if test -d "$gonkdir/external/bluetooth/bluez"; then
           GONK_INCLUDES="$GONK_INCLUDES -I$gonkdir/external/dbus -I$gonkdir/external/bluetooth/bluez/lib"
             MOZ_B2G_BT=1
             MOZ_B2G_BT_BLUEZ=1
         elif test -d "$gonkdir/external/bluetooth/bluedroid"; then
             MOZ_B2G_BT=1
             MOZ_B2G_BT_BLUEDROID=1
             if test -d "$gonkdir/system/bluetoothd"; then
@@ -247,17 +247,17 @@ if test -n "$gonkdir" ; then
         AC_SUBST(MOZ_OMX_DECODER)
         MOZ_OMX_ENCODER=1
         AC_SUBST(MOZ_OMX_ENCODER)
         AC_DEFINE(MOZ_OMX_ENCODER)
 	MOZ_FMP4=1
         MOZ_SECUREELEMENT=1
         ;;
     19)
-        GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include"
+        GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include -I$gonkdir/external/mdnsresponder/mDNSShared"
         MOZ_B2G_CAMERA=1
         MOZ_B2G_BT=1
         MOZ_B2G_BT_BLUEDROID=1
         if test -d "$gonkdir/system/bluetoothd"; then
             MOZ_B2G_BT_DAEMON=1
         fi
         MOZ_NFC=1
         MOZ_RTSP=1
@@ -266,17 +266,17 @@ if test -n "$gonkdir" ; then
         AC_DEFINE(MOZ_OMX_ENCODER)
         MOZ_AUDIO_OFFLOAD=1
         MOZ_SECUREELEMENT=1
         AC_SUBST(MOZ_AUDIO_OFFLOAD)
         AC_DEFINE(MOZ_AUDIO_OFFLOAD)
         MOZ_FMP4=1
         ;;
     21|22)
-        GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include"
+        GONK_INCLUDES="-I$gonkdir/frameworks/native/include -I$gonkdir/frameworks/av/include -I$gonkdir/frameworks/av/include/media -I$gonkdir/frameworks/av/include/camera -I$gonkdir/frameworks/native/include/media/openmax -I$gonkdir/frameworks/av/media/libstagefright/include -I$gonkdir/external/mdnsresponder/mDNSShared"
         MOZ_AUDIO_OFFLOAD=1
         MOZ_OMX_DECODER=1
         MOZ_OMX_ENCODER=1
         AC_DEFINE(MOZ_OMX_ENCODER)
         AC_SUBST(MOZ_AUDIO_OFFLOAD)
         AC_DEFINE(MOZ_AUDIO_OFFLOAD)
         MOZ_FMP4=
         MOZ_B2G_CAMERA=1
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
@@ -474,17 +474,20 @@ BluetoothGattManager::UnregisterClient(i
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mUnregisterClientRunnable = aRunnable;
 
   sBluetoothGattClientInterface->UnregisterClient(
     aClientIf,
     new UnregisterClientResultHandler(client));
 }
@@ -575,17 +578,17 @@ BluetoothGattManager::StartLeScan(const 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   nsString appUuidStr;
   GenerateUuid(appUuidStr);
 
   size_t index = sClients->IndexOf(appUuidStr, 0 /* Start */, UuidComparator());
 
   // Reject the startLeScan request if the clientIf is being used.
-  if (index != sClients->NoIndex) {
+  if (NS_WARN_IF(index != sClients->NoIndex)) {
     DispatchReplyError(aRunnable,
                        NS_LITERAL_STRING("start LE scan failed"));
     return;
   }
 
   index = sClients->Length();
   sClients->AppendElement(new BluetoothGattClient(appUuidStr, EmptyString()));
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
@@ -733,17 +736,20 @@ BluetoothGattManager::Disconnect(const n
                                  BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mDisconnectRunnable = aRunnable;
 
   sBluetoothGattClientInterface->Disconnect(
     client->mClientIf,
     aDeviceAddr,
     client->mConnId,
@@ -777,17 +783,20 @@ BluetoothGattManager::Discover(const nsA
                                BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   MOZ_ASSERT(client->mConnId > 0);
   MOZ_ASSERT(!client->mDiscoverRunnable);
 
   client->mDiscoverRunnable = aRunnable;
 
   /**
@@ -846,17 +855,20 @@ BluetoothGattManager::ReadRemoteRssi(int
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mReadRemoteRssiRunnable = aRunnable;
 
   sBluetoothGattClientInterface->ReadRemoteRssi(
     aClientIf, aDeviceAddr,
     new ReadRemoteRssiResultHandler(client));
 }
@@ -910,17 +922,20 @@ BluetoothGattManager::RegisterNotificati
   const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   // Reject the request if there is an ongoing request or client is already
   // disconnected
   if (client->mRegisterNotificationsRunnable || client->mConnId <= 0) {
     DispatchReplyError(aRunnable,
                        NS_LITERAL_STRING("RegisterNotifications failed"));
@@ -983,17 +998,20 @@ BluetoothGattManager::DeregisterNotifica
   const BluetoothGattId& aCharId, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   // Reject the request if there is an ongoing request
   if (client->mDeregisterNotificationsRunnable) {
     DispatchReplyError(aRunnable,
                        NS_LITERAL_STRING("DeregisterNotifications failed"));
     return;
@@ -1044,19 +1062,17 @@ BluetoothGattManager::ReadCharacteristic
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
   if (NS_WARN_IF(index == sClients->NoIndex)) {
-    // Reject the read characteristic value request
-    DispatchReplyError(aRunnable,
-                       NS_LITERAL_STRING("ReadCharacteristicValue failed"));
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
     return;
   }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   /**
    * Reject subsequent reading requests to follow ATT sequential protocol that
    * handles one request at a time. Otherwise underlying layers would drop the
@@ -1126,19 +1142,17 @@ BluetoothGattManager::WriteCharacteristi
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
   if (NS_WARN_IF(index == sClients->NoIndex)) {
-    // Reject the write characteristic value request
-    DispatchReplyError(aRunnable,
-                       NS_LITERAL_STRING("WriteCharacteristicValue failed"));
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
     return;
   }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   /**
    * Reject subsequent writing requests to follow ATT sequential protocol that
    * handles one request at a time. Otherwise underlying layers would drop the
@@ -1209,19 +1223,17 @@ BluetoothGattManager::ReadDescriptorValu
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
   if (NS_WARN_IF(index == sClients->NoIndex)) {
-    // Reject the read descriptor value request
-    DispatchReplyError(aRunnable,
-                       NS_LITERAL_STRING("ReadDescriptorValue failed"));
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
     return;
   }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   /**
    * Reject subsequent reading requests to follow ATT sequential protocol that
    * handles one request at a time. Otherwise underlying layers would drop the
@@ -1292,19 +1304,17 @@ BluetoothGattManager::WriteDescriptorVal
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
   if (NS_WARN_IF(index == sClients->NoIndex)) {
-    // Reject the write descriptor value request
-    DispatchReplyError(aRunnable,
-                       NS_LITERAL_STRING("WriteDescriptorValue failed"));
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
     return;
   }
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   /**
    * Reject subsequent writing requests to follow ATT sequential protocol that
    * handles one request at a time. Otherwise underlying layers would drop the
@@ -1347,17 +1357,18 @@ BluetoothGattManager::RegisterClientNoti
 {
   BT_API2_LOGR("Client Registered, clientIf = %d", aClientIf);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsString uuid;
   UuidToString(aAppUuid, uuid);
 
   size_t index = sClients->IndexOf(uuid, 0 /* Start */, UuidComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     BT_API2_LOGR(
       "RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
@@ -1442,17 +1453,18 @@ BluetoothGattManager::ConnectNotificatio
   BT_API2_LOGR();
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     BT_API2_LOGR("Connect failed, clientIf = %d, connId = %d, status = %d",
                  aClientIf, aConnId, aStatus);
 
     // Notify BluetoothGatt that the client remains disconnected
     bs->DistributeSignal(
@@ -1494,17 +1506,18 @@ BluetoothGattManager::DisconnectNotifica
   BT_API2_LOGR();
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     // Notify BluetoothGatt that the client remains connected
     bs->DistributeSignal(
       NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
       client->mAppUuid,
       BluetoothValue(true)); // Connected
@@ -1540,17 +1553,17 @@ BluetoothGattManager::SearchCompleteNoti
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
                                    ConnIdComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   MOZ_ASSERT(client->mDiscoverRunnable);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     client->NotifyDiscoverCompleted(false);
     return;
   }
@@ -1577,17 +1590,17 @@ BluetoothGattManager::SearchCompleteNoti
 void
 BluetoothGattManager::SearchResultNotification(
   int aConnId, const BluetoothGattServiceId& aServiceId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
                                    ConnIdComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
 
   // Save to mServices for distributing to application and discovering
   // included services, characteristics of this service later
   sClients->ElementAt(index)->mServices.AppendElement(aServiceId);
 }
 
 void
 BluetoothGattManager::GetCharacteristicNotification(
@@ -1598,17 +1611,17 @@ BluetoothGattManager::GetCharacteristicN
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
                                    ConnIdComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   MOZ_ASSERT(client->mDiscoverRunnable);
 
   if (aStatus == GATT_STATUS_SUCCESS) {
     BluetoothGattCharAttribute attribute;
     attribute.mId = aCharId;
     attribute.mProperties = aCharProperty;
@@ -1650,17 +1663,17 @@ BluetoothGattManager::GetDescriptorNotif
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
                                    ConnIdComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   MOZ_ASSERT(client->mDiscoverRunnable);
 
   if (aStatus == GATT_STATUS_SUCCESS) {
     // Save to mDescriptors for distributing to applications
     client->mDescriptors.AppendElement(aDescriptorId);
 
@@ -1694,17 +1707,17 @@ BluetoothGattManager::GetIncludedService
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
                                    ConnIdComparator());
-  MOZ_ASSERT(index != sClients->NoIndex);
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   MOZ_ASSERT(client->mDiscoverRunnable);
 
   if (aStatus == GATT_STATUS_SUCCESS) {
     // Save to mIncludedServices for distributing to applications
     client->mIncludedServices.AppendElement(aIncludedServId);
 
@@ -2013,16 +2026,17 @@ BluetoothGattManager::ReadRemoteRssiNoti
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
   NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   if (aStatus != GATT_STATUS_SUCCESS) { // operation failed
     BT_API2_LOGR("ReadRemoteRssi failed, clientIf = %d, bdAddr = %s, " \
                  "rssi = %d, status = %d", aClientIf,
                  NS_ConvertUTF16toUTF8(aBdAddr).get(), aRssi, (int)aStatus);
 
     // Reject the read remote rssi request
--- a/dom/bluetooth/bluetooth2/BluetoothDevice.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothDevice.cpp
@@ -380,26 +380,30 @@ BluetoothDevice::UpdatePropertiesFromAdv
             uuid[i] = aAdvData[offset++];
             uuid[i] += (aAdvData[offset++] << 8);
             dataLength -= 2;
           }
 
           char uuidStr[36];
           if (type == GAP_INCOMPLETE_UUID16 || type == GAP_COMPLETE_UUID16) {
             // Convert 16-bits UUID into string.
-            sprintf(uuidStr, "0000%04x-0000-1000-8000-00805f9b34fb", uuid[0]);
-          } else if (type == GAP_INCOMPLETE_UUID32 || type == GAP_COMPLETE_UUID32) {
+            snprintf(uuidStr, sizeof(uuidStr),
+                     "0000%04x-0000-1000-8000-00805f9b34fb", uuid[0]);
+          } else if (type == GAP_INCOMPLETE_UUID32 ||
+                     type == GAP_COMPLETE_UUID32) {
             // Convert 32-bits UUID into string.
-            sprintf(uuidStr, "%04x%04x-0000-1000-8000-00805f9b34fb",
-              uuid[1], uuid[0]);
-          } else if (type == GAP_INCOMPLETE_UUID128 || type == GAP_COMPLETE_UUID128) {
+            snprintf(uuidStr, sizeof(uuidStr),
+                     "%04x%04x-0000-1000-8000-00805f9b34fb", uuid[1], uuid[0]);
+          } else if (type == GAP_INCOMPLETE_UUID128 ||
+                     type == GAP_COMPLETE_UUID128) {
             // Convert 128-bits UUID into string.
-            sprintf(uuidStr, "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
-              uuid[7], uuid[6], uuid[5], uuid[4],
-              uuid[3], uuid[2], uuid[1], uuid[0]);
+            snprintf(uuidStr, sizeof(uuidStr),
+                     "%04x%04x-%04x-%04x-%04x-%04x%04x%04x",
+                     uuid[7], uuid[6], uuid[5], uuid[4],
+                     uuid[3], uuid[2], uuid[1], uuid[0]);
           }
           nsString uuidNsString;
           uuidNsString.AssignLiteral(uuidStr);
 
           mUuids.AppendElement(uuidNsString);
         }
 
         BluetoothDeviceBinding::ClearCachedUuidsValue(this);
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -321,147 +321,188 @@ GonkCameraHardware::GetSensorOrientation
       return 0;
   }
 }
 
 int
 GonkCameraHardware::AutoFocus()
 {
   DOM_CAMERA_LOGI("%s\n", __func__);
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   return mCamera->autoFocus();
 }
 
 int
 GonkCameraHardware::CancelAutoFocus()
 {
   DOM_CAMERA_LOGI("%s\n", __func__);
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   return mCamera->cancelAutoFocus();
 }
 
 int
 GonkCameraHardware::StartFaceDetection()
 {
   DOM_CAMERA_LOGI("%s\n", __func__);
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
+
   int rv = INVALID_OPERATION;
-
 #if ANDROID_VERSION >= 15
   rv = mCamera->sendCommand(CAMERA_CMD_START_FACE_DETECTION, CAMERA_FACE_DETECTION_HW, 0);
 #endif
   if (rv != OK) {
     DOM_CAMERA_LOGE("Start face detection failed with status %d", rv);
   }
 
   return rv;
 }
 
 int
 GonkCameraHardware::StopFaceDetection()
 {
   DOM_CAMERA_LOGI("%s\n", __func__);
+  if (mClosing) {
+    return DEAD_OBJECT;
+  }
+
   int rv = INVALID_OPERATION;
-
 #if ANDROID_VERSION >= 15
   rv = mCamera->sendCommand(CAMERA_CMD_STOP_FACE_DETECTION, 0, 0);
 #endif
   if (rv != OK) {
     DOM_CAMERA_LOGE("Stop face detection failed with status %d", rv);
   }
 
   return rv;
 }
 
 int
 GonkCameraHardware::TakePicture()
 {
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   return mCamera->takePicture(CAMERA_MSG_SHUTTER | CAMERA_MSG_COMPRESSED_IMAGE);
 }
 
 void
 GonkCameraHardware::CancelTakePicture()
 {
   DOM_CAMERA_LOGW("%s: android::Camera do not provide this capability\n", __func__);
 }
 
 int
 GonkCameraHardware::PushParameters(const GonkCameraParameters& aParams)
 {
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   const String8 s = aParams.Flatten();
   return mCamera->setParameters(s);
 }
 
 nsresult
 GonkCameraHardware::PullParameters(GonkCameraParameters& aParams)
 {
+  if (NS_WARN_IF(mClosing)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
   const String8 s = mCamera->getParameters();
   return aParams.Unflatten(s);
 }
 
 #ifdef MOZ_WIDGET_GONK
 int
 GonkCameraHardware::PushParameters(const CameraParameters& aParams)
 {
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   String8 s = aParams.flatten();
   return mCamera->setParameters(s);
 }
 
 void
 GonkCameraHardware::PullParameters(CameraParameters& aParams)
 {
-  const String8 s = mCamera->getParameters();
-  aParams.unflatten(s);
+  if (!NS_WARN_IF(mClosing)) {
+    const String8 s = mCamera->getParameters();
+    aParams.unflatten(s);
+  }
 }
 #endif
 
 int
 GonkCameraHardware::StartPreview()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   return mCamera->startPreview();
 }
 
 void
 GonkCameraHardware::StopPreview()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  mCamera->stopPreview();
+  if (!mClosing) {
+    mCamera->stopPreview();
+  }
 }
 
 int
 GonkCameraHardware::StartRecording()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  int rv = OK;
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
 
-  rv = mCamera->startRecording();
+  int rv = mCamera->startRecording();
   if (rv != OK) {
     DOM_CAMERA_LOGE("mHardware->startRecording() failed with status %d", rv);
   }
   return rv;
 }
 
 int
 GonkCameraHardware::StopRecording()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  if (mClosing) {
+    return DEAD_OBJECT;
+  }
   mCamera->stopRecording();
   return OK;
 }
 
 #ifdef MOZ_WIDGET_GONK
 int
 GonkCameraHardware::SetListener(const sp<GonkCameraListener>& aListener)
 {
   mListener = aListener;
   return OK;
 }
 
 void
 GonkCameraHardware::ReleaseRecordingFrame(const sp<IMemory>& aFrame)
 {
-  mCamera->releaseRecordingFrame(aFrame);
+  if (!NS_WARN_IF(mClosing)) {
+    mCamera->releaseRecordingFrame(aFrame);
+  }
 }
 #endif
 
 int
 GonkCameraHardware::StoreMetaDataInBuffers(bool aEnabled)
 {
+  if (NS_WARN_IF(mClosing)) {
+    return DEAD_OBJECT;
+  }
   return mCamera->storeMetaDataInBuffers(aEnabled);
 }
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -213,20 +213,19 @@ NfcContentHelper.prototype = {
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:CheckP2PRegistration", {
       appId: appId,
       requestId: requestId
     });
   },
 
-  notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId, tabId) {
+  notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId) {
     cpmm.sendAsyncMessage("NFC:NotifyUserAcceptedP2P", {
-      appId: appId,
-      tabId: tabId
+      appId: appId
     });
   },
 
   changeRFState: function changeRFState(rfState, callback) {
     let requestId = callback.getCallbackId();
     this._requestMap[requestId] = callback;
 
     cpmm.sendAsyncMessage("NFC:ChangeRFState",
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -287,26 +287,26 @@ XPCOMUtils.defineLazyGetter(this, "gMess
       let respMsg = { requestId: message.data.requestId };
       if (!isValid) {
         respMsg.errorMsg = this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_P2P_REG_INVALID);
       }
       // Notify the content process immediately of the status
       message.target.sendAsyncMessage(message.name + "Response", respMsg);
     },
 
-    notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId, tabId) {
+    notifyUserAcceptedP2P: function notifyUserAcceptedP2P(appId) {
       let target = this.peerTargets[appId];
       let sessionToken = SessionHelper.getCurrentP2PToken();
       let isValid = (sessionToken != null) && (target != null);
       if (!isValid) {
         debug("Peer already lost or " + appId + " is not a registered PeerReadytarget");
         return;
       }
 
-      this.notifyDOMEvent(target, {tabId: tabId,
+      this.notifyDOMEvent(target, {tabId: this.focusApp,
                                    event: NFC.PEER_EVENT_READY,
                                    sessionToken: sessionToken});
     },
 
     notifySendFileStatus: function notifySendFileStatus(message) {
       if (message.data.status) {
         message.data.errorMsg =
             this.nfc.getErrorMessage(NFC.NFC_GECKO_ERROR_SEND_FILE_FAILED);
@@ -403,17 +403,17 @@ XPCOMUtils.defineLazyGetter(this, "gMess
           return null;
         case "NFC:UnregisterPeerReadyTarget":
           this.unregisterPeerReadyTarget(message.data.appId);
           return null;
         case "NFC:CheckP2PRegistration":
           this.checkP2PRegistration(message);
           return null;
         case "NFC:NotifyUserAcceptedP2P":
-          this.notifyUserAcceptedP2P(message.data.appId, message.data.tabId);
+          this.notifyUserAcceptedP2P(message.data.appId);
           return null;
         case "NFC:NotifySendFileStatus":
           // Upon receiving the status of sendFile operation, send the response
           // to appropriate content process.
           this.notifySendFileStatus(message);
           return null;
         case "NFC:CallDefaultFoundHandler":
           this.callDefaultFoundHandler(message.data);
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -119,17 +119,17 @@ interface nsINfcRequestCallback : nsISup
 interface nsINfcBrowserAPI : nsISupports
 {
   const int32_t SYSTEM_APP_ID = 0;
 
   void setFocusApp(in uint64_t tabId,
                    in boolean isFocus);
 };
 
-[scriptable, uuid(f0ed35c5-3f59-4806-b6bb-e77b879887af)]
+[scriptable, uuid(75f0c8c0-2e5a-491f-a75d-4f3849c4feec)]
 interface nsINfcContentHelper : nsISupports
 {
   /**
    * Read current NDEF data on the tag.
    *
    * @param sessionToken
    *        Current token
    *
@@ -272,20 +272,18 @@ interface nsINfcContentHelper : nsISuppo
   void checkP2PRegistration(in unsigned long appId,
                             in nsINfcRequestCallback callback);
 
   /**
    * Notify the parent process that user has accepted to share nfc message on P2P UI
    *
    * @param appId
    *        Application ID that is capable of handling NFC_EVENT_PEER_READY event
-   * @param tabId
-   *        Tab Id of the window calling this interface.
    */
-  void notifyUserAcceptedP2P(in unsigned long appId, in uint64_t tabId);
+  void notifyUserAcceptedP2P(in unsigned long appId);
 
   /**
    * Notify the status of sendFile operation to parent process
    *
    * @param status
    *        Status of sendFile operation
    *
    * @param requestId
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -415,17 +415,17 @@ MozNFCImpl.prototype = {
     let callback = new NfcCallback(this.window);
     this._nfcContentHelper.checkP2PRegistration(appID, callback);
     return callback.promise;
   },
 
   notifyUserAcceptedP2P: function notifyUserAcceptedP2P(manifestUrl) {
     let appID = appsService.getAppLocalIdByManifestURL(manifestUrl);
     // Notify chrome process of user's acknowledgement
-    this._nfcContentHelper.notifyUserAcceptedP2P(appID, this._tabId);
+    this._nfcContentHelper.notifyUserAcceptedP2P(appID);
   },
 
   notifySendFileStatus: function notifySendFileStatus(status, requestId) {
     this._nfcContentHelper.notifySendFileStatus(status, requestId);
   },
 
   startPoll: function startPoll() {
     let callback = new NfcCallback(this.window);
--- 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,7 @@
 [DEFAULT]
 head =
 tail =
 
+[test_multicast_dns_device_provider.js]
 [test_presentation_device_manager.js]
 [test_tcp_control_channel.js]
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -363,81 +363,63 @@ nsVolumeService::FindVolumeByMountLockNa
     if (mountLockName.Equals(aMountLockName)) {
       return vol.forget();
     }
   }
   return nullptr;
 }
 
 already_AddRefed<nsVolume>
-nsVolumeService::FindVolumeByName(const nsAString& aName)
+nsVolumeService::FindVolumeByName(const nsAString& aName, nsVolume::Array::index_type* aIndex)
 {
   mArrayMonitor.AssertCurrentThreadOwns();
 
   nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
   nsVolume::Array::index_type volIndex;
   for (volIndex = 0; volIndex < numVolumes; volIndex++) {
     nsRefPtr<nsVolume> vol = mVolumeArray[volIndex];
     if (vol->Name().Equals(aName)) {
+      if (aIndex) {
+        *aIndex = volIndex;
+      }
       return vol.forget();
     }
   }
   return nullptr;
 }
 
-//static
-already_AddRefed<nsVolume>
-nsVolumeService::CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake /*= false*/)
-{
-  MonitorAutoLock autoLock(mArrayMonitor);
-
-  nsRefPtr<nsVolume> vol;
-  vol = FindVolumeByName(aName);
-  if (vol) {
-    return vol.forget();
-  }
-  // Volume not found - add a new one
-  vol = new nsVolume(aName);
-  vol->SetIsFake(aIsFake);
-  mVolumeArray.AppendElement(vol);
-  return vol.forget();
-}
-
 void
-nsVolumeService::UpdateVolume(nsIVolume* aVolume, bool aNotifyObservers)
+nsVolumeService::UpdateVolume(nsVolume* aVolume, bool aNotifyObservers)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsString volName;
-  aVolume->GetName(volName);
-  bool aIsFake;
-  aVolume->GetIsFake(&aIsFake);
-  nsRefPtr<nsVolume> vol = CreateOrFindVolumeByName(volName, aIsFake);
-  if (vol->Equals(aVolume)) {
-    // Nothing has really changed. Don't bother telling anybody.
-    return;
+  {
+    MonitorAutoLock autoLock(mArrayMonitor);
+    nsVolume::Array::index_type volIndex;
+    nsRefPtr<nsVolume> vol = FindVolumeByName(aVolume->Name(), &volIndex);
+    if (!vol) {
+      mVolumeArray.AppendElement(aVolume);
+    } else if (vol->Equals(aVolume) || (!vol->IsFake() && aVolume->IsFake())) {
+      // Ignore if nothing changed or if a fake tries to override a real volume.
+      return;
+    } else {
+      mVolumeArray.ReplaceElementAt(volIndex, aVolume);
+    }
   }
 
-  if (!vol->IsFake() && aIsFake) {
-    // Prevent an incoming fake volume from overriding an existing real volume.
-    return;
-  }
-
-  vol->Set(aVolume);
-
   if (!aNotifyObservers) {
     return;
   }
 
   nsCOMPtr<nsIObserverService> obs = GetObserverService();
   if (!obs) {
     return;
   }
-  NS_ConvertUTF8toUTF16 stateStr(vol->StateStr());
-  obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get());
+  NS_ConvertUTF8toUTF16 stateStr(aVolume->StateStr());
+  obs->NotifyObservers(aVolume, NS_VOLUME_STATE_CHANGED, stateStr.get());
 }
 
 NS_IMETHODIMP
 nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsRefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_INIT,
                                           -1    /* mountGeneration */,
@@ -466,19 +448,17 @@ nsVolumeService::SetFakeVolumeState(cons
     {
       MonitorAutoLock autoLock(mArrayMonitor);
       vol = FindVolumeByName(name);
     }
     if (!vol || !vol->IsFake()) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
-    // UpdateVolume expects the volume passed in to NOT be the
-    // same pointer as what CreateOrFindVolumeByName would return,
-    // which is why we allocate a temporary volume here.
+    // Clone the existing volume so we can replace it
     nsRefPtr<nsVolume> volume = new nsVolume(name);
     volume->Set(vol);
     volume->SetState(state);
     volume->LogState();
     UpdateVolume(volume.get());
     return NS_OK;
   }
 
@@ -497,25 +477,25 @@ nsVolumeService::RemoveFakeVolume(const 
 
   ContentChild::GetSingleton()->SendRemoveFakeVolume(nsString(name));
   return NS_OK;
 }
 
 void
 nsVolumeService::RemoveVolumeByName(const nsAString& aName)
 {
-  nsRefPtr<nsVolume> vol;
   {
     MonitorAutoLock autoLock(mArrayMonitor);
-    vol = FindVolumeByName(aName);
+    nsVolume::Array::index_type volIndex;
+    nsRefPtr<nsVolume> vol = FindVolumeByName(aName, &volIndex);
+    if (!vol) {
+      return;
+    }
+    mVolumeArray.RemoveElementAt(volIndex);
   }
-  if (!vol) {
-    return;
-  }
-  mVolumeArray.RemoveElement(vol);
 
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsCOMPtr<nsIObserverService> obs = GetObserverService();
     if (!obs) {
       return;
     }
     obs->NotifyObservers(nullptr, NS_VOLUME_REMOVED, nsString(aName).get());
   }
--- a/dom/system/gonk/nsVolumeService.h
+++ b/dom/system/gonk/nsVolumeService.h
@@ -42,32 +42,33 @@ public:
 
   static already_AddRefed<nsVolumeService> GetSingleton();
   //static nsVolumeService* GetSingleton();
   static void Shutdown();
 
   void DumpNoLock(const char* aLabel);
 
   // To use this function, you have to create a new volume and pass it in.
-  void UpdateVolume(nsIVolume* aVolume, bool aNotifyObservers = true);
+  void UpdateVolume(nsVolume* aVolume, bool aNotifyObservers = true);
   void UpdateVolumeIOThread(const Volume* aVolume);
 
   void RecvVolumesFromParent(const nsTArray<dom::VolumeInfo>& aVolumes);
   void GetVolumesForIPC(nsTArray<dom::VolumeInfo>* aResult);
 
   void RemoveVolumeByName(const nsAString& aName);
 
 private:
   ~nsVolumeService();
 
   void CheckMountLock(const nsAString& aMountLockName,
                       const nsAString& aMountLockState);
   already_AddRefed<nsVolume> FindVolumeByMountLockName(const nsAString& aMountLockName);
-  already_AddRefed<nsVolume> FindVolumeByName(const nsAString& aName);
-  already_AddRefed<nsVolume> CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake = false);
+
+  already_AddRefed<nsVolume> FindVolumeByName(const nsAString& aName,
+                                              nsVolume::Array::index_type* aIndex = nullptr);
 
   Monitor mArrayMonitor;
   nsVolume::Array mVolumeArray;
 
   static StaticRefPtr<nsVolumeService> sSingleton;
   bool mGotVolumesFromParent;
 };
 
new file mode 100644
--- /dev/null
+++ b/dom/telephony/gonk/TelephonyUtils.jsm
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["TelephonyUtils"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+/* global TelephonyService */
+XPCOMUtils.defineLazyServiceGetter(this,
+                                   "TelephonyService",
+                                   "@mozilla.org/telephony/telephonyservice;1",
+                                   "nsITelephonyService");
+
+function getCurrentCalls(aFilter) {
+ if (aFilter === undefined) {
+   aFilter = call => true;
+ }
+
+ let calls = [];
+
+ // nsITelephonyService.enumerateCalls is synchronous.
+ TelephonyService.enumerateCalls({
+   QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyListener]),
+   enumerateCallStateComplete: function() {},
+   enumerateCallState: function(call) {
+     if (aFilter(call)) {
+       calls.push(call);
+     }
+   },
+ });
+
+ return calls;
+}
+
+this.TelephonyUtils = {
+  /**
+   * Check whether there are any calls.
+   *
+   * @param aClientId [optional] If provided, only check on aClientId
+   * @return boolean
+   */
+  hasAnyCalls: function(aClientId) {
+    let calls = getCurrentCalls(call => {
+      if (aClientId !== undefined && call.clientId !== aClientId) {
+        return false;
+      }
+      return true;
+    });
+
+    return calls.length !== 0;
+  },
+
+  /**
+   * Check whether there are any connected calls.
+   *
+   * @param aClientId [optional] If provided, only check on aClientId
+   * @return boolean
+   */
+  hasConnectedCalls: function(aClientId) {
+    let calls = getCurrentCalls(call => {
+      if (aClientId !== undefined && call.clientId !== aClientId) {
+        return false;
+      }
+      return call.callState === Ci.nsITelephonyService.CALL_STATE_CONNECTED;
+    });
+
+    return calls.length !== 0;
+  },
+
+  /**
+   * Return a promise which will be resolved when there are no calls.
+   *
+   * @param aClientId [optional] only check on aClientId if provided
+   * @return Promise
+   */
+  waitForNoCalls: function(aClientId) {
+    if (!this.hasAnyCalls(aClientId)) {
+      return Promise.resolve();
+    }
+
+    let self = this;
+    return new Promise(resolve => {
+      let listener = {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyListener]),
+
+        enumerateCallStateComplete: function() {},
+        enumerateCallState: function() {},
+        callStateChanged: function() {
+          if (!self.hasAnyCalls(aClientId)) {
+            TelephonyService.unregisterListener(this);
+            resolve();
+          }
+        },
+        conferenceCallStateChanged: function() {},
+        supplementaryServiceNotification: function() {},
+        notifyError: function() {},
+        notifyCdmaCallWaiting: function() {},
+        notifyConferenceError: function() {}
+      };
+
+      TelephonyService.registerListener(listener);
+    });
+  }
+};
--- a/dom/telephony/moz.build
+++ b/dom/telephony/moz.build
@@ -67,15 +67,16 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
         'gonk/USSDReceivedWrapper.manifest',
     ]
     if not CONFIG['DISABLE_MOZ_RIL_GEOLOC']:
         EXTRA_COMPONENTS += [
             'gonk/TelephonyService.js',
             'gonk/TelephonyService.manifest',
         ]
     EXTRA_JS_MODULES += [
-        'gonk/DialNumberUtils.jsm'
+        'gonk/DialNumberUtils.jsm',
+        'gonk/TelephonyUtils.jsm',
     ]
 
 FAIL_ON_WARNINGS = True
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/dom/telephony/test/marionette/head.js
+++ b/dom/telephony/test/marionette/head.js
@@ -1102,22 +1102,29 @@ let emulator = (function() {
     log("Set radio: " + desiredRadioState);
 
     if (connection.radioState === desiredRadioState) {
       return Promise.resolve();
     }
 
     let promises = [];
 
-    let promise = gWaitForEvent(connection, "radiostatechange", event => {
+    promises.push(gWaitForEvent(connection, "radiostatechange", event => {
       let state = connection.radioState;
       log("current radioState: " + state);
       return state == desiredRadioState;
-    });
-    promises.push(promise);
+    }));
+
+    // Wait for icc status to finish updating. Please see bug 1169504 for the
+    // reason why we need this.
+    promises.push(gWaitForEvent(connection, "iccchange", event => {
+      let iccId = connection.iccId;
+      log("current iccId: " + iccId);
+      return !!iccId === enabled;
+    }));
 
     promises.push(connection.setRadioEnabled(enabled));
 
     return Promise.all(promises);
   }
 
   function setRadioEnabledAll(enabled) {
     let promises = [];
--- a/dom/telephony/test/marionette/manifest.ini
+++ b/dom/telephony/test/marionette/manifest.ini
@@ -49,9 +49,10 @@ qemu = true
 [test_outgoing_from_stk.js]
 [test_outgoing_onstatechange.js]
 [test_outgoing_radio_off.js]
 [test_outgoing_when_two_calls_on_line.js]
 [test_ready.js]
 [test_redundant_operations.js]
 [test_swap_held_and_active.js]
 [test_system_message_telephony_call_ended.js]
+[test_TelephonyUtils.js]
 [test_temporary_clir.js]
new file mode 100644
--- /dev/null
+++ b/dom/telephony/test/marionette/test_TelephonyUtils.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_CONTEXT = "chrome";
+MARIONETTE_HEAD_JS = 'head.js';
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this,
+                                   "TelephonyService",
+                                   "@mozilla.org/telephony/telephonyservice;1",
+                                   "nsIGonkTelephonyService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "TelephonyUtils",
+                                 "resource://gre/modules/TelephonyUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "RIL", function () {
+  let ns = {};
+  Cu.import("resource://gre/modules/ril_consts.js", ns);
+  return ns;
+});
+
+const number = "0912345678";
+
+function dial() {
+  return new Promise(resolve => {
+    TelephonyService.dial(0, number, false, {
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyDialCallback]),
+      notifyDialCallSuccess: function() { resolve(); }
+    });
+  });
+}
+
+function waitForStateChanged() {
+  return new Promise(resolve => {
+    let listener = {
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyListener]),
+
+      callStateChanged: function(length, allInfo) {
+        resolve(allInfo);
+        TelephonyService.unregisterListener(listener);
+      },
+      conferenceCallStateChanged: function() {},
+      supplementaryServiceNotification: function() {},
+      notifyError: function() {},
+      notifyCdmaCallWaiting: function() {},
+      notifyConferenceError: function() {}
+    };
+
+    TelephonyService.registerListener(listener);
+  });
+}
+
+function test_noCall()   {
+  log("== test_noCall ==");
+  is(TelephonyUtils.hasAnyCalls(), false, "hasAnyCalls");
+  is(TelephonyUtils.hasConnectedCalls(), false, "hasConnectedCalls");
+  return TelephonyUtils.waitForNoCalls();
+}
+
+function test_oneCall() {
+  log("== test_oneCall ==");
+
+  return dial()
+    .then(() => {
+      is(TelephonyUtils.hasAnyCalls(), true, "hasAnyCalls");
+      is(TelephonyUtils.hasConnectedCalls(), false, "hasConnectedCalls");
+    })
+    .then(() => {
+      let p = waitForStateChanged();
+      emulator.runCmd("gsm accept " + number);
+      return p;
+    })
+    .then(allInfo => {
+      is(allInfo[0].callState, Ci.nsITelephonyService.CALL_STATE_CONNECTED);
+      is(TelephonyUtils.hasAnyCalls(), true, "hasAnyCalls");
+      is(TelephonyUtils.hasConnectedCalls(), true, "hasConnectedCalls");
+    })
+    .then(() => {
+      let p = TelephonyUtils.waitForNoCalls();
+      emulator.runCmd("gsm cancel " + number);
+      return p;
+    });
+}
+
+test_noCall()
+  .then(test_oneCall)
+  .catch(error => ok(false, "Promise reject: " + error))
+  .then(finish);
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -411,17 +411,17 @@ var WifiManager = (function() {
 
   var staticIpConfig = Object.create(null);
   function setStaticIpMode(network, info, callback) {
     let setNetworkKey = getNetworkKey(network);
     let curNetworkKey = null;
     let currentNetwork = Object.create(null);
     currentNetwork.netId = manager.connectionInfo.id;
 
-    manager.getNetworkConfiguration(currentNetwork, function (){
+    manager.getNetworkConfiguration(currentNetwork, function () {
       curNetworkKey = getNetworkKey(currentNetwork);
 
       // Add additional information to static ip configuration
       // It is used to compatiable with information dhcp callback.
       info.ipaddr = netHelpers.stringToIP(info.ipaddr_str);
       info.gateway = netHelpers.stringToIP(info.gateway_str);
       info.mask_str = netHelpers.ipToString(netHelpers.makeMask(info.maskLength));
 
@@ -430,25 +430,29 @@ var WifiManager = (function() {
       info.dns2 = netHelpers.stringToIP(info.dns2_str);
       info.proxy = netHelpers.stringToIP(info.proxy_str);
 
       staticIpConfig[setNetworkKey] = info;
 
       // If the ssid of current connection is the same as configured ssid
       // It means we need update current connection to use static IP address.
       if (setNetworkKey == curNetworkKey) {
-        // Use configureInterface directly doesn't work, the network iterface
+        // Use configureInterface directly doesn't work, the network interface
         // and routing table is changed but still cannot connect to network
         // so the workaround here is disable interface the enable again to
         // trigger network reconnect with static ip.
         gNetworkService.disableInterface(manager.ifname, function (ok) {
           gNetworkService.enableInterface(manager.ifname, function (ok) {
+            callback(ok);
           });
         });
+        return;
       }
+
+      callback(true);
     });
   }
 
   var dhcpInfo = null;
 
   function runStaticIp(ifname, key) {
     debug("Run static ip");
 
@@ -3489,17 +3493,17 @@ WifiWorker.prototype = {
         self._sendMessage(message, true, true, msg);
       } else {
         self._sendMessage(message, false, "Set http proxy failed", msg);
       }
     });
   },
 
   setStaticIpMode: function(msg) {
-    const message = "WifiManager:setStaticMode:Return";
+    const message = "WifiManager:setStaticIpMode:Return";
     let self = this;
     let network = msg.data.network;
     let info = msg.data.info;
 
     if (!WifiManager.enabled) {
       this._sendMessage(message, false, "Wifi is disabled", msg);
       return;
     }
--- a/dom/wifi/test/marionette/head.js
+++ b/dom/wifi/test/marionette/head.js
@@ -429,16 +429,33 @@ let gTestSuite = (function() {
    */
   function getKnownNetworks() {
     let request = wifiManager.getKnownNetworks();
     return wrapDomRequestAsPromise(request)
       .then(event => event.target.result);
   }
 
   /**
+   * Set the given network to static ip mode.
+   *
+   * Resolve when we set static ip mode successfully; reject when any error
+   * occurs.
+   *
+   * Fulfill params: (none)
+   * Reject params: (none)
+   *
+   * @return A deferred promise.
+   */
+  function setStaticIpMode(aNetwork, aConfig) {
+    let request = wifiManager.setStaticIpMode(aNetwork, aConfig);
+    return wrapDomRequestAsPromise(request)
+      .then(event => event.target.result);
+  }
+
+  /**
    * Issue a request to scan all wifi available networks.
    *
    * Resolve when we get the scan result; reject when any error
    * occurs.
    *
    * Fulfill params: An array of MozWifiNetwork
    * Reject params: (none)
    *
@@ -1021,16 +1038,127 @@ let gTestSuite = (function() {
           return;
         }
         throw 'Unexpected number of running processes:' + aProcessName +
               ', expected: ' + aExpectedNum + ', actual: ' + detail.length;
       });
   }
 
   /**
+   * Execute 'netcfg' shell and parse the result.
+   *
+   * Resolve when the executing is successful and reject otherwise.
+   *
+   * Fulfill params: Command result object, each key of which is the interface
+   *                 name and value is { ip(string), prefix(string) }.
+   * Reject params: String that indicates the reason of rejection.
+   *
+   * @return A deferred promise.
+   */
+  function exeAndParseNetcfg() {
+    return runEmulatorShellSafe(['netcfg'])
+      .then(function (aLines) {
+        // Sample output:
+        //
+        // lo       UP     127.0.0.1/8   0x00000049 00:00:00:00:00:00
+        // eth0     UP     10.0.2.15/24  0x00001043 52:54:00:12:34:56
+        // rmnet1   DOWN   0.0.0.0/0   0x00001002 52:54:00:12:34:58
+        // rmnet2   DOWN   0.0.0.0/0   0x00001002 52:54:00:12:34:59
+        // rmnet3   DOWN   0.0.0.0/0   0x00001002 52:54:00:12:34:5a
+        // wlan0    UP     192.168.1.1/24  0x00001043 52:54:00:12:34:5b
+        // sit0     DOWN   0.0.0.0/0   0x00000080 00:00:00:00:00:00
+        // rmnet0   UP     10.0.2.100/24  0x00001043 52:54:00:12:34:57
+        //
+        let netcfgResult = {};
+        aLines.forEach(function (aLine) {
+          let tokens = aLine.split(/\s+/);
+          if (tokens.length < 5) {
+            return;
+          }
+          let ifname = tokens[0];
+          let [ip, prefix] = tokens[2].split('/');
+          netcfgResult[ifname] = { ip: ip, prefix: prefix };
+        });
+        log("netcfg result:" + JSON.stringify(netcfgResult));
+
+        return netcfgResult;
+      });
+  }
+
+  /**
+   * Execute 'ip route' and parse the result.
+   *
+   * Resolve when the executing is successful and reject otherwise.
+   *
+   * Fulfill params: Command result object, each key of which is the interface
+   *                 name and value is { src(string), gateway(string),
+   *                 default(boolean) }.
+   * Reject params: String that indicates the reason of rejection.
+   *
+   * @return A deferred promise.
+   */
+  function exeAndParseIpRoute() {
+    return runEmulatorShellSafe(['ip', 'route'])
+      .then(function (aLines) {
+        // Sample output:
+        //
+        // 10.0.2.4 via 10.0.2.2 dev rmnet0
+        // 10.0.2.3 via 10.0.2.2 dev rmnet0
+        // 192.168.1.0/24 dev wlan0  proto kernel  scope link  src 192.168.1.1
+        // 10.0.2.0/24 dev eth0  proto kernel  scope link  src 10.0.2.15
+        // 10.0.2.0/24 dev rmnet0  proto kernel  scope link  src 10.0.2.100
+        // default via 10.0.2.2 dev rmnet0
+        // default via 10.0.2.2 dev eth0  metric 2
+        //
+
+        let ipRouteResult = {};
+
+        // Parse source ip for each interface.
+        aLines.forEach(function (aLine) {
+          let tokens = aLine.trim().split(/\s+/);
+          let srcIndex = tokens.indexOf('src');
+          if (srcIndex < 0 || srcIndex + 1 >= tokens.length) {
+            return;
+          }
+          let ifname = tokens[2];
+          let src = tokens[srcIndex + 1];
+          ipRouteResult[ifname] = { src: src, default: false, gateway: null };
+        });
+
+        // Parse default interfaces.
+        aLines.forEach(function (aLine) {
+          let tokens = aLine.split(/\s+/);
+          if (tokens.length < 2) {
+            return;
+          }
+          if ('default' === tokens[0]) {
+            let ifnameIndex = tokens.indexOf('dev');
+            if (ifnameIndex < 0 || ifnameIndex + 1 >= tokens.length) {
+              return;
+            }
+            let ifname = tokens[ifnameIndex + 1];
+            if (!ipRouteResult[ifname]) {
+              return;
+            }
+            ipRouteResult[ifname].default = true;
+            let gwIndex = tokens.indexOf('via');
+            if (gwIndex < 0 || gwIndex + 1 >= tokens.length) {
+              return;
+            }
+            ipRouteResult[ifname].gateway = tokens[gwIndex + 1];
+            return;
+          }
+        });
+        log("ip route result:" + JSON.stringify(ipRouteResult));
+
+        return ipRouteResult;
+      });
+  }
+
+  /**
    * Verify everything about routing when the wifi tethering is either on or off.
    *
    * We use two unix commands to verify the routing: 'netcfg' and 'ip route'.
    * For now the following two things will be checked:
    *   1) The default route interface should be 'rmnet0'.
    *   2) wlan0 is up and its ip is set to a private subnet.
    *
    * We also verify iptables output as netd's NatController will execute
@@ -1039,97 +1167,18 @@ let gTestSuite = (function() {
    * Resolve when the verification is successful and reject otherwise.
    *
    * Fulfill params: (none)
    * Reject params: String that indicates the reason of rejection.
    *
    * @return A deferred promise.
    */
   function verifyTetheringRouting(aEnabled) {
-    let netcfgResult = {};
-    let ipRouteResult = {};
-
-    // Execute 'netcfg' and parse to |netcfgResult|, each key of which is the
-    // interface name and value is { ip(string) }.
-    function exeAndParseNetcfg() {
-      return runEmulatorShellSafe(['netcfg'])
-        .then(function (aLines) {
-          // Sample output:
-          //
-          // lo       UP     127.0.0.1/8   0x00000049 00:00:00:00:00:00
-          // eth0     UP     10.0.2.15/24  0x00001043 52:54:00:12:34:56
-          // rmnet1   DOWN   0.0.0.0/0   0x00001002 52:54:00:12:34:58
-          // rmnet2   DOWN   0.0.0.0/0   0x00001002 52:54:00:12:34:59
-          // rmnet3   DOWN   0.0.0.0/0   0x00001002 52:54:00:12:34:5a
-          // wlan0    UP     192.168.1.1/24  0x00001043 52:54:00:12:34:5b
-          // sit0     DOWN   0.0.0.0/0   0x00000080 00:00:00:00:00:00
-          // rmnet0   UP     10.0.2.100/24  0x00001043 52:54:00:12:34:57
-          //
-          aLines.forEach(function (aLine) {
-            let tokens = aLine.split(/\s+/);
-            if (tokens.length < 5) {
-              return;
-            }
-            let ifname = tokens[0];
-            let ip = (tokens[2].split('/'))[0];
-            netcfgResult[ifname] = { ip: ip };
-          });
-        });
-    }
-
-    // Execute 'ip route' and parse to |ipRouteResult|, each key of which is the
-    // interface name and value is { src(string), default(boolean) }.
-    function exeAndParseIpRoute() {
-      return runEmulatorShellSafe(['ip', 'route'])
-        .then(function (aLines) {
-          // Sample output:
-          //
-          // 10.0.2.4 via 10.0.2.2 dev rmnet0
-          // 10.0.2.3 via 10.0.2.2 dev rmnet0
-          // 192.168.1.0/24 dev wlan0  proto kernel  scope link  src 192.168.1.1
-          // 10.0.2.0/24 dev eth0  proto kernel  scope link  src 10.0.2.15
-          // 10.0.2.0/24 dev rmnet0  proto kernel  scope link  src 10.0.2.100
-          // default via 10.0.2.2 dev rmnet0
-          // default via 10.0.2.2 dev eth0  metric 2
-          //
-
-          // Parse source ip for each interface.
-          aLines.forEach(function (aLine) {
-            let tokens = aLine.trim().split(/\s+/);
-            let srcIndex = tokens.indexOf('src');
-            if (srcIndex < 0 || srcIndex + 1 >= tokens.length) {
-              return;
-            }
-            let ifname = tokens[2];
-            let src = tokens[srcIndex + 1];
-            ipRouteResult[ifname] = { src: src, default: false };
-          });
-
-          // Parse default interfaces.
-          aLines.forEach(function (aLine) {
-            let tokens = aLine.split(/\s+/);
-            if (tokens.length < 2) {
-              return;
-            }
-            if ('default' === tokens[0]) {
-              let ifnameIndex = tokens.indexOf('dev');
-              if (ifnameIndex < 0 || ifnameIndex + 1 >= tokens.length) {
-                return;
-              }
-              let ifname = tokens[ifnameIndex + 1];
-              if (ipRouteResult[ifname]) {
-                ipRouteResult[ifname].default = true;
-              }
-              return;
-            }
-          });
-
-        });
-
-    }
+    let netcfgResult;
+    let ipRouteResult;
 
     // Find MASQUERADE in POSTROUTING section. 'MASQUERADE' should be found
     // when tethering is enabled. 'MASQUERADE' shouldn't be found when tethering
     // is disabled.
     function verifyIptables() {
       return runEmulatorShellSafe(['iptables', '-t', 'nat', '-L', 'POSTROUTING'])
         .then(function(aLines) {
           // $ iptables -t nat -L POSTROUTING
@@ -1153,32 +1202,31 @@ let gTestSuite = (function() {
           if ((aEnabled && !found) || (!aEnabled && found)) {
             throw 'MASQUERADE' + (found ? '' : ' not') + ' found while tethering is ' +
                   (aEnabled ? 'enabled' : 'disabled');
           }
         });
     }
 
     function verifyDefaultRouteAndIp(aExpectedWifiTetheringIp) {
-      log(JSON.stringify(ipRouteResult));
-      log(JSON.stringify(netcfgResult));
-
       if (aEnabled) {
         isOrThrow(ipRouteResult['rmnet0'].src, netcfgResult['rmnet0'].ip, 'rmnet0.ip');
         isOrThrow(ipRouteResult['rmnet0'].default, true, 'rmnet0.default');
 
         isOrThrow(ipRouteResult['wlan0'].src, netcfgResult['wlan0'].ip, 'wlan0.ip');
         isOrThrow(ipRouteResult['wlan0'].src, aExpectedWifiTetheringIp, 'expected ip');
         isOrThrow(ipRouteResult['wlan0'].default, false, 'wlan0.default');
       }
     }
 
     return verifyIptables()
       .then(exeAndParseNetcfg)
+      .then((aResult) => { netcfgResult = aResult; })
       .then(exeAndParseIpRoute)
+      .then((aResult) => { ipRouteResult = aResult; })
       .then(() => getSettings(SETTINGS_TETHERING_WIFI_IP))
       .then(ip => verifyDefaultRouteAndIp(ip));
   }
 
   /**
    * Clean up all the allocated resources and running services for the test.
    *
    * After the test no matter success or failure, we should
@@ -1239,26 +1287,29 @@ let gTestSuite = (function() {
   suite.killAllHostapd = killAllHostapd;
   suite.wrapDomRequestAsPromise = wrapDomRequestAsPromise;
   suite.waitForWifiManagerEventOnce = waitForWifiManagerEventOnce;
   suite.verifyNumOfProcesses = verifyNumOfProcesses;
   suite.testWifiScanWithRetry = testWifiScanWithRetry;
   suite.getFirstIndexBySsid = getFirstIndexBySsid;
   suite.testAssociate = testAssociate;
   suite.getKnownNetworks = getKnownNetworks;
+  suite.setStaticIpMode = setStaticIpMode;
   suite.requestWifiScan = requestWifiScan;
   suite.waitForConnected = waitForConnected;
   suite.forgetNetwork = forgetNetwork;
   suite.waitForTimeout = waitForTimeout;
   suite.waitForRilDataConnected = waitForRilDataConnected;
   suite.requestTetheringEnabled = requestTetheringEnabled;
   suite.importCert = importCert;
   suite.getImportedCerts = getImportedCerts;
   suite.deleteCert = deleteCert;
   suite.writeFile = writeFile;
+  suite.exeAndParseNetcfg = exeAndParseNetcfg;
+  suite.exeAndParseIpRoute = exeAndParseIpRoute;
 
   /**
    * Common test routine.
    *
    * Start a test with the given test case chain. The test environment will be
    * settled down before the test. After the test, all the affected things will
    * be restored.
    *
--- a/dom/wifi/test/marionette/manifest.ini
+++ b/dom/wifi/test/marionette/manifest.ini
@@ -3,16 +3,17 @@ b2g = true
 browser = false
 qemu = true
 
 [test_wifi_enable.js]
 [test_wifi_scan.js]
 [test_wifi_associate.js]
 [test_wifi_associate_wo_connect.js]
 [test_wifi_auto_connect.js]
+[test_wifi_static_ip.js]
 [test_wifi_tethering_wifi_disabled.js]
 [test_wifi_tethering_wifi_inactive.js]
 [test_wifi_tethering_wifi_active.js]
 [test_wifi_manage_server_certificate.js]
 [test_wifi_manage_user_certificate.js]
 [test_wifi_manage_pkcs12_certificate.js]
 [test_wifi_associate_WPA_EAP_PEAP.js]
 [test_wifi_associate_WPA_EAP_TTLS.js]
new file mode 100644
--- /dev/null
+++ b/dom/wifi/test/marionette/test_wifi_static_ip.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const STATIC_IP_CONFIG = {
+  enabled: true,
+  ipaddr: "192.168.111.222",
+  proxy: "",
+  maskLength: 24,
+  gateway: "192.168.111.1",
+  dns1: "8.8.8.8",
+  dns2: "8.8.4.4",
+};
+
+function testAssociateWithStaticIp(aNetwork, aStaticIpConfig) {
+  return gTestSuite.setStaticIpMode(aNetwork, aStaticIpConfig)
+    .then(() => gTestSuite.testAssociate(aNetwork))
+    // Check ip address and prefix.
+    .then(() => gTestSuite.exeAndParseNetcfg())
+    .then((aResult) => {
+      is(aResult["wlan0"].ip, aStaticIpConfig.ipaddr, "Check ip address");
+      is(aResult["wlan0"].prefix, aStaticIpConfig.maskLength, "Check prefix");
+    })
+    // Check routing.
+    .then(() => gTestSuite.exeAndParseIpRoute())
+    .then((aResult) => {
+      is(aResult["wlan0"].src, aStaticIpConfig.ipaddr, "Check ip address");
+      is(aResult["wlan0"].default, true, "Check default route");
+      is(aResult["wlan0"].gateway, aStaticIpConfig.gateway, "Check gateway");
+    });
+}
+
+// Start test.
+gTestSuite.doTest(function() {
+  return gTestSuite.ensureWifiEnabled(true)
+    .then(() => gTestSuite.requestWifiScan())
+    .then((aNetworks) => testAssociateWithStaticIp(aNetworks[0],
+                                                   STATIC_IP_CONFIG));
+});
--- a/ipc/bluetooth/BluetoothDaemonConnection.cpp
+++ b/ipc/bluetooth/BluetoothDaemonConnection.cpp
@@ -7,18 +7,16 @@
 #include "BluetoothDaemonConnection.h"
 #include <fcntl.h>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <sys/un.h>
 #include <unistd.h>
 #include "mozilla/ipc/BluetoothDaemonConnectionConsumer.h"
 #include "mozilla/ipc/DataSocket.h"
-#include "mozilla/ipc/UnixSocketConnector.h"
-#include "mozilla/ipc/UnixSocketWatcher.h"
 #include "nsTArray.h"
 #include "nsXULAppAPI.h"
 
 #ifdef CHROMIUM_LOG
 #undef CHROMIUM_LOG
 #endif
 
 #if defined(MOZ_WIDGET_GONK)
@@ -196,45 +194,26 @@ BluetoothDaemonPDUConsumer::BluetoothDae
 
 BluetoothDaemonPDUConsumer::~BluetoothDaemonPDUConsumer()
 { }
 
 //
 // BluetoothDaemonConnectionIO
 //
 
-class BluetoothDaemonConnectionIO final
-  : public UnixSocketWatcher
-  , public ConnectionOrientedSocketIO
+class BluetoothDaemonConnectionIO final : public ConnectionOrientedSocketIO
 {
 public:
   BluetoothDaemonConnectionIO(nsIThread* aConsumerThread,
                               MessageLoop* aIOLoop,
                               int aFd, ConnectionStatus aConnectionStatus,
+                              UnixSocketConnector* aConnector,
                               BluetoothDaemonConnection* aConnection,
                               BluetoothDaemonPDUConsumer* aConsumer);
 
-  // Task callback methods
-  //
-
-  void Send(UnixSocketIOBuffer* aBuffer);
-
-  void OnSocketCanReceiveWithoutBlocking() override;
-  void OnSocketCanSendWithoutBlocking() override;
-
-  void OnConnected() override;
-  void OnError(const char* aFunction, int aErrno) override;
-
-  // Methods for |ConnectionOrientedSocketIO|
-  //
-
-  nsresult Accept(int aFd,
-                  const struct sockaddr* aAddress,
-                  socklen_t aAddressLength) override;
-
   // Methods for |DataSocketIO|
   //
 
   nsresult QueryReceiveBuffer(UnixSocketIOBuffer** aBuffer) override;
   void ConsumeBuffer() override;
   void DiscardBuffer() override;
 
   // Methods for |SocketIOBase|
@@ -255,120 +234,32 @@ private:
   bool mShuttingDownOnIOThread;
 };
 
 BluetoothDaemonConnectionIO::BluetoothDaemonConnectionIO(
   nsIThread* aConsumerThread,
   MessageLoop* aIOLoop,
   int aFd,
   ConnectionStatus aConnectionStatus,
+  UnixSocketConnector* aConnector,
   BluetoothDaemonConnection* aConnection,
   BluetoothDaemonPDUConsumer* aConsumer)
-  : UnixSocketWatcher(aIOLoop, aFd, aConnectionStatus)
-  , ConnectionOrientedSocketIO(aConsumerThread)
+  : ConnectionOrientedSocketIO(aConsumerThread,
+                               aIOLoop,
+                               aFd,
+                               aConnectionStatus,
+                               aConnector)
   , mConnection(aConnection)
   , mConsumer(aConsumer)
   , mShuttingDownOnIOThread(false)
 {
   MOZ_ASSERT(mConnection);
   MOZ_ASSERT(mConsumer);
 }
 
-void
-BluetoothDaemonConnectionIO::Send(UnixSocketIOBuffer* aBuffer)
-{
-  MOZ_ASSERT(aBuffer);
-
-  EnqueueData(aBuffer);
-  AddWatchers(WRITE_WATCHER, false);
-}
-
-void
-BluetoothDaemonConnectionIO::OnSocketCanReceiveWithoutBlocking()
-{
-  ssize_t res = ReceiveData(GetFd());
-  if (res < 0) {
-    /* I/O error */
-    RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
-  } else if (!res) {
-    /* EOF or peer shutdown */
-    RemoveWatchers(READ_WATCHER);
-  }
-}
-
-void
-BluetoothDaemonConnectionIO::OnSocketCanSendWithoutBlocking()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
-  MOZ_ASSERT(!IsShutdownOnIOThread());
-
-  if (NS_WARN_IF(NS_FAILED(SendPendingData(GetFd())))) {
-    RemoveWatchers(WRITE_WATCHER);
-  }
-}
-
-void
-BluetoothDaemonConnectionIO::OnConnected()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
-
-  GetConsumerThread()->Dispatch(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS),
-    NS_DISPATCH_NORMAL);
-
-  AddWatchers(READ_WATCHER, true);
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-}
-
-void
-BluetoothDaemonConnectionIO::OnError(const char* aFunction, int aErrno)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-
-  UnixFdWatcher::OnError(aFunction, aErrno);
-
-  // Clean up watchers, status, fd
-  Close();
-
-  // Tell the consumer thread we've errored
-  GetConsumerThread()->Dispatch(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_ERROR),
-    NS_DISPATCH_NORMAL);
-}
-
-// |ConnectionOrientedSocketIO|
-
-nsresult
-BluetoothDaemonConnectionIO::Accept(int aFd,
-                                    const struct sockaddr* aAddress,
-                                    socklen_t aAddressLength)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTING);
-
-  // File-descriptor setup
-
-  if (TEMP_FAILURE_RETRY(fcntl(aFd, F_SETFL, O_NONBLOCK)) < 0) {
-    OnError("fcntl", errno);
-    ScopedClose cleanupFd(aFd);
-    return NS_ERROR_FAILURE;
-  }
-
-  SetSocket(aFd, SOCKET_IS_CONNECTED);
-
-  // Signal success
-  OnConnected();
-
-  return NS_OK;
-}
-
 // |DataSocketIO|
 
 nsresult
 BluetoothDaemonConnectionIO::QueryReceiveBuffer(UnixSocketIOBuffer** aBuffer)
 {
   MOZ_ASSERT(aBuffer);
 
   if (!mPDU) {
@@ -459,26 +350,21 @@ BluetoothDaemonConnection::~BluetoothDae
 nsresult
 BluetoothDaemonConnection::PrepareAccept(UnixSocketConnector* aConnector,
                                          nsIThread* aConsumerThread,
                                          MessageLoop* aIOLoop,
                                          ConnectionOrientedSocketIO*& aIO)
 {
   MOZ_ASSERT(!mIO);
 
-  // |BluetoothDaemonConnection| now owns the connector, but doesn't
-  // actually use it. So the connector is stored in an auto pointer
-  // to be deleted at the end of the method.
-  nsAutoPtr<UnixSocketConnector> connector(aConnector);
-
   SetConnectionStatus(SOCKET_CONNECTING);
 
   mIO = new BluetoothDaemonConnectionIO(
     aConsumerThread, aIOLoop, -1, UnixSocketWatcher::SOCKET_IS_CONNECTING,
-    this, mPDUConsumer);
+    aConnector, this, mPDUConsumer);
   aIO = mIO;
 
   return NS_OK;
 }
 
 // |DataSocket|
 
 void
--- a/ipc/unixsocket/ConnectionOrientedSocket.cpp
+++ b/ipc/unixsocket/ConnectionOrientedSocket.cpp
@@ -1,31 +1,191 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ConnectionOrientedSocket.h"
+#include "UnixSocketConnector.h"
 
 namespace mozilla {
 namespace ipc {
 
 //
 // ConnectionOrientedSocketIO
 //
 
 ConnectionOrientedSocketIO::ConnectionOrientedSocketIO(
-  nsIThread* aConsumerThread)
+  nsIThread* aConsumerThread,
+  MessageLoop* aIOLoop,
+  int aFd,
+  ConnectionStatus aConnectionStatus,
+  UnixSocketConnector* aConnector)
   : DataSocketIO(aConsumerThread)
-{ }
+  , UnixSocketWatcher(aIOLoop, aFd, aConnectionStatus)
+  , mConnector(aConnector)
+  , mPeerAddressLength(0)
+{
+  MOZ_ASSERT(mConnector);
+}
+
+ConnectionOrientedSocketIO::ConnectionOrientedSocketIO(
+  nsIThread* aConsumerThread,
+  MessageLoop* aIOLoop,
+  UnixSocketConnector* aConnector)
+  : DataSocketIO(aConsumerThread)
+  , UnixSocketWatcher(aIOLoop)
+  , mConnector(aConnector)
+  , mPeerAddressLength(0)
+{
+  MOZ_ASSERT(mConnector);
+}
 
 ConnectionOrientedSocketIO::~ConnectionOrientedSocketIO()
 { }
 
+nsresult
+ConnectionOrientedSocketIO::Accept(int aFd,
+                                   const struct sockaddr* aPeerAddress,
+                                   socklen_t aPeerAddressLength)
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTING);
+
+  SetSocket(aFd, SOCKET_IS_CONNECTED);
+
+  // Address setup
+  mPeerAddressLength = aPeerAddressLength;
+  memcpy(&mPeerAddress, aPeerAddress, mPeerAddressLength);
+
+  // Signal success and start data transfer
+  OnConnected();
+
+  return NS_OK;
+}
+
+nsresult
+ConnectionOrientedSocketIO::Connect()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(!IsOpen());
+
+  struct sockaddr* peerAddress =
+    reinterpret_cast<struct sockaddr*>(&mPeerAddress);
+  mPeerAddressLength = sizeof(mPeerAddress);
+
+  int fd;
+  nsresult rv = mConnector->CreateStreamSocket(peerAddress,
+                                               &mPeerAddressLength,
+                                               fd);
+  if (NS_FAILED(rv)) {
+    // Tell the consumer thread we've errored
+    GetConsumerThread()->Dispatch(
+      new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_ERROR),
+      NS_DISPATCH_NORMAL);
+    return NS_ERROR_FAILURE;
+  }
+
+  SetFd(fd);
+
+  // calls OnConnected() on success, or OnError() otherwise
+  rv = UnixSocketWatcher::Connect(peerAddress, mPeerAddressLength);
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+void
+ConnectionOrientedSocketIO::Send(UnixSocketIOBuffer* aBuffer)
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+
+  EnqueueData(aBuffer);
+  AddWatchers(WRITE_WATCHER, false);
+}
+
+// |UnixSocketWatcher|
+
+void
+ConnectionOrientedSocketIO::OnSocketCanReceiveWithoutBlocking()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984
+
+  ssize_t res = ReceiveData(GetFd());
+  if (res < 0) {
+    /* I/O error */
+    RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
+  } else if (!res) {
+    /* EOF or peer shutdown */
+    RemoveWatchers(READ_WATCHER);
+  }
+}
+
+void
+ConnectionOrientedSocketIO::OnSocketCanSendWithoutBlocking()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984
+  MOZ_ASSERT(!IsShutdownOnIOThread());
+
+  nsresult rv = SendPendingData(GetFd());
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  if (HasPendingData()) {
+    AddWatchers(WRITE_WATCHER, false);
+  }
+}
+
+void
+ConnectionOrientedSocketIO::OnConnected()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
+
+  GetConsumerThread()->Dispatch(
+    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS),
+    NS_DISPATCH_NORMAL);
+
+  AddWatchers(READ_WATCHER, true);
+  if (HasPendingData()) {
+    AddWatchers(WRITE_WATCHER, false);
+  }
+}
+
+void
+ConnectionOrientedSocketIO::OnListening()
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+
+  NS_NOTREACHED("Invalid call to |ConnectionOrientedSocketIO::OnListening|");
+}
+
+void
+ConnectionOrientedSocketIO::OnError(const char* aFunction, int aErrno)
+{
+  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
+
+  UnixFdWatcher::OnError(aFunction, aErrno);
+
+  // Clean up watchers, status, fd
+  Close();
+
+  // Tell the consumer thread we've errored
+  GetConsumerThread()->Dispatch(
+    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_ERROR),
+    NS_DISPATCH_NORMAL);
+}
+
 //
 // ConnectionOrientedSocket
 //
 
 ConnectionOrientedSocket::~ConnectionOrientedSocket()
 { }
 
 }
--- a/ipc/unixsocket/ConnectionOrientedSocket.h
+++ b/ipc/unixsocket/ConnectionOrientedSocket.h
@@ -4,39 +4,97 @@
  * 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_ipc_connectionorientedsocket_h
 #define mozilla_ipc_connectionorientedsocket_h
 
 #include <sys/socket.h>
 #include "DataSocket.h"
+#include "mozilla/ipc/UnixSocketWatcher.h"
 
 class MessageLoop;
 
 namespace mozilla {
 namespace ipc {
 
 class UnixSocketConnector;
 
 /*
  * |ConnectionOrientedSocketIO| and |ConnectionOrientedSocket| define
  * interfaces for implementing stream sockets on I/O and consumer thread.
  * |ListenSocket| uses these classes to handle accepted sockets.
  */
 
-class ConnectionOrientedSocketIO : public DataSocketIO
+class ConnectionOrientedSocketIO
+  : public DataSocketIO
+  , public UnixSocketWatcher
 {
 public:
-  ConnectionOrientedSocketIO(nsIThread* aConsumerThread);
   virtual ~ConnectionOrientedSocketIO();
 
-  virtual nsresult Accept(int aFd,
-                          const struct sockaddr* aAddress,
-                          socklen_t aAddressLength) = 0;
+  nsresult Accept(int aFd,
+                  const struct sockaddr* aAddress,
+                  socklen_t aAddressLength);
+
+  nsresult Connect();
+
+  void Send(UnixSocketIOBuffer* aBuffer);
+
+  // Methods for |UnixSocketWatcher|
+  //
+
+  void OnSocketCanReceiveWithoutBlocking() final;
+  void OnSocketCanSendWithoutBlocking() final;
+
+  void OnListening() final;
+  void OnConnected() final;
+  void OnError(const char* aFunction, int aErrno) final;
+
+protected:
+  /**
+   * Constructs an instance of |ConnectionOrientedSocketIO|
+   *
+   * @param aConsumerThread The socket's consumer thread.
+   * @param aIOLoop The socket's I/O loop.
+   * @param aFd The socket file descriptor.
+   * @param aConnectionStatus The connection status for |aFd|.
+   * @param aConnector Connector object for socket-type-specific methods.
+   */
+  ConnectionOrientedSocketIO(nsIThread* aConsumerThread,
+                             MessageLoop* aIOLoop,
+                             int aFd, ConnectionStatus aConnectionStatus,
+                             UnixSocketConnector* aConnector);
+
+  /**
+   * Constructs an instance of |ConnectionOrientedSocketIO|
+   *
+   * @param aConsumerThread The socket's consumer thread.
+   * @param aIOLoop The socket's I/O loop.
+   * @param aConnector Connector object for socket-type-specific methods.
+   */
+  ConnectionOrientedSocketIO(nsIThread* aConsumerThread,
+                             MessageLoop* aIOLoop,
+                             UnixSocketConnector* aConnector);
+
+private:
+  /**
+   * Connector object used to create the connection we are currently using.
+   */
+  nsAutoPtr<UnixSocketConnector> mConnector;
+
+  /**
+   * Number of valid bytes in |mPeerAddress|.
+   */
+  socklen_t mPeerAddressLength;
+
+  /**
+   * Address of the socket's current peer.
+   */
+  struct sockaddr_storage mPeerAddress;
 };
 
 class ConnectionOrientedSocket : public DataSocket
 {
 public:
   /**
    * Prepares an instance of |ConnectionOrientedSocket| in DISCONNECTED
    * state for accepting a connection. Consumer-thread only.
--- a/ipc/unixsocket/StreamSocket.cpp
+++ b/ipc/unixsocket/StreamSocket.cpp
@@ -15,19 +15,17 @@ static const size_t MAX_READ_SIZE = 1 <<
 
 namespace mozilla {
 namespace ipc {
 
 //
 // StreamSocketIO
 //
 
-class StreamSocketIO final
-  : public UnixSocketWatcher
-  , public ConnectionOrientedSocketIO
+class StreamSocketIO final : public ConnectionOrientedSocketIO
 {
 public:
   class ConnectTask;
   class DelayedConnectTask;
   class ReceiveRunnable;
 
   StreamSocketIO(nsIThread* aConsumerThread,
                  MessageLoop* mIOLoop,
@@ -45,42 +43,16 @@ public:
 
   // Delayed-task handling
   //
 
   void SetDelayedConnectTask(CancelableTask* aTask);
   void ClearDelayedConnectTask();
   void CancelDelayedConnectTask();
 
-  // Task callback methods
-  //
-
-  /**
-   * Connect to a socket
-   */
-  void Connect();
-
-  void Send(UnixSocketIOBuffer* aBuffer);
-
-  // I/O callback methods
-  //
-
-  void OnConnected() override;
-  void OnError(const char* aFunction, int aErrno) override;
-  void OnListening() override;
-  void OnSocketCanReceiveWithoutBlocking() override;
-  void OnSocketCanSendWithoutBlocking() override;
-
-  // Methods for |ConnectionOrientedSocketIO|
-  //
-
-  nsresult Accept(int aFd,
-                  const struct sockaddr* aAddress,
-                  socklen_t aAddressLength) override;
-
   // Methods for |DataSocket|
   //
 
   nsresult QueryReceiveBuffer(UnixSocketIOBuffer** aBuffer) override;
   void ConsumeBuffer() override;
   void DiscardBuffer() override;
 
   // Methods for |SocketIOBase|
@@ -90,88 +62,67 @@ public:
 
   bool IsShutdownOnConsumerThread() const override;
   bool IsShutdownOnIOThread() const override;
 
   void ShutdownOnConsumerThread() override;
   void ShutdownOnIOThread() override;
 
 private:
-  void FireSocketError();
-
   /**
    * Consumer pointer. Non-thread safe RefPtr, so should only be manipulated
    * directly from consumer thread. All non-consumer-thread accesses should
    * happen with mIO as container.
    */
   RefPtr<StreamSocket> mStreamSocket;
 
   /**
-   * Connector object used to create the connection we are currently using.
-   */
-  nsAutoPtr<UnixSocketConnector> mConnector;
-
-  /**
    * If true, do not requeue whatever task we're running
    */
   bool mShuttingDownOnIOThread;
 
   /**
-   * Number of valid bytes in |mAddress|
-   */
-  socklen_t mAddressLength;
-
-  /**
-   * Address structure of the socket currently in use
-   */
-  struct sockaddr_storage mAddress;
-
-  /**
    * Task member for delayed connect task. Should only be access on consumer
    * thread.
    */
   CancelableTask* mDelayedConnectTask;
 
   /**
    * I/O buffer for received data
    */
   nsAutoPtr<UnixSocketRawData> mBuffer;
 };
 
 StreamSocketIO::StreamSocketIO(nsIThread* aConsumerThread,
                                MessageLoop* aIOLoop,
                                StreamSocket* aStreamSocket,
                                UnixSocketConnector* aConnector)
-  : UnixSocketWatcher(aIOLoop)
-  , ConnectionOrientedSocketIO(aConsumerThread)
+  : ConnectionOrientedSocketIO(aConsumerThread, aIOLoop, aConnector)
   , mStreamSocket(aStreamSocket)
-  , mConnector(aConnector)
   , mShuttingDownOnIOThread(false)
-  , mAddressLength(0)
   , mDelayedConnectTask(nullptr)
 {
   MOZ_ASSERT(mStreamSocket);
-  MOZ_ASSERT(mConnector);
 }
 
 StreamSocketIO::StreamSocketIO(nsIThread* aConsumerThread,
                                MessageLoop* aIOLoop,
                                int aFd, ConnectionStatus aConnectionStatus,
                                StreamSocket* aStreamSocket,
                                UnixSocketConnector* aConnector)
-  : UnixSocketWatcher(aIOLoop, aFd, aConnectionStatus)
-  , ConnectionOrientedSocketIO(aConsumerThread)
+  : ConnectionOrientedSocketIO(aConsumerThread,
+                               aIOLoop,
+                               aFd,
+                               aConnectionStatus,
+                               aConnector)
   , mStreamSocket(aStreamSocket)
-  , mConnector(aConnector)
   , mShuttingDownOnIOThread(false)
-  , mAddressLength(0)
   , mDelayedConnectTask(nullptr)
 {
   MOZ_ASSERT(mStreamSocket);
-  MOZ_ASSERT(mConnector);
 }
 
 StreamSocketIO::~StreamSocketIO()
 {
   MOZ_ASSERT(IsConsumerThread());
   MOZ_ASSERT(IsShutdownOnConsumerThread());
 }
 
@@ -211,155 +162,16 @@ StreamSocketIO::CancelDelayedConnectTask
   if (!mDelayedConnectTask) {
     return;
   }
 
   mDelayedConnectTask->Cancel();
   ClearDelayedConnectTask();
 }
 
-void
-StreamSocketIO::Connect()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(mConnector);
-
-  MOZ_ASSERT(!IsOpen());
-
-  struct sockaddr* address = reinterpret_cast<struct sockaddr*>(&mAddress);
-  mAddressLength = sizeof(mAddress);
-
-  int fd;
-  nsresult rv = mConnector->CreateStreamSocket(address, &mAddressLength, fd);
-  if (NS_FAILED(rv)) {
-    FireSocketError();
-    return;
-  }
-  SetFd(fd);
-
-  // calls OnConnected() on success, or OnError() otherwise
-  rv = UnixSocketWatcher::Connect(address, mAddressLength);
-  NS_WARN_IF(NS_FAILED(rv));
-}
-
-void
-StreamSocketIO::Send(UnixSocketIOBuffer* aData)
-{
-  EnqueueData(aData);
-  AddWatchers(WRITE_WATCHER, false);
-}
-
-void
-StreamSocketIO::OnConnected()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED);
-
-  GetConsumerThread()->Dispatch(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS),
-    NS_DISPATCH_NORMAL);
-
-  AddWatchers(READ_WATCHER, true);
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-}
-
-void
-StreamSocketIO::OnListening()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-
-  NS_NOTREACHED("Invalid call to |StreamSocketIO::OnListening|");
-}
-
-void
-StreamSocketIO::OnError(const char* aFunction, int aErrno)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-
-  UnixFdWatcher::OnError(aFunction, aErrno);
-  FireSocketError();
-}
-
-void
-StreamSocketIO::OnSocketCanReceiveWithoutBlocking()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984
-
-  ssize_t res = ReceiveData(GetFd());
-  if (res < 0) {
-    /* I/O error */
-    RemoveWatchers(READ_WATCHER|WRITE_WATCHER);
-  } else if (!res) {
-    /* EOF or peer shutdown */
-    RemoveWatchers(READ_WATCHER);
-  }
-}
-
-void
-StreamSocketIO::OnSocketCanSendWithoutBlocking()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTED); // see bug 990984
-
-  nsresult rv = SendPendingData(GetFd());
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-}
-
-void
-StreamSocketIO::FireSocketError()
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-
-  // Clean up watchers, statuses, fds
-  Close();
-
-  // Tell the consumer thread we've errored
-  GetConsumerThread()->Dispatch(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_ERROR),
-    NS_DISPATCH_NORMAL);
-}
-
-// |ConnectionOrientedSocketIO|
-
-nsresult
-StreamSocketIO::Accept(int aFd,
-                       const struct sockaddr* aAddress,
-                       socklen_t aAddressLength)
-{
-  MOZ_ASSERT(MessageLoopForIO::current() == GetIOLoop());
-  MOZ_ASSERT(GetConnectionStatus() == SOCKET_IS_CONNECTING);
-
-  SetSocket(aFd, SOCKET_IS_CONNECTED);
-
-  // Address setup
-  mAddressLength = aAddressLength;
-  memcpy(&mAddress, aAddress, mAddressLength);
-
-  // Signal success
-  GetConsumerThread()->Dispatch(
-    new SocketIOEventRunnable(this, SocketIOEventRunnable::CONNECT_SUCCESS),
-    NS_DISPATCH_NORMAL);
-
-  AddWatchers(READ_WATCHER, true);
-  if (HasPendingData()) {
-    AddWatchers(WRITE_WATCHER, false);
-  }
-
-  return NS_OK;
-}
-
 // |DataSocketIO|
 
 nsresult
 StreamSocketIO::QueryReceiveBuffer(UnixSocketIOBuffer** aBuffer)
 {
   MOZ_ASSERT(aBuffer);
 
   if (!mBuffer) {
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -208,16 +208,17 @@
 @BINPATH@/components/necko_about.xpt
 @BINPATH@/components/necko_cache.xpt
 @BINPATH@/components/necko_cache2.xpt
 @BINPATH@/components/necko_cookie.xpt
 @BINPATH@/components/necko_dns.xpt
 @BINPATH@/components/necko_file.xpt
 @BINPATH@/components/necko_ftp.xpt
 @BINPATH@/components/necko_http.xpt
+@BINPATH@/components/necko_mdns.xpt
 @BINPATH@/components/necko_res.xpt
 @BINPATH@/components/necko_socket.xpt
 @BINPATH@/components/necko_strconv.xpt
 @BINPATH@/components/necko_viewsource.xpt
 @BINPATH@/components/necko_websocket.xpt
 @BINPATH@/components/necko_wifi.xpt
 @BINPATH@/components/necko_wyciwyg.xpt
 @BINPATH@/components/necko.xpt
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.cpp
@@ -0,0 +1,665 @@
+/* -*- 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 "MDNSResponderOperator.h"
+#include "MDNSResponderReply.h"
+#include "mozilla/Endian.h"
+#include "mozilla/Logging.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsDNSServiceInfo.h"
+#include "nsHashPropertyBag.h"
+#include "nsIProperty.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIVariant.h"
+#include "nsServiceManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsSocketTransportService2.h"
+#include "nsThreadUtils.h"
+#include "nsXPCOMCID.h"
+#include "private/pprio.h"
+
+#include "nsASocketHandler.h"
+
+inline PRLogModuleInfo*
+GetOperatorLog()
+{
+  static PRLogModuleInfo* log = PR_NewLogModule("MDNSResponderOperator");
+  return log;
+}
+#undef LOG_I
+#define LOG_I(...) PR_LOG(GetOperatorLog(), PR_LOG_NOTICE, (__VA_ARGS__))
+#undef LOG_E
+#define LOG_E(...) PR_LOG(GetOperatorLog(), PR_LOG_ERROR, (__VA_ARGS__))
+
+namespace mozilla {
+namespace net {
+
+class MDNSResponderOperator::ServiceWatcher final
+  : public nsASocketHandler
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  // nsASocketHandler methods
+  virtual void OnSocketReady(PRFileDesc* fd, int16_t outFlags) override
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(fd == mFD);
+
+    if (outFlags & (PR_POLL_ERR | PR_POLL_HUP | PR_POLL_NVAL)) {
+      LOG_E("error polling on listening socket (%p)", fd);
+      mCondition = NS_ERROR_UNEXPECTED;
+    }
+
+    if (!(outFlags & PR_POLL_READ)) {
+      return;
+    }
+
+    DNSServiceProcessResult(mService);
+  }
+
+  virtual void OnSocketDetached(PRFileDesc *fd) override
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+    MOZ_ASSERT(fd == mFD);
+
+    if (!mFD) {
+      return;
+    }
+
+    PR_Close(mFD);
+    mFD = nullptr;
+  }
+
+  virtual void IsLocal(bool *aIsLocal) override { *aIsLocal = true; }
+
+  virtual void KeepWhenOffline(bool *aKeepWhenOffline) override
+  {
+    *aKeepWhenOffline = true;
+  }
+
+  virtual uint64_t ByteCountSent() override { return 0; }
+  virtual uint64_t ByteCountReceived() override { return 0; }
+
+  explicit ServiceWatcher(DNSServiceRef aService)
+    : mSts(nullptr)
+    , mService(aService)
+    , mFD(nullptr)
+    , mAttached(false)
+  {
+    if (!gSocketTransportService)
+    {
+      nsCOMPtr<nsISocketTransportService> sts =
+        do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+    }
+  }
+
+  nsresult Init()
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
+
+    if (!mService) {
+      return NS_OK;
+    }
+
+    if (!gSocketTransportService) {
+      return NS_ERROR_FAILURE;
+    }
+    mSts = gSocketTransportService;
+
+    int osfd = DNSServiceRefSockFD(mService);
+    if (osfd == -1) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mFD = PR_ImportFile(osfd);
+    return PostEvent(&ServiceWatcher::OnMsgAttach);
+  }
+
+  void Close()
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() != gSocketThread);
+
+    if (mService) {
+      DNSServiceRefDeallocate(mService);
+      mService = nullptr;
+    }
+
+    if (!gSocketTransportService) {
+      return;
+    }
+
+    PostEvent(&ServiceWatcher::OnMsgClose);
+  }
+
+private:
+  ~ServiceWatcher() = default;
+
+  nsresult PostEvent(void(ServiceWatcher::*func)(void))
+  {
+    nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this, func);
+    return gSocketTransportService->Dispatch(ev, NS_DISPATCH_NORMAL);
+  }
+
+  void OnMsgClose()
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (NS_FAILED(mCondition)) {
+      return;
+    }
+
+    // tear down socket. this signals the STS to detach our socket handler.
+    mCondition = NS_BINDING_ABORTED;
+
+    // if we are attached, then socket transport service will call our
+    // OnSocketDetached method automatically. Otherwise, we have to call it
+    // (and thus close the socket) manually.
+    if (!mAttached) {
+      OnSocketDetached(mFD);
+    }
+  }
+
+  void OnMsgAttach()
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    if (NS_FAILED(mCondition)) {
+      return;
+    }
+
+    mCondition = TryAttach();
+
+    // if we hit an error while trying to attach then bail...
+    if (NS_FAILED(mCondition)) {
+      NS_ASSERTION(!mAttached, "should not be attached already");
+      OnSocketDetached(mFD);
+    }
+
+  }
+
+  nsresult TryAttach()
+  {
+    MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+    nsresult rv;
+
+    if (!gSocketTransportService) {
+      return NS_ERROR_FAILURE;
+    }
+
+    //
+    // find out if it is going to be ok to attach another socket to the STS.
+    // if not then we have to wait for the STS to tell us that it is ok.
+    // the notification is asynchronous, which means that when we could be
+    // in a race to call AttachSocket once notified.  for this reason, when
+    // we get notified, we just re-enter this function.  as a result, we are
+    // sure to ask again before calling AttachSocket.  in this way we deal
+    // with the race condition.  though it isn't the most elegant solution,
+    // it is far simpler than trying to build a system that would guarantee
+    // FIFO ordering (which wouldn't even be that valuable IMO).  see bug
+    // 194402 for more info.
+    //
+    if (!gSocketTransportService->CanAttachSocket()) {
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(this, &ServiceWatcher::OnMsgAttach);
+
+      nsresult rv = gSocketTransportService->NotifyWhenCanAttachSocket(event);
+      if (NS_FAILED(rv)) {
+        return rv;
+      }
+    }
+
+    //
+    // ok, we can now attach our socket to the STS for polling
+    //
+    rv = gSocketTransportService->AttachSocket(mFD, this);
+    if (NS_FAILED(rv)) {
+      return rv;
+    }
+
+    mAttached = true;
+
+    //
+    // now, configure our poll flags for listening...
+    //
+    mPollFlags = (PR_POLL_READ | PR_POLL_EXCEPT);
+
+    return NS_OK;
+  }
+
+  nsRefPtr<nsSocketTransportService> mSts;
+  DNSServiceRef mService;
+  PRFileDesc* mFD;
+  bool mAttached;
+};
+
+NS_IMPL_ISUPPORTS(MDNSResponderOperator::ServiceWatcher, nsISupports)
+
+MDNSResponderOperator::MDNSResponderOperator()
+  : mService(nullptr)
+  , mWatcher(nullptr)
+  , mThread(NS_GetCurrentThread())
+  , mIsCancelled(false)
+{
+}
+
+MDNSResponderOperator::~MDNSResponderOperator()
+{
+  Stop();
+}
+
+nsresult
+MDNSResponderOperator::Start()
+{
+  if (mIsCancelled) {
+    return NS_OK;
+  }
+
+  if (IsServing()) {
+    Stop();
+  }
+
+  return NS_OK;
+}
+
+nsresult
+MDNSResponderOperator::Stop()
+{
+  mThread = nullptr;
+  return ResetService(nullptr);
+}
+
+nsresult
+MDNSResponderOperator::ResetService(DNSServiceRef aService)
+{
+  nsresult rv;
+
+  if (aService != mService) {
+    if (mWatcher) {
+      mWatcher->Close();
+      mWatcher = nullptr;
+    }
+
+    if (aService) {
+      nsRefPtr<ServiceWatcher> watcher = new ServiceWatcher(aService);
+      if (NS_WARN_IF(NS_FAILED(watcher->Init()))) {
+        return rv;
+      }
+      mWatcher = watcher;
+    }
+
+    mService = aService;
+  }
+  return NS_OK;
+}
+
+BrowseOperator::BrowseOperator(const nsACString& aServiceType,
+                               nsIDNSServiceDiscoveryListener* aListener)
+  : MDNSResponderOperator()
+  , mServiceType(aServiceType)
+  , mListener(aListener)
+{
+}
+
+nsresult
+BrowseOperator::Start()
+{
+  nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+    return rv;
+  }
+
+  DNSServiceRef service = nullptr;
+  DNSServiceErrorType err = DNSServiceBrowse(&service,
+                                             0,
+                                             kDNSServiceInterfaceIndexAny,
+                                             mServiceType.get(),
+                                             nullptr,
+                                             &BrowseReplyRunnable::Reply,
+                                             this);
+  NS_WARN_IF(kDNSServiceErr_NoError != err);
+
+  if (mListener) {
+    if (kDNSServiceErr_NoError == err) {
+      mListener->OnDiscoveryStarted(mServiceType);
+    } else {
+      mListener->OnStartDiscoveryFailed(mServiceType, err);
+    }
+  }
+
+  if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return ResetService(service);
+}
+
+nsresult
+BrowseOperator::Stop()
+{
+  bool isServing = IsServing();
+  nsresult rv = MDNSResponderOperator::Stop();
+
+  if (isServing && mListener) {
+    if (NS_SUCCEEDED(rv)) {
+      mListener->OnDiscoveryStopped(mServiceType);
+    } else {
+      mListener->OnStopDiscoveryFailed(mServiceType,
+                                       static_cast<uint32_t>(rv));
+    }
+  }
+
+  return rv;
+}
+
+void
+BrowseOperator::Reply(DNSServiceRef aSdRef,
+                      DNSServiceFlags aFlags,
+                      uint32_t aInterfaceIndex,
+                      DNSServiceErrorType aErrorCode,
+                      const nsACString& aServiceName,
+                      const nsACString& aRegType,
+                      const nsACString& aReplyDomain)
+{
+  MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+  if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+    LOG_E("BrowseOperator::Reply (%d)", aErrorCode);
+    if (mListener) {
+      mListener->OnStartDiscoveryFailed(mServiceType, aErrorCode);
+    }
+    return;
+  }
+
+  if (!mListener) { return; }
+  nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo();
+
+  if (NS_WARN_IF(!info)) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aServiceName)))) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aReplyDomain)))) { return; }
+
+  if (aFlags & kDNSServiceFlagsAdd) {
+    mListener->OnServiceFound(info);
+  } else {
+    mListener->OnServiceLost(info);
+  }
+}
+
+RegisterOperator::RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
+                                   nsIDNSRegistrationListener* aListener)
+  : MDNSResponderOperator()
+  , mServiceInfo(aServiceInfo)
+  , mListener(aListener)
+{
+}
+
+nsresult
+RegisterOperator::Start()
+{
+  nsresult rv;
+
+  rv = MDNSResponderOperator::Start();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  uint16_t port;
+  if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetPort(&port)))) {
+    return rv;
+  }
+  nsAutoCString type;
+  if (NS_WARN_IF(NS_FAILED(rv = mServiceInfo->GetServiceType(type)))) {
+    return rv;
+  }
+
+  TXTRecordRef txtRecord;
+  char buf[TXT_BUFFER_SIZE] = { 0 };
+  TXTRecordCreate(&txtRecord, TXT_BUFFER_SIZE, buf);
+
+  nsCOMPtr<nsIPropertyBag2> attributes;
+  if (NS_FAILED(rv = mServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
+    LOG_I("register: no attributes");
+  } else {
+    nsCOMPtr<nsISimpleEnumerator> enumerator;
+    if (NS_WARN_IF(NS_FAILED(rv =
+        attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
+      return rv;
+    }
+
+    bool hasMoreElements;
+    while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+           hasMoreElements) {
+      nsCOMPtr<nsISupports> element;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(element))));
+      nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+      MOZ_ASSERT(property);
+
+      nsAutoString name;
+      nsCOMPtr<nsIVariant> value;
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(property->GetName(name)));
+      MOZ_ALWAYS_TRUE(NS_SUCCEEDED(property->GetValue(getter_AddRefs(value))));
+
+      nsAutoCString str;
+      if (NS_WARN_IF(NS_FAILED(value->GetAsACString(str)))) {
+        continue;
+      }
+
+      TXTRecordSetValue(&txtRecord,
+                        /* it's safe because key name is ASCII only. */
+                        NS_LossyConvertUTF16toASCII(name).get(),
+                        str.Length(),
+                        str.get());
+    }
+  }
+
+  nsAutoCString host;
+  nsAutoCString name;
+  nsAutoCString domain;
+
+  DNSServiceRef service = nullptr;
+  DNSServiceErrorType err =
+    DNSServiceRegister(&service,
+                       0,
+                       0,
+                       NS_SUCCEEDED(mServiceInfo->GetServiceName(name)) ?
+                         name.get() : nullptr,
+                       type.get(),
+                       NS_SUCCEEDED(mServiceInfo->GetDomainName(domain)) ?
+                         domain.get() : nullptr,
+                       NS_SUCCEEDED(mServiceInfo->GetHost(host)) ?
+                         host.get() : nullptr,
+                       NativeEndian::swapToNetworkOrder(port),
+                       TXTRecordGetLength(&txtRecord),
+                       TXTRecordGetBytesPtr(&txtRecord),
+                       &RegisterReplyRunnable::Reply,
+                       this);
+  NS_WARN_IF(kDNSServiceErr_NoError != err);
+
+  TXTRecordDeallocate(&txtRecord);
+
+  if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+    if (mListener) {
+      mListener->OnRegistrationFailed(mServiceInfo, err);
+    }
+    return NS_ERROR_FAILURE;
+  }
+
+  return ResetService(service);
+}
+
+nsresult
+RegisterOperator::Stop()
+{
+  bool isServing = IsServing();
+  nsresult rv = MDNSResponderOperator::Stop();
+
+  if (isServing && mListener) {
+    if (NS_SUCCEEDED(rv)) {
+      mListener->OnServiceUnregistered(mServiceInfo);
+    } else {
+      mListener->OnUnregistrationFailed(mServiceInfo,
+                                        static_cast<uint32_t>(rv));
+    }
+  }
+
+  return rv;
+}
+
+void
+RegisterOperator::Reply(DNSServiceRef aSdRef,
+                        DNSServiceFlags aFlags,
+                        DNSServiceErrorType aErrorCode,
+                        const nsACString& aName,
+                        const nsACString& aRegType,
+                        const nsACString& aDomain)
+{
+  MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+  if (kDNSServiceErr_NoError != aErrorCode) {
+    LOG_E("RegisterOperator::Reply (%d)", aErrorCode);
+  }
+
+  if (!mListener) { return; }
+  nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+  if (NS_WARN_IF(NS_FAILED(info->SetServiceName(aName)))) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetServiceType(aRegType)))) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetDomainName(aDomain)))) { return; }
+
+  if (kDNSServiceErr_NoError == aErrorCode) {
+    mListener->OnServiceRegistered(info);
+  } else {
+    mListener->OnRegistrationFailed(info, aErrorCode);
+  }
+}
+
+ResolveOperator::ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
+                                 nsIDNSServiceResolveListener* aListener)
+  : MDNSResponderOperator()
+  , mServiceInfo(aServiceInfo)
+  , mListener(aListener)
+  , mDeleteProtector()
+{
+}
+
+nsresult
+ResolveOperator::Start()
+{
+  nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = MDNSResponderOperator::Start()))) {
+    return rv;
+  }
+
+  nsAutoCString name;
+  mServiceInfo->GetServiceName(name);
+  nsAutoCString type;
+  mServiceInfo->GetServiceType(type);
+  nsAutoCString domain;
+  mServiceInfo->GetDomainName(domain);
+
+  LOG_I("Resolve: (%s), (%s), (%s)", name.get(), type.get(), domain.get());
+
+  DNSServiceRef service = nullptr;
+  DNSServiceErrorType err =
+    DNSServiceResolve(&service,
+                      0,
+                      kDNSServiceInterfaceIndexAny,
+                      name.get(),
+                      type.get(),
+                      domain.get(),
+                      (DNSServiceResolveReply)&ResolveReplyRunnable::Reply,
+                      this);
+
+  if (NS_WARN_IF(kDNSServiceErr_NoError != err)) {
+    if (mListener) {
+      mListener->OnResolveFailed(mServiceInfo, err);
+    }
+    return NS_ERROR_FAILURE;
+  }
+
+  mDeleteProtector = this;
+  return ResetService(service);
+}
+
+nsresult
+ResolveOperator::Stop()
+{
+  nsresult rv = MDNSResponderOperator::Stop();
+  return rv;
+}
+
+void
+ResolveOperator::Reply(DNSServiceRef aSdRef,
+                       DNSServiceFlags aFlags,
+                       uint32_t aInterfaceIndex,
+                       DNSServiceErrorType aErrorCode,
+                       const nsACString& aFullName,
+                       const nsACString& aHostTarget,
+                       uint16_t aPort,
+                       uint16_t aTxtLen,
+                       const unsigned char* aTxtRecord)
+{
+  MOZ_ASSERT(GetThread() == NS_GetCurrentThread());
+
+  mDeleteProtector = nullptr;
+
+  if (NS_WARN_IF(kDNSServiceErr_NoError != aErrorCode)) {
+    LOG_E("ResolveOperator::Reply (%d)", aErrorCode);
+    return;
+  }
+
+  // Resolve TXT record
+  int count = TXTRecordGetCount(aTxtLen, aTxtRecord);
+  LOG_I("resolve: txt count = %d, len = %d", count, aTxtLen);
+  nsCOMPtr<nsIWritablePropertyBag2> attributes = nullptr;
+  if (count) {
+    attributes = new nsHashPropertyBag();
+    if (NS_WARN_IF(!attributes)) { return; }
+    for (int i = 0; i < count; ++i) {
+      char key[TXT_BUFFER_SIZE] = { '\0' };
+      uint8_t vSize = 0;
+      const void* value = nullptr;
+      if (kDNSServiceErr_NoError !=
+          TXTRecordGetItemAtIndex(aTxtLen,
+                                  aTxtRecord,
+                                  i,
+                                  TXT_BUFFER_SIZE,
+                                  key,
+                                  &vSize,
+                                  &value)) {
+        break;
+      }
+
+      nsAutoCString str(reinterpret_cast<const char*>(value), vSize);
+      LOG_I("resolve TXT: (%d) %s=%s", vSize, key, str.get());
+
+      if (NS_WARN_IF(NS_FAILED(attributes->SetPropertyAsACString(
+          /* it's safe to convert because key name is ASCII only. */
+          NS_ConvertASCIItoUTF16(key),
+          str)))) {
+        break;
+      }
+    }
+  }
+
+  if (!mListener) { return; }
+  nsCOMPtr<nsIDNSServiceInfo> info = new nsDNSServiceInfo(mServiceInfo);
+  if (NS_WARN_IF(NS_FAILED(info->SetHost(aHostTarget)))) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetPort(aPort)))) { return; }
+  if (NS_WARN_IF(NS_FAILED(info->SetAttributes(attributes)))) { return; }
+
+  if (kDNSServiceErr_NoError == aErrorCode) {
+    mListener->OnServiceResolved(info);
+  } else {
+    mListener->OnResolveFailed(info, aErrorCode);
+  }
+
+  NS_WARN_IF(NS_FAILED(Stop()));
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderOperator.h
@@ -0,0 +1,139 @@
+/* -*- 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_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
+#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
+
+#include "dns_sd.h"
+#include "mozilla/Atomics.h"
+#include "nsCOMPtr.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsIThread.h"
+#include "nsRefPtr.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class MDNSResponderOperator
+{
+public:
+  MDNSResponderOperator();
+
+  virtual nsresult Start();
+  virtual nsresult Stop();
+  void Cancel() { mIsCancelled = true; }
+  nsIThread* GetThread() const { return mThread; }
+
+protected:
+  virtual ~MDNSResponderOperator();
+
+  bool IsServing() const { return mService; }
+  nsresult ResetService(DNSServiceRef aService);
+
+private:
+  class ServiceWatcher;
+
+  DNSServiceRef mService;
+  nsRefPtr<ServiceWatcher> mWatcher;
+  nsCOMPtr<nsIThread> mThread; // remember caller thread for callback
+  Atomic<bool> mIsCancelled;
+};
+
+class BrowseOperator final : private MDNSResponderOperator
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BrowseOperator)
+
+  BrowseOperator(const nsACString& aServiceType,
+                 nsIDNSServiceDiscoveryListener* aListener);
+
+  nsresult Start() override;
+  nsresult Stop() override;
+  using MDNSResponderOperator::Cancel;
+  using MDNSResponderOperator::GetThread;
+
+  void Reply(DNSServiceRef aSdRef,
+             DNSServiceFlags aFlags,
+             uint32_t aInterfaceIndex,
+             DNSServiceErrorType aErrorCode,
+             const nsACString& aServiceName,
+             const nsACString& aRegType,
+             const nsACString& aReplyDomain);
+
+private:
+  ~BrowseOperator() = default;
+
+  nsCString mServiceType;
+  nsCOMPtr<nsIDNSServiceDiscoveryListener> mListener;
+};
+
+class RegisterOperator final : private MDNSResponderOperator
+{
+  enum { TXT_BUFFER_SIZE = 256 };
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RegisterOperator)
+
+  RegisterOperator(nsIDNSServiceInfo* aServiceInfo,
+                   nsIDNSRegistrationListener* aListener);
+
+  nsresult Start() override;
+  nsresult Stop() override;
+  using MDNSResponderOperator::Cancel;
+  using MDNSResponderOperator::GetThread;
+
+  void Reply(DNSServiceRef aSdRef,
+             DNSServiceFlags aFlags,
+             DNSServiceErrorType aErrorCode,
+             const nsACString& aName,
+             const nsACString& aRegType,
+             const nsACString& aDomain);
+
+private:
+  ~RegisterOperator() = default;
+
+  nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+  nsCOMPtr<nsIDNSRegistrationListener> mListener;
+};
+
+class ResolveOperator final : private MDNSResponderOperator
+{
+  enum { TXT_BUFFER_SIZE = 256 };
+
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ResolveOperator)
+
+  ResolveOperator(nsIDNSServiceInfo* aServiceInfo,
+                  nsIDNSServiceResolveListener* aListener);
+
+  nsresult Start() override;
+  nsresult Stop() override;
+  using MDNSResponderOperator::GetThread;
+
+  void Reply(DNSServiceRef aSdRef,
+             DNSServiceFlags aFlags,
+             uint32_t aInterfaceIndex,
+             DNSServiceErrorType aErrorCode,
+             const nsACString& aFullName,
+             const nsACString& aHostTarget,
+             uint16_t aPort,
+             uint16_t aTxtLen,
+             const unsigned char* aTxtRecord);
+
+private:
+  ~ResolveOperator() = default;
+
+  nsCOMPtr<nsIDNSServiceInfo> mServiceInfo;
+  nsCOMPtr<nsIDNSServiceResolveListener> mListener;
+
+  // hold self until callback is made.
+  nsRefPtr<ResolveOperator> mDeleteProtector;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderOperator_h
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.cpp
@@ -0,0 +1,225 @@
+/* -*- 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 "MDNSResponderReply.h"
+#include "mozilla/Endian.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+BrowseReplyRunnable::BrowseReplyRunnable(DNSServiceRef aSdRef,
+                                         DNSServiceFlags aFlags,
+                                         uint32_t aInterfaceIndex,
+                                         DNSServiceErrorType aErrorCode,
+                                         const nsACString& aServiceName,
+                                         const nsACString& aRegType,
+                                         const nsACString& aReplyDomain,
+                                         BrowseOperator* aContext)
+  : mSdRef(aSdRef)
+  , mFlags(aFlags)
+  , mInterfaceIndex(aInterfaceIndex)
+  , mErrorCode(aErrorCode)
+  , mServiceName(aServiceName)
+  , mRegType(aRegType)
+  , mReplyDomain(aReplyDomain)
+  , mContext(aContext)
+{
+}
+
+NS_IMETHODIMP
+BrowseReplyRunnable::Run()
+{
+  MOZ_ASSERT(mContext);
+  mContext->Reply(mSdRef,
+                  mFlags,
+                  mInterfaceIndex,
+                  mErrorCode,
+                  mServiceName,
+                  mRegType,
+                  mReplyDomain);
+  return NS_OK;
+}
+
+void
+BrowseReplyRunnable::Reply(DNSServiceRef aSdRef,
+                           DNSServiceFlags aFlags,
+                           uint32_t aInterfaceIndex,
+                           DNSServiceErrorType aErrorCode,
+                           const char* aServiceName,
+                           const char* aRegType,
+                           const char* aReplyDomain,
+                           void* aContext)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  BrowseOperator* obj(reinterpret_cast<BrowseOperator*>(aContext));
+  if (!obj) {
+    return;
+  }
+
+  nsCOMPtr<nsIThread> thread(obj->GetThread());
+  if (!thread) {
+    return;
+  }
+
+  thread->Dispatch(new BrowseReplyRunnable(aSdRef,
+                                           aFlags,
+                                           aInterfaceIndex,
+                                           aErrorCode,
+                                           nsCString(aServiceName),
+                                           nsCString(aRegType),
+                                           nsCString(aReplyDomain),
+                                           obj),
+                   NS_DISPATCH_NORMAL);
+}
+
+RegisterReplyRunnable::RegisterReplyRunnable(DNSServiceRef aSdRef,
+                                             DNSServiceFlags aFlags,
+                                             DNSServiceErrorType aErrorCode,
+                                             const nsACString& aName,
+                                             const nsACString& aRegType,
+                                             const nsACString& domain,
+                                             RegisterOperator* aContext)
+  : mSdRef(aSdRef)
+  , mFlags(aFlags)
+  , mErrorCode(aErrorCode)
+  , mName(aName)
+  , mRegType(aRegType)
+  , mDomain(domain)
+  , mContext(aContext)
+{
+}
+
+NS_IMETHODIMP
+RegisterReplyRunnable::Run()
+{
+  MOZ_ASSERT(mContext);
+
+  mContext->Reply(mSdRef,
+                  mFlags,
+                  mErrorCode,
+                  mName,
+                  mRegType,
+                  mDomain);
+  return NS_OK;
+}
+
+void
+RegisterReplyRunnable::Reply(DNSServiceRef aSdRef,
+                             DNSServiceFlags aFlags,
+                             DNSServiceErrorType aErrorCode,
+                             const char* aName,
+                             const char* aRegType,
+                             const char* domain,
+                             void* aContext)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  RegisterOperator* obj(reinterpret_cast<RegisterOperator*>(aContext));
+  if (!obj) {
+    return;
+  }
+
+  nsCOMPtr<nsIThread> thread(obj->GetThread());
+  if (!thread) {
+    return;
+  }
+
+  thread->Dispatch(new RegisterReplyRunnable(aSdRef,
+                                             aFlags,
+                                             aErrorCode,
+                                             nsCString(aName),
+                                             nsCString(aRegType),
+                                             nsCString(domain),
+                                             obj),
+                   NS_DISPATCH_NORMAL);
+}
+
+ResolveReplyRunnable::ResolveReplyRunnable(DNSServiceRef aSdRef,
+                                           DNSServiceFlags aFlags,
+                                           uint32_t aInterfaceIndex,
+                                           DNSServiceErrorType aErrorCode,
+                                           const nsACString& aFullName,
+                                           const nsACString& aHostTarget,
+                                           uint16_t aPort,
+                                           uint16_t aTxtLen,
+                                           const unsigned char* aTxtRecord,
+                                           ResolveOperator* aContext)
+  : mSdRef(aSdRef)
+  , mFlags(aFlags)
+  , mInterfaceIndex(aInterfaceIndex)
+  , mErrorCode(aErrorCode)
+  , mFullname(aFullName)
+  , mHosttarget(aHostTarget)
+  , mPort(aPort)
+  , mTxtLen(aTxtLen)
+  , mTxtRecord(new unsigned char[aTxtLen])
+  , mContext(aContext)
+{
+  if (mTxtRecord) {
+    memcpy(mTxtRecord.get(), aTxtRecord, aTxtLen);
+  }
+}
+
+ResolveReplyRunnable::~ResolveReplyRunnable()
+{
+}
+
+NS_IMETHODIMP
+ResolveReplyRunnable::Run()
+{
+  MOZ_ASSERT(mContext);
+  mContext->Reply(mSdRef,
+                  mFlags,
+                  mInterfaceIndex,
+                  mErrorCode,
+                  mFullname,
+                  mHosttarget,
+                  mPort,
+                  mTxtLen,
+                  mTxtRecord.get());
+  return NS_OK;
+}
+
+void
+ResolveReplyRunnable::Reply(DNSServiceRef aSdRef,
+                            DNSServiceFlags aFlags,
+                            uint32_t aInterfaceIndex,
+                            DNSServiceErrorType aErrorCode,
+                            const char* aFullName,
+                            const char* aHostTarget,
+                            uint16_t aPort,
+                            uint16_t aTxtLen,
+                            const unsigned char* aTxtRecord,
+                            void* aContext)
+{
+  MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
+
+  ResolveOperator* obj(reinterpret_cast<ResolveOperator*>(aContext));
+  if (!obj) {
+    return;
+  }
+
+  nsCOMPtr<nsIThread> thread(obj->GetThread());
+  if (!thread) {
+    return;
+  }
+
+  thread->Dispatch(new ResolveReplyRunnable(aSdRef,
+                                            aFlags,
+                                            aInterfaceIndex,
+                                            aErrorCode,
+                                            nsCString(aFullName),
+                                            nsCString(aHostTarget),
+                                            NativeEndian::swapFromNetworkOrder(aPort),
+                                            aTxtLen,
+                                            aTxtRecord,
+                                            obj),
+                   NS_DISPATCH_NORMAL);
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/MDNSResponderReply.h
@@ -0,0 +1,128 @@
+/* -*- 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_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
+#define mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
+
+#include "dns_sd.h"
+#include "MDNSResponderOperator.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIThread.h"
+#include "nsRefPtr.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace net {
+
+class BrowseReplyRunnable final : public nsRunnable
+{
+public:
+  BrowseReplyRunnable(DNSServiceRef aSdRef,
+                      DNSServiceFlags aFlags,
+                      uint32_t aInterfaceIndex,
+                      DNSServiceErrorType aErrorCode,
+                      const nsACString& aServiceName,
+                      const nsACString& aRegType,
+                      const nsACString& aReplyDomain,
+                      BrowseOperator* aContext);
+
+  NS_IMETHODIMP Run() override;
+
+  static void Reply(DNSServiceRef aSdRef,
+                    DNSServiceFlags aFlags,
+                    uint32_t aInterfaceIndex,
+                    DNSServiceErrorType aErrorCode,
+                    const char* aServiceName,
+                    const char* aRegType,
+                    const char* aReplyDomain,
+                    void* aContext);
+
+private:
+  DNSServiceRef mSdRef;
+  DNSServiceFlags mFlags;
+  uint32_t mInterfaceIndex;
+  DNSServiceErrorType mErrorCode;
+  nsCString mServiceName;
+  nsCString mRegType;
+  nsCString mReplyDomain;
+  BrowseOperator* mContext;
+};
+
+class RegisterReplyRunnable final : public nsRunnable
+{
+public:
+  RegisterReplyRunnable(DNSServiceRef aSdRef,
+                        DNSServiceFlags aFlags,
+                        DNSServiceErrorType aErrorCode,
+                        const nsACString& aName,
+                        const nsACString& aRegType,
+                        const nsACString& aDomain,
+                        RegisterOperator* aContext);
+
+  NS_IMETHODIMP Run() override;
+
+  static void Reply(DNSServiceRef aSdRef,
+                    DNSServiceFlags aFlags,
+                    DNSServiceErrorType aErrorCode,
+                    const char* aName,
+                    const char* aRegType,
+                    const char* aDomain,
+                    void* aContext);
+
+private:
+  DNSServiceRef mSdRef;
+  DNSServiceFlags mFlags;
+  DNSServiceErrorType mErrorCode;
+  nsCString mName;
+  nsCString mRegType;
+  nsCString mDomain;
+  RegisterOperator* mContext;
+};
+
+class ResolveReplyRunnable final : public nsRunnable
+{
+public:
+  ResolveReplyRunnable(DNSServiceRef aSdRef,
+                       DNSServiceFlags aFlags,
+                       uint32_t aInterfaceIndex,
+                       DNSServiceErrorType aErrorCode,
+                       const nsACString& aFullName,
+                       const nsACString& aHostTarget,
+                       uint16_t aPort,
+                       uint16_t aTxtLen,
+                       const unsigned char* aTxtRecord,
+                       ResolveOperator* aContext);
+  ~ResolveReplyRunnable();
+
+  NS_IMETHODIMP Run() override;
+
+  static void Reply(DNSServiceRef aSdRef,
+                    DNSServiceFlags aFlags,
+                    uint32_t aInterfaceIndex,
+                    DNSServiceErrorType aErrorCode,
+                    const char* aFullName,
+                    const char* aHostTarget,
+                    uint16_t aPort,
+                    uint16_t aTxtLen,
+                    const unsigned char* aTxtRecord,
+                    void* aContext);
+
+private:
+  DNSServiceRef mSdRef;
+  DNSServiceFlags mFlags;
+  uint32_t mInterfaceIndex;
+  DNSServiceErrorType mErrorCode;
+  nsCString mFullname;
+  nsCString mHosttarget;
+  uint16_t mPort;
+  uint16_t mTxtLen;
+  UniquePtr<unsigned char> mTxtRecord;
+  nsRefPtr<ResolveOperator> mContext;
+};
+
+} // namespace net
+} // namespace mozilla
+
+ #endif // mozilla_netwerk_dns_mdns_libmdns_MDNSResponderReply_h
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/moz.build
@@ -0,0 +1,33 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['ANDROID_VERSION'] >= '16':
+    UNIFIED_SOURCES += [
+        'MDNSResponderOperator.cpp',
+        'MDNSResponderReply.cpp',
+        'nsDNSServiceDiscovery.cpp',
+    ]
+
+    CXXFLAGS += [
+        '-I%s/%s' % (CONFIG['ANDROID_SOURCE'], d) for d in [
+            'external/mdnsresponder/mDNSShared',
+        ]
+    ]
+
+    LOCAL_INCLUDES += [
+      '/netwerk/base',
+    ]
+
+UNIFIED_SOURCES += [
+    'nsDNSServiceInfo.cpp',
+    'nsMulticastDNSModule.cpp',
+]
+
+FAIL_ON_WARNINGS = True
+
+include('/ipc/chromium/chromium-config.mozbuild')
+FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.cpp
@@ -0,0 +1,230 @@
+/* -*- 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 "nsDNSServiceDiscovery.h"
+#include <cutils/properties.h>
+#include "MDNSResponderOperator.h"
+#include "nsICancelable.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace net {
+
+namespace {
+
+void
+StartService()
+{
+  char value[PROPERTY_VALUE_MAX] = { '\0' };
+  property_get("init.svc.mdnsd", value, "");
+
+  if (strcmp(value, "running") == 0) {
+    return;
+  }
+  property_set("ctl.start", "mdnsd");
+}
+
+void
+StopService()
+{
+  char value[PROPERTY_VALUE_MAX] = { '\0' };
+  property_get("init.svc.mdnsd", value, "");
+
+  if (strcmp(value, "stopped") == 0) {
+    return;
+  }
+  property_set("ctl.stop", "mdnsd");
+}
+
+class DiscoveryRequest final : public nsICancelable
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICANCELABLE
+
+  explicit DiscoveryRequest(nsDNSServiceDiscovery* aService,
+                            nsIDNSServiceDiscoveryListener* aListener);
+
+private:
+  virtual ~DiscoveryRequest() { Cancel(NS_OK); }
+
+  nsRefPtr<nsDNSServiceDiscovery> mService;
+  nsIDNSServiceDiscoveryListener* mListener;
+};
+
+NS_IMPL_ISUPPORTS(DiscoveryRequest, nsICancelable)
+
+DiscoveryRequest::DiscoveryRequest(nsDNSServiceDiscovery* aService,
+                                   nsIDNSServiceDiscoveryListener* aListener)
+  : mService(aService)
+  , mListener(aListener)
+{
+}
+
+NS_IMETHODIMP
+DiscoveryRequest::Cancel(nsresult aReason)
+{
+  if (mService) {
+    mService->StopDiscovery(mListener);
+  }
+
+  mService = nullptr;
+  return NS_OK;
+}
+
+class RegisterRequest final : public nsICancelable
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICANCELABLE
+
+  explicit RegisterRequest(nsDNSServiceDiscovery* aService,
+                           nsIDNSRegistrationListener* aListener);
+
+private:
+  virtual ~RegisterRequest() { Cancel(NS_OK); }
+
+  nsRefPtr<nsDNSServiceDiscovery> mService;
+  nsIDNSRegistrationListener* mListener;
+};
+
+NS_IMPL_ISUPPORTS(RegisterRequest, nsICancelable)
+
+RegisterRequest::RegisterRequest(nsDNSServiceDiscovery* aService,
+                                 nsIDNSRegistrationListener* aListener)
+  : mService(aService)
+  , mListener(aListener)
+{
+}
+
+NS_IMETHODIMP
+RegisterRequest::Cancel(nsresult aReason)
+{
+  if (mService) {
+    mService->UnregisterService(mListener);
+  }
+
+  mService = nullptr;
+  return NS_OK;
+}
+
+} // namespace anonymous
+
+NS_IMPL_ISUPPORTS(nsDNSServiceDiscovery, nsIDNSServiceDiscovery)
+
+nsresult
+nsDNSServiceDiscovery::Init()
+{
+  StartService();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::StartDiscovery(const nsACString& aServiceType,
+                                      nsIDNSServiceDiscoveryListener* aListener,
+                                      nsICancelable** aRetVal)
+{
+  MOZ_ASSERT(aRetVal);
+
+  nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = StopDiscovery(aListener)))) {
+    return rv;
+  }
+
+  nsRefPtr<BrowseOperator> browserOp = new BrowseOperator(aServiceType,
+                                                          aListener);
+  if (NS_WARN_IF(NS_FAILED(rv = browserOp->Start()))) {
+    return rv;
+  }
+
+  mDiscoveryMap.Put(aListener, browserOp);
+
+  nsCOMPtr<nsICancelable> req = new DiscoveryRequest(this, aListener);
+  req.forget(aRetVal);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::StopDiscovery(nsIDNSServiceDiscoveryListener* aListener)
+{
+  nsresult rv;
+
+  nsRefPtr<BrowseOperator> browserOp;
+  if (!mDiscoveryMap.Get(aListener, getter_AddRefs(browserOp))) {
+    return NS_OK;
+  }
+
+  browserOp->Cancel(); // cancel non-started operation
+  if (NS_WARN_IF(NS_FAILED(rv = browserOp->Stop()))) {
+    return rv;
+  }
+
+  mDiscoveryMap.Remove(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::RegisterService(nsIDNSServiceInfo* aServiceInfo,
+                                       nsIDNSRegistrationListener* aListener,
+                                       nsICancelable** aRetVal)
+{
+  MOZ_ASSERT(aRetVal);
+
+  nsresult rv;
+  if (NS_WARN_IF(NS_FAILED(rv = UnregisterService(aListener)))) {
+    return rv;
+  }
+
+  nsRefPtr<RegisterOperator> registerOp = new RegisterOperator(aServiceInfo,
+                                                               aListener);
+  if (NS_WARN_IF(NS_FAILED(rv = registerOp->Start()))) {
+    return rv;
+  }
+
+  mRegisterMap.Put(aListener, registerOp);
+
+  nsCOMPtr<nsICancelable> req = new RegisterRequest(this, aListener);
+  req.forget(aRetVal);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::UnregisterService(nsIDNSRegistrationListener* aListener)
+{
+  nsresult rv;
+
+  nsRefPtr<RegisterOperator> registerOp;
+  if (!mRegisterMap.Get(aListener, getter_AddRefs(registerOp))) {
+    return NS_OK;
+  }
+
+  registerOp->Cancel(); // cancel non-started operation
+  if (NS_WARN_IF(NS_FAILED(rv = registerOp->Stop()))) {
+    return rv;
+  }
+
+  mRegisterMap.Remove(aListener);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceDiscovery::ResolveService(nsIDNSServiceInfo* aServiceInfo,
+                                      nsIDNSServiceResolveListener* aListener)
+{
+  nsresult rv;
+
+  nsRefPtr<ResolveOperator> resolveOp = new ResolveOperator(aServiceInfo,
+                                                            aListener);
+  if (NS_WARN_IF(NS_FAILED(rv = resolveOp->Start()))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceDiscovery.h
@@ -0,0 +1,48 @@
+/* -*- 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_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
+#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
+
+#include "nsIDNSServiceDiscovery.h"
+#include "nsCOMPtr.h"
+#include "nsRefPtr.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace net {
+
+class BrowseOperator;
+class RegisterOperator;
+
+class nsDNSServiceDiscovery final : public nsIDNSServiceDiscovery
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIDNSSERVICEDISCOVERY
+
+  explicit nsDNSServiceDiscovery() = default;
+
+  /*
+  ** The mDNS service is started in this function. However, the function returns
+  ** without waiting. Therefore, all operations before service started will fail
+  ** and get error code |kDNSServiceErr_ServiceNotRunning| defined in dns_sd.h.
+  **/
+  nsresult Init();
+
+  nsresult StopDiscovery(nsIDNSServiceDiscoveryListener* aListener);
+  nsresult UnregisterService(nsIDNSRegistrationListener* aListener);
+
+private:
+  virtual ~nsDNSServiceDiscovery() = default;
+
+  nsRefPtrHashtable<nsISupportsHashKey, BrowseOperator> mDiscoveryMap;
+  nsRefPtrHashtable<nsISupportsHashKey, RegisterOperator> mRegisterMap;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceDiscovery_h
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.cpp
@@ -0,0 +1,183 @@
+/* -*- 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 "nsDNSServiceInfo.h"
+#include "nsHashPropertyBag.h"
+#include "nsIProperty.h"
+#include "nsISimpleEnumerator.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla {
+namespace net {
+
+NS_IMPL_ISUPPORTS(nsDNSServiceInfo, nsIDNSServiceInfo)
+
+nsDNSServiceInfo::nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo)
+{
+  if (NS_WARN_IF(!aServiceInfo)) {
+    return;
+  }
+
+  nsAutoCString str;
+  uint16_t value;
+
+  if (NS_SUCCEEDED(aServiceInfo->GetHost(str))) {
+    NS_WARN_IF(NS_FAILED(SetHost(str)));
+  }
+  if (NS_SUCCEEDED(aServiceInfo->GetPort(&value))) {
+    NS_WARN_IF(NS_FAILED(SetPort(value)));
+  }
+  if (NS_SUCCEEDED(aServiceInfo->GetServiceName(str))) {
+    NS_WARN_IF(NS_FAILED(SetServiceName(str)));
+  }
+  if (NS_SUCCEEDED(aServiceInfo->GetServiceType(str))) {
+    NS_WARN_IF(NS_FAILED(SetServiceType(str)));
+  }
+  if (NS_SUCCEEDED(aServiceInfo->GetDomainName(str))) {
+    NS_WARN_IF(NS_FAILED(SetDomainName(str)));
+  }
+
+  nsCOMPtr<nsIPropertyBag2> attributes; // deep copy
+  if (NS_SUCCEEDED(aServiceInfo->GetAttributes(getter_AddRefs(attributes)))) {
+    nsCOMPtr<nsISimpleEnumerator> enumerator;
+    if (NS_WARN_IF(NS_FAILED(attributes->GetEnumerator(getter_AddRefs(enumerator))))) {
+      return;
+    }
+
+    nsCOMPtr<nsIWritablePropertyBag2> newAttributes = new nsHashPropertyBag();
+
+    bool hasMoreElements;
+    while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreElements)) &&
+           hasMoreElements) {
+      nsCOMPtr<nsISupports> element;
+      NS_WARN_IF(NS_FAILED(enumerator->GetNext(getter_AddRefs(element))));
+      nsCOMPtr<nsIProperty> property = do_QueryInterface(element);
+      MOZ_ASSERT(property);
+
+      nsAutoString name;
+      nsCOMPtr<nsIVariant> value;
+      NS_WARN_IF(NS_FAILED(property->GetName(name)));
+      NS_WARN_IF(NS_FAILED(property->GetValue(getter_AddRefs(value))));
+      NS_WARN_IF(NS_FAILED(newAttributes->SetPropertyAsInterface(name, value)));
+    }
+
+    NS_WARN_IF(NS_FAILED(SetAttributes(newAttributes)));
+  }
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetHost(nsACString& aHost)
+{
+  if (!mIsHostSet) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  aHost = mHost;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetHost(const nsACString& aHost)
+{
+  mHost = aHost;
+  mIsHostSet = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetPort(uint16_t* aPort)
+{
+  if (NS_WARN_IF(!aPort)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  if (!mIsPortSet) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  *aPort = mPort;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetPort(uint16_t aPort)
+{
+  mPort = aPort;
+  mIsPortSet = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetServiceName(nsACString& aServiceName)
+{
+  if (!mIsServiceNameSet) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  aServiceName = mServiceName;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetServiceName(const nsACString& aServiceName)
+{
+  mServiceName = aServiceName;
+  mIsServiceNameSet = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetServiceType(nsACString& aServiceType)
+{
+  if (!mIsServiceTypeSet) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  aServiceType = mServiceType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetServiceType(const nsACString& aServiceType)
+{
+  mServiceType = aServiceType;
+  mIsServiceTypeSet = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetDomainName(nsACString& aDomainName)
+{
+  if (!mIsDomainNameSet) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  aDomainName = mDomainName;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetDomainName(const nsACString& aDomainName)
+{
+  mDomainName = aDomainName;
+  mIsDomainNameSet = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::GetAttributes(nsIPropertyBag2** aAttributes)
+{
+  if (!mIsAttributesSet) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  nsCOMPtr<nsIPropertyBag2> attributes(mAttributes);
+  attributes.forget(aAttributes);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDNSServiceInfo::SetAttributes(nsIPropertyBag2* aAttributes)
+{
+  mAttributes = aAttributes;
+  mIsAttributesSet = true;
+  return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsDNSServiceInfo.h
@@ -0,0 +1,48 @@
+/* -*- 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_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
+#define mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
+
+#include "nsCOMPtr.h"
+#include "nsIDNSServiceDiscovery.h"
+#include "nsIPropertyBag2.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace net {
+
+class nsDNSServiceInfo final : public nsIDNSServiceInfo
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDNSSERVICEINFO
+
+  explicit nsDNSServiceInfo() = default;
+  explicit nsDNSServiceInfo(nsIDNSServiceInfo* aServiceInfo);
+
+private:
+  virtual ~nsDNSServiceInfo() = default;
+
+private:
+  nsCString mHost;
+  uint16_t mPort = 0;
+  nsCString mServiceName;
+  nsCString mServiceType;
+  nsCString mDomainName;
+  nsCOMPtr<nsIPropertyBag2> mAttributes;
+
+  bool mIsHostSet = false;
+  bool mIsPortSet = false;
+  bool mIsServiceNameSet = false;
+  bool mIsServiceTypeSet = false;
+  bool mIsDomainNameSet = false;
+  bool mIsAttributesSet = false;
+};
+
+} // namespace net
+} // namespace mozilla
+
+#endif // mozilla_netwerk_dns_mdns_libmdns_nsDNSServiceInfo_h
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/libmdns/nsMulticastDNSModule.cpp
@@ -0,0 +1,61 @@
+/* -*- 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/. */
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 16
+#define ENABLE_DNS_SERVICE_DISCOVERY
+#endif
+
+#include "mozilla/ModuleUtils.h"
+
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+#include "nsDNSServiceDiscovery.h"
+#endif
+
+#include "nsDNSServiceInfo.h"
+
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+using mozilla::net::nsDNSServiceDiscovery;
+#define DNSSERVICEDISCOVERY_CID \
+  {0x8df43d23, 0xd3f9, 0x4dd5, \
+    { 0xb9, 0x65, 0xde, 0x2c, 0xa3, 0xf6, 0xa4, 0x2c }}
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsDNSServiceDiscovery, Init)
+NS_DEFINE_NAMED_CID(DNSSERVICEDISCOVERY_CID);
+#endif // ENABLE_DNS_SERVICE_DISCOVERY
+
+using mozilla::net::nsDNSServiceInfo;
+#define DNSSERVICEINFO_CID \
+  {0x14a50f2b, 0x7ff6, 0x48a5, \
+    { 0x88, 0xe3, 0x61, 0x5f, 0xd1, 0x11, 0xf5, 0xd3 }}
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsDNSServiceInfo)
+NS_DEFINE_NAMED_CID(DNSSERVICEINFO_CID);
+
+static const mozilla::Module::CIDEntry knsDNSServiceDiscoveryCIDs[] = {
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+  { &kDNSSERVICEDISCOVERY_CID, false, nullptr, nsDNSServiceDiscoveryConstructor },
+#endif
+  { &kDNSSERVICEINFO_CID, false, nullptr, nsDNSServiceInfoConstructor },
+  { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry knsDNSServiceDiscoveryContracts[] = {
+#ifdef ENABLE_DNS_SERVICE_DISCOVERY
+  { DNSSERVICEDISCOVERY_CONTRACT_ID, &kDNSSERVICEDISCOVERY_CID },
+#endif
+  { DNSSERVICEINFO_CONTRACT_ID, &kDNSSERVICEINFO_CID },
+  { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry knsDNSServiceDiscoveryCategories[] = {
+  { nullptr }
+};
+
+static const mozilla::Module knsDNSServiceDiscoveryModule = {
+  mozilla::Module::kVersion,
+  knsDNSServiceDiscoveryCIDs,
+  knsDNSServiceDiscoveryContracts,
+  knsDNSServiceDiscoveryCategories
+};
+
+NSMODULE_DEFN(nsDNSServiceDiscoveryModule) = &knsDNSServiceDiscoveryModule;
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['libmdns']
+
+XPIDL_SOURCES += [
+    'nsIDNSServiceDiscovery.idl',
+]
+
+XPIDL_MODULE = 'necko_mdns'
new file mode 100644
--- /dev/null
+++ b/netwerk/dns/mdns/nsIDNSServiceDiscovery.idl
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICancelable;
+interface nsIPropertyBag2;
+
+/**
+ * Service information
+ */
+[scriptable, uuid(112bfa89-1b57-4acf-8287-48e5466c1b39)]
+interface nsIDNSServiceInfo : nsISupports
+{
+  /**
+   * The host name of the service. (E.g. "Android.local.")
+   * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+   */
+  attribute AUTF8String host;
+
+  /**
+   * The port number of the service. (E.g. 80)
+   * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+   */
+  attribute unsigned short port;
+
+  /**
+   * The service name of the service for display. (E.g. "My TV")
+   * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+   */
+  attribute AUTF8String serviceName;
+
+  /**
+   * The type of the service. (E.g. "_http._tcp")
+   * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+   */
+  attribute AUTF8String serviceType;
+
+  /**
+   * The domain name of the service. (E.g. "local.")
+   * @throws NS_ERROR_NOT_INITIALIZED when getting unset value.
+   */
+  attribute AUTF8String domainName;
+
+  /**
+   * The attributes of the service.
+   */
+  attribute nsIPropertyBag2 attributes;
+};
+
+/**
+ * The callback interface for service discovery
+ */
+[scriptable, uuid(3025b7f2-97bb-435b-b43d-26731b3f5fc4)]
+interface nsIDNSServiceDiscoveryListener : nsISupports
+{
+  /**
+   * Callback when the discovery begins.
+   * @param   aServiceType
+   *          the service type of |startDiscovery|.
+   */
+  void onDiscoveryStarted(in AUTF8String aServiceType);
+
+  /**
+   * Callback when the discovery ends.
+   * @param   aServiceType
+   *          the service type of |startDiscovery|.
+   */
+  void onDiscoveryStopped(in AUTF8String aServiceType);
+
+  /**
+   * Callback when the a service is found.
+   * @param   aServiceInfo
+   *          the info about the found service, where |serviceName|, |aServiceType|, and |domainName| are set.
+   */
+  void onServiceFound(in nsIDNSServiceInfo aServiceInfo);
+
+  /**
+   * Callback when the a service is lost.
+   * @param   aServiceInfo
+   *          the info about the lost service, where |serviceName|, |aServiceType|, and |domainName| are set.
+   */
+  void onServiceLost(in nsIDNSServiceInfo aServiceInfo);
+
+  /**
+   * Callback when the discovery cannot start.
+   * @param   aServiceType
+   *          the service type of |startDiscovery|.
+   * @param   aErrorCode
+   *          the error code.
+   */
+  void onStartDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode);
+
+  /**
+   * Callback when the discovery cannot stop.
+   * @param   aServiceType
+   *          the service type of |startDiscovery|.
+   * @param   aErrorCode
+   *          the error code.
+   */
+  void onStopDiscoveryFailed(in AUTF8String aServiceType, in long aErrorCode);
+};
+
+/**
+ * The callback interface for service registration
+ */
+[scriptable, uuid(e165e4be-abf4-4963-a66d-ed3ca116e5e4)]
+interface nsIDNSRegistrationListener : nsISupports
+{
+  const long ERROR_SERVICE_NOT_RUNNING = -65563;
+
+  /**
+   * Callback when the service is registered successfully.
+   * @param   aServiceInfo
+   *          the info about the registered service,
+   *          where |serviceName|, |aServiceType|, and |domainName| are set.
+   */
+  void onServiceRegistered(in nsIDNSServiceInfo aServiceInfo);
+
+  /**
+   * Callback when the service is unregistered successfully.
+   * @param   aServiceInfo
+   *          the info about the unregistered service.
+   */
+  void onServiceUnregistered(in nsIDNSServiceInfo aServiceInfo);
+
+  /**
+   * Callback when the service cannot be registered.
+   * @param   aServiceInfo
+   *          the info about the service to be registered.
+   * @param   aErrorCode
+   *          the error code.
+   */
+  void onRegistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
+
+  /**
+   * Callback when the service cannot be unregistered.
+   * @param   aServiceInfo
+   *          the info about the registered service.
+   * @param   aErrorCode
+   *          the error code.
+   */
+  void onUnregistrationFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
+};
+
+/**
+ * The callback interface for service resolve
+ */
+[scriptable, uuid(24ee6408-648e-421d-accf-c6e5adeccf97)]
+interface nsIDNSServiceResolveListener : nsISupports
+{
+  /**
+   * Callback when the service is resolved successfully.
+   * @param   aServiceInfo
+   *          the info about the resolved service, where |host| and |port| are set.
+   */
+  void onServiceResolved(in nsIDNSServiceInfo aServiceInfo);
+
+  /**
+   * Callback when the service cannot be resolved.
+   * @param   aServiceInfo
+   *          the info about the service to be resolved.
+   * @param   aErrorCode
+   *          the error code.
+   */
+  void onResolveFailed(in nsIDNSServiceInfo aServiceInfo, in long aErrorCode);
+};
+
+/**
+ * The interface for DNS service discovery/registration/resolve
+ */
+[scriptable, uuid(6487899b-beb1-455a-ba65-e4fd465066d7)]
+interface nsIDNSServiceDiscovery : nsISupports
+{
+  /**
+   * Browse for instances of a service.
+   * @param   aServiceType
+   *          the service type to be discovered, E.g. "_http._tcp".
+   * @param   aListener
+   *          callback interface for discovery notifications.
+   * @return  An object that can be used to cancel the service discovery.
+   */
+  nsICancelable startDiscovery(in AUTF8String aServiceType, in nsIDNSServiceDiscoveryListener aListener);
+
+  /**
+   * Register a service that is discovered via |startDiscovery| and |resolveService| calls.
+   * @param   aServiceInfo
+   *          the service information to be registered.
+   *          |port| and |aServiceType| are required attributes.
+   * @param   aListener
+   *          callback interface for registration notifications.
+   * @return  An object that can be used to cancel the service registration.
+   */
+  nsICancelable registerService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSRegistrationListener aListener);
+
+  /**
+   * Resolve a service name discovered via |startDiscovery| to a target host name, port number.
+   * @param   aServiceInfo
+   *          the service information to be registered.
+   *          |serviceName|, |aServiceType|, and |domainName| are required attributes as reported to the |onServiceFound| callback.
+   * @param   aListener
+   *          callback interface for registration notifications.
+   */
+  void resolveService(in nsIDNSServiceInfo aServiceInfo, in nsIDNSServiceResolveListener aListener);
+};
+
+%{ C++
+#define DNSSERVICEDISCOVERY_CONTRACT_ID \
+  "@mozilla.org/toolkit/components/mdnsresponder/dns-sd;1"
+#define DNSSERVICEINFO_CONTRACT_ID \
+  "@mozilla.org/toolkit/components/mdnsresponder/dns-info;1"
+%}
--- a/netwerk/dns/moz.build
+++ b/netwerk/dns/moz.build
@@ -1,14 +1,18 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+DIRS += [
+    'mdns',
+]
+
 XPIDL_SOURCES += [
     'nsIDNSListener.idl',
     'nsIDNSRecord.idl',
     'nsIDNSService.idl',
     'nsIEffectiveTLDService.idl',
     'nsIIDNService.idl',
     'nsPIDNSService.idl',
 ]
--- a/testing/taskcluster/mach_commands.py
+++ b/testing/taskcluster/mach_commands.py
@@ -149,17 +149,17 @@ class DecisionTask(object):
             'project': params['project'],
             'comment': params['comment'],
             'url': params['url'],
             'revision': params['revision'],
             'revision_hash': params.get('revision_hash', ''),
             'owner': params['owner'],
             'as_slugid': SlugidJar(),
             'from_now': json_time_from_now,
-            'now': datetime.datetime.now().isoformat()
+            'now': current_json_time()
         }.items())
         task = templates.load(params['task'], parameters)
         print(json.dumps(task, indent=4))
 
 @CommandProvider
 class Graph(object):
     @Command('taskcluster-graph', category="ci",
         description="Create taskcluster task graph")
@@ -220,17 +220,17 @@ class Graph(object):
             'docker_image': docker_image,
             'base_repository': params['base_repository'] or \
                 params['head_repository'],
             'head_repository': params['head_repository'],
             'head_ref': params['head_ref'] or params['head_rev'],
             'head_rev': params['head_rev'],
             'owner': params['owner'],
             'from_now': json_time_from_now,
-            'now': datetime.datetime.now().isoformat(),
+            'now': current_json_time(),
             'mozharness_repository': mozharness['repo'],
             'mozharness_rev': mozharness['revision'],
             'mozharness_ref':mozharness.get('reference', mozharness['revision']),
             'revision_hash': params['revision_hash']
         }.items())
 
         treeherder_route = '{}.{}'.format(
             params['project'],
--- a/toolkit/library/moz.build
+++ b/toolkit/library/moz.build
@@ -253,16 +253,21 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
         'mtp',
     ]
 
     if int(CONFIG['ANDROID_VERSION']) >= 17:
         OS_LIBS += [
             'sync',
         ]
 
+    if CONFIG['ANDROID_VERSION'] >= '16':
+        OS_LIBS += [
+            'mdnssd',
+        ]
+
 if 'rtsp' in CONFIG['NECKO_PROTOCOLS']:
     OS_LIBS += [
         'stagefright_foundation',
     ]
 
 OS_LIBS += CONFIG['ICONV_LIBS']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
--- a/tools/profiler/merge-profiles.py
+++ b/tools/profiler/merge-profiles.py
@@ -45,39 +45,55 @@ def MergeProfiles(files):
             thread['name'] = thread['name'] + " (" + pname + ":" + pid + ")"
             threads.append(thread)
 
             # Note that pid + sym, pid + location could be ambigious
             # if we had pid=11 sym=1 && pid=1 sym=11.
             pidStr = pid + ":"
 
             thread['startTime'] = fileData['profileJSON']['meta']['startTime']
-            samples = thread['samples']
-            for sample in thread['samples']:
-                for frame in sample['frames']:
-                    if "location" in frame and frame['location'][0:2] == '0x':
-                        oldLoc = frame['location']
-                        newLoc = pidStr + oldLoc
-                        frame['location'] = newLoc
-                        # Default to the unprefixed symbol if no translation is available
-                        symTable[newLoc] = oldLoc
+            if meta['version'] >= 3:
+                stringTable = thread['stringTable']
+                for i, str in enumerate(stringTable):
+                    if str[:2] == '0x':
+                        newLoc = pidStr + str
+                        stringTable[i] = newLoc
+                        symTable[newLoc] = str
+            else:
+                samples = thread['samples']
+                for sample in thread['samples']:
+                    for frame in sample['frames']:
+                        if "location" in frame and frame['location'][0:2] == '0x':
+                            oldLoc = frame['location']
+                            newLoc = pidStr + oldLoc
+                            frame['location'] = newLoc
+                            # Default to the unprefixed symbol if no translation is
+                            symTable[newLoc] = oldLoc
 
         filesyms = fileData['symbolicationTable']
         for sym in filesyms.keys():
             symTable[pidStr + sym] = filesyms[sym]
 
     # For each thread, make the time offsets line up based on the
     # earliest start
     for thread in threads:
         delta = thread['startTime'] - minStartTime
-        for sample in thread['samples']:
-            if "time" in sample:
-                sample['time'] += delta
-        for marker in thread['markers']:
-            marker['time'] += delta
+        if meta['version'] >= 3:
+            idxTime = thread['samples']['schema']['time']
+            for sample in thread['samples']['data']:
+                sample[idxTime] += delta
+            idxTime = thread['markers']['schema']['time']
+            for marker in thread['markers']['data']:
+                marker[idxTime] += delta
+        else:
+            for sample in thread['samples']:
+                if "time" in sample:
+                    sample['time'] += delta
+            for marker in thread['markers']:
+                marker['time'] += delta
 
     result = dict()
     result['profileJSON'] = dict()
     result['profileJSON']['meta'] = meta
     result['profileJSON']['libs'] = libs
     result['profileJSON']['threads'] = threads
     result['symbolicationTable'] = symTable
     result['format'] = "profileJSONWithSymbolicationTable,1"
--- a/widget/gonk/libui/KeyLayoutMap.cpp
+++ b/widget/gonk/libui/KeyLayoutMap.cpp
@@ -144,16 +144,50 @@ status_t KeyLayoutMap::mapAxis(int32_t s
             "splitValue=%d, flatOverride=%d.",
             scanCode,
             outAxisInfo->mode, outAxisInfo->axis, outAxisInfo->highAxis,
             outAxisInfo->splitValue, outAxisInfo->flatOverride);
 #endif
     return NO_ERROR;
 }
 
+status_t KeyLayoutMap::findScanCodeForLed(int32_t ledCode, int32_t* outScanCode) const {
+    const size_t N = mLedsByScanCode.size();
+    for (size_t i = 0; i < N; i++) {
+        if (mLedsByScanCode.valueAt(i).ledCode == ledCode) {
+            *outScanCode = mLedsByScanCode.keyAt(i);
+#if DEBUG_MAPPING
+            ALOGD("findScanCodeForLed: ledCode=%d, scanCode=%d.", ledCode, *outScanCode);
+#endif
+            return NO_ERROR;
+        }
+    }
+#if DEBUG_MAPPING
+            ALOGD("findScanCodeForLed: ledCode=%d ~ Not found.", ledCode);
+#endif
+    return NAME_NOT_FOUND;
+}
+
+status_t KeyLayoutMap::findUsageCodeForLed(int32_t ledCode, int32_t* outUsageCode) const {
+    const size_t N = mLedsByUsageCode.size();
+    for (size_t i = 0; i < N; i++) {
+        if (mLedsByUsageCode.valueAt(i).ledCode == ledCode) {
+            *outUsageCode = mLedsByUsageCode.keyAt(i);
+#if DEBUG_MAPPING
+            ALOGD("findUsageForLed: ledCode=%d, usage=%x.", ledCode, *outUsageCode);
+#endif
+            return NO_ERROR;
+        }
+    }
+#if DEBUG_MAPPING
+            ALOGD("findUsageForLed: ledCode=%d ~ Not found.", ledCode);
+#endif
+    return NAME_NOT_FOUND;
+}
+
 
 // --- KeyLayoutMap::Parser ---
 
 KeyLayoutMap::Parser::Parser(KeyLayoutMap* map, Tokenizer* tokenizer) :
         mMap(map), mTokenizer(tokenizer) {
 }
 
 KeyLayoutMap::Parser::~Parser() {
@@ -173,16 +207,20 @@ status_t KeyLayoutMap::Parser::parse() {
             if (keywordToken == "key") {
                 mTokenizer->skipDelimiters(WHITESPACE);
                 status_t status = parseKey();
                 if (status) return status;
             } else if (keywordToken == "axis") {
                 mTokenizer->skipDelimiters(WHITESPACE);
                 status_t status = parseAxis();
                 if (status) return status;
+            } else if (keywordToken == "led") {
+                mTokenizer->skipDelimiters(WHITESPACE);
+                status_t status = parseLed();
+                if (status) return status;
             } else {
                 ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
                         keywordToken.string());
                 return BAD_VALUE;
             }
 
             mTokenizer->skipDelimiters(WHITESPACE);
             if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
@@ -358,9 +396,51 @@ status_t KeyLayoutMap::Parser::parseAxis
             scanCode,
             axisInfo.mode, axisInfo.axis, axisInfo.highAxis,
             axisInfo.splitValue, axisInfo.flatOverride);
 #endif
     mMap->mAxes.add(scanCode, axisInfo);
     return NO_ERROR;
 }
 
+status_t KeyLayoutMap::Parser::parseLed() {
+    String8 codeToken = mTokenizer->nextToken(WHITESPACE);
+    bool mapUsage = false;
+    if (codeToken == "usage") {
+        mapUsage = true;
+        mTokenizer->skipDelimiters(WHITESPACE);
+        codeToken = mTokenizer->nextToken(WHITESPACE);
+    }
+    char* end;
+    int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+    if (*end) {
+        ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().string(),
+                mapUsage ? "usage" : "scan code", codeToken.string());
+        return BAD_VALUE;
+    }
+
+    KeyedVector<int32_t, Led>& map = mapUsage ? mMap->mLedsByUsageCode : mMap->mLedsByScanCode;
+    if (map.indexOfKey(code) >= 0) {
+        ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().string(),
+                mapUsage ? "usage" : "scan code", codeToken.string());
+        return BAD_VALUE;
+    }
+
+    mTokenizer->skipDelimiters(WHITESPACE);
+    String8 ledCodeToken = mTokenizer->nextToken(WHITESPACE);
+    int32_t ledCode = getLedByLabel(ledCodeToken.string());
+    if (ledCode < 0) {
+        ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().string(),
+                ledCodeToken.string());
+        return BAD_VALUE;
+    }
+
+#if DEBUG_PARSER
+    ALOGD("Parsed led %s: code=%d, ledCode=%d.",
+            mapUsage ? "usage" : "scan code", code, ledCode);
+#endif
+
+    Led led;
+    led.ledCode = ledCode;
+    map.add(code, led);
+    return NO_ERROR;
+}
 };
--- a/widget/gonk/libui/KeyLayoutMap.h
+++ b/widget/gonk/libui/KeyLayoutMap.h
@@ -62,31 +62,40 @@ struct AxisInfo {
  */
 class KeyLayoutMap : public RefBase {
 public:
     static status_t load(const String8& filename, sp<KeyLayoutMap>* outMap);
 
     status_t mapKey(int32_t scanCode, int32_t usageCode,
             int32_t* outKeyCode, uint32_t* outFlags) const;
     status_t findScanCodesForKey(int32_t keyCode, Vector<int32_t>* outScanCodes) const;
+    status_t findScanCodeForLed(int32_t ledCode, int32_t* outScanCode) const;
+    status_t findUsageCodeForLed(int32_t ledCode, int32_t* outUsageCode) const;
 
     status_t mapAxis(int32_t scanCode, AxisInfo* outAxisInfo) const;
 
 protected:
     virtual ~KeyLayoutMap();
 
 private:
     struct Key {
         int32_t keyCode;
         uint32_t flags;
     };
 
+    struct Led {
+        int32_t ledCode;
+    };
+
+
     KeyedVector<int32_t, Key> mKeysByScanCode;
     KeyedVector<int32_t, Key> mKeysByUsageCode;
     KeyedVector<int32_t, AxisInfo> mAxes;
+    KeyedVector<int32_t, Led> mLedsByScanCode;
+    KeyedVector<int32_t, Led> mLedsByUsageCode;
 
     KeyLayoutMap();
 
     const Key* getKey(int32_t scanCode, int32_t usageCode) const;
 
     class Parser {
         KeyLayoutMap* mMap;
         Tokenizer* mTokenizer;
@@ -94,14 +103,15 @@ private:
     public:
         Parser(KeyLayoutMap* map, Tokenizer* tokenizer);
         ~Parser();
         status_t parse();
 
     private:
         status_t parseKey();
         status_t parseAxis();
+        status_t parseLed();
     };
 };
 
 } // namespace android
 
 #endif // _ANDROIDFW_KEY_LAYOUT_MAP_H
--- a/widget/gonk/libui/Keyboard.cpp
+++ b/widget/gonk/libui/Keyboard.cpp
@@ -199,16 +199,19 @@ uint32_t getKeyFlagByLabel(const char* l
 int32_t getAxisByLabel(const char* label) {
     return int32_t(lookupValueByLabel(label, AXES));
 }
 
 const char* getAxisLabel(int32_t axisId) {
     return lookupLabelByValue(axisId, AXES);
 }
 
+int32_t getLedByLabel(const char* label) {
+    return int32_t(lookupValueByLabel(label, LEDS));
+}
 static int32_t setEphemeralMetaState(int32_t mask, bool down, int32_t oldMetaState) {
     int32_t newMetaState;
     if (down) {
         newMetaState = oldMetaState | mask;
     } else {
         newMetaState = oldMetaState &
                 ~(mask | AMETA_ALT_ON | AMETA_SHIFT_ON | AMETA_CTRL_ON | AMETA_META_ON);
     }
--- a/widget/gonk/libui/Keyboard.h
+++ b/widget/gonk/libui/Keyboard.h
@@ -100,16 +100,18 @@ extern uint32_t getKeyFlagByLabel(const 
 extern int32_t getAxisByLabel(const char* label);
 
 /**
  * Gets a axis label by its id.
  * Returns NULL if unknown.
  */
 extern const char* getAxisLabel(int32_t axisId);
 
+extern int32_t getLedByLabel(const char* label);
+
 /**
  * Updates a meta state field when a key is pressed or released.
  */
 extern int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState);
 
 /**
  * Returns true if a key is a meta key like ALT or CAPS_LOCK.
  */
--- a/widget/gonk/libui/KeycodeLabels.h
+++ b/widget/gonk/libui/KeycodeLabels.h
@@ -351,9 +351,30 @@ static const KeycodeLabel AXES[] = {
     { "GENERIC_16", 47 },
 
     // NOTE: If you add a new axis here you must also add it to several other files.
     //       Refer to frameworks/base/core/java/android/view/MotionEvent.java for the full list.
 
     { NULL, -1 }
 };
 
+static const KeycodeLabel LEDS[] = {
+    { "NUM_LOCK", 1},
+    { "CAPS_LOCK", 2},
+    { "SCROLL_LOCK", 3},
+    { "COMPOSE", 4},
+    { "KANA", 5},
+    { "SLEEP", 6},
+    { "SUSPEND", 7},
+    { "MUTE", 8},
+    { "MISC", 9},
+    { "MAIL", 10},
+    { "CHARGING", 11},
+    { "CONTROLLER_1", 12},
+    { "CONTROLLER_2", 13},
+    { "CONTROLLER_3", 14},
+    { "CONTROLLER_4", 15},
+
+    // NOTE: If you add new LEDs here, you must also add them to Input.h
+    { NULL, 0 }
+};
+
 #endif // _ANDROIDFW_KEYCODE_LABELS_H