Merge m-c to inbound, a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Wed, 23 Sep 2015 13:23:42 -0700
changeset 264116 b00078e693a0fc6c5d45e410519c62db0f05bd00
parent 264115 027ddfe2c4afed797a06e5ef74ca9aa91d3da86b (current diff)
parent 264061 4a46de29431baa621d98d8f1168e43297ce1a916 (diff)
child 264117 085253afab5842cf88a1a09b15b8d02172b4edb1
push id17661
push usercbook@mozilla.com
push dateThu, 24 Sep 2015 10:08:50 +0000
treeherderb2g-inbound@001942e4617b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.0a1
Merge m-c to inbound, a=merge CLOSED TREE
browser/components/search/test/testEngine.src
dom/webidl/moz.build
modules/libpref/init/all.js
testing/config/mozharness/b2g_emulator_config.py
testing/config/mozharness/linux_config.py
testing/config/mozharness/linux_mulet_config.py
testing/config/mozharness/marionette.py
testing/mozharness/scripts/b2g_build.py
toolkit/components/search/tests/xpcshell/data/engine.src
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="12ff7481566587aa4198cf1287598acb3a999973"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--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="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <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="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <!-- 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"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
@@ -124,17 +124,17 @@
   <project name="platform/system/vold" path="system/vold" revision="d4455b8cf361f8353e8aebac15ffd64b4aedd2b9"/>
   <project name="platform/external/icu4c" path="external/icu4c" remote="aosp" revision="b4c6379528887dc25ca9991a535a8d92a61ad6b6"/>
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="2da3a2d5100f8afa1229bb50aa2a29ea0aaf8417"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="8586f55fe4b015911b48e731b69c592ad82a0807"/>
   <default remote="caf" revision="refs/tags/android-4.4.2_r1" sync-j="4"/>
   <!-- Emulator specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="72ffdf71c68a96309212eb13d63560d66db14c9e"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="8d4018ebd33ac3f1a043b2d54bc578028656a659"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="b2d449276b015d8fe6cd9ea60c389c7e6975f841"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="e002268b30ce69725e60fdc827a19d729cce7396"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="f37bd545063039e30a92f2550ae78c0e6e4e2d08"/>
   <project name="platform_external_wpa_supplicant_8" path="external/wpa_supplicant_8" remote="b2g" revision="0c6a6547cd1fd302fa2b0f6e375654df36bf0ec4"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="7132bc11fbc68acfebcd509095562ca04fad5584"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5f4b68c799927b6e078f987b12722c3a6ccd4a45"/>
   <project name="platform/development" path="development" revision="5968ff4e13e0d696ad8d972281fc27ae5a12829b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="0951179277915335251c5e11d242e4e1a8c2236f"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--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="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--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="1b0db93fb6b870b03467aff50d6419771ba0d88c">
     <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="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="27eb2f04e149fc2c9976d881b1b5984bbe7ee089"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="12ff7481566587aa4198cf1287598acb3a999973"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--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="8d83715f08b7849f16a0dfc88f78d5c3a89c0a54">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "aad942e6960cdd6b2d09900b157b16f4ddb9423c", 
+        "git_revision": "8472f0c736660072799aaae60e4b6001a6aaceb4", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "1c723e1073f387534a091afba1ed5fbac0461c57", 
+    "revision": "e4b5ba76d5a4de0cd220310e0fe2ba334f0e250a", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--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="660169a3d7e034a892359e39135e8c2785a6ad6f">
     <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="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <!-- 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"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--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="c9d4fe680662ee44a4bdea42ae00366f5df399cf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="aad942e6960cdd6b2d09900b157b16f4ddb9423c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8472f0c736660072799aaae60e4b6001a6aaceb4"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="9465d16f95ab87636b2ae07538ee88e5aeff2d7d"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="828317e64d28138f24d578ab340c2a0ff8552df0"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="5f931350fbc87c3df9db8b0ceb37734b8b471593"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="48d8c7c950745f1b166b42125e6f0d3293d71636"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f004530b30a63c08a16d82536858600446b2abf5"/>
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -270,17 +270,17 @@ var gTests = [
         deferred.resolve();
       });
     });
     Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
     registerCleanupFunction(function () {
       Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
     });
     Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
-                              Ci.nsISearchEngine.DATA_XML, null, false);
+                              null, null, false);
     return deferred.promise;
   }
 },
 
 {
   desc: "Make sure that a page can't imitate about:home",
   setup: function () { },
   run: function (aSnippetsMap)
@@ -654,17 +654,17 @@ function promiseContentSearchChange(newE
     });
   });
 }
 
 function promiseNewEngine(basename) {
   info("Waiting for engine to be added: " + basename);
   let addDeferred = Promise.defer();
   let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+  Services.search.addEngine(url, null, "", false, {
     onSuccess: function (engine) {
       info("Search engine added: " + basename);
       registerCleanupFunction(() => {
         try {
           Services.search.removeEngine(engine);
         } catch (ex) { /* Can't remove the engine more than once */ }
       });
       addDeferred.resolve(engine);
--- a/browser/base/content/test/general/browser_contentSearchUI.js
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -189,42 +189,42 @@ add_task(function* cycleSuggestions() {
   yield setUp();
   yield msg("key", { key: "x", waitForSuggestions: true });
 
   let cycle = Task.async(function* (aSelectedButtonIndex) {
     let modifiers = {
       shiftKey: true,
       accelKey: true,
     };
-  
+
     let state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
     checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
     checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
     checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_DOWN", modifiers: modifiers });
     checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
     checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
     checkState(state, "xbar", ["xfoo", "xbar"], 1, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
     checkState(state, "xfoo", ["xfoo", "xbar"], 0, aSelectedButtonIndex);
-  
+
     state = yield msg("key", { key: "VK_UP", modifiers: modifiers });
     checkState(state, "x", ["xfoo", "xbar"], -1, aSelectedButtonIndex);
   });
-  
+
   yield cycle();
 
   // Repeat with a one-off selected.
   let state = yield msg("key", "VK_TAB");
   checkState(state, "x", ["xfoo", "xbar"], 2);
   yield cycle(0);
 
   // Repeat with the settings button selected.
@@ -684,17 +684,17 @@ function checkState(actualState, expecte
       str: sugg,
       type: "remote",
     };
   });
 
   if (expectedSelectedIdx == -1 && expectedSelectedButtonIdx != undefined) {
     expectedSelectedIdx = expectedSuggestions.length + expectedSelectedButtonIdx;
   }
-  
+
   let expectedState = {
     selectedIndex: expectedSelectedIdx,
     numSuggestions: expectedSuggestions.length,
     suggestionAtIndex: expectedSuggestions.map(s => s.str),
     isFormHistorySuggestionAtIndex:
       expectedSuggestions.map(s => s.type == "formHistory"),
 
     tableHidden: expectedSuggestions.length == 0,
@@ -754,39 +754,20 @@ function promiseMsg(name, type, msgMan) 
 }
 
 function setUpEngines() {
   return Task.spawn(function* () {
     info("Removing default search engines");
     let currentEngineName = Services.search.currentEngine.name;
     let currentEngines = Services.search.getVisibleEngines();
     info("Adding test search engines");
-    let engine1 = yield promiseNewEngine(TEST_ENGINE_BASENAME);
-    let engine2 = yield promiseNewEngine(TEST_ENGINE_2_BASENAME);
+    let engine1 = yield promiseNewSearchEngine(TEST_ENGINE_BASENAME);
+    let engine2 = yield promiseNewSearchEngine(TEST_ENGINE_2_BASENAME);
     Services.search.currentEngine = engine1;
     for (let engine of currentEngines) {
       Services.search.removeEngine(engine);
     }
     registerCleanupFunction(() => {
       Services.search.restoreDefaultEngines();
-      Services.search.removeEngine(engine1);
-      Services.search.removeEngine(engine2);
       Services.search.currentEngine = Services.search.getEngineByName(currentEngineName);
     });
   });
 }
-
-function promiseNewEngine(basename) {
-  info("Waiting for engine to be added: " + basename);
-  let addDeferred = Promise.defer();
-  let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
-    onSuccess: function (engine) {
-      info("Search engine added: " + basename);
-      addDeferred.resolve(engine);
-    },
-    onError: function (errCode) {
-      ok(false, "addEngine failed with error code " + errCode);
-      addDeferred.reject();
-    },
-  });
-  return addDeferred.promise;
-}
--- a/browser/base/content/test/general/browser_keywordSearch_postData.js
+++ b/browser/base/content/test/general/browser_keywordSearch_postData.js
@@ -45,17 +45,17 @@ function test() {
 
   registerCleanupFunction(function () {
     gBrowser.removeTab(tab);
 
     Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
   });
 
   Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
-                            Ci.nsISearchEngine.DATA_XML, null, false);
+                            null, null, false);
 }
 
 var gCurrTest;
 function nextTest() {
   if (gTests.length) {
     gCurrTest = gTests.shift();
     doTest();
   } else {
--- a/browser/base/content/test/general/browser_parsable_css.js
+++ b/browser/base/content/test/general/browser_parsable_css.js
@@ -60,16 +60,86 @@ function once(target, name) {
     let cb = () => {
       target.removeEventListener(name, cb);
       resolve();
     };
     target.addEventListener(name, cb);
   });
 }
 
+function fetchFile(uri) {
+  return new Promise((resolve, reject) => {
+    let xhr = new XMLHttpRequest();
+    xhr.open("GET", uri, true);
+    xhr.onreadystatechange = function() {
+      if (this.readyState != this.DONE) {
+        return;
+      }
+      try {
+        resolve(this.responseText);
+      } catch (ex) {
+        ok(false, `Script error reading ${uri}: ${ex}`);
+        resolve("");
+      }
+    };
+    xhr.onerror = error => {
+      ok(false, `XHR error reading ${uri}: ${error}`);
+      resolve("");
+    };
+    xhr.send(null);
+  });
+}
+
+var gChromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]
+                 .getService(Ci.nsIChromeRegistry);
+var gChromeMap = new Map();
+
+function getBaseUriForChromeUri(chromeUri) {
+  let chromeFile = chromeUri + "gobbledygooknonexistentfile.reallynothere";
+  let uri = Services.io.newURI(chromeFile, null, null);
+  let fileUri = gChromeReg.convertChromeURL(uri);
+  return fileUri.resolve(".");
+}
+
+function parseManifest(manifestUri) {
+  return fetchFile(manifestUri.spec).then(data => {
+    for (let line of data.split('\n')) {
+      let [type, ...argv] = line.split(/\s+/);
+      let component;
+      if (type == "content" || type == "skin") {
+        [component] = argv;
+      } else {
+        // skip unrelated lines
+        continue;
+      }
+      let chromeUri = `chrome://${component}/${type}/`;
+      gChromeMap.set(getBaseUriForChromeUri(chromeUri), chromeUri);
+    }
+  });
+}
+
+function convertToChromeUri(fileUri) {
+  let baseUri = fileUri.spec;
+  let path = "";
+  while (true) {
+    let slashPos = baseUri.lastIndexOf("/", baseUri.length - 2);
+    if (slashPos < 0) {
+      info(`File not accessible from chrome protocol: ${fileUri.path}`);
+      return fileUri;
+    }
+    path = baseUri.slice(slashPos + 1) + path;
+    baseUri = baseUri.slice(0, slashPos + 1);
+    if (gChromeMap.has(baseUri)) {
+      let chromeBaseUri = gChromeMap.get(baseUri);
+      let chromeUri = `${chromeBaseUri}${path}`;
+      return Services.io.newURI(chromeUri, null, null);
+    }
+  }
+}
+
 function messageIsCSSError(msg, innerWindowID, outerWindowID) {
   // Only care about CSS errors generated by our iframe:
   if ((msg instanceof Ci.nsIScriptError) &&
       msg.category.includes("CSS") &&
       msg.innerWindowID === innerWindowID && msg.outerWindowID === outerWindowID) {
     // Check if this error is whitelisted in kWhitelist
     if (!ignoredError(msg)) {
       ok(false, "Got error message for " + msg.sourceName + ": " + msg.errorMessage);
@@ -80,37 +150,47 @@ function messageIsCSSError(msg, innerWin
   return false;
 }
 
 add_task(function checkAllTheCSS() {
   let appDir = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
   // This asynchronously produces a list of URLs (sadly, mostly sync on our
   // test infrastructure because it runs against jarfiles there, and
   // our zipreader APIs are all sync)
-  let uris = yield generateURIsFromDirTree(appDir, ".css");
+  let uris = yield generateURIsFromDirTree(appDir, [".css", ".manifest"]);
 
   // Create a clean iframe to load all the files into. This needs to live at a
-  // file or jar URI (depending on whether we're using a packaged build or not)
-  // so that it's allowed to load other same-scheme URIs (i.e. the browser css).
-  let resHandler = Services.io.getProtocolHandler("resource")
-                           .QueryInterface(Ci.nsISubstitutingProtocolHandler);
-  let resURI = Services.io.newURI('resource://testing-common/resource_test_file.html', null, null);
-  let testFile = resHandler.resolveURI(resURI);
+  // chrome URI so that it's allowed to load and parse any styles.
+  let testFile = getRootDirectory(gTestPath) + "dummy_page.html";
   let windowless = Services.appShell.createWindowlessBrowser();
   let iframe = windowless.document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
   windowless.document.documentElement.appendChild(iframe);
   let iframeLoaded = once(iframe, 'load');
   iframe.contentWindow.location = testFile;
   yield iframeLoaded;
   let doc = iframe.contentWindow.document;
   let windowUtils = iframe.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                         .getInterface(Ci.nsIDOMWindowUtils);
   let innerWindowID = windowUtils.currentInnerWindowID;
   let outerWindowID = windowUtils.outerWindowID;
 
+  // Parse and remove all manifests from the list.
+  // NOTE that this must be done before filtering out devtools paths
+  // so that all chrome paths can be recorded.
+  let manifestPromises = [];
+  uris = uris.filter(uri => {
+    if (uri.path.endsWith(".manifest")) {
+      manifestPromises.push(parseManifest(uri));
+      return false;
+    }
+    return true;
+  });
+  // Wait for all manifest to be parsed
+  yield Promise.all(manifestPromises);
+
   // We build a list of promises that get resolved when their respective
   // files have loaded and produced no errors.
   let allPromises = [];
 
   // filter out either the devtools paths or the non-devtools paths:
   let isDevtools = SimpleTest.harnessParameters.subsuite == "devtools";
   let devtoolsPathBits = ["webide", "devtools"];
   uris = uris.filter(uri => isDevtools == devtoolsPathBits.some(path => uri.spec.includes(path)));
@@ -128,17 +208,18 @@ add_task(function checkAllTheCSS() {
       ok(false, "Loading " + linkEl.getAttribute("href") + " threw an error!");
       promiseForThisSpec.resolve();
       linkEl.removeEventListener("load", onLoad);
       linkEl.removeEventListener("error", onError);
     };
     linkEl.addEventListener("load", onLoad);
     linkEl.addEventListener("error", onError);
     linkEl.setAttribute("type", "text/css");
-    linkEl.setAttribute("href", uri.spec);
+    let chromeUri = convertToChromeUri(uri);
+    linkEl.setAttribute("href", chromeUri.spec);
     allPromises.push(promiseForThisSpec.promise);
     doc.head.appendChild(linkEl);
   }
 
   // Wait for all the files to have actually loaded:
   yield Promise.all(allPromises);
 
   let messages = Services.console.getMessageArray();
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -1028,18 +1028,17 @@ function promiseTopicObserved(aTopic)
       }, aTopic, false);
   });
 }
 
 function promiseNewSearchEngine(basename) {
   return new Promise((resolve, reject) => {
     info("Waiting for engine to be added: " + basename);
     let url = getRootDirectory(gTestPath) + basename;
-    Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "",
-                              false, {
+    Services.search.addEngine(url, null, "", false, {
       onSuccess: function (engine) {
         info("Search engine added: " + basename);
         registerCleanupFunction(() => Services.search.removeEngine(engine));
         resolve(engine);
       },
       onError: function (errCode) {
         Assert.ok(false, "addEngine failed with error code " + errCode);
         reject();
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -232,17 +232,17 @@ function promiseNewSearchEngine({name: b
   for (let i = 0; i < numLogos; i++) {
     expectedSearchEvents.push("CurrentState");
   }
   let eventPromise = promiseSearchEvents(expectedSearchEvents);
 
   // Wait for addEngine().
   let addDeferred = Promise.defer();
   let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+  Services.search.addEngine(url, null, "", false, {
     onSuccess: function (engine) {
       info("Search engine added: " + basename);
       gNewEngines.push(engine);
       addDeferred.resolve(engine);
     },
     onError: function (errCode) {
       ok(false, "addEngine failed with error code " + errCode);
       addDeferred.reject();
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -925,16 +925,20 @@ loop.OTSdkDriver = (function() {
           this.publisher.off("accessAllowed accessDenied accessDialogOpened streamCreated");
           this.publisher.destroy();
           delete this.publisher;
           delete this._mockPublisherEl;
         }
         this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
           reason: FAILURE_DETAILS.UNABLE_TO_PUBLISH_MEDIA
         }));
+      } else if (event.code === OT.ExceptionCodes.UNABLE_TO_PUBLISH) {
+        // We need to log the message so that we can understand where the exception
+        // is coming from. Potentially a temporary addition.
+        this._notifyMetricsEvent("sdk.exception." + event.code + "." + event.message);
       } else {
         this._notifyMetricsEvent("sdk.exception." + event.code);
       }
     },
 
     /**
      * Handles publishing of property changes to a stream.
      */
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -1532,16 +1532,36 @@ describe("loop.OTSdkDriver", function ()
             event: "sdk.exception." + OT.ExceptionCodes.CONNECT_FAILED,
             state: "starting",
             connections: 0,
             sendStreams: 0,
             recvStreams: 0
           }));
       });
 
+      describe("Unable to publish (not GetUserMedia)", function() {
+        it("should notify metrics", function() {
+          sdk.trigger("exception", {
+            code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
+            message: "Fake",
+            title: "Connect Failed"
+          });
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          sinon.assert.calledWithExactly(dispatcher.dispatch,
+            new sharedActions.ConnectionStatus({
+              event: "sdk.exception." + OT.ExceptionCodes.UNABLE_TO_PUBLISH + ".Fake",
+              state: "starting",
+              connections: 0,
+              sendStreams: 0,
+              recvStreams: 0
+            }));
+        });
+      });
+
       describe("Unable to publish (GetUserMedia)", function() {
         it("should destroy the publisher", function() {
           sdk.trigger("exception", {
             code: OT.ExceptionCodes.UNABLE_TO_PUBLISH,
             message: "GetUserMedia"
           });
 
           sinon.assert.calledOnce(publisher.destroy);
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -441,23 +441,21 @@
     </implementation>
 
     <handlers>
       <handler event="command"><![CDATA[
         const target = event.originalTarget;
         if (target.engine) {
           this.currentEngine = target.engine;
         } else if (target.classList.contains("addengine-item")) {
-          // We only detect OpenSearch files
-          var type = Ci.nsISearchEngine.DATA_XML;
           // Select the installed engine if the installation succeeds
           var installCallback = {
             onSuccess: engine => this.currentEngine = engine
           }
-          Services.search.addEngine(target.getAttribute("uri"), type,
+          Services.search.addEngine(target.getAttribute("uri"), null,
                                     target.getAttribute("src"), false,
                                     installCallback);
         }
         else
           return;
 
         this.focus();
         this.select();
@@ -1427,18 +1425,17 @@
             onSuccess: function(engine) {
               event.target.hidePopup();
               BrowserSearch.searchBar.openSuggestionsPanel();
             },
             onError: function(errorCode) {
               Components.utils.reportError("Error adding search engine: " + errorCode);
             }
           }
-          Services.search.addEngine(target.getAttribute("uri"),
-                                    Ci.nsISearchEngine.DATA_XML,
+          Services.search.addEngine(target.getAttribute("uri"), null,
                                     target.getAttribute("image"), false,
                                     installCallback);
         }
         let anonid = target.getAttribute("anonid");
         if (anonid == "search-one-offs-context-open-in-new-tab") {
           let searchbar = document.getElementById("searchbar");
           searchbar.handleSearchCommand(event, this._contextEngine, true);
         }
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -2,17 +2,16 @@
 skip-if = buildapp == 'mulet'
 support-files =
   426329.xml
   483086-1.xml
   483086-2.xml
   head.js
   opensearch.html
   test.html
-  testEngine.src
   testEngine.xml
   testEngine_diacritics.xml
   testEngine_dupe.xml
   testEngine_mozsearch.xml
   webapi.html
 
 [browser_426329.js]
 skip-if = e10s # Bug ?????? - Test uses load event and checks event.target.
--- a/browser/components/search/test/browser_426329.js
+++ b/browser/components/search/test/browser_426329.js
@@ -94,18 +94,17 @@ function* promiseSetEngine() {
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         deferred.resolve();
         break;
     }
   };
 
   Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/426329.xml",
-               Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
-               false);
+               null, "data:image/x-icon,%00", false);
 
   return deferred.promise;
 }
 
 function* promiseRemoveEngine() {
   let deferred = Promise.defer();
   var ss = Services.search;
 
--- a/browser/components/search/test/browser_483086.js
+++ b/browser/components/search/test/browser_483086.js
@@ -19,33 +19,31 @@ function test() {
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         test2();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-1.xml",
-                Ci.nsISearchEngine.DATA_XML, "data:image/x-icon;%00",
-                false);
+                null, "data:image/x-icon;%00", false);
 }
 
 function test2() {
   function observer(aSubject, aTopic, aData) {
     switch (aData) {
       case "engine-added":
         let engine = gSS.getEngineByName("483086b");
         ok(engine, "Test engine 2 installed");
         is(engine.searchForm, "http://example.com", "SearchForm is correct");
         gSS.removeEngine(engine);
         break;
-      case "engine-removed":  
+      case "engine-removed":
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/483086-2.xml",
-                Ci.nsISearchEngine.DATA_XML, "data:image/x-icon;%00",
-                false);
+                null, "data:image/x-icon;%00", false);
 }
--- a/browser/components/search/test/browser_addEngine.js
+++ b/browser/components/search/test/browser_addEngine.js
@@ -39,22 +39,21 @@ function checkEngine(checkObj, engineObj
 
 var gTests = [
   {
     name: "opensearch install",
     engine: {
       name: "Foo",
       alias: null,
       description: "Foo Search",
-      searchForm: "http://mochi.test:8888/browser/browser/components/search/test/",
-      type: Ci.nsISearchEngine.TYPE_OPENSEARCH
+      searchForm: "http://mochi.test:8888/browser/browser/components/search/test/"
     },
     run: function () {
       gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
-                    Ci.nsISearchEngine.DATA_XML, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC",
+                    null, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC",
                     false);
     },
     added: function (engine) {
       ok(engine, "engine was added.");
 
       checkEngine(this.engine, engine);
 
       let engineFromSS = gSS.getEngineByName(this.engine.name);
@@ -74,47 +73,16 @@ var gTests = [
     },
     removed: function (engine) {
       let currentEngine = gSS.currentEngine;
       ok(currentEngine, "An engine is present.");
       isnot(currentEngine.name, this.engine.name, "Current engine reset after removal");
 
       nextTest();
     }
-  },
-  {
-    name: "sherlock install",
-    engine: {
-      name: "Test Sherlock",
-      alias: null,
-      description: "Test Description",
-      searchForm: "http://example.com/searchform",
-      type: Ci.nsISearchEngine.TYPE_SHERLOCK
-    },
-    run: function () {
-      gSS.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.src",
-                    Ci.nsISearchEngine.DATA_TEXT, "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABGklEQVQoz2NgGB6AnZ1dUlJSXl4eSDIyMhLW4Ovr%2B%2Fr168uXL69Zs4YoG%2BLi4i5dusTExMTGxsbNzd3f37937976%2BnpmZmagbHR09J49e5YvX66kpATVEBYW9ubNm2nTphkbG7e2tp44cQLIuHfvXm5urpaWFlDKysqqu7v73LlzECMYIiIiHj58mJCQoKKicvXq1bS0NKBgW1vbjh074uPjgeqAXE1NzSdPnvDz84M0AEUvXLgAsW379u1z5swBen3jxo2zZ892cHB4%2BvQp0KlAfwI1cHJyghQFBwfv2rULokFXV%2FfixYu7d%2B8GGqGgoMDKyrpu3br9%2B%2FcDuXl5eVA%2FAEWBfoWHAdAYoNuAYQ0XAeoUERFhGDYAAPoUaT2dfWJuAAAAAElFTkSuQmCC",
-                    false);
-    },
-    added: function (engine) {
-      ok(engine, "engine was added.");
-      checkEngine(this.engine, engine);
-
-      let engineFromSS = gSS.getEngineByName(this.engine.name);
-      is(engineFromSS, engine, "engine is obtainable via getEngineByName");
-
-      gSS.removeEngine(engine);
-    },
-    removed: function (engine) {
-      let currentEngine = gSS.currentEngine;
-      ok(currentEngine, "An engine is present.");
-      isnot(currentEngine.name, this.engine.name, "Current engine reset after removal");
-
-      nextTest();
-    }
   }
 ];
 
 var gCurrentTest = null;
 function nextTest() {
   if (gTests.length) {
     gCurrentTest = gTests.shift();
     info("Running " + gCurrentTest.name);
--- a/browser/components/search/test/browser_amazon.js
+++ b/browser/components/search/test/browser_amazon.js
@@ -25,17 +25,16 @@ function test() {
   is(url, "https://completion.amazon.com/search/complete?q=foo&search-alias=aps&mkt=1", "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Amazon.com",
     alias: null,
     description: "Amazon.com Search",
     searchForm: "https://www.amazon.com/exec/obidos/external-search/?field-keywords=&mode=blended&tag=mozilla-20&sourceid=Mozilla-search",
-    type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/browser_bing.js
+++ b/browser/components/search/test/browser_bing.js
@@ -35,17 +35,16 @@ function test() {
   is(url, "https://www.bing.com/osjson.aspx?query=foo&form=OSDJAS&language=" + getLocale(), "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Bing",
     alias: null,
     description: "Bing. Search by Microsoft.",
     searchForm: "https://www.bing.com/search?q=&pc=MOZI",
-    type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAEACADaCwAAJgAAACAgAAABAAgAlAIAAAAMAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIAgAAAJCRaDYAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABBUlEQVR42mL8v4OBJMAEZ/0nTgMLnLXtitKRO9JmCi9cNR/wsP8mrOHfP8YbL4RvvBBWFXuvI/WGsJMYSHUSMujbY8LN/ttM4bmO1BtW5n+ENdipPmndbrHjqiIn6x9DuZc2yk8tlZ7hc5Kx/AtzxecMDAzff7Mcuys9/7gOAT8wMjAUOZ9x0XhI2A98HL+Eub/vuSG/8ozGmy+cEEF+zp/YNYjxfvPTv9O63fLpBx6ICCvz32DD24EGt7Fo4Gb/zcX2Z84RPbiIqfyLZJtL4rzfsDvJUf3R91+sC09o//7LJMn/NdXmkqHsSyzeQ0t8j9/znn8s7ql9Dy34cWogIbUSCQADAJ+jWQrH9LCsAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAACW0lEQVR4nGP8v5OBpoCJtsbTwQIWTKGUxe5mCi9M5V/oSL9mZf5HoQWMmHEQOD0AwuBg/WMo+8pB/ZGZwguyLcDiAzj48Zvl+D2pTz/YKLGAcBxcfSZCtulEWUAhGDQW3HstcOOF8P//jKRagC+SkQE/58/0pa7c7L9N5V+aKTw3kH3FxvKXmhYI83y3VXl64Jbs3htye2/IsbH8NZB9Zabw3FT+JR/nTypYwMDAEGBw+8AtWQj71x/mU/clT92XZGT8ry7+zlzxhbnic0n+LxRZIC/8yUju5blH4siC//8z3nghfOOF8MLj2jKCnydH7EXTRVoqCjC4g0f2yXteTEHSLNCVft0WcNhM4QXxiYmEIIIATcm3mpJvn37gmX7Q8OozYYLqycloTz/wLDulRYzpDMT4QFf6NZz95gvnyjMa+27I/SM6xxGwQJj7R6rtJQYGhk8/2NaeU9t+RfH3X2ZcihWEP5Fmgazg53qfY9zsv1ed0dh4UeXbL5yKudl/R5tdd9O6T4IFGhJvyz1OHbkts/qc2qfv7LiUMTIwOGk8irW4yo8jP2O3wEzxubHcy7I1Dq+/cOIymoGBQVn0Q5rtRTXx93jUYLFAX+b1sw88p+5L4tHGy/Er2uy6m9YDRsb/eJRht8BS+emCY7q4NDAyMLhpPYixuMbD/gu/0VD1WBtezz7w9O81vvNKEE1cTfxdmu0lZdEPxBiNzwIGBoa//xhXndFYfU4NUsnwcf6Ms7jmpPGQ1BoHpwUQcOOF0OT9RoayryJNr3Oz/ybRcCIsoBwMmkp/8FoAADmgy6ulKggYAAAAAElFTkSuQmCC",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/browser_contextmenu.js
+++ b/browser/components/search/test/browser_contextmenu.js
@@ -26,18 +26,17 @@ function test() {
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   ss.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine_mozsearch.xml",
-               Ci.nsISearchEngine.DATA_XML, "data:image/x-icon,%00",
-               false);
+               null, "data:image/x-icon,%00", false);
 
   function startTest() {
     contextMenu = document.getElementById("contentAreaContextMenu");
     ok(contextMenu, "Got context menu XUL");
 
     doOnloadOnce(testContextMenu);
     let tab = gBrowser.selectedTab = gBrowser.addTab("data:text/plain;charset=utf8,test%20search");
     registerCleanupFunction(function () {
--- a/browser/components/search/test/browser_eBay.js
+++ b/browser/components/search/test/browser_eBay.js
@@ -25,17 +25,16 @@ function test() {
   is(url, "http://autosug.ebay.com/autosug?sId=0&kwd=foo&fmt=osr", "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "eBay",
     alias: null,
     description: "eBay - Online auctions",
     searchForm: "http://search.ebay.com/",
-    type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
     hidden: false,
     wrappedJSObject: {
       queryCharset: "ISO-8859-1",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAB6AQAAJgAAACAgAAAAAAAAQgMAAKABAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAFBSURBVDjLtZPdK0MBGIf3J5Babhx3rinFBWuipaUskX9DYvkopqgV90q5UJpyp0OKrUWM2VrRsS9D0zZKHGaOnW1nj4vtypVtPPe/533r9746QAAOAJXfo5Yzgg44pHrcugon/6Sgo0b+XuAOZ2iZiVQmyPoDpIwmUkYTzqM7GsdDdC7F6Lbf8pzOkfWOouzqeZem2b+2AqAV8zjD8yVBqqcf2b7C66yNiMGMfixIQSvi8Mp0LEbR5ADq1QSKWM+Gx0RC9nOZ2GLzwlIWdPWiuNzk4w/EpThNkyEAXKEP2ud8KGId2sspilhPMrmNwzfCuqePr/xbSfC5I/I0MMSj2YJ3z49gDdO2cEOrLUowJpE9G0QRG1ClKbR0EIdvmOPYcnUtnN+vsnZiQC1k/qnGagQ1n3LNzySUJZVskitnmr8BlQG7T2hvgxsAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAMJSURBVFjD7ZddSFNhGMeHXXQTZFFCWfR1pRhUECQlBdWVToo+6KYu1KigtDASG5qUfZgFZvahEDosECPDktKZS1FL+1DRnEvdUptjug91X2dnZzv/3vO6OZbWnR4v9sADL+fs7P97/s/znu2VAJD4UkpSSdKG+QubTyPBr+sXz8XCR64fIAHihVTis0SsUAoAVhEBrBKIHCGAEMB/ARi3F5LkbpS2WMRzYEEBXC2tsD6T03R9agsCGLNyqPw6CXmrBT06JvhbPHZwmkdwtR0B138PPKOHgzXD5jLAy3tmibo4K9weZwDAazJj/FQKRnfugfHMeRiTz0K3Ixam1HQKcPC+Fisu9NK1P08Uj4DleHgMdXC+WQ7nu3UEOhFMfTQcVUvQ1H4IN2sj8H2k7K+2TqCc3GseyA8AmDOzMBq7D9bS8sAr6nEJdNt3UbHVF1XQGtmZew8bTPT6tWoD3KpsUvlR8NxUoEICMvl6KQo+xqCwcRs4T8Ax5c8bFExjbAgAjO7aS8VsLypgq3g5nWStjztAhWRVhqAqeB6IuKTClkw1eNYEbrCQQBwD8yGGOsAooogLYejQPKBi7UPF9DkH+ezd+o141ZkUPAOC+L9SAMivNc7q46YMNSLTe4n1kaQF4XD3ZIDTPgU3XEYciKcAHrsGJS1xKFBGgyVzouiT4VbdGhjt/cEA5isyKsaz7jl3we7bg7Rqf6j0LoSldON4wWcqJDgQNGTN++l13vELA+MK6kKd6iryFOvxtidt9i5gO7owdjKJQliflNAU1pas6xQgnAzg1ux+lJEdILixNr0Pq9JUUA8NwVG9DM73G0jlcnh+V4BpjIWzJmIGQIjnXw5TiDuKSEwxurm3ITc8DNO51BnrLbIcsrW0dNA6RxgUKU1UdGVqLy5X6qGzTLvlnewiBZyGs3Yz6X8UeaYI3olvZDhzwLumZ+eHvooCCC0Q5VUsb4unwycM4YIDqA01tPqmgbzQr2EIYPECiPm33LYoDiZSsY9moh9O/Znoa4d9HkXtPg2pX/cPKCoRQ+ocZa4AAAAASUVORK5CYII=",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/browser_google.js
+++ b/browser/components/search/test/browser_google.js
@@ -41,17 +41,16 @@ function test() {
      "Check alternate domain");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Google",
     alias: null,
     description: "Google Search",
     searchForm: "https://www.google.com/search?q=&ie=utf-8&oe=utf-8",
-    type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAB9AQAAJgAAACAgAAAAAAAA8gIAAKMBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAFESURBVDjLpZNJSwNBEIXnt4lE4kHxovgT9BDwJHqPy0HEEOJBiAuCRg+KUdC4QS4KrpC4gCBGE3NQ48JsnZ6eZ3UOM6gjaePhQU93v6+qq2q0pqgeJj2S8EdJT1hr0OxBtKCD5iEd8QxDYpvhvOBAuMDKURX9C9aPu4GA1GEVkzvMg10UBfYveWAWgYAP00V01fa+R9M2bA51wJvhIn3qR+ybt3D3JNQBE5sMjCIOLFpoHzOwdsLRO22qA6R6kiZiWwxUvy/PUQZIhYZ1vFM9cvcOOsYNdcBgysISdSJBnZjJMlR0Fw8vAp0xoz5gao/h+NZBy4i/10XGwrPA+hmvDyhVRG2Avu/LwcrkFADZa16L1h330w1RNgc3DiJzCpPYRm1bpveXX11clQR28xwblHpk1vq1iP/5mcoS0CoXDZiL0vsJ+dzfl+3T/VYAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAK5SURBVFjDxVfrSxRRFJ9/Jta/oyWjF5XQm6D6EkHRgygIIgjUTcueVgqVWSRRkppEUQYWWB8ye1iGWilWlo/Ude489s7M6Zw7D9dlt53dmd29cFiWvXvO77x+51xpaaUsoSxBaUWZQ4ECy5xji2xKZDyCMlMEw6lCNiOSgwZKJK1SkcKeSealfP64t0mBjl4Ow39MkDUL0p2RSROOtqhZdeUEYM1pBl39XCg/fEeFtWcY7G9W4csvUxjlBkCsQ4Nt9QyWVfvT6RsAKXw3aoDGATZeYIt+W1kjw7cJG0RctWDTRebbKd8A6h5pwsDb70ba3w/eUr3wt/cmwgfw6Yft4TNMQaY7o1P2ncm4FT4ANQH/jQBJ2xv7kqIXEADDql8eS3+n8bku7oxNm+EDIM/dU92upb3T/NJGeaNbDx/AsbsLRUY5Xn92caWXY5d8RV6gWllxSg4fAEnTC90DQW13BLlgXR2D3dcUeDVkwOthA1bXspxILWcm3HdThcfvufB26LcJpkOEAz9NKI/lzqpSEC7feol5EWnpSeSlIxCALUkApmULdjUqxQVAQnl3D/X/yQda4QBEq2TYc12By091MQ17Bg3R88nHKlQbVmHvj89awNBLYrwT9zXY2aBAxTkGFdiSxP/Jp6FLDw+AS7GfsdJTJ2EqSO5khD43nGfBARy/ZxOQgZHe7GPM1jzUvChUtmnBAXQPcKGMJp3fdFGq6NByEhiAO4b/YptFfQJwNyQ/bZkVQGcf90Ja25ndIyrKBOa/f8wIpwi3X1G8UcxNu7ozUS7tiH0jBswwS3RIaF1w6LYKU/ML2+8sGnjygQswtKrVIy/Qd9qQP6LnO64q4fPAKpxyZIymHo1jWk6p1ag2BsdNwQMHcC+M5kHFJX+YlPxpVlbCx2mZ5DzPI04k4kUwHHdskU3pH76iftG8yWlkAAAAAElFTkSuQmCC",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/browser_healthreport.js
+++ b/browser/components/search/test/browser_healthreport.js
@@ -91,19 +91,17 @@ function test() {
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified", false);
   Services.search.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
-                            Ci.nsISearchEngine.DATA_XML,
-                            "data:image/x-icon,%00",
-                            false);
+                            null, "data:image/x-icon,%00", false);
 
 }
 
 function resetPreferences() {
   let service = Components.classes["@mozilla.org/datareporting/service;1"]
                                   .getService(Components.interfaces.nsISupports)
                                   .wrappedJSObject;
   service.policy._prefs.resetBranch("datareporting.policy.");
--- a/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
+++ b/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
@@ -4,17 +4,17 @@
 const testPref = "Foo,FooDupe";
 
 function promiseNewEngine(basename) {
   return new Promise((resolve, reject) => {
     info("Waiting for engine to be added: " + basename);
     Services.search.init({
       onInitComplete: function() {
         let url = getRootDirectory(gTestPath) + basename;
-        Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+        Services.search.addEngine(url, null, "", false, {
           onSuccess: function (engine) {
             info("Search engine added: " + basename);
             resolve(engine);
           },
           onError: function (errCode) {
             ok(false, "addEngine failed with error code " + errCode);
             reject();
           }
--- a/browser/components/search/test/browser_private_search_perwindowpb.js
+++ b/browser/components/search/test/browser_private_search_perwindowpb.js
@@ -46,18 +46,17 @@ function test() {
       onSuccess: function (engine) {
         Services.search.currentEngine = engine;
         aCallback();
       },
       onError: function (errorCode) {
         ok(false, "failed to install engine: " + errorCode);
       }
     };
-    Services.search.addEngine(engineURL + "426329.xml",
-                              Ci.nsISearchEngine.DATA_XML,
+    Services.search.addEngine(engineURL + "426329.xml", null,
                               "data:image/x-icon,%00", false, installCallback);
   }
 
   function testOnWindow(aIsPrivate, aCallback) {
     let win = whenNewWindowLoaded({ private: aIsPrivate }, function() {
       waitForFocus(aCallback, win);
     });
     windowsToClose.push(win);
--- a/browser/components/search/test/browser_webapi.js
+++ b/browser/components/search/test/browser_webapi.js
@@ -3,21 +3,17 @@ const searchBundle = Services.strings.cr
 const brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
 const brandName = brandBundle.GetStringFromName("brandShortName");
 
 function getString(key, ...params) {
   return searchBundle.formatStringFromName(key, params, params.length);
 }
 
 function AddSearchProvider(...args) {
-  return gBrowser.addTab(ROOT + "webapi.html?AddSearchProvider:" + encodeURIComponent(JSON.stringify(args)));
-}
-
-function addSearchEngine(...args) {
-  return gBrowser.addTab(ROOT + "webapi.html?addSearchEngine:" + encodeURIComponent(JSON.stringify(args)));
+  return gBrowser.addTab(ROOT + "webapi.html?" + encodeURIComponent(JSON.stringify(args)));
 }
 
 function promiseDialogOpened() {
   return new Promise((resolve, reject) => {
     Services.wm.addListener({
       onOpenWindow: function(xulWin) {
         Services.wm.removeListener(this);
 
@@ -29,153 +25,68 @@ function promiseDialogOpened() {
           else
             reject();
         }, win);
       }
     });
   });
 }
 
-add_task(function* test_working_AddSearchProvider() {
+add_task(function* test_working() {
   gBrowser.selectedTab = AddSearchProvider(ROOT + "testEngine.xml");
 
   let dialog = yield promiseDialogOpened();
   is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
   is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
      "Should have seen the right install message");
   dialog.document.documentElement.cancelDialog();
 
   gBrowser.removeCurrentTab();
 });
 
-add_task(function* test_HTTP_AddSearchProvider() {
+add_task(function* test_HTTP() {
   gBrowser.selectedTab = AddSearchProvider(ROOT.replace("http:", "HTTP:") + "testEngine.xml");
 
   let dialog = yield promiseDialogOpened();
   is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
   is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
      "Should have seen the right install message");
   dialog.document.documentElement.cancelDialog();
 
   gBrowser.removeCurrentTab();
 });
 
-add_task(function* test_relative_AddSearchProvider() {
+add_task(function* test_relative() {
   gBrowser.selectedTab = AddSearchProvider("testEngine.xml");
 
   let dialog = yield promiseDialogOpened();
   is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
   is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
      "Should have seen the right install message");
   dialog.document.documentElement.cancelDialog();
 
   gBrowser.removeCurrentTab();
 });
 
-add_task(function* test_invalid_AddSearchProvider() {
+add_task(function* test_invalid() {
   gBrowser.selectedTab = AddSearchProvider("z://foobar");
 
   let dialog = yield promiseDialogOpened();
   is(dialog.args.promptType, "alert", "Should see the alert dialog.");
   is(dialog.args.text, getString("error_invalid_engine_msg", brandName),
      "Should have seen the right error message")
   dialog.document.documentElement.acceptDialog();
 
   gBrowser.removeCurrentTab();
 });
 
-add_task(function* test_missing_AddSearchProvider() {
+add_task(function* test_missing() {
   let url = ROOT + "foobar.xml";
   gBrowser.selectedTab = AddSearchProvider(url);
 
   let dialog = yield promiseDialogOpened();
   is(dialog.args.promptType, "alert", "Should see the alert dialog.");
   is(dialog.args.text, getString("error_loading_engine_msg2", brandName, url),
      "Should have seen the right error message")
   dialog.document.documentElement.acceptDialog();
 
   gBrowser.removeCurrentTab();
 });
-
-add_task(function* test_working_addSearchEngine_xml() {
-  gBrowser.selectedTab = addSearchEngine(ROOT + "testEngine.xml", "", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
-  is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
-     "Should have seen the right install message");
-  dialog.document.documentElement.cancelDialog();
-
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* test_working_addSearchEngine_src() {
-  gBrowser.selectedTab = addSearchEngine(ROOT + "testEngine.src", "", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
-  is(dialog.args.text, getString("addEngineConfirmation", "Test Sherlock", "example.com"),
-     "Should have seen the right install message");
-  dialog.document.documentElement.cancelDialog();
-
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* test_relative_addSearchEngine_xml() {
-  gBrowser.selectedTab = addSearchEngine("testEngine.xml", "", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
-  is(dialog.args.text, getString("addEngineConfirmation", "Foo", "example.com"),
-     "Should have seen the right install message");
-  dialog.document.documentElement.cancelDialog();
-
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* test_relative_addSearchEngine_src() {
-  gBrowser.selectedTab = addSearchEngine("testEngine.src", "", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "confirmEx", "Should see the confirmation dialog.");
-  is(dialog.args.text, getString("addEngineConfirmation", "Test Sherlock", "example.com"),
-     "Should have seen the right install message");
-  dialog.document.documentElement.cancelDialog();
-
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* test_invalid_addSearchEngine() {
-  gBrowser.selectedTab = addSearchEngine("z://foobar", "", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "alert", "Should see the alert dialog.");
-  is(dialog.args.text, getString("error_invalid_engine_msg", brandName),
-     "Should have seen the right error message")
-  dialog.document.documentElement.acceptDialog();
-
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* test_invalid_icon_addSearchEngine() {
-  gBrowser.selectedTab = addSearchEngine(ROOT + "testEngine.src", "z://foobar", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "alert", "Should see the alert dialog.");
-  is(dialog.args.text, getString("error_invalid_engine_msg", brandName),
-     "Should have seen the right error message")
-  dialog.document.documentElement.acceptDialog();
-
-  gBrowser.removeCurrentTab();
-});
-
-add_task(function* test_missing_addSearchEngine() {
-  let url = ROOT + "foobar.xml";
-  gBrowser.selectedTab = addSearchEngine(url, "", "", "");
-
-  let dialog = yield promiseDialogOpened();
-  is(dialog.args.promptType, "alert", "Should see the alert dialog.");
-  is(dialog.args.text, getString("error_loading_engine_msg2", brandName, url),
-     "Should have seen the right error message")
-  dialog.document.documentElement.acceptDialog();
-
-  gBrowser.removeCurrentTab();
-});
--- a/browser/components/search/test/browser_yahoo.js
+++ b/browser/components/search/test/browser_yahoo.js
@@ -25,17 +25,16 @@ function test() {
   is(url, "https://search.yahoo.com/sugg/ff?output=fxjson&appid=ffd&command=foo", "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Yahoo",
     alias: null,
     description: "Yahoo Search",
     searchForm: "https://search.yahoo.com/yhs/search?p=&ei=UTF-8&hspart=mozilla",
-    type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
--- a/browser/components/search/test/head.js
+++ b/browser/components/search/test/head.js
@@ -141,17 +141,17 @@ function promiseNewEngine(basename, opti
     //Default the setAsCurrent option to true.
     let setAsCurrent =
       options.setAsCurrent == undefined ? true : options.setAsCurrent;
     info("Waiting for engine to be added: " + basename);
     Services.search.init({
       onInitComplete: function() {
         let url = getRootDirectory(gTestPath) + basename;
         let current = Services.search.currentEngine;
-        Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+        Services.search.addEngine(url, null, "", false, {
           onSuccess: function (engine) {
             info("Search engine added: " + basename);
             if (setAsCurrent) {
               Services.search.currentEngine = engine;
             }
             registerCleanupFunction(() => {
               if (setAsCurrent) {
                 Services.search.currentEngine = current;
deleted file mode 100644
--- a/browser/components/search/test/testEngine.src
+++ /dev/null
@@ -1,11 +0,0 @@
-<search 
-  name="Test Sherlock"
-  description="Test Description"
-  method="GET"
-  searchform="http://example.com/searchform"
-  action="http://example.com/action"
-  queryCharset="UTF-8"
->
-  <input name="userParam" user>
-  <input name="param" value="value">
-</search>
--- a/browser/components/search/test/webapi.html
+++ b/browser/components/search/test/webapi.html
@@ -1,20 +1,16 @@
 <!DOCTYPE html>
 
 <html>
 <head>
 <script>
 function installEngine() {
-  var query = window.location.search.substring(1).split(":");
-  var func = query[0];
-  var args = JSON.parse(decodeURIComponent(query[1]));
+  var query = window.location.search.substring(1);
+  var args = JSON.parse(decodeURIComponent(query));
 
-  if (func == "AddSearchProvider")
-    window.external.AddSearchProvider(...args);
-  else if (func == "addSearchEngine")
-    window.sidebar.addSearchEngine(...args);
+  window.external.AddSearchProvider(...args);
 }
 </script>
 </head>
 <body onload="installEngine()">
 </body>
 </html>
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -312,17 +312,17 @@ function waitForNewEngine(basename, numI
   for (let i = 0; i < numImages; i++) {
     expectedSearchEvents.push("CurrentState");
   }
   let eventPromises = expectedSearchEvents.map(e => waitForTestMsg(e));
 
   // Wait for addEngine().
   let addDeferred = Promise.defer();
   let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
+  Services.search.addEngine(url, null, "", false, {
     onSuccess: function (engine) {
       info("Search engine added: " + basename);
       addDeferred.resolve(engine);
     },
     onError: function (errCode) {
       ok(false, "addEngine failed with error code " + errCode);
       addDeferred.reject();
     },
--- a/devtools/client/themes/floating-scrollbars-light.css
+++ b/devtools/client/themes/floating-scrollbars-light.css
@@ -1,10 +1,10 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* 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/. */
 
-@import url("chrome://devtools/skin/themes/floating-scrollbars.css");
+@import url("floating-scrollbars.css");
 
 scrollbar thumb {
   background-color: rgba(170,170,170,0.2) !important;
 }
--- a/dom/base/UseCounters.conf
+++ b/dom/base/UseCounters.conf
@@ -41,8 +41,12 @@
 method SVGSVGElement.getElementById
 attribute SVGSVGElement.currentScale
 property Fill
 property FillOpacity
 
 // Push API
 method PushManager.subscribe
 method PushSubscription.unsubscribe
+
+// window.sidebar.addSearchEngine
+attribute Window.sidebar
+method External.addSearchEngine
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -679,16 +679,18 @@ GK_ATOM(onafterprint, "onafterprint")
 GK_ATOM(onafterscriptexecute, "onafterscriptexecute")
 GK_ATOM(onalerting, "onalerting")
 GK_ATOM(onanimationend, "onanimationend")
 GK_ATOM(onanimationiteration, "onanimationiteration")
 GK_ATOM(onanimationstart, "onanimationstart")
 GK_ATOM(onantennaavailablechange, "onantennaavailablechange")
 GK_ATOM(onAppCommand, "onAppCommand")
 GK_ATOM(onattributechanged, "onattributechanged")
+GK_ATOM(onattributereadreq, "onattributereadreq")
+GK_ATOM(onattributewritereq, "onattributewritereq")
 GK_ATOM(onaudioprocess, "onaudioprocess")
 GK_ATOM(onbeforecopy, "onbeforecopy")
 GK_ATOM(onbeforecut, "onbeforecut")
 GK_ATOM(onbeforepaste, "onbeforepaste")
 GK_ATOM(onbeforeevicted, "onbeforeevicted")
 GK_ATOM(onbeforeprint, "onbeforeprint")
 GK_ATOM(onbeforescriptexecute, "onbeforescriptexecute")
 GK_ATOM(onbeforeunload, "onbeforeunload")
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -161,16 +161,20 @@ DOMInterfaces = {
 'BluetoothDiscoveryHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothDiscoveryHandle',
 },
 
 'BluetoothGatt': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGatt',
 },
 
+'BluetoothGattAttributeEvent': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothGattAttributeEvent',
+},
+
 'BluetoothGattCharacteristic': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattCharacteristic',
 },
 
 'BluetoothGattDescriptor': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGattDescriptor',
 },
 
--- a/dom/bluetooth/bluedroid/BluetoothDaemonGattInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonGattInterface.cpp
@@ -757,215 +757,218 @@ BluetoothDaemonGattModule::ServerDisconn
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerAddServiceCmd(
-  int aServerIf, const BluetoothGattServiceId& aServiceId, int aNumHandles,
+  int aServerIf, const BluetoothGattServiceId& aServiceId, uint16_t aNumHandles,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_ADD_SERVICE,
                         4 + // Server Interface
                         18 + // Service ID
                         4)); // Number of Handles
 
   nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf), aServiceId,
-                        PackConversion<int, int32_t>(aNumHandles), *pdu);
+                        PackConversion<uint16_t, int32_t>(aNumHandles), *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerAddIncludedServiceCmd(
-  int aServerIf, int aServiceHandle, int aIncludedServiceHandle,
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_ADD_INCLUDED_SERVICE,
                         4 + // Server Interface
                         4 + // Service Handle
                         4)); // Included Service Handle
 
   nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf),
-                        PackConversion<int, int32_t>(aServiceHandle),
-                        PackConversion<int, int32_t>(aIncludedServiceHandle),
-                        *pdu);
+                        aServiceHandle, aIncludedServiceHandle, *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerAddCharacteristicCmd(
-  int aServerIf, int aServiceHandle, const BluetoothUuid& aUuid,
-  BluetoothGattCharProp aProperties, BluetoothGattAttrPerm aPermissions,
-  BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aUuid, BluetoothGattCharProp aProperties,
+  BluetoothGattAttrPerm aPermissions, BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_ADD_CHARACTERISTIC,
                         4 + // Server Interface
                         4 + // Service Handle
                         16 + // UUID
                         4 + // Properties
                         4)); // Permissions
 
-  nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf),
-                        PackConversion<int, int32_t>(aServiceHandle),
-                        aUuid, aProperties, aPermissions, *pdu);
+  nsresult rv = PackPDU(
+    PackConversion<int, int32_t>(aServerIf), aServiceHandle, aUuid,
+    PackConversion<BluetoothGattCharProp, int32_t>(aProperties),
+    PackConversion<BluetoothGattAttrPerm, int32_t>(aPermissions), *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerAddDescriptorCmd(
-  int aServerIf, int aServiceHandle, const BluetoothUuid& aUuid,
-  BluetoothGattAttrPerm aPermissions, BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aUuid, BluetoothGattAttrPerm aPermissions,
+  BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_ADD_DESCRIPTOR,
                         4 + // Server Interface
                         4 + // Service Handle
                         16 + // UUID
                         4)); // Permissions
 
-  nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf),
-                        PackConversion<int, int32_t>(aServiceHandle),
-                        aUuid, aPermissions, *pdu);
+  nsresult rv = PackPDU(
+    PackConversion<int, int32_t>(aServerIf), aServiceHandle, aUuid,
+    PackConversion<BluetoothGattAttrPerm, int32_t>(aPermissions), *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerStartServiceCmd(
-  int aServerIf, int aServiceHandle, BluetoothTransport aTransport,
-  BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothTransport aTransport, BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_START_SERVICE,
                         4 + // Server Interface
                         4 + // Service Handle
                         4)); // Transport
 
   nsresult rv = PackPDU(
-    PackConversion<int, int32_t>(aServerIf),
-    PackConversion<int, int32_t>(aServiceHandle),
+    PackConversion<int, int32_t>(aServerIf), aServiceHandle,
     PackConversion<BluetoothTransport, int32_t>(aTransport), *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerStopServiceCmd(
-  int aServerIf, int aServiceHandle, BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_STOP_SERVICE,
                         4 + // Server Interface
                         4)); // Service Handle
 
-  nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf),
-                        PackConversion<int, int32_t>(aServiceHandle), *pdu);
+  nsresult rv = PackPDU(
+    PackConversion<int, int32_t>(aServerIf), aServiceHandle, *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerDeleteServiceCmd(
-  int aServerIf, int aServiceHandle, BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_DELETE_SERVICE,
                         4 + // Server Interface
                         4)); // Service Handle
 
-  nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf),
-                        PackConversion<int, int32_t>(aServiceHandle), *pdu);
+  nsresult rv = PackPDU(
+    PackConversion<int, int32_t>(aServerIf), aServiceHandle, *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerSendIndicationCmd(
-  int aServerIf, int aAttributeHandle, int aConnId, int aLength, bool aConfirm,
+  int aServerIf, const BluetoothAttributeHandle& aCharacteristicHandle,
+  int aConnId, int aLength, bool aConfirm,
   uint8_t* aValue, BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_SEND_INDICATION,
                         0));
 
   nsresult rv = PackPDU(PackConversion<int, int32_t>(aServerIf),
-                        PackConversion<int, int32_t>(aAttributeHandle),
+                        aCharacteristicHandle,
                         PackConversion<int, int32_t>(aConnId),
                         PackConversion<int, int32_t>(aLength),
                         PackConversion<bool, int32_t>(aConfirm),
                         PackArray<uint8_t>(aValue, aLength), *pdu);
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
@@ -973,33 +976,33 @@ BluetoothDaemonGattModule::ServerSendInd
     return rv;
   }
   unused << pdu.forget();
   return NS_OK;
 }
 
 nsresult
 BluetoothDaemonGattModule::ServerSendResponseCmd(
-  int aConnId, int aTransId, BluetoothGattStatus aStatus,
+  int aConnId, int aTransId, uint16_t aStatus,
   const BluetoothGattResponse& aResponse,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsAutoPtr<DaemonSocketPDU> pdu(
     new DaemonSocketPDU(SERVICE_ID, OPCODE_SERVER_SEND_RESPONSE,
                         0));
 
   nsresult rv = PackPDU(
     PackConversion<int, int32_t>(aConnId),
     PackConversion<int, int32_t>(aTransId),
     aResponse.mHandle,
     aResponse.mOffset,
     PackConversion<BluetoothGattAuthReq, uint8_t>(aResponse.mAuthReq),
-    PackConversion<BluetoothGattStatus, int32_t>(aStatus),
+    PackConversion<uint16_t, int32_t>(aStatus),
     aResponse.mLength,
     PackArray<uint8_t>(aResponse.mValue, aResponse.mLength), *pdu);
 
   if (NS_FAILED(rv)) {
     return rv;
   }
   rv = Send(pdu, aRes);
   if (NS_FAILED(rv)) {
@@ -1933,17 +1936,17 @@ public:
   ServerRequestReadInitOp(DaemonSocketPDU& aPDU)
     : PDUInitOp(aPDU)
   { }
 
   nsresult
   operator () (int& aArg1,
                int& aArg2,
                nsString& aArg3,
-               int& aArg4,
+               BluetoothAttributeHandle& aArg4,
                int& aArg5,
                bool& aArg6) const
   {
     DaemonSocketPDU& pdu = GetPDU();
 
     /* Read connection ID */
     nsresult rv = UnpackPDU(pdu, aArg1);
     if (NS_FAILED(rv)) {
@@ -1998,17 +2001,17 @@ public:
   ServerRequestWriteInitOp(DaemonSocketPDU& aPDU)
     : PDUInitOp(aPDU)
   { }
 
   nsresult
   operator () (int& aArg1,
                int& aArg2,
                nsString& aArg3,
-               int& aArg4,
+               BluetoothAttributeHandle& aArg4,
                int& aArg5,
                int& aArg6,
                nsAutoArrayPtr<uint8_t>& aArg7,
                bool& aArg8,
                bool& aArg9) const
   {
     DaemonSocketPDU& pdu = GetPDU();
 
@@ -2691,139 +2694,143 @@ BluetoothDaemonGattInterface::Disconnect
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 /* Add a services / a characteristic / a descriptor */
 void
 BluetoothDaemonGattInterface::AddService(
-  int aServerIf, const BluetoothGattServiceId& aServiceId, int aNumHandles,
+  int aServerIf, const BluetoothGattServiceId& aServiceId, uint16_t aNumHandles,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerAddServiceCmd(
     aServerIf, aServiceId, aNumHandles, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::AddIncludedService(
-  int aServerIf, int aServiceHandle, int aIncludedServiceHandle,
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerAddIncludedServiceCmd(
     aServerIf, aServiceHandle, aIncludedServiceHandle, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::AddCharacteristic(
-  int aServerIf, int aServiceHandle, const BluetoothUuid& aUuid,
-  BluetoothGattCharProp aProperties, BluetoothGattAttrPerm aPermissions,
-  BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aUuid, BluetoothGattCharProp aProperties,
+  BluetoothGattAttrPerm aPermissions, BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerAddCharacteristicCmd(
     aServerIf, aServiceHandle, aUuid, aProperties, aPermissions, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::AddDescriptor(
-  int aServerIf, int aServiceHandle, const BluetoothUuid& aUuid,
-  BluetoothGattAttrPerm aPermissions, BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aUuid, BluetoothGattAttrPerm aPermissions,
+  BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerAddDescriptorCmd(
     aServerIf, aServiceHandle, aUuid, aPermissions, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 /* Start / Stop / Delete a service */
 void
 BluetoothDaemonGattInterface::StartService(
-  int aServerIf, int aServiceHandle, BluetoothTransport aTransport,
-  BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothTransport aTransport, BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerStartServiceCmd(
     aServerIf, aServiceHandle, aTransport, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::StopService(
-  int aServerIf, int aServiceHandle, BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerStopServiceCmd(aServerIf, aServiceHandle, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::DeleteService(
-  int aServerIf, int aServiceHandle, BluetoothGattResultHandler* aRes)
+  int aServerIf, const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerDeleteServiceCmd(
     aServerIf, aServiceHandle, aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::SendIndication(
-  int aServerIf, int aAttributeHandle, int aConnId,
-  const nsTArray<uint8_t>& aValue, bool aConfirm,
+  int aServerIf, const BluetoothAttributeHandle& aCharacteristicHandle,
+  int aConnId, const nsTArray<uint8_t>& aValue, bool aConfirm,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerSendIndicationCmd(
-    aServerIf, aAttributeHandle, aConnId,
+    aServerIf, aCharacteristicHandle, aConnId,
     aValue.Length() * sizeof(uint8_t), aConfirm,
     const_cast<uint8_t*>(aValue.Elements()), aRes);
 
   if (NS_FAILED(rv)) {
     DispatchError(aRes, rv);
   }
 }
 
 void
 BluetoothDaemonGattInterface::SendResponse(
-  int aConnId, int aTransId, BluetoothGattStatus aStatus,
+  int aConnId, int aTransId, uint16_t aStatus,
   const BluetoothGattResponse& aResponse,
   BluetoothGattResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
   nsresult rv = mModule->ServerSendResponseCmd(
     aConnId, aTransId, aStatus, aResponse, aRes);
 
--- a/dom/bluetooth/bluedroid/BluetoothDaemonGattInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonGattInterface.h
@@ -242,64 +242,69 @@ public:
     int aServerIf,
     const nsAString& aBdAddr,
     int aConnId,
     BluetoothGattResultHandler* aRes);
 
   /* Add a services / a characteristic / a descriptor */
   nsresult ServerAddServiceCmd(int aServerIf,
                                const BluetoothGattServiceId& aServiceId,
-                               int aNumHandles,
+                               uint16_t aNumHandles,
                                BluetoothGattResultHandler* aRes);
 
-  nsresult ServerAddIncludedServiceCmd(int aServerIf,
-                                       int aServiceHandle,
-                                       int aIncludedServiceHandle,
-                                       BluetoothGattResultHandler* aRes);
+  nsresult ServerAddIncludedServiceCmd(
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothGattResultHandler* aRes);
 
-  nsresult ServerAddCharacteristicCmd(int aServerIf,
-                                      int aServiceHandle,
-                                      const BluetoothUuid& aUuid,
-                                      BluetoothGattCharProp aProperties,
-                                      BluetoothGattAttrPerm aPermissions,
-                                      BluetoothGattResultHandler* aRes);
+  nsresult ServerAddCharacteristicCmd(
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aUuid,
+    BluetoothGattCharProp aProperties,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattResultHandler* aRes);
 
-  nsresult ServerAddDescriptorCmd(int aServerIf,
-                                  int aServiceHandle,
-                                  const BluetoothUuid& aUuid,
-                                  BluetoothGattAttrPerm aPermissions,
-                                  BluetoothGattResultHandler* aRes);
+  nsresult ServerAddDescriptorCmd(
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattResultHandler* aRes);
 
   /* Start / Stop / Delete a service */
   nsresult ServerStartServiceCmd(int aServerIf,
-                                 int aServiceHandle,
+                                 const BluetoothAttributeHandle& aServiceHandle,
                                  BluetoothTransport aTransport,
                                  BluetoothGattResultHandler* aRes);
 
   nsresult ServerStopServiceCmd(int aServerIf,
-                                int aServiceHandle,
+                                const BluetoothAttributeHandle& aServiceHandle,
                                 BluetoothGattResultHandler* aRes);
 
-  nsresult ServerDeleteServiceCmd(int aServerIf,
-                                  int aServiceHandle,
-                                  BluetoothGattResultHandler* aRes);
+  nsresult ServerDeleteServiceCmd(
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothGattResultHandler* aRes);
 
   /* Send an indication or a notification */
-  nsresult ServerSendIndicationCmd(int aServerIf,
-                                   int aAttributeHandle,
-                                   int aConnId,
-                                   int aLength,
-                                   bool aConfirm,
-                                   uint8_t* aValue,
-                                   BluetoothGattResultHandler* aRes);
+  nsresult ServerSendIndicationCmd(
+    int aServerIf,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    int aConnId,
+    int aLength,
+    bool aConfirm,
+    uint8_t* aValue,
+    BluetoothGattResultHandler* aRes);
 
   /* Send a response for an incoming indication */
   nsresult ServerSendResponseCmd(int aConnId,
                                  int aTransId,
-                                 BluetoothGattStatus aStatus,
+                                 uint16_t aStatus,
                                  const BluetoothGattResponse& aResponse,
                                  BluetoothGattResultHandler* aRes);
   // TODO: Add L support
 
 protected:
   void HandleSvc(const DaemonSocketPDUHeader& aHeader,
                  DaemonSocketPDU& aPDU, DaemonSocketResultHandler* aRes);
 
@@ -597,62 +602,75 @@ protected:
   typedef mozilla::ipc::DaemonNotificationRunnable4<
     NotificationHandlerWrapper, void,
     int, int, bool, nsString,
     int, int, bool, const nsAString&>
     ServerConnectionNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable4<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, BluetoothGattServiceId, int,
-    BluetoothGattStatus, int, const BluetoothGattServiceId&, int>
+    BluetoothGattStatus, int, BluetoothGattServiceId, BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothGattServiceId&,
+    const BluetoothAttributeHandle&>
     ServerServiceAddedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable4<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, int, int>
+    BluetoothGattStatus, int, BluetoothAttributeHandle,
+    BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothAttributeHandle&,
+    const BluetoothAttributeHandle&>
     ServerIncludedServiceAddedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable5<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, BluetoothUuid, int, int,
-    BluetoothGattStatus, int, const BluetoothUuid&, int, int>
+    BluetoothGattStatus, int, BluetoothUuid, BluetoothAttributeHandle,
+    BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothUuid&,
+    const BluetoothAttributeHandle&, const BluetoothAttributeHandle&>
     ServerCharacteristicAddedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable5<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, BluetoothUuid, int, int,
-    BluetoothGattStatus, int, const BluetoothUuid&, int, int>
+    BluetoothGattStatus, int, BluetoothUuid, BluetoothAttributeHandle,
+    BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothUuid&,
+    const BluetoothAttributeHandle&, const BluetoothAttributeHandle&>
     ServerDescriptorAddedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable3<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, int>
+    BluetoothGattStatus, int, BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothAttributeHandle&>
     ServerServiceStartedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable3<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, int>
+    BluetoothGattStatus, int, BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothAttributeHandle&>
     ServerServiceStoppedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable3<
     NotificationHandlerWrapper, void,
-    BluetoothGattStatus, int, int>
+    BluetoothGattStatus, int, BluetoothAttributeHandle,
+    BluetoothGattStatus, int, const BluetoothAttributeHandle&>
     ServerServiceDeletedNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable6<
     NotificationHandlerWrapper, void,
-    int, int, nsString, int, int, bool,
-    int, int, const nsAString&, int, int, bool>
+    int, int, nsString, BluetoothAttributeHandle, int, bool,
+    int, int, const nsAString&, const BluetoothAttributeHandle&, int, bool>
     ServerRequestReadNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable9<
     NotificationHandlerWrapper, void,
-    int, int, nsString, int, int, int, nsAutoArrayPtr<uint8_t>, bool, bool,
-    int, int, const nsAString&, int, int, int, const uint8_t*, bool, bool>
+    int, int, nsString, BluetoothAttributeHandle, int, int,
+    nsAutoArrayPtr<uint8_t>, bool, bool,
+    int, int, const nsAString&, const BluetoothAttributeHandle&, int, int,
+    const uint8_t*, bool, bool>
     ServerRequestWriteNotification;
 
   typedef mozilla::ipc::DaemonNotificationRunnable4<
     NotificationHandlerWrapper, void,
     int, int, nsString, bool,
     int, int, const nsAString&, bool>
     ServerRequestExecuteWriteNotification;
 
@@ -924,59 +942,59 @@ public:
   void DisconnectPeripheral(int aServerIf,
                             const nsAString& aBdAddr,
                             int aConnId,
                             BluetoothGattResultHandler* aRes) override;
 
   /* Add a services / a characteristic / a descriptor */
   void AddService(int aServerIf,
                   const BluetoothGattServiceId& aServiceId,
-                  int aNumHandles,
+                  uint16_t aNumHandles,
                   BluetoothGattResultHandler* aRes) override;
   void AddIncludedService(int aServerIf,
-                          int aServiceHandle,
-                          int aIncludedServiceHandle,
+                          const BluetoothAttributeHandle& aServiceHandle,
+                          const BluetoothAttributeHandle& aIncludedServiceHandle,
                           BluetoothGattResultHandler* aRes) override;
   void AddCharacteristic(int aServerIf,
-                         int aServiceHandle,
+                         const BluetoothAttributeHandle& aServiceHandle,
                          const BluetoothUuid& aUuid,
                          BluetoothGattCharProp aProperties,
                          BluetoothGattAttrPerm aPermissions,
                          BluetoothGattResultHandler* aRes) override;
   void AddDescriptor(int aServerIf,
-                     int aServiceHandle,
+                     const BluetoothAttributeHandle& aServiceHandle,
                      const BluetoothUuid& aUuid,
                      BluetoothGattAttrPerm aPermissions,
                      BluetoothGattResultHandler* aRes) override;
 
   /* Start / Stop / Delete a service */
   void StartService(int aServerIf,
-                    int aServiceHandle,
+                    const BluetoothAttributeHandle& aServiceHandle,
                     BluetoothTransport aTransport,
                     BluetoothGattResultHandler* aRes) override;
   void StopService(int aServerIf,
-                   int aServiceHandle,
+                   const BluetoothAttributeHandle& aServiceHandle,
                    BluetoothGattResultHandler* aRes) override;
   void DeleteService(int aServerIf,
-                     int aServiceHandle,
+                     const BluetoothAttributeHandle& aServiceHandle,
                      BluetoothGattResultHandler* aRes) override;
 
   /* Send an indication or a notification */
   void SendIndication(
     int aServerIf,
-    int aAttributeHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
     int aConnId,
     const nsTArray<uint8_t>& aValue,
     bool aConfirm, /* true: indication, false: notification */
     BluetoothGattResultHandler* aRes) override;
 
   /* Send a response for an incoming indication */
   void SendResponse(int aConnId,
                     int aTransId,
-                    BluetoothGattStatus aStatus,
+                    uint16_t aStatus,
                     const BluetoothGattResponse& aResponse,
                     BluetoothGattResultHandler* aRes) override;
 
 private:
   void DispatchError(BluetoothGattResultHandler* aRes,
                      BluetoothStatus aStatus);
   void DispatchError(BluetoothGattResultHandler* aRes, nsresult aRv);
 
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.cpp
@@ -462,16 +462,27 @@ Convert(uint8_t aIn, BluetoothStatus& aO
         aIn >= MOZ_ARRAY_LENGTH(sStatus), uint8_t, BluetoothStatus)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   aOut = sStatus[aIn];
   return NS_OK;
 }
 
 nsresult
+Convert(int32_t aIn, BluetoothAttributeHandle& aOut)
+{
+  if (NS_WARN_IF(aIn < 0x0000) || NS_WARN_IF(aIn > 0xFFFF)) {
+    aOut.mHandle = 0x0000; // silences compiler warning
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  aOut.mHandle = static_cast<uint16_t>(aIn);
+  return NS_OK;
+}
+
+nsresult
 Convert(int32_t aIn, BluetoothGattStatus& aOut)
 {
   /* Reference: $B2G/external/bluetooth/bluedroid/stack/include/gatt_api.h */
   static const BluetoothGattStatus sGattStatus[] = {
     [0x0000] = GATT_STATUS_SUCCESS,
     [0x0001] = GATT_STATUS_INVALID_HANDLE,
     [0x0002] = GATT_STATUS_READ_NOT_PERMITTED,
     [0x0003] = GATT_STATUS_WRITE_NOT_PERMITTED,
@@ -630,16 +641,23 @@ Convert(const BluetoothAddress& aIn, nsA
   }
 
   aOut = NS_ConvertUTF8toUTF16(str);
 
   return NS_OK;
 }
 
 nsresult
+Convert(const BluetoothAttributeHandle& aIn, int32_t& aOut)
+{
+  aOut = static_cast<int32_t>(aIn.mHandle);
+  return NS_OK;
+}
+
+nsresult
 Convert(BluetoothAvrcpEvent aIn, uint8_t& aOut)
 {
   static const uint8_t sValue[] = {
     [AVRCP_EVENT_PLAY_STATUS_CHANGED] = 0x01,
     [AVRCP_EVENT_TRACK_CHANGE] = 0x02,
     [AVRCP_EVENT_TRACK_REACHED_END] = 0x03,
     [AVRCP_EVENT_TRACK_REACHED_START] = 0x04,
     [AVRCP_EVENT_PLAY_POS_CHANGED] = 0x05,
@@ -1076,16 +1094,22 @@ Convert(const ConvertArray<Tin>& aIn, To
 
 nsresult
 PackPDU(const BluetoothAddress& aIn, DaemonSocketPDU& aPDU)
 {
   return PackPDU(PackArray<uint8_t>(aIn.mAddr, sizeof(aIn.mAddr)), aPDU);
 }
 
 nsresult
+PackPDU(const BluetoothAttributeHandle& aIn, DaemonSocketPDU& aPDU)
+{
+  return PackPDU(PackConversion<BluetoothAttributeHandle, int32_t>(aIn), aPDU);
+}
+
+nsresult
 PackPDU(const BluetoothAvrcpAttributeTextPairs& aIn,
         DaemonSocketPDU& aPDU)
 {
   size_t i;
 
   for (i = 0; i < aIn.mLength; ++i) {
     nsresult rv = PackPDU(aIn.mAttr[i], aPDU);
     if (NS_FAILED(rv)) {
@@ -1452,16 +1476,23 @@ UnpackPDU(DaemonSocketPDU& aPDU, Bluetoo
 
 nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAclState& aOut)
 {
   return UnpackPDU(aPDU, UnpackConversion<uint8_t, BluetoothAclState>(aOut));
 }
 
 nsresult
+UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAttributeHandle& aOut)
+{
+  return UnpackPDU(
+    aPDU, UnpackConversion<int32_t, BluetoothAttributeHandle>(aOut));
+}
+
+nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAvrcpEvent& aOut)
 {
   return UnpackPDU(
     aPDU, UnpackConversion<uint8_t, BluetoothAvrcpEvent>(aOut));
 }
 
 nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAvrcpMediaAttribute& aOut)
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHelpers.h
@@ -194,16 +194,19 @@ Convert(uint8_t aIn, BluetoothScanMode& 
 
 nsresult
 Convert(uint8_t aIn, BluetoothSspVariant& aOut);
 
 nsresult
 Convert(uint8_t aIn, BluetoothStatus& aOut);
 
 nsresult
+Convert(int32_t aIn, BluetoothAttributeHandle& aOut);
+
+nsresult
 Convert(int32_t aIn, BluetoothGattStatus& aOut);
 
 nsresult
 Convert(const nsAString& aIn, BluetoothAddress& aOut);
 
 nsresult
 Convert(const nsAString& aIn, BluetoothPinCode& aOut);
 
@@ -215,16 +218,19 @@ Convert(const nsAString& aIn, BluetoothS
 
 nsresult
 Convert(BluetoothAclState aIn, bool& aOut);
 
 nsresult
 Convert(const BluetoothAddress& aIn, nsAString& aOut);
 
 nsresult
+Convert(const BluetoothAttributeHandle& aIn, int32_t& aOut);
+
+nsresult
 Convert(BluetoothAvrcpEvent aIn, uint8_t& aOut);
 
 nsresult
 Convert(BluetoothAvrcpNotification aIn, uint8_t& aOut);
 
 nsresult
 Convert(BluetoothAvrcpPlayerAttribute aIn, uint8_t& aOut);
 
@@ -294,16 +300,19 @@ Convert(nsresult aIn, BluetoothStatus& a
 //
 // Packing
 //
 
 nsresult
 PackPDU(const BluetoothAddress& aIn, DaemonSocketPDU& aPDU);
 
 nsresult
+PackPDU(const BluetoothAttributeHandle& aIn, DaemonSocketPDU& aPDU);
+
+nsresult
 PackPDU(const BluetoothAvrcpAttributeTextPairs& aIn,
         DaemonSocketPDU& aPDU);
 
 nsresult
 PackPDU(const BluetoothAvrcpAttributeValuePairs& aIn,
         DaemonSocketPDU& aPDU);
 
 nsresult
@@ -426,16 +435,19 @@ UnpackPDU(DaemonSocketPDU& aPDU, Bluetoo
 
 inline nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAddress& aOut)
 {
   return aPDU.Read(aOut.mAddr, sizeof(aOut.mAddr));
 }
 
 nsresult
+UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAttributeHandle& aOut);
+
+nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAvrcpEvent& aOut);
 
 nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAvrcpMediaAttribute& aOut);
 
 nsresult
 UnpackPDU(DaemonSocketPDU& aPDU, BluetoothAvrcpPlayerAttribute& aOut);
 
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
@@ -201,33 +201,104 @@ public:
 
 private:
   ~BluetoothGattClient()
   { }
 };
 
 NS_IMPL_ISUPPORTS0(BluetoothGattClient)
 
+struct BluetoothGattServerAddServiceState
+{
+  BluetoothGattServiceId mServiceId;
+  uint16_t mHandleCount;
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  void Assign(const BluetoothGattServiceId& aServiceId,
+              uint16_t aHandleCount,
+              BluetoothReplyRunnable* aRunnable)
+  {
+    mServiceId = aServiceId;
+    mHandleCount = aHandleCount;
+    mRunnable = aRunnable;
+  }
+
+  void Reset()
+  {
+    memset(&mServiceId, 0, sizeof(mServiceId));
+    mHandleCount = 0;
+    mRunnable = nullptr;
+  }
+};
+
+struct BluetoothGattServerAddDescriptorState
+{
+  BluetoothAttributeHandle mServiceHandle;
+  BluetoothAttributeHandle mCharacteristicHandle;
+  BluetoothUuid mDescriptorUuid;
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  void Assign(const BluetoothAttributeHandle& aServiceHandle,
+              const BluetoothAttributeHandle& aCharacteristicHandle,
+              const BluetoothUuid& aDescriptorUuid,
+              BluetoothReplyRunnable* aRunnable)
+  {
+    mServiceHandle = aServiceHandle;
+    mCharacteristicHandle = aCharacteristicHandle;
+    mDescriptorUuid = aDescriptorUuid;
+    mRunnable = aRunnable;
+  }
+
+  void Reset()
+  {
+    memset(&mServiceHandle, 0, sizeof(mServiceHandle));
+    memset(&mCharacteristicHandle, 0, sizeof(mCharacteristicHandle));
+    memset(&mDescriptorUuid, 0, sizeof(mDescriptorUuid));
+    mRunnable = nullptr;
+  }
+};
+
 class BluetoothGattServer final : public nsISupports
 {
 public:
   NS_DECL_ISUPPORTS
 
   BluetoothGattServer(const nsAString& aAppUuid)
   : mAppUuid(aAppUuid)
   , mServerIf(0)
+  , mIsRegistering(false)
   { }
 
   nsString mAppUuid;
   int mServerIf;
 
+  /*
+   * Some actions will trigger the registration procedure:
+   *  - Connect the GATT server to a peripheral client
+   *  - Add a service to the GATT server
+   * These actions will be taken only after the registration has been done
+   * successfully. If the registration fails, all the existing actions above
+   * should be rejected.
+   */
+  bool mIsRegistering;
+
   nsRefPtr<BluetoothReplyRunnable> mConnectPeripheralRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDisconnectPeripheralRunnable;
   nsRefPtr<BluetoothReplyRunnable> mUnregisterServerRunnable;
 
+  BluetoothGattServerAddServiceState mAddServiceState;
+  nsRefPtr<BluetoothReplyRunnable> mAddIncludedServiceRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mAddCharacteristicRunnable;
+  BluetoothGattServerAddDescriptorState mAddDescriptorState;
+  nsRefPtr<BluetoothReplyRunnable> mRemoveServiceRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mStartServiceRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mStopServiceRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mSendResponseRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mSendIndicationRunnable;
+
   // Map connection id from device address
   nsDataHashtable<nsStringHashKey, int> mConnectionMap;
 private:
   ~BluetoothGattServer()
   { }
 };
 
 NS_IMPL_ISUPPORTS0(BluetoothGattServer)
@@ -267,16 +338,28 @@ public:
 class ConnIdComparator
 {
 public:
   bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
               int aConnId) const
   {
     return aClient->mConnId == aConnId;
   }
+
+  bool Equals(const nsRefPtr<BluetoothGattServer>& aServer,
+              int aConnId) const
+  {
+    for (
+      auto iter = aServer->mConnectionMap.Iter(); !iter.Done(); iter.Next()) {
+      if (aConnId == iter.Data()) {
+        return true;
+      }
+    }
+    return false;
+  }
 };
 
 BluetoothGattManager*
 BluetoothGattManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // If sBluetoothGattManager already exists, exit early
@@ -612,34 +695,38 @@ BluetoothGattManager::StartLeScan(const 
                                   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
 
   nsString appUuidStr;
-  GenerateUuid(appUuidStr);
+  if (NS_WARN_IF(NS_FAILED(GenerateUuid(appUuidStr))) || appUuidStr.IsEmpty()) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("start LE scan failed"));
+    return;
+  }
 
   size_t index = sClients->IndexOf(appUuidStr, 0 /* Start */, UuidComparator());
 
   // Reject the startLeScan request if the clientIf is being used.
   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);
   client->mStartLeScanRunnable = aRunnable;
 
   BluetoothUuid appUuid;
-  StringToUuid(NS_ConvertUTF16toUTF8(appUuidStr).get(), appUuid);
+  StringToUuid(appUuidStr, appUuid);
 
   // 'startLeScan' will be proceeded after client registered
   sBluetoothGattInterface->RegisterClient(
     appUuid, new RegisterClientResultHandler(client));
 }
 
 void
 BluetoothGattManager::StopLeScan(const nsAString& aScanUuid,
@@ -721,17 +808,17 @@ BluetoothGattManager::Connect(const nsAS
   if (client->mClientIf > 0) {
     sBluetoothGattInterface->Connect(client->mClientIf,
                                            aDeviceAddr,
                                            true, // direct connect
                                            TRANSPORT_AUTO,
                                            new ConnectResultHandler(client));
   } else {
     BluetoothUuid uuid;
-    StringToUuid(NS_ConvertUTF16toUTF8(aAppUuid).get(), uuid);
+    StringToUuid(aAppUuid, uuid);
 
     // connect will be proceeded after client registered
     sBluetoothGattInterface->RegisterClient(
       uuid, new RegisterClientResultHandler(client));
   }
 }
 
 class BluetoothGattManager::DisconnectResultHandler final
@@ -869,19 +956,16 @@ public:
   }
 
   void OnError(BluetoothStatus aStatus) override
   {
     BT_WARNING("BluetoothGattInterface::ReadRemoteRssi failed: %d",
                (int)aStatus);
     MOZ_ASSERT(mClient->mReadRemoteRssiRunnable);
 
-    BluetoothService* bs = BluetoothService::Get();
-    NS_ENSURE_TRUE_VOID(bs);
-
     // Reject the read remote rssi request
     DispatchReplyError(mClient->mReadRemoteRssiRunnable,
                        NS_LITERAL_STRING("ReadRemoteRssi failed"));
     mClient->mReadRemoteRssiRunnable = nullptr;
   }
 
 private:
   nsRefPtr<BluetoothGattClient> mClient;
@@ -1390,33 +1474,47 @@ BluetoothGattManager::WriteDescriptorVal
 class BluetoothGattManager::RegisterServerResultHandler final
   : public BluetoothGattResultHandler
 {
 public:
   RegisterServerResultHandler(BluetoothGattServer* aServer)
   : mServer(aServer)
   {
     MOZ_ASSERT(mServer);
+    MOZ_ASSERT(!mServer->mIsRegistering);
+
+    mServer->mIsRegistering = true;
   }
 
+  /*
+   * Some actions will trigger the registration procedure. These actions will
+   * be taken only after the registration has been done successfully.
+   * If the registration fails, all the existing actions above should be
+   * rejected.
+   */
   void OnError(BluetoothStatus aStatus) override
   {
     BT_WARNING("BluetoothGattServerInterface::RegisterServer failed: %d",
                (int)aStatus);
 
-    BluetoothService* bs = BluetoothService::Get();
-    NS_ENSURE_TRUE_VOID(bs);
-
     // Reject the connect request
     if (mServer->mConnectPeripheralRunnable) {
       DispatchReplyError(mServer->mConnectPeripheralRunnable,
                          NS_LITERAL_STRING("Register GATT server failed"));
       mServer->mConnectPeripheralRunnable = nullptr;
     }
 
+    // Reject the add service request
+    if (mServer->mAddServiceState.mRunnable) {
+      DispatchReplyError(mServer->mAddServiceState.mRunnable,
+                         NS_LITERAL_STRING("Register GATT server failed"));
+      mServer->mAddServiceState.Reset();
+    }
+
+    mServer->mIsRegistering = false;
     sServers->RemoveElement(mServer);
   }
 
 private:
   nsRefPtr<BluetoothGattServer> mServer;
 };
 
 class BluetoothGattManager::ConnectPeripheralResultHandler final
@@ -1433,19 +1531,16 @@ public:
   }
 
   void OnError(BluetoothStatus aStatus) override
   {
     BT_WARNING("BluetoothGattServerInterface::ConnectPeripheral failed: %d",
                (int)aStatus);
     MOZ_ASSERT(mServer->mConnectPeripheralRunnable);
 
-    BluetoothService* bs = BluetoothService::Get();
-    NS_ENSURE_TRUE_VOID(bs);
-
     DispatchReplyError(mServer->mConnectPeripheralRunnable,
                        NS_LITERAL_STRING("ConnectPeripheral failed"));
     mServer->mConnectPeripheralRunnable = nullptr;
     mServer->mConnectionMap.Remove(mDeviceAddr);
   }
 
 private:
   nsRefPtr<BluetoothGattServer> mServer;
@@ -1500,19 +1595,21 @@ BluetoothGattManager::ConnectPeripheral(
 
   if (server->mServerIf > 0) {
     sBluetoothGattInterface->ConnectPeripheral(
       server->mServerIf,
       aAddress,
       true, // direct connect
       TRANSPORT_AUTO,
       new ConnectPeripheralResultHandler(server, aAddress));
-  } else {
+  } else if (!server->mIsRegistering) { /* avoid triggering another registration
+                                         * procedure if there is an on-going one
+                                         * already */
     BluetoothUuid uuid;
-    StringToUuid(NS_ConvertUTF16toUTF8(aAppUuid).get(), uuid);
+    StringToUuid(aAppUuid, uuid);
 
     // connect will be proceeded after server registered
     sBluetoothGattInterface->RegisterServer(
       uuid, new RegisterServerResultHandler(server));
   }
 }
 
 class BluetoothGattManager::DisconnectPeripheralResultHandler final
@@ -1526,19 +1623,16 @@ public:
   }
 
   void OnError(BluetoothStatus aStatus) override
   {
     BT_WARNING("BluetoothGattServerInterface::DisconnectPeripheral failed: %d",
                (int)aStatus);
     MOZ_ASSERT(mServer->mDisconnectPeripheralRunnable);
 
-    BluetoothService* bs = BluetoothService::Get();
-    NS_ENSURE_TRUE_VOID(bs);
-
     // Reject the disconnect request
     DispatchReplyError(mServer->mDisconnectPeripheralRunnable,
                        NS_LITERAL_STRING("DisconnectPeripheral failed"));
     mServer->mDisconnectPeripheralRunnable = nullptr;
   }
 
 private:
   nsRefPtr<BluetoothGattServer> mServer;
@@ -1654,16 +1748,652 @@ BluetoothGattManager::UnregisterServer(i
   nsRefPtr<BluetoothGattServer> server = (*sServers)[index];
   server->mUnregisterServerRunnable = aRunnable;
 
   sBluetoothGattInterface->UnregisterServer(
     aServerIf,
     new UnregisterServerResultHandler(server));
 }
 
+class BluetoothGattManager::ServerAddServiceResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerAddServiceResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::ServerAddService failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mServer->mAddServiceState.mRunnable);
+
+    DispatchReplyError(mServer->mAddServiceState.mRunnable,
+                       NS_LITERAL_STRING("ServerAddService failed"));
+    mServer->mAddServiceState.Reset();
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerAddService(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  uint16_t aHandleCount,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (index == sServers->NoIndex) {
+    index = sServers->Length();
+    sServers->AppendElement(new BluetoothGattServer(aAppUuid));
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if there is an ongoing add service request.
+  if (server->mAddServiceState.mRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mAddServiceState.Assign(aServiceId, aHandleCount, aRunnable);
+
+  if (server->mServerIf > 0) {
+    sBluetoothGattInterface->AddService(
+      server->mServerIf,
+      aServiceId,
+      aHandleCount,
+      new ServerAddServiceResultHandler(server));
+  } else if (!server->mIsRegistering) { /* avoid triggering another registration
+                                         * procedure if there is an on-going one
+                                         * already */
+    BluetoothUuid uuid;
+    StringToUuid(aAppUuid, uuid);
+
+    // add service will be proceeded after server registered
+    sBluetoothGattInterface->RegisterServer(
+      uuid, new RegisterServerResultHandler(server));
+  }
+}
+
+class BluetoothGattManager::ServerAddIncludedServiceResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerAddIncludedServiceResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::AddIncludedService failed: %d",
+               (int)aStatus);
+
+    // Reject the add included service request
+    if (mServer->mAddIncludedServiceRunnable) {
+      DispatchReplyError(mServer->mAddIncludedServiceRunnable,
+                         NS_LITERAL_STRING("Add GATT included service failed"));
+      mServer->mAddIncludedServiceRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerAddIncludedService(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sServers->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the service has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing add included service request.
+  if (server->mAddIncludedServiceRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mAddIncludedServiceRunnable = aRunnable;
+
+  sBluetoothGattInterface->AddIncludedService(
+    server->mServerIf,
+    aServiceHandle,
+    aIncludedServiceHandle,
+    new ServerAddIncludedServiceResultHandler(server));
+}
+
+class BluetoothGattManager::ServerAddCharacteristicResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerAddCharacteristicResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::AddCharacteristic failed: %d",
+               (int)aStatus);
+
+    // Reject the add characteristic request
+    if (mServer->mAddCharacteristicRunnable) {
+      DispatchReplyError(mServer->mAddCharacteristicRunnable,
+                         NS_LITERAL_STRING("Add GATT characteristic failed"));
+      mServer->mAddCharacteristicRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerAddCharacteristic(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aCharacteristicUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothGattCharProp aProperties,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sServers->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the service has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing add characteristic request.
+  if (server->mAddCharacteristicRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mAddCharacteristicRunnable = aRunnable;
+
+  sBluetoothGattInterface->AddCharacteristic(
+    server->mServerIf,
+    aServiceHandle,
+    aCharacteristicUuid,
+    aPermissions,
+    aProperties,
+    new ServerAddCharacteristicResultHandler(server));
+}
+
+class BluetoothGattManager::ServerAddDescriptorResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerAddDescriptorResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::AddDescriptor failed: %d",
+               (int)aStatus);
+
+    // Reject the add descriptor request
+    if (mServer->mAddDescriptorState.mRunnable) {
+      DispatchReplyError(mServer->mAddDescriptorState.mRunnable,
+                         NS_LITERAL_STRING("Add GATT descriptor failed"));
+      mServer->mAddDescriptorState.Reset();
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerAddDescriptor(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  const BluetoothUuid& aDescriptorUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sServers->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the service has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing add descriptor request.
+  if (server->mAddDescriptorState.mRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mAddDescriptorState.Assign(aServiceHandle,
+                                     aCharacteristicHandle,
+                                     aDescriptorUuid,
+                                     aRunnable);
+
+  sBluetoothGattInterface->AddDescriptor(
+    server->mServerIf,
+    aServiceHandle,
+    aDescriptorUuid,
+    aPermissions,
+    new ServerAddDescriptorResultHandler(server));
+}
+
+class BluetoothGattManager::ServerRemoveDescriptorResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerRemoveDescriptorResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::RemoveService failed: %d",
+               (int)aStatus);
+
+    // Reject the remove service request
+    if (mServer->mRemoveServiceRunnable) {
+      DispatchReplyError(mServer->mRemoveServiceRunnable,
+                         NS_LITERAL_STRING("Remove GATT service failed"));
+      mServer->mRemoveServiceRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerRemoveService(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sServers->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the service has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing remove service request.
+  if (server->mRemoveServiceRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mRemoveServiceRunnable = aRunnable;
+
+  sBluetoothGattInterface->DeleteService(
+    server->mServerIf,
+    aServiceHandle,
+    new ServerRemoveDescriptorResultHandler(server));
+}
+
+class BluetoothGattManager::ServerStartServiceResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerStartServiceResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::StartService failed: %d",
+               (int)aStatus);
+
+    // Reject the remove service request
+    if (mServer->mStartServiceRunnable) {
+      DispatchReplyError(mServer->mStartServiceRunnable,
+                         NS_LITERAL_STRING("Start GATT service failed"));
+      mServer->mStartServiceRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerStartService(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sServers->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the service has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing start service request.
+  if (server->mStartServiceRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mStartServiceRunnable = aRunnable;
+
+  sBluetoothGattInterface->StartService(
+    server->mServerIf,
+    aServiceHandle,
+    TRANSPORT_AUTO,
+    new ServerStartServiceResultHandler(server));
+}
+
+class BluetoothGattManager::ServerStopServiceResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerStopServiceResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::StopService failed: %d",
+               (int)aStatus);
+
+    // Reject the remove service request
+    if (mServer->mStopServiceRunnable) {
+      DispatchReplyError(mServer->mStopServiceRunnable,
+                         NS_LITERAL_STRING("Stop GATT service failed"));
+      mServer->mStopServiceRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerStopService(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sServers->NoIndex)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the service has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing stop service request.
+  if (server->mStopServiceRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  server->mStopServiceRunnable = aRunnable;
+
+  sBluetoothGattInterface->StopService(
+    server->mServerIf,
+    aServiceHandle,
+    new ServerStopServiceResultHandler(server));
+}
+
+class BluetoothGattManager::ServerSendResponseResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerSendResponseResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void SendResponse() override
+  {
+    if (mServer->mSendResponseRunnable) {
+      DispatchReplySuccess(mServer->mSendResponseRunnable);
+      mServer->mSendResponseRunnable = nullptr;
+    }
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::SendResponse failed: %d",
+               (int)aStatus);
+
+    // Reject the send response request
+    if (mServer->mSendResponseRunnable) {
+      DispatchReplyError(mServer->mSendResponseRunnable,
+                         NS_LITERAL_STRING("Send response failed"));
+      mServer->mSendResponseRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerSendResponse(const nsAString& aAppUuid,
+                                         const nsAString& aAddress,
+                                         uint16_t aStatus,
+                                         int aRequestId,
+                                         const BluetoothGattResponse& aRsp,
+                                         BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (index == sServers->NoIndex) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  if (server->mSendResponseRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  int connId = 0;
+  server->mConnectionMap.Get(aAddress, &connId);
+  if (!connId) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+
+  sBluetoothGattInterface->SendResponse(
+    connId,
+    aRequestId,
+    aStatus,
+    aRsp,
+    new ServerSendResponseResultHandler(server));
+}
+
+class BluetoothGattManager::ServerSendIndicationResultHandler final
+  : public BluetoothGattResultHandler
+{
+public:
+  ServerSendIndicationResultHandler(BluetoothGattServer* aServer)
+  : mServer(aServer)
+  {
+    MOZ_ASSERT(mServer);
+  }
+
+  void SendIndication() override
+  {
+    if (mServer->mSendIndicationRunnable) {
+      DispatchReplySuccess(mServer->mSendIndicationRunnable);
+      mServer->mSendIndicationRunnable = nullptr;
+    }
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattServerInterface::NotifyCharacteristicChanged"
+               "failed: %d", (int)aStatus);
+
+    // Reject the send indication request
+    if (mServer->mSendIndicationRunnable) {
+      DispatchReplyError(mServer->mSendIndicationRunnable,
+                         NS_LITERAL_STRING("Send GATT indication failed"));
+      mServer->mSendIndicationRunnable = nullptr;
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+};
+
+void
+BluetoothGattManager::ServerSendIndication(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  bool aConfirm,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sServers->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  // Reject the request if the server has not been registered yet.
+  if (index == sServers->NoIndex) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  // Reject the request if the server has not been registered successfully.
+  if (!server->mServerIf) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+  // Reject the request if there is an ongoing send indication request.
+  if (server->mSendIndicationRunnable) {
+    DispatchReplyError(aRunnable, STATUS_BUSY);
+    return;
+  }
+
+  int connId = 0;
+  if (!server->mConnectionMap.Get(aAddress, &connId)) {
+    DispatchReplyError(aRunnable, STATUS_PARM_INVALID);
+    return;
+  }
+
+  if (!connId) {
+    DispatchReplyError(aRunnable, STATUS_NOT_READY);
+    return;
+  }
+
+  server->mSendIndicationRunnable = aRunnable;
+
+  sBluetoothGattInterface->SendIndication(
+    server->mServerIf,
+    aCharacteristicHandle,
+    connId,
+    aValue,
+    aConfirm,
+    new ServerSendIndicationResultHandler(server));
+}
+
 //
 // Notification Handlers
 //
 void
 BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus,
                                                  int aClientIf,
                                                  const BluetoothUuid& aAppUuid)
 {
@@ -2371,19 +3101,16 @@ BluetoothGattManager::ExecuteWriteNotifi
 void
 BluetoothGattManager::ReadRemoteRssiNotification(int aClientIf,
                                                  const nsAString& aBdAddr,
                                                  int aRssi,
                                                  BluetoothGattStatus aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE_VOID(bs);
-
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    InterfaceIdComparator());
   NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   if (aStatus != GATT_STATUS_SUCCESS) { // operation failed
     BT_LOGD("ReadRemoteRssi failed: clientIf = %d, bdAddr = %s, rssi = %d, " \
@@ -2408,48 +3135,64 @@ BluetoothGattManager::ReadRemoteRssiNoti
   }
 }
 
 void
 BluetoothGattManager::ListenNotification(BluetoothGattStatus aStatus,
                                          int aServerIf)
 { }
 
+/*
+ * Some actions will trigger the registration procedure. These actions will
+ * be taken only after the registration has been done successfully.
+ * If the registration fails, all the existing actions above should be
+ * rejected.
+ */
 void
 BluetoothGattManager::RegisterServerNotification(BluetoothGattStatus aStatus,
                                                  int aServerIf,
                                                  const BluetoothUuid& aAppUuid)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsString uuid;
   UuidToString(aAppUuid, uuid);
 
   size_t index = sServers->IndexOf(uuid, 0 /* Start */, UuidComparator());
   NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
 
   nsRefPtr<BluetoothGattServer> server = (*sServers)[index];
 
+  server->mIsRegistering = false;
+
   BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE_VOID(bs);
-
-  if (aStatus != GATT_STATUS_SUCCESS) {
+  if (!bs || aStatus != GATT_STATUS_SUCCESS) {
     BT_LOGD("RegisterServer failed: serverIf = %d, status = %d, appUuid = %s",
              aServerIf, aStatus, NS_ConvertUTF16toUTF8(uuid).get());
 
     if (server->mConnectPeripheralRunnable) {
       // Reject the connect peripheral request
       DispatchReplyError(
         server->mConnectPeripheralRunnable,
         NS_LITERAL_STRING(
           "ConnectPeripheral failed due to registration failed"));
       server->mConnectPeripheralRunnable = nullptr;
     }
 
+    if (server->mAddServiceState.mRunnable) {
+      // Reject the add service request
+      DispatchReplyError(
+        server->mAddServiceState.mRunnable,
+        NS_LITERAL_STRING(
+          "AddService failed due to registration failed"));
+      server->mAddServiceState.Reset();
+    }
+
     sServers->RemoveElement(server);
+    return;
   }
 
   server->mServerIf = aServerIf;
 
   // Notify BluetoothGattServer to update the serverIf
   bs->DistributeSignal(
     NS_LITERAL_STRING("ServerRegistered"),
     uuid, BluetoothValue(uint32_t(aServerIf)));
@@ -2457,16 +3200,24 @@ BluetoothGattManager::RegisterServerNoti
   if (server->mConnectPeripheralRunnable) {
     // Only one entry exists in the map during first connect peripheral request
     nsString deviceAddr(server->mConnectionMap.Iter().Key());
 
     sBluetoothGattInterface->ConnectPeripheral(
       aServerIf, deviceAddr, true /* direct connect */, TRANSPORT_AUTO,
       new ConnectPeripheralResultHandler(server, deviceAddr));
   }
+
+  if (server->mAddServiceState.mRunnable) {
+    sBluetoothGattInterface->AddService(
+      server->mServerIf,
+      server->mAddServiceState.mServiceId,
+      server->mAddServiceState.mHandleCount,
+      new ServerAddServiceResultHandler(server));
+  }
 }
 
 void
 BluetoothGattManager::ConnectionNotification(int aConnId,
                                              int aServerIf,
                                              bool aConnected,
                                              const nsAString& aBdAddr)
 {
@@ -2512,16 +3263,367 @@ BluetoothGattManager::ConnectionNotifica
     } else {
       DispatchReplyError(server->mDisconnectPeripheralRunnable,
                          NS_LITERAL_STRING("DisconnectPeripheral failed"));
     }
     server->mDisconnectPeripheralRunnable = nullptr;
   }
 }
 
+void
+BluetoothGattManager::ServiceAddedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothAttributeHandle& aServiceHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mAddServiceState.mRunnable) {
+      DispatchReplyError(server->mAddServiceState.mRunnable,
+                         NS_LITERAL_STRING("ServiceAddedNotification failed"));
+      server->mAddServiceState.Reset();
+    }
+    return;
+  }
+
+  // Notify BluetoothGattServer to update service handle
+  InfallibleTArray<BluetoothNamedValue> props;
+  AppendNamedValue(props, "ServiceId", aServiceId);
+  AppendNamedValue(props, "ServiceHandle", aServiceHandle);
+  bs->DistributeSignal(NS_LITERAL_STRING("ServiceHandleUpdated"),
+                       server->mAppUuid,
+                       BluetoothValue(props));
+
+  if (server->mAddServiceState.mRunnable) {
+    DispatchReplySuccess(server->mAddServiceState.mRunnable);
+    server->mAddServiceState.Reset();
+  }
+}
+
+void
+BluetoothGattManager::IncludedServiceAddedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  if (aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mAddIncludedServiceRunnable) {
+      DispatchReplyError(
+        server->mAddIncludedServiceRunnable,
+        NS_LITERAL_STRING("IncludedServiceAddedNotification failed"));
+      server->mAddIncludedServiceRunnable = nullptr;
+    }
+    return;
+  }
+
+  if (server->mAddIncludedServiceRunnable) {
+    DispatchReplySuccess(server->mAddIncludedServiceRunnable);
+    server->mAddIncludedServiceRunnable = nullptr;
+  }
+}
+
+void
+BluetoothGattManager::CharacteristicAddedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothUuid& aCharId,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aCharacteristicHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mAddCharacteristicRunnable) {
+      DispatchReplyError(
+        server->mAddCharacteristicRunnable,
+        NS_LITERAL_STRING("CharacteristicAddedNotification failed"));
+      server->mAddCharacteristicRunnable = nullptr;
+    }
+    return;
+  }
+
+  // Notify BluetoothGattServer to update characteristic handle
+  InfallibleTArray<BluetoothNamedValue> props;
+  AppendNamedValue(props, "CharacteristicUuid", aCharId);
+  AppendNamedValue(props, "ServiceHandle", aServiceHandle);
+  AppendNamedValue(props, "CharacteristicHandle", aCharacteristicHandle);
+  bs->DistributeSignal(NS_LITERAL_STRING("CharacteristicHandleUpdated"),
+                       server->mAppUuid,
+                       BluetoothValue(props));
+
+  if (server->mAddCharacteristicRunnable) {
+    DispatchReplySuccess(server->mAddCharacteristicRunnable);
+    server->mAddCharacteristicRunnable = nullptr;
+  }
+}
+
+void
+BluetoothGattManager::DescriptorAddedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothUuid& aCharId,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aDescriptorHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+  MOZ_ASSERT(aServiceHandle == server->mAddDescriptorState.mServiceHandle);
+
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mAddDescriptorState.mRunnable) {
+      DispatchReplyError(
+        server->mAddDescriptorState.mRunnable,
+        NS_LITERAL_STRING("DescriptorAddedNotification failed"));
+      server->mAddDescriptorState.Reset();
+    }
+    return;
+  }
+
+  // Notify BluetoothGattServer to update descriptor handle
+  InfallibleTArray<BluetoothNamedValue> props;
+  AppendNamedValue(props, "CharacteristicUuid", aCharId);
+  AppendNamedValue(props, "ServiceHandle", aServiceHandle);
+  AppendNamedValue(props, "CharacteristicHandle",
+    server->mAddDescriptorState.mCharacteristicHandle);
+  AppendNamedValue(props, "DescriptorHandle", aDescriptorHandle);
+  bs->DistributeSignal(NS_LITERAL_STRING("DescriptorHandleUpdated"),
+                       server->mAppUuid,
+                       BluetoothValue(props));
+
+  if (server->mAddDescriptorState.mRunnable) {
+    DispatchReplySuccess(server->mAddDescriptorState.mRunnable);
+    server->mAddDescriptorState.Reset();
+  }
+}
+
+void
+BluetoothGattManager::ServiceStartedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  if (aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mStartServiceRunnable) {
+      DispatchReplyError(
+        server->mStartServiceRunnable,
+        NS_LITERAL_STRING("ServiceStartedNotification failed"));
+      server->mStartServiceRunnable = nullptr;
+    }
+    return;
+  }
+
+  if (server->mStartServiceRunnable) {
+    DispatchReplySuccess(server->mStartServiceRunnable);
+    server->mStartServiceRunnable = nullptr;
+  }
+}
+
+void
+BluetoothGattManager::ServiceStoppedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  if (aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mStopServiceRunnable) {
+      DispatchReplyError(
+        server->mStopServiceRunnable,
+        NS_LITERAL_STRING("ServiceStoppedNotification failed"));
+      server->mStopServiceRunnable = nullptr;
+    }
+    return;
+  }
+
+  if (server->mStopServiceRunnable) {
+    DispatchReplySuccess(server->mStopServiceRunnable);
+    server->mStopServiceRunnable = nullptr;
+  }
+}
+
+void
+BluetoothGattManager::ServiceDeletedNotification(
+  BluetoothGattStatus aStatus,
+  int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sServers->IndexOf(aServerIf, 0 /* Start */,
+                                   InterfaceIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = sServers->ElementAt(index);
+
+  if (aStatus != GATT_STATUS_SUCCESS) {
+    if (server->mRemoveServiceRunnable) {
+      DispatchReplyError(
+        server->mRemoveServiceRunnable,
+        NS_LITERAL_STRING("ServiceStoppedNotification failed"));
+      server->mRemoveServiceRunnable = nullptr;
+    }
+    return;
+  }
+
+  if (server->mRemoveServiceRunnable) {
+    DispatchReplySuccess(server->mRemoveServiceRunnable);
+    server->mRemoveServiceRunnable = nullptr;
+  }
+}
+
+void
+BluetoothGattManager::RequestReadNotification(
+  int aConnId,
+  int aTransId,
+  const nsAString& aBdAddr,
+  const BluetoothAttributeHandle& aAttributeHandle,
+  int aOffset,
+  bool aIsLong)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_TRUE_VOID(aConnId);
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sServers->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = (*sServers)[index];
+
+  // Send an error response for unsupported requests
+  if (aIsLong || aOffset > 0) {
+    BT_LOGR("Unsupported long attribute read requests");
+    BluetoothGattResponse response;
+    memset(&response, 0, sizeof(BluetoothGattResponse));
+    sBluetoothGattInterface->SendResponse(
+      aConnId,
+      aTransId,
+      GATT_STATUS_REQUEST_NOT_SUPPORTED,
+      response,
+      new ServerSendResponseResultHandler(server));
+    return;
+  }
+
+  // Distribute a signal to gattServer
+  InfallibleTArray<BluetoothNamedValue> properties;
+
+  AppendNamedValue(properties, "TransId", aTransId);
+  AppendNamedValue(properties, "AttrHandle", aAttributeHandle);
+  AppendNamedValue(properties, "Address", nsString(aBdAddr));
+  AppendNamedValue(properties, "NeedResponse", true);
+  AppendNamedValue(properties, "Value", new nsTArray<uint8_t>());
+
+  bs->DistributeSignal(NS_LITERAL_STRING("ReadRequested"),
+                       server->mAppUuid,
+                       properties);
+}
+
+void
+BluetoothGattManager::RequestWriteNotification(
+  int aConnId,
+  int aTransId,
+  const nsAString& aBdAddr,
+  const BluetoothAttributeHandle& aAttributeHandle,
+  int aOffset,
+  int aLength,
+  const uint8_t* aValue,
+  bool aNeedResponse,
+  bool aIsPrepareWrite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  NS_ENSURE_TRUE_VOID(aConnId);
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sServers->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sServers->NoIndex);
+
+  nsRefPtr<BluetoothGattServer> server = (*sServers)[index];
+
+  // Send an error response for unsupported requests
+  if (aIsPrepareWrite || aOffset > 0) {
+    BT_LOGR("Unsupported prepare write or long attribute write requests");
+    if (aNeedResponse) {
+      BluetoothGattResponse response;
+      memset(&response, 0, sizeof(BluetoothGattResponse));
+      sBluetoothGattInterface->SendResponse(
+        aConnId,
+        aTransId,
+        GATT_STATUS_REQUEST_NOT_SUPPORTED,
+        response,
+        new ServerSendResponseResultHandler(server));
+    }
+    return;
+  }
+
+  // Distribute a signal to gattServer
+  InfallibleTArray<BluetoothNamedValue> properties;
+
+  AppendNamedValue(properties, "TransId", aTransId);
+  AppendNamedValue(properties, "AttrHandle", aAttributeHandle);
+  AppendNamedValue(properties, "Address", nsString(aBdAddr));
+  AppendNamedValue(properties, "NeedResponse", aNeedResponse);
+
+  nsTArray<uint8_t> value;
+  value.AppendElements(aValue, aLength);
+  AppendNamedValue(properties, "Value", value);
+
+  bs->DistributeSignal(NS_LITERAL_STRING("WrtieRequested"),
+                       server->mAppUuid,
+                       properties);
+}
+
 BluetoothGattManager::BluetoothGattManager()
 { }
 
 BluetoothGattManager::~BluetoothGattManager()
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.h
@@ -98,16 +98,75 @@ public:
   void DisconnectPeripheral(
     const nsAString& aAppUuid,
     const nsAString& aAddress,
     BluetoothReplyRunnable* aRunnable);
 
   void UnregisterServer(int aServerIf,
                         BluetoothReplyRunnable* aRunnable);
 
+  void ServerAddService(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    uint16_t aHandleCount,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerAddIncludedService(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerAddCharacteristic(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aCharacteristicUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattCharProp aProperties,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerAddDescriptor(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    const BluetoothUuid& aDescriptorUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerRemoveService(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerStartService(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerStopService(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerSendResponse(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ServerSendIndication(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    bool aConfirm,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable);
+
 private:
   ~BluetoothGattManager();
 
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
   class RegisterClientResultHandler;
   class UnregisterClientResultHandler;
@@ -124,16 +183,25 @@ private:
   class ReadDescriptorValueResultHandler;
   class WriteDescriptorValueResultHandler;
   class ScanDeviceTypeResultHandler;
 
   class RegisterServerResultHandler;
   class ConnectPeripheralResultHandler;
   class DisconnectPeripheralResultHandler;
   class UnregisterServerResultHandler;
+  class ServerAddServiceResultHandler;
+  class ServerAddIncludedServiceResultHandler;
+  class ServerAddCharacteristicResultHandler;
+  class ServerAddDescriptorResultHandler;
+  class ServerRemoveDescriptorResultHandler;
+  class ServerStartServiceResultHandler;
+  class ServerStopServiceResultHandler;
+  class ServerSendResponseResultHandler;
+  class ServerSendIndicationResultHandler;
 
   BluetoothGattManager();
 
   void HandleShutdown();
 
   void RegisterClientNotification(BluetoothGattStatus aStatus,
                                   int aClientIf,
                                   const BluetoothUuid& aAppUuid) override;
@@ -222,14 +290,81 @@ private:
                                   int aServerIf,
                                   const BluetoothUuid& aAppUuid) override;
 
   void ConnectionNotification(int aConnId,
                               int aServerIf,
                               bool aConnected,
                               const nsAString& aBdAddr) override;
 
+  void
+  ServiceAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothAttributeHandle& aServiceHandle) override;
+
+  void
+  IncludedServiceAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle) override;
+
+  void
+  CharacteristicAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothUuid& aCharId,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle) override;
+
+  void
+  DescriptorAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothUuid& aCharId,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aDescriptorHandle) override;
+
+  void
+  ServiceStartedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle) override;
+
+  void
+  ServiceStoppedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle) override;
+
+  void
+  ServiceDeletedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle) override;
+
+  void
+  RequestReadNotification(int aConnId,
+                          int aTransId,
+                          const nsAString& aBdAddr,
+                          const BluetoothAttributeHandle& aAttributeHandle,
+                          int aOffset,
+                          bool aIsLong) override;
+
+  void
+  RequestWriteNotification(int aConnId,
+                           int aTransId,
+                           const nsAString& aBdAddr,
+                           const BluetoothAttributeHandle& aAttributeHandle,
+                           int aOffset,
+                           int aLength,
+                           const uint8_t* aValue,
+                           bool aNeedResponse,
+                           bool aIsPrepareWrite) override;
+
   static bool mInShutdown;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluedroid_BluetoothGattManager_h
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -598,16 +598,193 @@ BluetoothServiceBluedroid::UnregisterGat
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
   BluetoothGattManager* gatt = BluetoothGattManager::Get();
   ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
 
   gatt->UnregisterServer(aServerIf, aRunnable);
 }
 
+void
+BluetoothServiceBluedroid::GattServerAddServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  uint16_t aHandleCount,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerAddService(aAppUuid, aServiceId, aHandleCount, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerAddIncludedServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerAddIncludedService(aAppUuid,
+                                 aServiceHandle,
+                                 aIncludedServiceHandle,
+                                 aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerAddCharacteristicInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aCharacteristicUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothGattCharProp aProperties,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerAddCharacteristic(aAppUuid,
+                                aServiceHandle,
+                                aCharacteristicUuid,
+                                aPermissions,
+                                aProperties,
+                                aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerAddDescriptorInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  const BluetoothUuid& aDescriptorUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerAddDescriptor(aAppUuid,
+                            aServiceHandle,
+                            aCharacteristicHandle,
+                            aDescriptorUuid,
+                            aPermissions,
+                            aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerRemoveServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerRemoveService(aAppUuid, aServiceHandle, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerStartServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerStartService(aAppUuid, aServiceHandle, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerStopServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerStopService(aAppUuid, aServiceHandle, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerSendResponseInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  uint16_t aStatus,
+  int32_t aRequestId,
+  const BluetoothGattResponse& aRsp,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerSendResponse(
+    aAppUuid, aAddress, aStatus, aRequestId, aRsp, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattServerSendIndicationInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  bool aConfirm,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
+
+  BluetoothGattManager* gatt = BluetoothGattManager::Get();
+  ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
+
+  gatt->ServerSendIndication(aAppUuid,
+                             aAddress,
+                             aCharacteristicHandle,
+                             aConfirm,
+                             aValue,
+                             aRunnable);
+}
+
 nsresult
 BluetoothServiceBluedroid::GetAdaptersInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /**
    * Wrap BluetoothValue =
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -305,16 +305,84 @@ public:
     const nsAString& aAppUuid,
     const nsAString& aAddress,
     BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   UnregisterGattServerInternal(int aServerIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattServerAddServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    uint16_t aHandleCount,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddIncludedServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddCharacteristicInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aCharacteristicUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattCharProp aProperties,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddDescriptorInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    const BluetoothUuid& aDescriptorUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerRemoveServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerStartServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerStopServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerSendIndicationInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    bool aConfirm,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) override;
+
   //
   // Bluetooth notifications
   //
 
   virtual void AdapterStateChangedNotification(bool aState) override;
   virtual void AdapterPropertiesNotification(
     BluetoothStatus aStatus, int aNumProperties,
     const BluetoothProperty* aProperties) override;
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -4433,8 +4433,94 @@ BluetoothDBusService::GattServerDisconne
 {
 }
 
 void
 BluetoothDBusService::UnregisterGattServerInternal(
   int aServerIf, BluetoothReplyRunnable* aRunnable)
 {
 }
+
+void
+BluetoothDBusService::GattServerAddServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  uint16_t aHandleCount,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerAddIncludedServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerAddCharacteristicInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aCharacteristicUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothGattCharProp aProperties,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerAddDescriptorInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  const BluetoothUuid& aDescriptorUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerRemoveServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerStartServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerStopServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerSendIndicationInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  bool aConfirm,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattServerSendResponseInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  uint16_t aStatus,
+  int32_t aRequestId,
+  const BluetoothGattResponse& aRsp,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -316,16 +316,84 @@ public:
     const nsAString& aAppUuid,
     const nsAString& aAddress,
     BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   UnregisterGattServerInternal(int aServerIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattServerAddServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    uint16_t aHandleCount,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddIncludedServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddCharacteristicInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aCharacteristicUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattCharProp aProperties,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddDescriptorInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    const BluetoothUuid& aDescriptorUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerRemoveServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerStartServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerStopServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerSendIndicationInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    bool aConfirm,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) override;
+
 private:
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
                                   BluetoothReplyRunnable* aRunnable);
 
   nsresult SendDiscoveryMessage(const char* aMessageName,
                                 BluetoothReplyRunnable* aRunnable);
--- a/dom/bluetooth/common/BluetoothCommon.h
+++ b/dom/bluetooth/common/BluetoothCommon.h
@@ -107,16 +107,78 @@ extern bool gBluetoothDebugFlag;
   } while(0)
 
 /**
  * Reject |promise| with |ret| if nsresult |rv| is not successful.
  */
 #define BT_ENSURE_SUCCESS_REJECT(rv, promise, ret)                   \
   BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(rv), promise, ret)
 
+/**
+ * Resolve |promise| with |value| and return |ret| if |x| is false.
+ */
+#define BT_ENSURE_TRUE_RESOLVE_RETURN(x, promise, value, ret)        \
+  do {                                                               \
+    if (MOZ_UNLIKELY(!(x))) {                                        \
+      BT_LOGR("BT_ENSURE_TRUE_RESOLVE_RETURN(" #x ") failed");       \
+      (promise)->MaybeResolve(value);                                \
+      return ret;                                                    \
+    }                                                                \
+  } while(0)
+
+/**
+ * Resolve |promise| with |value| and return if |x| is false.
+ */
+#define BT_ENSURE_TRUE_RESOLVE_VOID(x, promise, value)               \
+  do {                                                               \
+    if (MOZ_UNLIKELY(!(x))) {                                        \
+      BT_LOGR("BT_ENSURE_TRUE_RESOLVE_VOID(" #x ") failed");         \
+      (promise)->MaybeResolve(value);                                \
+      return;                                                        \
+    }                                                                \
+  } while(0)
+
+/**
+ * Reject |promise| with |value| and return |ret| if |x| is false.
+ */
+#define BT_ENSURE_TRUE_REJECT_RETURN(x, promise, value, ret)         \
+  do {                                                               \
+    if (MOZ_UNLIKELY(!(x))) {                                        \
+      BT_LOGR("BT_ENSURE_TRUE_REJECT_RETURN(" #x ") failed");        \
+      (promise)->MaybeReject(value);                                 \
+      return ret;                                                    \
+    }                                                                \
+  } while(0)
+
+/**
+ * Reject |promise| with |value| and return if |x| is false.
+ */
+#define BT_ENSURE_TRUE_REJECT_VOID(x, promise, value)                \
+  do {                                                               \
+    if (MOZ_UNLIKELY(!(x))) {                                        \
+      BT_LOGR("BT_ENSURE_TRUE_REJECT_VOID(" #x ") failed");          \
+      (promise)->MaybeReject(value);                                 \
+      return;                                                        \
+    }                                                                \
+  } while(0)
+
+/**
+ * Reject |promise| with |value| and return |ret| if nsresult |rv|
+ * is not successful.
+ */
+#define BT_ENSURE_SUCCESS_REJECT_RETURN(rv, promise, value, ret)     \
+  BT_ENSURE_TRUE_REJECT_RETURN(NS_SUCCEEDED(rv), promise, value, ret)
+
+/**
+ * Reject |promise| with |value| and return if nsresult |rv|
+ * is not successful.
+ */
+#define BT_ENSURE_SUCCESS_REJECT_VOID(rv, promise, value)            \
+  BT_ENSURE_TRUE_REJECT_VOID(NS_SUCCEEDED(rv), promise, value)
+
 #define BEGIN_BLUETOOTH_NAMESPACE \
   namespace mozilla { namespace dom { namespace bluetooth {
 #define END_BLUETOOTH_NAMESPACE \
   } /* namespace bluetooth */ } /* namespace dom */ } /* namespace mozilla */
 #define USING_BLUETOOTH_NAMESPACE \
   using namespace mozilla::dom::bluetooth;
 
 #define KEY_LOCAL_AGENT       "/B2G/bluetooth/agent"
@@ -207,16 +269,23 @@ extern bool gBluetoothDebugFlag;
 #define GATT_CONNECTION_STATE_CHANGED_ID     "connectionstatechanged"
 
 /**
  * When attributes of BluetoothManager, BluetoothAdapter, or BluetoothDevice
  * are changed, we'll dispatch an event.
  */
 #define ATTRIBUTE_CHANGED_ID                 "attributechanged"
 
+/**
+ * When the local GATT server received attribute read/write requests, we'll
+ * dispatch an event.
+ */
+#define ATTRIBUTE_READ_REQUEST               "attributereadreq"
+#define ATTRIBUTE_WRITE_REQUEST              "attributewritereq"
+
 // Bluetooth address format: xx:xx:xx:xx:xx:xx (or xx_xx_xx_xx_xx_xx)
 #define BLUETOOTH_ADDRESS_LENGTH 17
 #define BLUETOOTH_ADDRESS_NONE   "00:00:00:00:00:00"
 #define BLUETOOTH_ADDRESS_BYTES  6
 
 // Bluetooth stack internal error, such as I/O error
 #define ERR_INTERNAL_ERROR "InternalError"
 
@@ -596,16 +665,21 @@ struct BluetoothAvrcpNotificationParam {
 };
 
 struct BluetoothAvrcpPlayerSettings {
   uint8_t mNumAttr;
   uint8_t mIds[256];
   uint8_t mValues[256];
 };
 
+enum BluetoothAttRole {
+  ATT_SERVER_ROLE,
+  ATT_CLIENT_ROLE
+};
+
 enum BluetoothGattStatus {
   GATT_STATUS_SUCCESS,
   GATT_STATUS_INVALID_HANDLE,
   GATT_STATUS_READ_NOT_PERMITTED,
   GATT_STATUS_WRITE_NOT_PERMITTED,
   GATT_STATUS_INVALID_PDU,
   GATT_STATUS_INSUFFICIENT_AUTHENTICATION,
   GATT_STATUS_REQUEST_NOT_SUPPORTED,
@@ -623,17 +697,18 @@ enum BluetoothGattStatus {
   GATT_STATUS_UNKNOWN_ERROR
 };
 
 enum BluetoothGattAuthReq {
   GATT_AUTH_REQ_NONE,
   GATT_AUTH_REQ_NO_MITM,
   GATT_AUTH_REQ_MITM,
   GATT_AUTH_REQ_SIGNED_NO_MITM,
-  GATT_AUTH_REQ_SIGNED_MITM
+  GATT_AUTH_REQ_SIGNED_MITM,
+  GATT_AUTH_REQ_END_GUARD
 };
 
 enum BluetoothGattWriteType {
   GATT_WRITE_TYPE_NO_RESPONSE,
   GATT_WRITE_TYPE_NORMAL,
   GATT_WRITE_TYPE_PREPARE,
   GATT_WRITE_TYPE_SIGNED,
   GATT_WRITE_TYPE_END_GUARD
@@ -749,34 +824,56 @@ struct BluetoothGattTestParam {
   BluetoothUuid mUuid;
   uint16_t mU1;
   uint16_t mU2;
   uint16_t mU3;
   uint16_t mU4;
   uint16_t mU5;
 };
 
+struct BluetoothAttributeHandle {
+  uint16_t mHandle;
+
+  BluetoothAttributeHandle()
+    : mHandle(0x0000)
+  { }
+
+  bool operator==(const BluetoothAttributeHandle& aOther) const
+  {
+    return mHandle == aOther.mHandle;
+  }
+};
+
 struct BluetoothGattResponse {
-  uint16_t mHandle;
+  BluetoothAttributeHandle mHandle;
   uint16_t mOffset;
   uint16_t mLength;
   BluetoothGattAuthReq mAuthReq;
   uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
+
+  bool operator==(const BluetoothGattResponse& aOther) const
+  {
+    return mHandle == aOther.mHandle &&
+           mOffset == aOther.mOffset &&
+           mLength == aOther.mLength &&
+           mAuthReq == aOther.mAuthReq &&
+           !memcmp(mValue, aOther.mValue, mLength);
+  }
 };
 
 /**
  * EIR Data Type, Advertising Data Type (AD Type) and OOB Data Type Definitions
  * Please refer to https://www.bluetooth.org/en-us/specification/\
  * assigned-numbers/generic-access-profile
  */
 enum BluetoothGapDataType {
   GAP_INCOMPLETE_UUID16  = 0X02, // Incomplete List of 16-bit Service Class UUIDs
   GAP_COMPLETE_UUID16    = 0X03, // Complete List of 16-bit Service Class UUIDs
   GAP_INCOMPLETE_UUID32  = 0X04, // Incomplete List of 32-bit Service Class UUIDs
-  GAP_COMPLETE_UUID32    = 0X05, // Complete List of 32-bit Service Class UUIDs┬╗
+  GAP_COMPLETE_UUID32    = 0X05, // Complete List of 32-bit Service Class UUIDs
   GAP_INCOMPLETE_UUID128 = 0X06, // Incomplete List of 128-bit Service Class UUIDs
   GAP_COMPLETE_UUID128   = 0X07, // Complete List of 128-bit Service Class UUIDs
   GAP_SHORTENED_NAME     = 0X08, // Shortened Local Name
   GAP_COMPLETE_NAME      = 0X09, // Complete Local Name
 };
 
 END_BLUETOOTH_NAMESPACE
 
--- a/dom/bluetooth/common/BluetoothInterface.cpp
+++ b/dom/bluetooth/common/BluetoothInterface.cpp
@@ -584,63 +584,70 @@ BluetoothGattNotificationHandler::Regist
 void
 BluetoothGattNotificationHandler::ConnectionNotification(
   int aConnId, int aServerIf, bool aConnected, const nsAString& aBdAddr)
 { }
 
 void
 BluetoothGattNotificationHandler::ServiceAddedNotification(
   BluetoothGattStatus aStatus, int aServerIf,
-  const BluetoothGattServiceId& aServiceId, int aServiceHandle)
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothAttributeHandle& aServiceHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::IncludedServiceAddedNotification(
-  BluetoothGattStatus aStatus, int aServerIf, int aServiceHandle,
-  int aIncludedServiceHandle)
+  BluetoothGattStatus aStatus, int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::CharacteristicAddedNotification(
   BluetoothGattStatus aStatus, int aServerIf, const BluetoothUuid& aCharId,
-  int aServiceHandle, int aCharacteristicHandle)
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aCharacteristicHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::DescriptorAddedNotification(
   BluetoothGattStatus aStatus, int aServerIf, const BluetoothUuid& aCharId,
-  int aServiceHandle, int aDescriptorHandle)
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aDescriptorHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::ServiceStartedNotification(
-  BluetoothGattStatus aStatus, int aServerIf, int aServiceHandle)
+  BluetoothGattStatus aStatus, int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::ServiceStoppedNotification(
-  BluetoothGattStatus aStatus, int aServerIf, int aServiceHandle)
+  BluetoothGattStatus aStatus, int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::ServiceDeletedNotification(
-  BluetoothGattStatus aStatus, int aServerIf, int aServiceHandle)
+  BluetoothGattStatus aStatus, int aServerIf,
+  const BluetoothAttributeHandle& aServiceHandle)
 { }
 
 void
 BluetoothGattNotificationHandler::RequestReadNotification(
-  int aConnId, int aTransId, const nsAString& aBdAddr, int aAttributeHandle,
-  int aOffset, bool aIsLong)
+  int aConnId, int aTransId, const nsAString& aBdAddr,
+  const BluetoothAttributeHandle& aAttributeHandle, int aOffset, bool aIsLong)
 { }
 
 void
 BluetoothGattNotificationHandler::RequestWriteNotification(
-  int aConnId, int aTransId, const nsAString& aBdAddr, int aAttributeHandle,
-  int aOffset, int aLength, const uint8_t* aValue, bool aNeedResponse,
-  bool aIsPrepareWrite)
+  int aConnId, int aTransId, const nsAString& aBdAddr,
+  const BluetoothAttributeHandle& aAttributeHandle, int aOffset, int aLength,
+  const uint8_t* aValue, bool aNeedResponse, bool aIsPrepareWrite)
 { }
 
 void
 BluetoothGattNotificationHandler::RequestExecuteWriteNotification(
   int aConnId, int aTransId, const nsAString& aBdAddr, bool aExecute)
 { }
 
 void
--- a/dom/bluetooth/common/BluetoothInterface.h
+++ b/dom/bluetooth/common/BluetoothInterface.h
@@ -564,66 +564,69 @@ public:
                          int aServerIf,
                          bool aConnected,
                          const nsAString& aBdAddr);
 
   virtual void
   ServiceAddedNotification(BluetoothGattStatus aStatus,
                            int aServerIf,
                            const BluetoothGattServiceId& aServiceId,
-                           int aServiceHandle);
+                           const BluetoothAttributeHandle& aServiceHandle);
 
   virtual void
-  IncludedServiceAddedNotification(BluetoothGattStatus aStatus,
-                                   int aServerIf,
-                                   int aServiceHandle,
-                                   int aIncludedServiceHandle);
+  IncludedServiceAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle);
 
   virtual void
-  CharacteristicAddedNotification(BluetoothGattStatus aStatus,
-                                  int aServerIf,
-                                  const BluetoothUuid& aCharId,
-                                  int aServiceHandle,
-                                  int aCharacteristicHandle);
+  CharacteristicAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothUuid& aCharId,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle);
 
   virtual void
-  DescriptorAddedNotification(BluetoothGattStatus aStatus,
-                              int aServerIf,
-                              const BluetoothUuid& aCharId,
-                              int aServiceHandle,
-                              int aDescriptorHandle);
+  DescriptorAddedNotification(
+    BluetoothGattStatus aStatus,
+    int aServerIf,
+    const BluetoothUuid& aCharId,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aDescriptorHandle);
 
   virtual void
   ServiceStartedNotification(BluetoothGattStatus aStatus,
                              int aServerIf,
-                             int aServiceHandle);
+                             const BluetoothAttributeHandle& aServiceHandle);
 
   virtual void
   ServiceStoppedNotification(BluetoothGattStatus aStatus,
                              int aServerIf,
-                             int aServiceHandle);
+                             const BluetoothAttributeHandle& aServiceHandle);
 
   virtual void
   ServiceDeletedNotification(BluetoothGattStatus aStatus,
                              int aServerIf,
-                             int aServiceHandle);
+                             const BluetoothAttributeHandle& aServiceHandle);
 
   virtual void
   RequestReadNotification(int aConnId,
                           int aTransId,
                           const nsAString& aBdAddr,
-                          int aAttributeHandle,
+                          const BluetoothAttributeHandle& aAttributeHandle,
                           int aOffset,
                           bool aIsLong);
 
   virtual void
   RequestWriteNotification(int aConnId,
                            int aTransId,
                            const nsAString& aBdAddr,
-                           int aAttributeHandle,
+                           const BluetoothAttributeHandle& aAttributeHandle,
                            int aOffset,
                            int aLength,
                            const uint8_t* aValue,
                            bool aNeedResponse,
                            bool aIsPrepareWrite);
 
   virtual void
   RequestExecuteWriteNotification(int aConnId,
@@ -866,59 +869,60 @@ public:
   virtual void DisconnectPeripheral(int aServerIf,
                                     const nsAString& aBdAddr,
                                     int aConnId,
                                     BluetoothGattResultHandler* aRes) = 0;
 
   /* Add a services / a characteristic / a descriptor */
   virtual void AddService(int aServerIf,
                           const BluetoothGattServiceId& aServiceId,
-                          int aNumHandles,
+                          uint16_t aNumHandles,
                           BluetoothGattResultHandler* aRes) = 0;
-  virtual void AddIncludedService(int aServerIf,
-                                  int aServiceHandle,
-                                  int aIncludedServiceHandle,
-                                  BluetoothGattResultHandler* aRes) = 0;
+  virtual void AddIncludedService(
+    int aServerIf,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothGattResultHandler* aRes) = 0;
   virtual void AddCharacteristic(int aServerIf,
-                                 int aServiceHandle,
+                                 const BluetoothAttributeHandle& aServiceHandle,
                                  const BluetoothUuid& aUuid,
                                  BluetoothGattCharProp aProperties,
                                  BluetoothGattAttrPerm aPermissions,
                                  BluetoothGattResultHandler* aRes) = 0;
   virtual void AddDescriptor(int aServerIf,
-                             int aServiceHandle,
+                             const BluetoothAttributeHandle& aServiceHandle,
                              const BluetoothUuid& aUuid,
                              BluetoothGattAttrPerm aPermissions,
                              BluetoothGattResultHandler* aRes) = 0;
 
   /* Start / Stop / Delete a service */
   virtual void StartService(int aServerIf,
-                            int aServiceHandle,
+                            const BluetoothAttributeHandle& aServiceHandle,
                             BluetoothTransport aTransport,
                             BluetoothGattResultHandler* aRes) = 0;
   virtual void StopService(int aServerIf,
-                           int aServiceHandle,
+                           const BluetoothAttributeHandle& aServiceHandle,
                            BluetoothGattResultHandler* aRes) = 0;
   virtual void DeleteService(int aServerIf,
-                             int aServiceHandle,
+                             const BluetoothAttributeHandle& aServiceHandle,
                              BluetoothGattResultHandler* aRes) = 0;
 
   /* Send an indication or a notification */
-  virtual void SendIndication(int aServerIf,
-                              int aAttributeHandle,
-                              int aConnId,
-                              const nsTArray<uint8_t>& aValue,
-                              bool aConfirm, /* true: indication */
-                                             /* false: notification */
-                              BluetoothGattResultHandler* aRes) = 0;
+  virtual void SendIndication(
+    int aServerIf,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    int aConnId,
+    const nsTArray<uint8_t>& aValue,
+    bool aConfirm, /* true: indication; false: notification */
+    BluetoothGattResultHandler* aRes) = 0;
 
   /* Send a response for an incoming indication */
   virtual void SendResponse(int aConnId,
                             int aTransId,
-                            BluetoothGattStatus aStatus,
+                            uint16_t aStatus,
                             const BluetoothGattResponse& aResponse,
                             BluetoothGattResultHandler* aRes) = 0;
 
 protected:
   BluetoothGattInterface();
   virtual ~BluetoothGattInterface();
 };
 
--- a/dom/bluetooth/common/BluetoothReplyRunnable.cpp
+++ b/dom/bluetooth/common/BluetoothReplyRunnable.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=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 "base/basictypes.h"
 #include "BluetoothReplyRunnable.h"
+#include "BluetoothUtils.h"
 #include "DOMRequest.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/Promise.h"
 #include "nsServiceManagerUtils.h"
 
 using namespace mozilla::dom;
 
@@ -53,16 +54,17 @@ BluetoothReplyRunnable::FireReplySuccess
     return rs->FireSuccessAsync(mDOMRequest, aVal);
   }
 
   // Promise
   if (mPromise) {
     mPromise->MaybeResolve(aVal);
   }
 
+  OnSuccessFired();
   return NS_OK;
 }
 
 nsresult
 BluetoothReplyRunnable::FireErrorString()
 {
   // DOMRequest
   if (mDOMRequest) {
@@ -75,16 +77,17 @@ BluetoothReplyRunnable::FireErrorString(
 
   // Promise
   if (mPromise) {
     nsresult rv =
       NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_DOM_BLUETOOTH, mErrorStatus);
     mPromise->MaybeReject(rv);
   }
 
+  OnErrorFired();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BluetoothReplyRunnable::Run()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mReply);
@@ -113,16 +116,202 @@ BluetoothReplyRunnable::Run()
              "BluetoothReplyRunnable::ReleaseMembers()!");
   MOZ_ASSERT(!mPromise,
              "mPromise is still alive! Deriving class should call "
              "BluetoothReplyRunnable::ReleaseMembers()!");
 
   return rv;
 }
 
+void
+BluetoothReplyRunnable::OnSuccessFired()
+{}
+
+void
+BluetoothReplyRunnable::OnErrorFired()
+{}
+
 BluetoothVoidReplyRunnable::BluetoothVoidReplyRunnable(nsIDOMDOMRequest* aReq,
                                                        Promise* aPromise)
   : BluetoothReplyRunnable(aReq, aPromise)
 {}
 
 BluetoothVoidReplyRunnable::~BluetoothVoidReplyRunnable()
 {}
 
+BluetoothReplyTaskQueue::SubReplyRunnable::SubReplyRunnable(
+  nsIDOMDOMRequest* aReq,
+  Promise* aPromise,
+  BluetoothReplyTaskQueue* aRootQueue)
+  : BluetoothReplyRunnable(aReq, aPromise)
+  , mRootQueue(aRootQueue)
+{}
+
+BluetoothReplyTaskQueue::SubReplyRunnable::~SubReplyRunnable()
+{}
+
+BluetoothReplyTaskQueue*
+BluetoothReplyTaskQueue::SubReplyRunnable::GetRootQueue() const
+{
+  return mRootQueue;
+}
+
+void
+BluetoothReplyTaskQueue::SubReplyRunnable::OnSuccessFired()
+{
+  mRootQueue->OnSubReplySuccessFired(this);
+}
+
+void
+BluetoothReplyTaskQueue::SubReplyRunnable::OnErrorFired()
+{
+  mRootQueue->OnSubReplyErrorFired(this);
+}
+
+BluetoothReplyTaskQueue::VoidSubReplyRunnable::VoidSubReplyRunnable(
+  nsIDOMDOMRequest* aReq,
+  Promise* aPromise,
+  BluetoothReplyTaskQueue* aRootQueue)
+  : BluetoothReplyTaskQueue::SubReplyRunnable(aReq, aPromise, aRootQueue)
+{}
+
+BluetoothReplyTaskQueue::VoidSubReplyRunnable::~VoidSubReplyRunnable()
+{}
+
+bool
+BluetoothReplyTaskQueue::VoidSubReplyRunnable::ParseSuccessfulReply(
+  JS::MutableHandle<JS::Value> aValue)
+{
+  aValue.setUndefined();
+  return true;
+}
+
+BluetoothReplyTaskQueue::SubTask::SubTask(
+  BluetoothReplyTaskQueue* aRootQueue,
+  SubReplyRunnable* aReply)
+  : mRootQueue(aRootQueue)
+  , mReply(aReply)
+{
+  if (!mReply) {
+    mReply = new VoidSubReplyRunnable(nullptr, nullptr, mRootQueue);
+  }
+}
+
+BluetoothReplyTaskQueue::SubTask::~SubTask()
+{}
+
+BluetoothReplyTaskQueue*
+BluetoothReplyTaskQueue::SubTask::GetRootQueue() const
+{
+  return mRootQueue;
+}
+
+BluetoothReplyTaskQueue::SubReplyRunnable*
+BluetoothReplyTaskQueue::SubTask::GetReply() const
+{
+  return mReply;
+}
+
+BluetoothReplyTaskQueue::BluetoothReplyTaskQueue(
+  BluetoothReplyRunnable* aReply)
+  : mReply(aReply)
+{}
+
+BluetoothReplyTaskQueue::~BluetoothReplyTaskQueue()
+{
+  Clear();
+}
+
+void
+BluetoothReplyTaskQueue::AppendTask(already_AddRefed<SubTask> aTask)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<SubTask> task(aTask);
+
+  if (task) {
+    mTasks.AppendElement(task.forget());
+  }
+}
+
+NS_IMETHODIMP
+BluetoothReplyTaskQueue::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mTasks.IsEmpty()) {
+    nsRefPtr<SubTask> task = mTasks[0];
+    mTasks.RemoveElementAt(0);
+
+    MOZ_ASSERT(task);
+
+    if (!task->Execute()) {
+      FireErrorReply();
+    }
+  }
+
+  return NS_OK;
+}
+
+void
+BluetoothReplyTaskQueue::OnSubReplySuccessFired(SubReplyRunnable* aSubReply)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSubReply);
+
+  if (mTasks.IsEmpty()) {
+    FireSuccessReply();
+  } else {
+    if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(this)))) {
+      FireErrorReply();
+    }
+  }
+}
+
+void
+BluetoothReplyTaskQueue::OnSubReplyErrorFired(SubReplyRunnable* aSubReply)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSubReply);
+
+  FireErrorReply();
+}
+
+void
+BluetoothReplyTaskQueue::FireSuccessReply()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mReply) {
+    DispatchReplySuccess(mReply);
+  }
+  OnSuccessFired();
+  Clear();
+}
+
+void
+BluetoothReplyTaskQueue::FireErrorReply()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mReply) {
+    DispatchReplyError(mReply, STATUS_FAIL);
+  }
+  OnErrorFired();
+  Clear();
+}
+
+void
+BluetoothReplyTaskQueue::OnSuccessFired()
+{}
+
+void
+BluetoothReplyTaskQueue::OnErrorFired()
+{}
+
+void
+BluetoothReplyTaskQueue::Clear()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mReply = nullptr;
+  mTasks.Clear();
+}
--- a/dom/bluetooth/common/BluetoothReplyRunnable.h
+++ b/dom/bluetooth/common/BluetoothReplyRunnable.h
@@ -52,16 +52,19 @@ protected:
   // header. We assume we'll only be running this once and it should die on
   // scope out of Run() anyways.
   nsAutoPtr<BluetoothReply> mReply;
 
 private:
   nsresult FireReplySuccess(JS::Handle<JS::Value> aVal);
   nsresult FireErrorString();
 
+  virtual void OnSuccessFired();
+  virtual void OnErrorFired();
+
   /**
    * Either mDOMRequest or mPromise is not nullptr to reply applications
    * success or error string. One special case is internal IPC that require
    * neither mDOMRequest nor mPromise to reply applications.
    * E.g., GetAdaptersTask triggered by BluetoothManager
    *
    * TODO: remove mDOMRequest once all methods adopt Promise.
    */
@@ -83,11 +86,112 @@ protected:
   virtual bool
   ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue) override
   {
     aValue.setUndefined();
     return true;
   }
 };
 
+class BluetoothReplyTaskQueue : public nsRunnable
+{
+public:
+  NS_DECL_NSIRUNNABLE
+
+  class SubReplyRunnable : public BluetoothReplyRunnable
+  {
+  public:
+    SubReplyRunnable(nsIDOMDOMRequest* aReq,
+                     Promise* aPromise,
+                     BluetoothReplyTaskQueue* aRootQueue);
+    ~SubReplyRunnable();
+
+    BluetoothReplyTaskQueue* GetRootQueue() const;
+
+  private:
+    virtual void OnSuccessFired() override;
+    virtual void OnErrorFired() override;
+
+    nsRefPtr<BluetoothReplyTaskQueue> mRootQueue;
+  };
+  friend class BluetoothReplyTaskQueue::SubReplyRunnable;
+
+  class VoidSubReplyRunnable : public SubReplyRunnable
+  {
+  public:
+    VoidSubReplyRunnable(nsIDOMDOMRequest* aReq,
+                         Promise* aPromise,
+                         BluetoothReplyTaskQueue* aRootQueue);
+    ~VoidSubReplyRunnable();
+
+  protected:
+    virtual bool ParseSuccessfulReply(
+      JS::MutableHandle<JS::Value> aValue) override;
+  };
+
+  class SubTask
+  {
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SubTask)
+  public:
+    SubTask(BluetoothReplyTaskQueue* aRootQueue,
+            SubReplyRunnable* aReply);
+
+    BluetoothReplyTaskQueue* GetRootQueue() const;
+    SubReplyRunnable* GetReply() const;
+
+    /*
+     * Use SubReplyRunnable as the reply runnable to execute the task.
+     *
+     * Example:
+     * <pre>
+     * <code>
+     * bool SomeInheritedSubTask::Execute()
+     * {
+     *   BluetoothService* bs = BluetoothService::Get();
+     *   if (!bs) {
+     *     return false;
+     *   }
+     *   bs->DoSomethingInternal(
+     *     aSomeParameter,
+     *     new SomeInheritedSubReplyRunnable(aSomeDOMRequest,
+     *                                       aSomePromise,
+     *                                       GetRootQueue()));
+     *   return true;
+     * }
+     * </code>
+     * </pre>
+     */
+    virtual bool Execute() = 0;
+
+  protected:
+    virtual ~SubTask();
+
+  private:
+    nsRefPtr<BluetoothReplyTaskQueue> mRootQueue;
+    nsRefPtr<SubReplyRunnable> mReply;
+  };
+
+  BluetoothReplyTaskQueue(BluetoothReplyRunnable* aReply);
+
+  void AppendTask(already_AddRefed<SubTask> aTask);
+
+protected:
+  ~BluetoothReplyTaskQueue();
+
+  void FireSuccessReply();
+  void FireErrorReply();
+
+private:
+  void Clear();
+
+  void OnSubReplySuccessFired(SubReplyRunnable* aSubReply);
+  void OnSubReplyErrorFired(SubReplyRunnable* aSubReply);
+
+  virtual void OnSuccessFired();
+  virtual void OnErrorFired();
+
+  nsRefPtr<BluetoothReplyRunnable> mReply;
+  nsTArray<nsRefPtr<SubTask>> mTasks;
+};
+
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_BluetoothReplyRunnable_h
--- a/dom/bluetooth/common/BluetoothService.h
+++ b/dom/bluetooth/common/BluetoothService.h
@@ -503,16 +503,84 @@ public:
 
   /**
    * Unregister a GATT server. (platform specific implementation)
    */
   virtual void
   UnregisterGattServerInternal(int aServerIf,
                                BluetoothReplyRunnable* aRunnable) = 0;
 
+  virtual void
+  GattServerAddServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    uint16_t aHandleCount,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerAddIncludedServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerAddCharacteristicInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aCharacteristicUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattCharProp aProperties,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerAddDescriptorInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    const BluetoothUuid& aDescriptorUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerRemoveServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerStartServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerStopServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  virtual void
+  GattServerSendIndicationInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    bool aConfirm,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
   bool
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
--- a/dom/bluetooth/common/BluetoothUtils.cpp
+++ b/dom/bluetooth/common/BluetoothUtils.cpp
@@ -3,16 +3,17 @@
 /* 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 "BluetoothUtils.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "jsapi.h"
+#include "mozilla/dom/BluetoothGattCharacteristicBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "nsContentUtils.h"
 #include "nsISystemMessagesInternal.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXULAppAPI.h"
 
@@ -38,22 +39,23 @@ UuidToString(const BluetoothUuid& aUuid,
            ntohs(uuid2), ntohs(uuid3),
            ntohl(uuid4), ntohs(uuid5));
 
   aString.Truncate();
   aString.AssignLiteral(uuidStr);
 }
 
 void
-StringToUuid(const char* aString, BluetoothUuid& aUuid)
+StringToUuid(const nsAString& aString, BluetoothUuid& aUuid)
 {
   uint32_t uuid0, uuid4;
   uint16_t uuid1, uuid2, uuid3, uuid5;
 
-  sscanf(aString, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
+  sscanf(NS_ConvertUTF16toUTF8(aString).get(),
+         "%08x-%04hx-%04hx-%04hx-%08x%04hx",
          &uuid0, &uuid1, &uuid2, &uuid3, &uuid4, &uuid5);
 
   uuid0 = htonl(uuid0);
   uuid1 = htons(uuid1);
   uuid2 = htons(uuid2);
   uuid3 = htons(uuid3);
   uuid4 = htonl(uuid4);
   uuid5 = htons(uuid5);
@@ -61,44 +63,136 @@ StringToUuid(const char* aString, Blueto
   memcpy(&aUuid.mUuid[0], &uuid0, sizeof(uint32_t));
   memcpy(&aUuid.mUuid[4], &uuid1, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[6], &uuid2, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[8], &uuid3, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[10], &uuid4, sizeof(uint32_t));
   memcpy(&aUuid.mUuid[14], &uuid5, sizeof(uint16_t));
 }
 
-void
-StringToUuid(const nsAString& aString, BluetoothUuid& aUuid)
-{
-  StringToUuid(NS_ConvertUTF16toUTF8(aString).get(), aUuid);
-}
-
-void
+nsresult
 GenerateUuid(nsAString &aUuidString)
 {
   nsresult rv;
   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
-  NS_ENSURE_SUCCESS_VOID(rv);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   nsID uuid;
   rv = uuidGenerator->GenerateUUIDInPlace(&uuid);
-  NS_ENSURE_SUCCESS_VOID(rv);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Build a string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format
   char uuidBuffer[NSID_LENGTH];
   uuid.ToProvidedString(uuidBuffer);
   NS_ConvertASCIItoUTF16 uuidString(uuidBuffer);
 
   // Remove {} and the null terminator
   aUuidString.Assign(Substring(uuidString, 1, NSID_LENGTH - 3));
+
+  return NS_OK;
 }
 
 void
+GattPermissionsToDictionary(BluetoothGattAttrPerm aBits,
+                            GattPermissions& aPermissions)
+{
+  aPermissions.mRead = aBits & GATT_ATTR_PERM_BIT_READ;
+  aPermissions.mReadEncrypted = aBits & GATT_ATTR_PERM_BIT_READ_ENCRYPTED;
+  aPermissions.mReadEncryptedMITM =
+    aBits & GATT_ATTR_PERM_BIT_READ_ENCRYPTED_MITM;
+  aPermissions.mWrite = aBits & GATT_ATTR_PERM_BIT_WRITE;
+  aPermissions.mWriteEncrypted = aBits & GATT_ATTR_PERM_BIT_WRITE_ENCRYPTED;
+  aPermissions.mWriteEncryptedMITM =
+    aBits & GATT_ATTR_PERM_BIT_WRITE_ENCRYPTED_MITM;
+  aPermissions.mWriteSigned = aBits & GATT_ATTR_PERM_BIT_WRITE_SIGNED;
+  aPermissions.mWriteSignedMITM = aBits & GATT_ATTR_PERM_BIT_WRITE_SIGNED_MITM;
+}
+
+void
+GattPermissionsToBits(const GattPermissions& aPermissions,
+                      BluetoothGattAttrPerm& aBits)
+{
+  aBits = BLUETOOTH_EMPTY_GATT_ATTR_PERM;
+
+  if (aPermissions.mRead) {
+    aBits |= GATT_ATTR_PERM_BIT_READ;
+  }
+  if (aPermissions.mReadEncrypted) {
+    aBits |= GATT_ATTR_PERM_BIT_READ_ENCRYPTED;
+  }
+  if (aPermissions.mReadEncryptedMITM) {
+    aBits |= GATT_ATTR_PERM_BIT_READ_ENCRYPTED_MITM;
+  }
+  if (aPermissions.mWrite) {
+    aBits |= GATT_ATTR_PERM_BIT_WRITE;
+  }
+  if (aPermissions.mWriteEncrypted) {
+    aBits |= GATT_ATTR_PERM_BIT_WRITE_ENCRYPTED;
+  }
+  if (aPermissions.mWriteEncryptedMITM) {
+    aBits |= GATT_ATTR_PERM_BIT_WRITE_ENCRYPTED_MITM;
+  }
+  if (aPermissions.mWriteSigned) {
+    aBits |= GATT_ATTR_PERM_BIT_WRITE_SIGNED;
+  }
+  if (aPermissions.mWriteSignedMITM) {
+    aBits |= GATT_ATTR_PERM_BIT_WRITE_SIGNED_MITM;
+  }
+}
+
+void
+GattPropertiesToDictionary(BluetoothGattCharProp aBits,
+                           GattCharacteristicProperties& aProperties)
+{
+  aProperties.mBroadcast = aBits & GATT_CHAR_PROP_BIT_BROADCAST;
+  aProperties.mRead = aBits & GATT_CHAR_PROP_BIT_READ;
+  aProperties.mWriteNoResponse = aBits & GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE;
+  aProperties.mWrite = aBits & GATT_CHAR_PROP_BIT_WRITE;
+  aProperties.mNotify = aBits & GATT_CHAR_PROP_BIT_NOTIFY;
+  aProperties.mIndicate = aBits & GATT_CHAR_PROP_BIT_INDICATE;
+  aProperties.mSignedWrite = aBits & GATT_CHAR_PROP_BIT_SIGNED_WRITE;
+  aProperties.mExtendedProps = aBits & GATT_CHAR_PROP_BIT_EXTENDED_PROPERTIES;
+}
+
+void
+GattPropertiesToBits(const GattCharacteristicProperties& aProperties,
+                     BluetoothGattCharProp& aBits)
+{
+  aBits = BLUETOOTH_EMPTY_GATT_CHAR_PROP;
+
+  if (aProperties.mBroadcast) {
+    aBits |= GATT_CHAR_PROP_BIT_BROADCAST;
+  }
+  if (aProperties.mRead) {
+    aBits |= GATT_CHAR_PROP_BIT_READ;
+  }
+  if (aProperties.mWriteNoResponse) {
+    aBits |= GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE;
+  }
+  if (aProperties.mWrite) {
+    aBits |= GATT_CHAR_PROP_BIT_WRITE;
+  }
+  if (aProperties.mNotify) {
+    aBits |= GATT_CHAR_PROP_BIT_NOTIFY;
+  }
+  if (aProperties.mIndicate) {
+    aBits |= GATT_CHAR_PROP_BIT_INDICATE;
+  }
+  if (aProperties.mSignedWrite) {
+    aBits |= GATT_CHAR_PROP_BIT_SIGNED_WRITE;
+  }
+  if (aProperties.mExtendedProps) {
+    aBits |= GATT_CHAR_PROP_BIT_EXTENDED_PROPERTIES;
+  }
+}
+
+
+
+void
 GeneratePathFromGattId(const BluetoothGattId& aId,
                        nsAString& aPath)
 {
   nsString uuidStr;
   UuidToString(aId.mUuid, uuidStr);
 
   aPath.Assign(uuidStr);
   aPath.AppendLiteral("_");
--- a/dom/bluetooth/common/BluetoothUtils.h
+++ b/dom/bluetooth/common/BluetoothUtils.h
@@ -5,16 +5,23 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_BluetoothUtils_h
 #define mozilla_dom_bluetooth_BluetoothUtils_h
 
 #include "BluetoothCommon.h"
 #include "js/TypeDecls.h"
 
+namespace mozilla {
+namespace dom {
+class GattPermissions;
+class GattCharacteristicProperties;
+}
+}
+
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothNamedValue;
 class BluetoothReplyRunnable;
 class BluetoothValue;
 
 //
 // BluetoothUuid <-> uuid string conversion
@@ -31,34 +38,67 @@ UuidToString(const BluetoothUuid& aUuid,
 
 /**
  * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
  *
  * Note: This utility function is used by gecko internal only to convert uuid
  * string created by gecko back to BluetoothUuid representation.
  */
 void
-StringToUuid(const char* aString, BluetoothUuid& aUuid);
-
-/**
- * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
- *
- * This utility function is used by gecko internal only to convert uuid string
- * created by gecko back to BluetoothUuid representation.
- */
-void
 StringToUuid(const nsAString& aString, BluetoothUuid& aUuid);
 
 /**
  * Generate a random uuid.
  *
  * @param aUuidString [out] String to store the generated uuid.
  */
+nsresult
+GenerateUuid(nsAString &aUuidString);
+
+/**
+ * Convert BluetoothGattAttrPerm bit masks to GattPermissions object.
+ *
+ * @param aBits [in] BluetoothGattAttrPerm bit masks.
+ * @param aPermissions [out] GattPermissions object.
+ */
 void
-GenerateUuid(nsAString &aUuidString);
+GattPermissionsToDictionary(BluetoothGattAttrPerm aBits,
+                            GattPermissions& aPermissions);
+
+/**
+ * Convert GattPermissions object to BluetoothGattAttrPerm bit masks.
+ *
+ * @param aPermissions [in] GattPermissions object.
+ * @param aBits [out] BluetoothGattAttrPerm bit masks.
+ */
+void
+GattPermissionsToBits(const GattPermissions& aPermissions,
+                      BluetoothGattAttrPerm& aBits);
+
+/**
+ * Convert BluetoothGattCharProp bit masks to GattCharacteristicProperties
+ * object.
+ *
+ * @param aBits [in] BluetoothGattCharProp bit masks.
+ * @param aProperties [out] GattCharacteristicProperties object.
+ */
+void
+GattPropertiesToDictionary(BluetoothGattCharProp aBits,
+                           GattCharacteristicProperties& aProperties);
+
+/**
+ * Convert GattCharacteristicProperties object to BluetoothGattCharProp bit
+ * masks.
+ *
+ * @param aProperties [in] GattCharacteristicProperties object.
+ * @param aBits [out] BluetoothGattCharProp bit masks.
+ */
+void
+GattPropertiesToBits(const GattCharacteristicProperties& aProperties,
+                     BluetoothGattCharProp& aBits);
 
 //
 // Generate bluetooth signal path from GattId
 //
 
 /**
  * Generate bluetooth signal path from a GattId.
  *
--- a/dom/bluetooth/common/webapi/BluetoothGatt.cpp
+++ b/dom/bluetooth/common/webapi/BluetoothGatt.cpp
@@ -108,18 +108,18 @@ BluetoothGatt::Connect(ErrorResult& aRv)
     mConnectionState == BluetoothConnectionState::Disconnected,
     promise,
     NS_ERROR_DOM_INVALID_STATE_ERR);
 
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   if (mAppUuid.IsEmpty()) {
-    GenerateUuid(mAppUuid);
-    BT_ENSURE_TRUE_REJECT(!mAppUuid.IsEmpty(),
+    nsresult rv = GenerateUuid(mAppUuid);
+    BT_ENSURE_TRUE_REJECT(NS_SUCCEEDED(rv) && !mAppUuid.IsEmpty(),
                           promise,
                           NS_ERROR_DOM_OPERATION_ERR);
     RegisterBluetoothSignalHandler(mAppUuid, this);
   }
 
   UpdateConnectionState(BluetoothConnectionState::Connecting);
   bs->ConnectGattClientInternal(
     mAppUuid, mDeviceAddr, new BluetoothVoidReplyRunnable(nullptr, promise));
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/common/webapi/BluetoothGattAttributeEvent.cpp
@@ -0,0 +1,181 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/bluetooth/BluetoothGattAttributeEvent.h"
+
+#include "js/GCAPI.h"
+#include "jsfriendapi.h"
+#include "mozilla/dom/BluetoothGattAttributeEventBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
+#include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
+#include "mozilla/dom/EventBinding.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/dom/PrimitiveConversions.h"
+#include "mozilla/dom/TypedArray.h"
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGattAttributeEvent)
+
+NS_IMPL_ADDREF_INHERITED(BluetoothGattAttributeEvent, Event)
+NS_IMPL_RELEASE_INHERITED(BluetoothGattAttributeEvent, Event)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGattAttributeEvent,
+                                                  Event)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCharacteristic)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDescriptor)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(BluetoothGattAttributeEvent,
+                                               Event)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mValue)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGattAttributeEvent,
+                                                Event)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCharacteristic)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDescriptor)
+  tmp->mValue = nullptr;
+  mozilla::DropJSObjects(this);
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGattAttributeEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+BluetoothGattAttributeEvent::BluetoothGattAttributeEvent(EventTarget* aOwner)
+  : Event(aOwner, nullptr, nullptr)
+{
+  mozilla::HoldJSObjects(this);
+}
+
+BluetoothGattAttributeEvent::~BluetoothGattAttributeEvent()
+{
+  mozilla::DropJSObjects(this);
+}
+
+JSObject*
+BluetoothGattAttributeEvent::WrapObjectInternal(
+  JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return BluetoothGattAttributeEventBinding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<BluetoothGattAttributeEvent>
+BluetoothGattAttributeEvent::Constructor(
+  EventTarget* aOwner,
+  const nsAString& aType,
+  const nsAString& aAddress,
+  int32_t aRequestId,
+  BluetoothGattCharacteristic* aCharacteristic,
+  BluetoothGattDescriptor* aDescriptor,
+  const nsTArray<uint8_t>* aValue,
+  bool aNeedResponse,
+  bool aBubbles,
+  bool aCancelable)
+{
+  nsRefPtr<BluetoothGattAttributeEvent> e =
+    new BluetoothGattAttributeEvent(aOwner);
+  bool trusted = e->Init(aOwner);
+
+  e->InitEvent(aType, aBubbles, aCancelable);
+  e->mAddress = aAddress;
+  e->mRequestId = aRequestId;
+  e->mCharacteristic = aCharacteristic;
+  e->mDescriptor = aDescriptor;
+  e->mNeedResponse = aNeedResponse;
+
+  if (aValue) {
+    e->mRawValue = *aValue;
+  }
+
+  e->SetTrusted(trusted);
+
+  return e.forget();
+}
+
+already_AddRefed<BluetoothGattAttributeEvent>
+BluetoothGattAttributeEvent::Constructor(
+  const GlobalObject& aGlobal,
+  const nsAString& aType,
+  const BluetoothGattAttributeEventInit& aEventInitDict,
+  ErrorResult& aRv)
+{
+  nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
+
+  nsRefPtr<BluetoothGattAttributeEvent> e =
+    Constructor(owner, aType, aEventInitDict.mAddress,
+                aEventInitDict.mRequestId, aEventInitDict.mCharacteristic,
+                aEventInitDict.mDescriptor, nullptr,
+                aEventInitDict.mNeedResponse, aEventInitDict.mBubbles,
+                aEventInitDict.mCancelable);
+
+  if (!aEventInitDict.mValue.IsNull()) {
+    const auto& value = aEventInitDict.mValue.Value();
+    value.ComputeLengthAndData();
+    e->mValue = ArrayBuffer::Create(aGlobal.Context(),
+                                    value.Length(),
+                                    value.Data());
+
+    if (!e->mValue) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+  }
+
+  return e.forget();
+}
+
+void
+BluetoothGattAttributeEvent::GetAddress(nsString& aRetVal) const
+{
+  aRetVal = mAddress;
+}
+
+int32_t
+BluetoothGattAttributeEvent::RequestId() const
+{
+  return mRequestId;
+}
+
+BluetoothGattCharacteristic*
+BluetoothGattAttributeEvent::GetCharacteristic() const
+{
+  return mCharacteristic;
+}
+
+BluetoothGattDescriptor*
+BluetoothGattAttributeEvent::GetDescriptor() const
+{
+  return mDescriptor;
+}
+
+void
+BluetoothGattAttributeEvent::GetValue(
+  JSContext* cx, JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv)
+{
+  if (!mValue) {
+    mValue = ArrayBuffer::Create(
+      cx, this, mRawValue.Length(), mRawValue.Elements());
+
+    if (!mValue) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    mRawValue.Clear();
+  }
+
+  JS::ExposeObjectToActiveJS(mValue);
+  aValue.set(mValue);
+
+  return;
+}
+
+bool
+BluetoothGattAttributeEvent::NeedResponse() const
+{
+  return mNeedResponse;
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/common/webapi/BluetoothGattAttributeEvent.h
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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_bluetooth_BluetoothGattAttributeEvent_h
+#define mozilla_dom_bluetooth_BluetoothGattAttributeEvent_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingUtils.h"
+#include "mozilla/dom/BluetoothGattAttributeEventBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/Event.h"
+
+struct JSContext;
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothGattAttributeEvent final : public Event
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(
+    BluetoothGattAttributeEvent, Event)
+protected:
+  virtual ~BluetoothGattAttributeEvent();
+  explicit BluetoothGattAttributeEvent(EventTarget* aOwner);
+
+  nsString mAddress;
+  int32_t mRequestId;
+  nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+  nsRefPtr<BluetoothGattDescriptor> mDescriptor;
+  JS::Heap<JSObject*> mValue;
+  bool mNeedResponse;
+
+public:
+  virtual
+  JSObject* WrapObjectInternal(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  static already_AddRefed<BluetoothGattAttributeEvent>
+  Constructor(EventTarget* aOwner,
+              const nsAString& aType,
+              const nsAString& aAddress,
+              int32_t aRequestId,
+              BluetoothGattCharacteristic* aCharacteristic,
+              BluetoothGattDescriptor* aDescriptor,
+              const nsTArray<uint8_t>* aValue,
+              bool aNeedResponse,
+              bool aBubbles,
+              bool aCancelable);
+
+  static already_AddRefed<BluetoothGattAttributeEvent>
+  Constructor(const GlobalObject& aGlobal,
+              const nsAString& aType,
+              const BluetoothGattAttributeEventInit& aEventInitDict,
+              ErrorResult& aRv);
+
+  void GetAddress(nsString& aRetVal) const;
+
+  int32_t RequestId() const;
+
+  BluetoothGattCharacteristic* GetCharacteristic() const;
+
+  BluetoothGattDescriptor* GetDescriptor() const;
+
+  void
+  GetValue(JSContext* cx,
+           JS::MutableHandle<JSObject*> aValue,
+           ErrorResult& aRv);
+
+  bool NeedResponse() const;
+
+private:
+  nsTArray<uint8_t> mRawValue;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif // mozilla_dom_BluetoothGattAttributeEvent_h
--- a/dom/bluetooth/common/webapi/BluetoothGattCharacteristic.cpp
+++ b/dom/bluetooth/common/webapi/BluetoothGattCharacteristic.cpp
@@ -5,17 +5,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "mozilla/dom/BluetoothGattCharacteristicBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
-#include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
 #include "mozilla/dom/bluetooth/BluetoothGattService.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/Promise.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
@@ -50,37 +49,89 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCA
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattCharacteristic)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattCharacteristic)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattCharacteristic)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+/*
+ * "A characteristic definition shall contain a characteristic declaration, a
+ *  Characteristic Value declaration and may contain characteristic descriptor
+ *  declarations."
+ *  ...
+ * "Each declaration above is contained in a separate Attribute. Two required
+ *  declarations are the characteristic declaration and the Characteristic
+ *  Value declaration."
+ * -- Bluetooth Core Specification version 4.2, Volume 3, Part G, Section 3.3
+ */
+const uint16_t BluetoothGattCharacteristic::sHandleCount = 2;
+
+// Constructor of BluetoothGattCharacteristic in ATT client role
 BluetoothGattCharacteristic::BluetoothGattCharacteristic(
   nsPIDOMWindow* aOwner,
   BluetoothGattService* aService,
   const BluetoothGattCharAttribute& aChar)
   : mOwner(aOwner)
   , mService(aService)
   , mCharId(aChar.mId)
+  , mPermissions(BLUETOOTH_EMPTY_GATT_ATTR_PERM)
   , mProperties(aChar.mProperties)
   , mWriteType(aChar.mWriteType)
+  , mAttRole(ATT_CLIENT_ROLE)
+  , mActive(true)
 {
   MOZ_ASSERT(aOwner);
   MOZ_ASSERT(mService);
 
   UuidToString(mCharId.mUuid, mUuidStr);
 
   // Generate bluetooth signal path of this characteristic to applications
   nsString path;
   GeneratePathFromGattId(mCharId, path);
   RegisterBluetoothSignalHandler(path, this);
 }
 
+
+// Constructor of BluetoothGattCharacteristic in ATT server role
+BluetoothGattCharacteristic::BluetoothGattCharacteristic(
+  nsPIDOMWindow* aOwner,
+  BluetoothGattService* aService,
+  const nsAString& aCharacteristicUuid,
+  const GattPermissions& aPermissions,
+  const GattCharacteristicProperties& aProperties,
+  const ArrayBuffer& aValue)
+  : mOwner(aOwner)
+  , mService(aService)
+  , mUuidStr(aCharacteristicUuid)
+  , mPermissions(BLUETOOTH_EMPTY_GATT_ATTR_PERM)
+  , mProperties(BLUETOOTH_EMPTY_GATT_CHAR_PROP)
+  , mWriteType(GATT_WRITE_TYPE_NORMAL)
+  , mAttRole(ATT_SERVER_ROLE)
+  , mActive(false)
+{
+  MOZ_ASSERT(aOwner);
+  MOZ_ASSERT(aService);
+
+  // UUID
+  memset(&mCharId, 0, sizeof(mCharId));
+  StringToUuid(aCharacteristicUuid, mCharId.mUuid);
+
+  // permissions
+  GattPermissionsToBits(aPermissions, mPermissions);
+
+  // properties
+  GattPropertiesToBits(aProperties, mProperties);
+
+  // value
+  aValue.ComputeLengthAndData();
+  mValue.AppendElements(aValue.Data(), aValue.Length());
+}
+
 BluetoothGattCharacteristic::~BluetoothGattCharacteristic()
 {
   nsString path;
   GeneratePathFromGattId(mCharId, path);
   UnregisterBluetoothSignalHandler(path, this);
 }
 
 already_AddRefed<Promise>
@@ -90,16 +141,20 @@ BluetoothGattCharacteristic::StartNotifi
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
+  BT_ENSURE_TRUE_REJECT(mAttRole == ATT_CLIENT_ROLE,
+                        promise,
+                        NS_ERROR_UNEXPECTED);
+
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
   BT_ENSURE_TRUE_REJECT(mService, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattClientStartNotificationsInternal(
     mService->GetAppUuid(), mService->GetServiceId(), mCharId,
     new BluetoothVoidReplyRunnable(nullptr, promise));
 
@@ -113,16 +168,20 @@ BluetoothGattCharacteristic::StopNotific
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
+  BT_ENSURE_TRUE_REJECT(mAttRole == ATT_CLIENT_ROLE,
+                        promise,
+                        NS_ERROR_UNEXPECTED);
+
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
   BT_ENSURE_TRUE_REJECT(mService, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattClientStopNotificationsInternal(
     mService->GetAppUuid(), mService->GetServiceId(), mCharId,
     new BluetoothVoidReplyRunnable(nullptr, promise));
 
@@ -147,30 +206,71 @@ BluetoothGattCharacteristic::HandleChara
   const BluetoothValue& aValue)
 {
   MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfuint8_t);
 
   mValue = aValue.get_ArrayOfuint8_t();
 }
 
 void
+BluetoothGattCharacteristic::AssignCharacteristicHandle(
+  const BluetoothAttributeHandle& aCharacteristicHandle)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+  MOZ_ASSERT(!mActive);
+  MOZ_ASSERT(!mCharacteristicHandle.mHandle);
+
+  mCharacteristicHandle = aCharacteristicHandle;
+  mActive = true;
+}
+
+void
+BluetoothGattCharacteristic::AssignDescriptorHandle(
+  const BluetoothUuid& aDescriptorUuid,
+  const BluetoothAttributeHandle& aDescriptorHandle)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+  MOZ_ASSERT(mActive);
+
+  size_t index = mDescriptors.IndexOf(aDescriptorUuid);
+  NS_ENSURE_TRUE_VOID(index != mDescriptors.NoIndex);
+  mDescriptors[index]->AssignDescriptorHandle(aDescriptorHandle);
+}
+
+void
 BluetoothGattCharacteristic::Notify(const BluetoothSignal& aData)
 {
   BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
   NS_ENSURE_TRUE_VOID(mSignalRegistered);
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("CharacteristicValueUpdated")) {
     HandleCharacteristicValueUpdated(v);
   } else {
     BT_WARNING("Not handling GATT Characteristic signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
+void
+BluetoothGattCharacteristic::GetUuid(BluetoothUuid& aUuid) const
+{
+  aUuid = mCharId.mUuid;
+}
+
+uint16_t
+BluetoothGattCharacteristic::GetHandleCount() const
+{
+  uint16_t count = sHandleCount;
+  for (size_t i = 0; i < mDescriptors.Length(); ++i) {
+    count += mDescriptors[i]->GetHandleCount();
+  }
+  return count;
+}
+
 JSObject*
 BluetoothGattCharacteristic::WrapObject(JSContext* aContext,
                                         JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothGattCharacteristicBinding::Wrap(aContext, this, aGivenProto);
 }
 
 void
@@ -178,29 +278,27 @@ BluetoothGattCharacteristic::GetValue(JS
                                       JS::MutableHandle<JSObject*> aValue) const
 {
   aValue.set(mValue.IsEmpty()
              ? nullptr
              : ArrayBuffer::Create(cx, mValue.Length(), mValue.Elements()));
 }
 
 void
-BluetoothGattCharacteristic::GetProperties(
-  mozilla::dom::GattCharacteristicProperties& aProperties) const
+BluetoothGattCharacteristic::GetPermissions(
+  GattPermissions& aPermissions) const
 {
-  aProperties.mBroadcast = mProperties & GATT_CHAR_PROP_BIT_BROADCAST;
-  aProperties.mRead = mProperties & GATT_CHAR_PROP_BIT_READ;
-  aProperties.mWriteNoResponse =
-    mProperties & GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE;
-  aProperties.mWrite = mProperties & GATT_CHAR_PROP_BIT_WRITE;
-  aProperties.mNotify = mProperties & GATT_CHAR_PROP_BIT_NOTIFY;
-  aProperties.mIndicate = mProperties & GATT_CHAR_PROP_BIT_INDICATE;
-  aProperties.mSignedWrite = mProperties & GATT_CHAR_PROP_BIT_SIGNED_WRITE;
-  aProperties.mExtendedProps =
-    mProperties & GATT_CHAR_PROP_BIT_EXTENDED_PROPERTIES;
+  GattPermissionsToDictionary(mPermissions, aPermissions);
+}
+
+void
+BluetoothGattCharacteristic::GetProperties(
+  GattCharacteristicProperties& aProperties) const
+{
+  GattPropertiesToDictionary(mProperties, aProperties);
 }
 
 class ReadValueTask final : public BluetoothReplyRunnable
 {
 public:
   ReadValueTask(BluetoothGattCharacteristic* aCharacteristic, Promise* aPromise)
     : BluetoothReplyRunnable(nullptr, aPromise)
     , mCharacteristic(aCharacteristic)
@@ -247,16 +345,21 @@ BluetoothGattCharacteristic::ReadValue(E
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
+  if (mAttRole == ATT_SERVER_ROLE) {
+    promise->MaybeResolve(mValue);
+    return promise.forget();
+  }
+
   BT_ENSURE_TRUE_REJECT(mProperties & GATT_CHAR_PROP_BIT_READ,
                         promise,
                         NS_ERROR_NOT_AVAILABLE);
 
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattClientReadCharacteristicValueInternal(
@@ -274,30 +377,74 @@ BluetoothGattCharacteristic::WriteValue(
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
+  aValue.ComputeLengthAndData();
+
+  if (mAttRole == ATT_SERVER_ROLE) {
+    mValue.Clear();
+    mValue.AppendElements(aValue.Data(), aValue.Length());
+
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return promise.forget();
+  }
+
   BT_ENSURE_TRUE_REJECT(mProperties &
                           (GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE |
                            GATT_CHAR_PROP_BIT_WRITE |
                            GATT_CHAR_PROP_BIT_SIGNED_WRITE),
                         promise,
                         NS_ERROR_NOT_AVAILABLE);
 
-  aValue.ComputeLengthAndData();
-
   nsTArray<uint8_t> value;
   value.AppendElements(aValue.Data(), aValue.Length());
 
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattClientWriteCharacteristicValueInternal(
     mService->GetAppUuid(), mService->GetServiceId(),
     mCharId, mWriteType, value,
     new BluetoothVoidReplyRunnable(nullptr, promise));
 
   return promise.forget();
 }
+
+already_AddRefed<Promise>
+BluetoothGattCharacteristic::AddDescriptor(const nsAString& aDescriptorUuid,
+                                           const GattPermissions& aPermissions,
+                                           const ArrayBuffer& aValue,
+                                           ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mAttRole == ATT_SERVER_ROLE,
+                        promise,
+                        NS_ERROR_UNEXPECTED);
+
+  /* The characteristic should not be actively acting with the Bluetooth
+   * backend. Otherwise, descriptors cannot be added into the characteristic. */
+  BT_ENSURE_TRUE_REJECT(!mActive, promise, NS_ERROR_UNEXPECTED);
+
+  nsRefPtr<BluetoothGattDescriptor> descriptor =
+    new BluetoothGattDescriptor(GetParentObject(),
+                                this,
+                                aDescriptorUuid,
+                                aPermissions,
+                                aValue);
+
+  mDescriptors.AppendElement(descriptor);
+  promise->MaybeResolve(descriptor);
+
+  return promise.forget();
+}
--- a/dom/bluetooth/common/webapi/BluetoothGattCharacteristic.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattCharacteristic.h
@@ -27,16 +27,17 @@ BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothGattService;
 class BluetoothSignal;
 class BluetoothValue;
 
 class BluetoothGattCharacteristic final : public nsISupports
                                         , public nsWrapperCache
                                         , public BluetoothSignalObserver
 {
+  friend class BluetoothGattServer;
   friend class BluetoothGattService;
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattCharacteristic)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
@@ -58,53 +59,88 @@ public:
 
   int InstanceId() const
   {
     return mCharId.mInstanceId;
   }
 
   void GetValue(JSContext* cx, JS::MutableHandle<JSObject*> aValue) const;
 
+  void GetPermissions(GattPermissions& aPermissions) const;
+
   void GetProperties(GattCharacteristicProperties& aProperties) const;
 
   /****************************************************************************
    * Methods (Web API Implementation)
    ***************************************************************************/
   already_AddRefed<Promise> ReadValue(ErrorResult& aRv);
   already_AddRefed<Promise> WriteValue(const ArrayBuffer& aValue,
                                        ErrorResult& aRv);
-
-  /****************************************************************************
-   * Methods (Web API Implementation)
-   ***************************************************************************/
   already_AddRefed<Promise> StartNotifications(ErrorResult& aRv);
   already_AddRefed<Promise> StopNotifications(ErrorResult& aRv);
+  already_AddRefed<Promise> AddDescriptor(const nsAString& aDescriptorUuid,
+                                          const GattPermissions& aPermissions,
+                                          const ArrayBuffer& aValue,
+                                          ErrorResult& aRv);
 
   /****************************************************************************
    * Others
    ***************************************************************************/
   const BluetoothGattId& GetCharacteristicId() const
   {
     return mCharId;
   }
 
   void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
 
+  const BluetoothAttributeHandle& GetCharacteristicHandle() const
+  {
+    return mCharacteristicHandle;
+  }
+
+  void GetUuid(BluetoothUuid& aUuid) const;
+
   nsPIDOMWindow* GetParentObject() const
   {
      return mOwner;
   }
 
+  BluetoothGattAttrPerm GetPermissions() const
+  {
+    return mPermissions;
+  }
+
+  BluetoothGattCharProp GetProperties() const
+  {
+    return mProperties;
+  }
+
+  uint16_t GetHandleCount() const;
+
+  const nsTArray<uint8_t>& GetValue() const
+  {
+    return mValue;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  // Constructor of BluetoothGattCharacteristic in ATT client role
   BluetoothGattCharacteristic(nsPIDOMWindow* aOwner,
                               BluetoothGattService* aService,
                               const BluetoothGattCharAttribute& aChar);
 
+  // Constructor of BluetoothGattCharacteristic in ATT server role
+  BluetoothGattCharacteristic(nsPIDOMWindow* aOwner,
+                              BluetoothGattService* aService,
+                              const nsAString& aCharacteristicUuid,
+                              const GattPermissions& aPermissions,
+                              const GattCharacteristicProperties& aProperties,
+                              const ArrayBuffer& aValue);
+
 private:
   ~BluetoothGattCharacteristic();
 
   /**
    * Add newly discovered GATT descriptors into mDescriptors and update the
    * cache value of mDescriptors.
    *
    * @param aDescriptorIds [in] An array of BluetoothGattId for each descriptor
@@ -114,16 +150,53 @@ private:
 
   /**
    * Update the value of this characteristic.
    *
    * @param aValue [in] BluetoothValue which contains an uint8_t array.
    */
   void HandleCharacteristicValueUpdated(const BluetoothValue& aValue);
 
+  /**
+   * Assign the handle value for this GATT characteristic. This function would
+   * be called only after a valid handle value is retrieved from the Bluetooth
+   * backend.
+   *
+   * @param aCharacteristicHandle [in] The handle value of this GATT
+   *                                   characteristic.
+   */
+  void AssignCharacteristicHandle(
+    const BluetoothAttributeHandle& aCharacteristicHandle);
+
+  /**
+   * Assign the handle value for one of the descriptor within this GATT
+   * characteristic. This function would be called only after a valid handle
+   * value is retrieved from the Bluetooth backend.
+   *
+   * @param aDescriptorUuid [in] BluetoothUuid of the target GATT descriptor.
+   * @param aDescriptorHandle [in] The handle value of the target GATT
+   *                               descriptor.
+   */
+  void AssignDescriptorHandle(
+    const BluetoothUuid& aDescriptorUuid,
+    const BluetoothAttributeHandle& aDescriptorHandle);
+
+  /**
+   * Examine whether this GATT characteristic can react with the Bluetooth
+   * backend.
+   *
+   * @return true if this characteristic can react with the Bluetooth backend;
+   *         false if this characteristic cannot react with the Bluetooth
+   *         backend.
+   */
+  bool IsActivated() const
+  {
+    return mActive;
+  }
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * Service that this characteristic belongs to.
    */
@@ -147,24 +220,60 @@ private:
   nsString mUuidStr;
 
   /**
    * Value of this GATT characteristic.
    */
   nsTArray<uint8_t> mValue;
 
   /**
+   * Permissions of this GATT characteristic.
+   */
+  BluetoothGattAttrPerm mPermissions;
+
+  /**
    * Properties of this GATT characteristic.
    */
   BluetoothGattCharProp mProperties;
 
   /**
    * Write type of this GATT characteristic.
    */
   BluetoothGattWriteType mWriteType;
+
+  /**
+   * ATT role of this GATT characteristic.
+   */
+  const BluetoothAttRole mAttRole;
+
+  /**
+   * Activeness of this GATT characteristic.
+   *
+   * True means this service does react with the Bluetooth backend. False means
+   * this characteristic doesn't react with the Bluetooth backend. The value
+   * should be true if |mAttRole| equals |ATT_CLIENT_ROLE| because the
+   * characteristic instance could be created only when the Bluetooth backend
+   * has found one GATT characteristic. The value would be false at the
+   * beginning if |mAttRole| equals |ATT_SERVER_ROLE|. Then the value would
+   * become true later if the corresponding GATT service has been added into
+   * Bluetooth backend.
+   */
+  bool mActive;
+
+  /**
+   * Handle of this GATT characteristic.
+   *
+   * The value is only valid if |mAttRole| equals |ATT_SERVER_ROLE|.
+   */
+  BluetoothAttributeHandle mCharacteristicHandle;
+
+  /**
+   * Total count of handles of this GATT characteristic itself.
+   */
+  static const uint16_t sHandleCount;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 /**
  * Explicit Specialization of Function Templates
  *
  * Allows customizing the template code for a given set of template arguments.
@@ -180,9 +289,54 @@ public:
   bool Equals(
     const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>& aChar,
     const mozilla::dom::bluetooth::BluetoothGattId& aCharId) const
   {
     return aChar->GetCharacteristicId() == aCharId;
   }
 };
 
+/**
+ * Explicit Specialization of Function Templates
+ *
+ * Allows customizing the template code for a given set of template arguments.
+ * With this function template, nsTArray can handle comparison between
+ * 'nsRefPtr<BluetoothGattCharacteristic>' and 'BluetoothUuid' properly,
+ * including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>,
+  mozilla::dom::bluetooth::BluetoothUuid> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>& aChar,
+    const mozilla::dom::bluetooth::BluetoothUuid& aUuid) const
+  {
+    mozilla::dom::bluetooth::BluetoothUuid uuid;
+    aChar->GetUuid(uuid);
+    return uuid == aUuid;
+  }
+};
+
+/**
+ * Explicit Specialization of Function Templates
+ *
+ * Allows customizing the template code for a given set of template arguments.
+ * With this function template, nsTArray can handle comparison between
+ * 'nsRefPtr<BluetoothGattCharacteristic>' and 'BluetoothAttributeHandle'
+ * properly, including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>,
+  mozilla::dom::bluetooth::BluetoothAttributeHandle> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>& aChar,
+    const mozilla::dom::bluetooth::BluetoothAttributeHandle& aCharacteristicHandle)
+    const
+  {
+    return aChar->GetCharacteristicHandle() == aCharacteristicHandle;
+  }
+};
+
 #endif // mozilla_dom_bluetooth_BluetoothGattCharacteristic_h
--- a/dom/bluetooth/common/webapi/BluetoothGattDescriptor.cpp
+++ b/dom/bluetooth/common/webapi/BluetoothGattDescriptor.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=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 "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
+#include "mozilla/dom/BluetoothGattCharacteristicBinding.h"
 #include "mozilla/dom/BluetoothGattDescriptorBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
 #include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
 #include "mozilla/dom/bluetooth/BluetoothGattService.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 
 using namespace mozilla;
@@ -47,35 +48,70 @@ NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCA
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattDescriptor)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattDescriptor)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattDescriptor)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+const uint16_t BluetoothGattDescriptor::sHandleCount = 1;
+
+// Constructor of BluetoothGattDescriptor in ATT client role
 BluetoothGattDescriptor::BluetoothGattDescriptor(
   nsPIDOMWindow* aOwner,
   BluetoothGattCharacteristic* aCharacteristic,
   const BluetoothGattId& aDescriptorId)
   : mOwner(aOwner)
   , mCharacteristic(aCharacteristic)
   , mDescriptorId(aDescriptorId)
+  , mPermissions(BLUETOOTH_EMPTY_GATT_ATTR_PERM)
+  , mAttRole(ATT_CLIENT_ROLE)
+  , mActive(true)
 {
   MOZ_ASSERT(aOwner);
   MOZ_ASSERT(aCharacteristic);
 
   UuidToString(mDescriptorId.mUuid, mUuidStr);
 
   // Generate bluetooth signal path of this descriptor to applications
   nsString path;
   GeneratePathFromGattId(mDescriptorId, path);
   RegisterBluetoothSignalHandler(path, this);
 }
 
+// Constructor of BluetoothGattDescriptor in ATT server role
+BluetoothGattDescriptor::BluetoothGattDescriptor(
+  nsPIDOMWindow* aOwner,
+  BluetoothGattCharacteristic* aCharacteristic,
+  const nsAString& aDescriptorUuid,
+  const GattPermissions& aPermissions,
+  const ArrayBuffer& aValue)
+  : mOwner(aOwner)
+  , mCharacteristic(aCharacteristic)
+  , mUuidStr(aDescriptorUuid)
+  , mPermissions(BLUETOOTH_EMPTY_GATT_ATTR_PERM)
+  , mAttRole(ATT_SERVER_ROLE)
+  , mActive(false)
+{
+  MOZ_ASSERT(aOwner);
+  MOZ_ASSERT(aCharacteristic);
+
+  // UUID
+  memset(&mDescriptorId, 0, sizeof(mDescriptorId));
+  StringToUuid(aDescriptorUuid, mDescriptorId.mUuid);
+
+  // permissions
+  GattPermissionsToBits(aPermissions, mPermissions);
+
+  // value
+  aValue.ComputeLengthAndData();
+  mValue.AppendElements(aValue.Data(), aValue.Length());
+}
+
 BluetoothGattDescriptor::~BluetoothGattDescriptor()
 {
   nsString path;
   GeneratePathFromGattId(mDescriptorId, path);
   UnregisterBluetoothSignalHandler(path, this);
 }
 
 void
@@ -83,46 +119,70 @@ BluetoothGattDescriptor::HandleDescripto
   const BluetoothValue& aValue)
 {
   MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfuint8_t);
 
   mValue = aValue.get_ArrayOfuint8_t();
 }
 
 void
+BluetoothGattDescriptor::AssignDescriptorHandle(
+  const BluetoothAttributeHandle& aDescriptorHandle)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+  MOZ_ASSERT(!mActive);
+  MOZ_ASSERT(!mDescriptorHandle.mHandle);
+
+  mDescriptorHandle = aDescriptorHandle;
+  mActive = true;
+}
+
+void
 BluetoothGattDescriptor::Notify(const BluetoothSignal& aData)
 {
   BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
   NS_ENSURE_TRUE_VOID(mSignalRegistered);
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("DescriptorValueUpdated")) {
     HandleDescriptorValueUpdated(v);
   } else {
     BT_WARNING("Not handling GATT Descriptor signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
+void
+BluetoothGattDescriptor::GetUuid(BluetoothUuid& aUuid) const
+{
+  aUuid = mDescriptorId.mUuid;
+}
+
 JSObject*
 BluetoothGattDescriptor::WrapObject(JSContext* aContext,
                                     JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothGattDescriptorBinding::Wrap(aContext, this, aGivenProto);
 }
 
 void
 BluetoothGattDescriptor::GetValue(JSContext* cx,
                                   JS::MutableHandle<JSObject*> aValue) const
 {
   aValue.set(mValue.IsEmpty()
              ? nullptr
              : ArrayBuffer::Create(cx, mValue.Length(), mValue.Elements()));
 }
 
+void
+BluetoothGattDescriptor::GetPermissions(GattPermissions& aPermissions) const
+{
+  GattPermissionsToDictionary(mPermissions, aPermissions);
+}
+
 class ReadValueTask final : public BluetoothReplyRunnable
 {
 public:
   ReadValueTask(BluetoothGattDescriptor* aDescriptor, Promise* aPromise)
     : BluetoothReplyRunnable(nullptr, aPromise)
     , mDescriptor(aDescriptor)
   {
     MOZ_ASSERT(aDescriptor);
@@ -167,16 +227,21 @@ BluetoothGattDescriptor::ReadValue(Error
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
+  if (mAttRole == ATT_SERVER_ROLE) {
+    promise->MaybeResolve(mValue);
+    return promise.forget();
+  }
+
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattClientReadDescriptorValueInternal(
     mCharacteristic->Service()->GetAppUuid(),
     mCharacteristic->Service()->GetServiceId(),
     mCharacteristic->GetCharacteristicId(),
     mDescriptorId,
@@ -195,16 +260,24 @@ BluetoothGattDescriptor::WriteValue(
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
   aValue.ComputeLengthAndData();
 
+  if (mAttRole == ATT_SERVER_ROLE) {
+    mValue.Clear();
+    mValue.AppendElements(aValue.Data(), aValue.Length());
+
+    promise->MaybeResolve(JS::UndefinedHandleValue);
+    return promise.forget();
+  }
+
   nsTArray<uint8_t> value;
   value.AppendElements(aValue.Data(), aValue.Length());
 
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattClientWriteDescriptorValueInternal(
     mCharacteristic->Service()->GetAppUuid(),
--- a/dom/bluetooth/common/webapi/BluetoothGattDescriptor.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattDescriptor.h
@@ -11,25 +11,34 @@
 #include "mozilla/dom/BluetoothGattDescriptorBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/TypedArray.h"
 #include "nsCOMPtr.h"
 #include "nsWrapperCache.h"
 #include "nsPIDOMWindow.h"
 
+namespace mozilla {
+namespace dom {
+struct GattPermissions;
+}
+}
+
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothGattCharacteristic;
 class BluetoothSignal;
+class BluetoothValue;
 
 class BluetoothGattDescriptor final : public nsISupports
                                     , public nsWrapperCache
                                     , public BluetoothSignalObserver
 {
+  friend class BluetoothGattServer;
+  friend class BluetoothGattCharacteristic;
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattDescriptor)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
   BluetoothGattCharacteristic* Characteristic() const
@@ -39,50 +48,109 @@ public:
 
   void GetUuid(nsString& aUuidStr) const
   {
     aUuidStr = mUuidStr;
   }
 
   void GetValue(JSContext* cx, JS::MutableHandle<JSObject*> aValue) const;
 
+  void GetPermissions(GattPermissions& aPermissions) const;
+
   /****************************************************************************
    * Methods (Web API Implementation)
    ***************************************************************************/
   already_AddRefed<Promise> ReadValue(ErrorResult& aRv);
   already_AddRefed<Promise> WriteValue(
     const RootedTypedArray<ArrayBuffer>& aValue, ErrorResult& aRv);
 
   /****************************************************************************
    * Others
    ***************************************************************************/
   void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
 
+  const BluetoothAttributeHandle& GetDescriptorHandle() const
+  {
+    return mDescriptorHandle;
+  }
+
   nsPIDOMWindow* GetParentObject() const
   {
      return mOwner;
   }
 
+  void GetUuid(BluetoothUuid& aUuid) const;
+
+  BluetoothGattAttrPerm GetPermissions() const
+  {
+    return mPermissions;
+  }
+
+  uint16_t GetHandleCount() const
+  {
+    return sHandleCount;
+  }
+
+  const nsTArray<uint8_t>& GetValue() const
+  {
+    return mValue;
+  }
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  // Constructor of BluetoothGattDescriptor in ATT client role
   BluetoothGattDescriptor(nsPIDOMWindow* aOwner,
                           BluetoothGattCharacteristic* aCharacteristic,
                           const BluetoothGattId& aDescriptorId);
 
+  // Constructor of BluetoothGattDescriptor in ATT server role
+  BluetoothGattDescriptor(nsPIDOMWindow* aOwner,
+                          BluetoothGattCharacteristic* aCharacteristic,
+                          const nsAString& aDescriptorUuid,
+                          const GattPermissions& aPermissions,
+                          const ArrayBuffer& aValue);
+
 private:
   ~BluetoothGattDescriptor();
 
   /**
    * Update the value of this descriptor.
    *
    * @param aValue [in] BluetoothValue which contains an uint8_t array.
    */
   void HandleDescriptorValueUpdated(const BluetoothValue& aValue);
 
+  /**
+   * Assign AppUuid of this GATT descriptor.
+   *
+   * @param aAppUuid The value of AppUuid.
+   */
+  void AssignAppUuid(const nsAString& aAppUuid);
+
+  /**
+   * Assign the handle value for this GATT descriptor. This function would be
+   * called only after a valid handle value is retrieved from the Bluetooth
+   * backend.
+   *
+   * @param aDescriptorHandle [in] The handle value of this GATT descriptor.
+   */
+  void AssignDescriptorHandle(const BluetoothAttributeHandle& aDescriptorHandle);
+
+  /**
+   * Examine whether this GATT descriptor can react with the Bluetooth backend.
+   *
+   * @return true if this descriptor can react with the Bluetooth backend;
+   *         false if this descriptor cannot react with the Bluetooth backend.
+   */
+  bool IsActivated() const
+  {
+    return mActive;
+  }
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * Characteristic that this descriptor belongs to.
    */
@@ -99,13 +167,92 @@ private:
    * UUID string of this GATT descriptor.
    */
   nsString mUuidStr;
 
   /**
    * Value of this GATT descriptor.
    */
   nsTArray<uint8_t> mValue;
+
+  /**
+   * Permissions of this GATT descriptor.
+   */
+  BluetoothGattAttrPerm mPermissions;
+
+  /**
+   * ATT role of this GATT descriptor.
+   */
+  const BluetoothAttRole mAttRole;
+
+  /**
+   * Activeness of this GATT descriptor.
+   *
+   * True means this service does react with the Bluetooth backend. False means
+   * this descriptor doesn't react with the Bluetooth backend. The value should
+   * be true if |mAttRole| equals |ATT_CLIENT_ROLE| because the descriptor
+   * instance could be created only when the Bluetooth backend has found one
+   * GATT descriptor. The value would be false at the beginning if |mAttRole|
+   * equals |ATT_SERVER_ROLE|. Then the value would become true later if the
+   * corresponding GATT service has been added into Bluetooth backend.
+   */
+  bool mActive;
+
+  /**
+   * Handle of this GATT descriptor.
+   *
+   * The value is only valid if |mAttRole| equals |ATT_SERVER_ROLE|.
+   */
+  BluetoothAttributeHandle mDescriptorHandle;
+
+  /**
+   * Total count of handles of this GATT descriptor itself.
+   */
+  static const uint16_t sHandleCount;
 };
 
 END_BLUETOOTH_NAMESPACE
 
+/**
+ * Explicit Specialization of Function Templates
+ *
+ * Allows customizing the template code for a given set of template arguments.
+ * With this function template, nsTArray can handle comparison between
+ * 'nsRefPtr<BluetoothGattDescriptor>' and 'BluetoothUuid' properly,
+ * including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattDescriptor>,
+  mozilla::dom::bluetooth::BluetoothUuid> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattDescriptor>& aDesc,
+    const mozilla::dom::bluetooth::BluetoothUuid& aUuid) const
+  {
+    mozilla::dom::bluetooth::BluetoothUuid uuid;
+    aDesc->GetUuid(uuid);
+    return uuid == aUuid;
+  }
+};
+
+/**
+ * Explicit Specialization of Function Templates
+ *
+ * Allows customizing the template code for a given set of template arguments.
+ * With this function template, nsTArray can handle comparison between
+ * 'nsRefPtr<BluetoothGattDescriptor>' and 'BluetoothAttributeHandle'
+ * properly, including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattDescriptor>,
+  mozilla::dom::bluetooth::BluetoothAttributeHandle> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattDescriptor>& aDesc,
+    const mozilla::dom::bluetooth::BluetoothAttributeHandle& aHandle) const
+  {
+    return aDesc->GetDescriptorHandle() == aHandle;
+  }
+};
+
 #endif // mozilla_dom_bluetooth_BluetoothGattDescriptor_h
--- a/dom/bluetooth/common/webapi/BluetoothGattServer.cpp
+++ b/dom/bluetooth/common/webapi/BluetoothGattServer.cpp
@@ -5,91 +5,271 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "BluetoothGattServer.h"
 
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "mozilla/dom/BluetoothStatusChangedEvent.h"
+#include "mozilla/dom/bluetooth/BluetoothGattAttributeEvent.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
+#include "mozilla/dom/bluetooth/BluetoothGattService.h"
 #include "mozilla/dom/Promise.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGattServer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGattServer,
                                                 DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServices)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingService)
 
   /**
    * Unregister the bluetooth signal handler after unlinked.
    *
    * This is needed to avoid ending up with exposing a deleted object to JS or
    * accessing deleted objects while receiving signals from parent process
    * after unlinked. Please see Bug 1138267 for detail informations.
    */
   UnregisterBluetoothSignalHandler(tmp->mAppUuid, tmp);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGattServer,
                                                   DOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServices)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingService)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGattServer)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothGattServer, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothGattServer, DOMEventTargetHelper)
 
 BluetoothGattServer::BluetoothGattServer(nsPIDOMWindow* aOwner)
   : mOwner(aOwner)
   , mServerIf(0)
   , mValid(true)
-{ }
+{
+  if (NS_SUCCEEDED(GenerateUuid(mAppUuid)) && !mAppUuid.IsEmpty()) {
+    RegisterBluetoothSignalHandler(mAppUuid, this);
+  }
+  if (!mSignalRegistered) {
+    Invalidate();
+  }
+}
 
 BluetoothGattServer::~BluetoothGattServer()
 {
   Invalidate();
 }
 
+void BluetoothGattServer::HandleServerRegistered(const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::Tuint32_t);
+  mServerIf = aValue.get_uint32_t();
+}
+
+void BluetoothGattServer::HandleServerUnregistered(const BluetoothValue& aValue)
+{
+  mServerIf = 0;
+}
+
+void BluetoothGattServer::HandleConnectionStateChanged(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  MOZ_ASSERT(arr.Length() == 2 &&
+             arr[0].value().type() == BluetoothValue::Tbool &&
+             arr[1].value().type() == BluetoothValue::TnsString);
+
+  BluetoothStatusChangedEventInit init;
+  init.mStatus = arr[0].value().get_bool();
+  init.mAddress = arr[1].value().get_nsString();
+
+  nsRefPtr<BluetoothStatusChangedEvent> event =
+    BluetoothStatusChangedEvent::Constructor(
+      this, NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID), init);
+
+  DispatchTrustedEvent(event);
+}
+
+void
+BluetoothGattServer::HandleServiceHandleUpdated(const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  MOZ_ASSERT(arr.Length() == 2 &&
+    arr[0].value().type() == BluetoothValue::TBluetoothGattServiceId &&
+    arr[1].value().type() == BluetoothValue::TBluetoothAttributeHandle);
+
+  BluetoothGattServiceId serviceId =
+    arr[0].value().get_BluetoothGattServiceId();
+  BluetoothAttributeHandle serviceHandle =
+    arr[1].value().get_BluetoothAttributeHandle();
+
+  NS_ENSURE_TRUE_VOID(mPendingService);
+  NS_ENSURE_TRUE_VOID(mPendingService->GetServiceId() == serviceId);
+  mPendingService->AssignServiceHandle(serviceHandle);
+}
+
+void
+BluetoothGattServer::HandleCharacteristicHandleUpdated(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  MOZ_ASSERT(arr.Length() == 3 &&
+    arr[0].value().type() == BluetoothValue::TBluetoothUuid &&
+    arr[1].value().type() == BluetoothValue::TBluetoothAttributeHandle &&
+    arr[2].value().type() == BluetoothValue::TBluetoothAttributeHandle);
+
+  BluetoothUuid characteristicUuid =
+    arr[0].value().get_BluetoothUuid();
+  BluetoothAttributeHandle serviceHandle =
+    arr[1].value().get_BluetoothAttributeHandle();
+  BluetoothAttributeHandle characteristicHandle =
+    arr[2].value().get_BluetoothAttributeHandle();
+
+  NS_ENSURE_TRUE_VOID(mPendingService);
+  NS_ENSURE_TRUE_VOID(mPendingService->GetServiceHandle() == serviceHandle);
+  mPendingService->AssignCharacteristicHandle(characteristicUuid,
+                                              characteristicHandle);
+}
+
+void
+BluetoothGattServer::HandleDescriptorHandleUpdated(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  MOZ_ASSERT(arr.Length() == 4 &&
+    arr[0].value().type() == BluetoothValue::TBluetoothUuid &&
+    arr[1].value().type() == BluetoothValue::TBluetoothAttributeHandle &&
+    arr[2].value().type() == BluetoothValue::TBluetoothAttributeHandle &&
+    arr[3].value().type() == BluetoothValue::TBluetoothAttributeHandle);
+
+  BluetoothUuid descriptorUuid =
+    arr[0].value().get_BluetoothUuid();
+  BluetoothAttributeHandle serviceHandle =
+    arr[1].value().get_BluetoothAttributeHandle();
+  BluetoothAttributeHandle characteristicHandle =
+    arr[2].value().get_BluetoothAttributeHandle();
+  BluetoothAttributeHandle descriptorHandle =
+    arr[3].value().get_BluetoothAttributeHandle();
+
+  NS_ENSURE_TRUE_VOID(mPendingService);
+  NS_ENSURE_TRUE_VOID(mPendingService->GetServiceHandle() == serviceHandle);
+  mPendingService->AssignDescriptorHandle(descriptorUuid,
+                                          characteristicHandle,
+                                          descriptorHandle);
+}
+
+void
+BluetoothGattServer::HandleReadWriteRequest(const BluetoothValue& aValue,
+                                            const nsAString& aEventName)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aValue.get_ArrayOfBluetoothNamedValue();
+
+  MOZ_ASSERT(arr.Length() == 5 &&
+    arr[0].value().type() == BluetoothValue::Tint32_t &&
+    arr[1].value().type() == BluetoothValue::TBluetoothAttributeHandle &&
+    arr[2].value().type() == BluetoothValue::TnsString &&
+    arr[3].value().type() == BluetoothValue::Tbool &&
+    arr[4].value().type() == BluetoothValue::TArrayOfuint8_t);
+
+  int32_t requestId = arr[0].value().get_int32_t();
+  BluetoothAttributeHandle handle =
+    arr[1].value().get_BluetoothAttributeHandle();
+  nsString address = arr[2].value().get_nsString();
+  bool needResponse = arr[3].value().get_bool();
+  nsTArray<uint8_t> value;
+  value = arr[4].value().get_ArrayOfuint8_t();
+
+  // Find the target characteristic or descriptor from the given handle
+  nsRefPtr<BluetoothGattCharacteristic> characteristic = nullptr;
+  nsRefPtr<BluetoothGattDescriptor> descriptor = nullptr;
+  for (uint32_t i = 0; i < mServices.Length(); i++) {
+    for (uint32_t j = 0; j < mServices[i]->mCharacteristics.Length(); j++) {
+      nsRefPtr<BluetoothGattCharacteristic> currentChar =
+        mServices[i]->mCharacteristics[j];
+
+      if (handle == currentChar->GetCharacteristicHandle()) {
+        characteristic = currentChar;
+        break;
+      }
+
+      size_t index = currentChar->mDescriptors.IndexOf(handle);
+      if (index != currentChar->mDescriptors.NoIndex) {
+        descriptor = currentChar->mDescriptors[index];
+        break;
+      }
+    }
+  }
+
+  if (!(characteristic || descriptor)) {
+    BT_WARNING("Wrong handle: no matched characteristic or descriptor");
+    return;
+  }
+
+  // Save the request information for sending the response later
+  RequestData data(handle,
+                   characteristic,
+                   descriptor);
+  mRequestMap.Put(requestId, &data);
+
+  nsRefPtr<BluetoothGattAttributeEvent> event =
+    BluetoothGattAttributeEvent::Constructor(
+      this, aEventName, address, requestId, characteristic, descriptor,
+      &value, needResponse, false /* Bubble */, false /* Cancelable*/);
+
+  DispatchTrustedEvent(event);
+}
+
 void
 BluetoothGattServer::Notify(const BluetoothSignal& aData)
 {
   BT_LOGD("[GattServer] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
   NS_ENSURE_TRUE_VOID(mSignalRegistered);
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("ServerRegistered")) {
-    MOZ_ASSERT(v.type() == BluetoothValue::Tuint32_t);
-    mServerIf = v.get_uint32_t();
+    HandleServerRegistered(v);
   } else if (aData.name().EqualsLiteral("ServerUnregistered")) {
-    mServerIf = 0;
+    HandleServerUnregistered(v);
   } else if (aData.name().EqualsLiteral(GATT_CONNECTION_STATE_CHANGED_ID)) {
-    MOZ_ASSERT(v.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
-    const InfallibleTArray<BluetoothNamedValue>& arr =
-      v.get_ArrayOfBluetoothNamedValue();
-
-    MOZ_ASSERT(arr.Length() == 2 &&
-               arr[0].value().type() == BluetoothValue::Tbool &&
-               arr[1].value().type() == BluetoothValue::TnsString);
-
-    BluetoothStatusChangedEventInit init;
-    init.mStatus = arr[0].value().get_bool();
-    init.mAddress = arr[1].value().get_nsString();
-
-    nsRefPtr<BluetoothStatusChangedEvent> event =
-      BluetoothStatusChangedEvent::Constructor(
-        this, NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID), init);
-
-    DispatchTrustedEvent(event);
+    HandleConnectionStateChanged(v);
+  } else if (aData.name().EqualsLiteral("ServiceHandleUpdated")) {
+    HandleServiceHandleUpdated(v);
+  } else if (aData.name().EqualsLiteral("CharacteristicHandleUpdated")) {
+    HandleCharacteristicHandleUpdated(v);
+  } else if (aData.name().EqualsLiteral("DescriptorHandleUpdated")) {
+    HandleDescriptorHandleUpdated(v);
+  } else if (aData.name().EqualsLiteral("ReadRequested")) {
+    HandleReadWriteRequest(v, NS_LITERAL_STRING(ATTRIBUTE_READ_REQUEST));
+  } else if (aData.name().EqualsLiteral("WriteRequested")) {
+    HandleReadWriteRequest(v, NS_LITERAL_STRING(ATTRIBUTE_WRITE_REQUEST));
   } else {
     BT_WARNING("Not handling GATT signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
 JSObject*
 BluetoothGattServer::WrapObject(JSContext* aContext,
@@ -104,25 +284,32 @@ BluetoothGattServer::DisconnectFromOwner
   DOMEventTargetHelper::DisconnectFromOwner();
   Invalidate();
 }
 
 void
 BluetoothGattServer::Invalidate()
 {
   mValid = false;
+  mPendingService = nullptr;
+  mServices.Clear();
+  mRequestMap.Clear();
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   if (mServerIf > 0) {
-    bs->UnregisterGattServerInternal(mServerIf, nullptr);
+    bs->UnregisterGattServerInternal(mServerIf,
+                                     new BluetoothVoidReplyRunnable(nullptr,
+                                                                    nullptr));
   }
 
-  UnregisterBluetoothSignalHandler(mAppUuid, this);
+  if (!mAppUuid.IsEmpty() && mSignalRegistered) {
+    UnregisterBluetoothSignalHandler(mAppUuid, this);
+  }
 }
 
 already_AddRefed<Promise>
 BluetoothGattServer::Connect(const nsAString& aAddress, ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
   if (!global) {
     aRv.Throw(NS_ERROR_FAILURE);
@@ -131,24 +318,16 @@ BluetoothGattServer::Connect(const nsASt
 
   nsRefPtr<Promise> promise = Promise::Create(global, aRv);
   NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
 
   BT_ENSURE_TRUE_REJECT(mValid, promise, NS_ERROR_NOT_AVAILABLE);
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
-  if (mAppUuid.IsEmpty()) {
-    GenerateUuid(mAppUuid);
-    BT_ENSURE_TRUE_REJECT(!mAppUuid.IsEmpty(),
-                          promise,
-                          NS_ERROR_DOM_OPERATION_ERR);
-    RegisterBluetoothSignalHandler(mAppUuid, this);
-  }
-
   bs->GattServerConnectPeripheralInternal(
     mAppUuid, aAddress, new BluetoothVoidReplyRunnable(nullptr, promise));
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 BluetoothGattServer::Disconnect(const nsAString& aAddress, ErrorResult& aRv)
@@ -166,8 +345,528 @@ BluetoothGattServer::Disconnect(const ns
   BluetoothService* bs = BluetoothService::Get();
   BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
 
   bs->GattServerDisconnectPeripheralInternal(
     mAppUuid, aAddress, new BluetoothVoidReplyRunnable(nullptr, promise));
 
   return promise.forget();
 }
+
+class BluetoothGattServer::AddIncludedServiceTask final
+  : public BluetoothReplyTaskQueue::SubTask
+{
+public:
+  AddIncludedServiceTask(BluetoothReplyTaskQueue* aRootQueue,
+                         BluetoothGattServer* aServer,
+                         BluetoothGattService* aService,
+                         BluetoothGattService* aIncludedService)
+    : BluetoothReplyTaskQueue::SubTask(aRootQueue, nullptr)
+    , mServer(aServer)
+    , mService(aService)
+    , mIncludedService(aIncludedService)
+  { }
+
+  bool Execute() override
+  {
+    BluetoothService* bs = BluetoothService::Get();
+    if (NS_WARN_IF(!bs)) {
+      return false;
+    }
+
+    bs->GattServerAddIncludedServiceInternal(
+      mServer->mAppUuid,
+      mService->GetServiceHandle(),
+      mIncludedService->GetServiceHandle(),
+      GetReply());
+
+    return true;
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+  nsRefPtr<BluetoothGattService> mIncludedService;
+};
+
+class BluetoothGattServer::AddCharacteristicTask final
+  : public BluetoothReplyTaskQueue::SubTask
+{
+public:
+  AddCharacteristicTask(BluetoothReplyTaskQueue* aRootQueue,
+                        BluetoothGattServer* aServer,
+                        BluetoothGattService* aService,
+                        BluetoothGattCharacteristic* aCharacteristic)
+    : BluetoothReplyTaskQueue::SubTask(aRootQueue, nullptr)
+    , mServer(aServer)
+    , mService(aService)
+    , mCharacteristic(aCharacteristic)
+  { }
+
+  bool Execute() override
+  {
+    BluetoothService* bs = BluetoothService::Get();
+    if (NS_WARN_IF(!bs)) {
+      return false;
+    }
+
+    BluetoothUuid uuid;
+    mCharacteristic->GetUuid(uuid);
+    bs->GattServerAddCharacteristicInternal(
+      mServer->mAppUuid,
+      mService->GetServiceHandle(),
+      uuid,
+      mCharacteristic->GetPermissions(),
+      mCharacteristic->GetProperties(),
+      GetReply());
+
+    return true;
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+  nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+};
+
+class BluetoothGattServer::AddDescriptorTask final
+  : public BluetoothReplyTaskQueue::SubTask
+{
+public:
+  AddDescriptorTask(BluetoothReplyTaskQueue* aRootQueue,
+                    BluetoothGattServer* aServer,
+                    BluetoothGattService* aService,
+                    BluetoothGattCharacteristic* aCharacteristic,
+                    BluetoothGattDescriptor* aDescriptor)
+    : BluetoothReplyTaskQueue::SubTask(aRootQueue, nullptr)
+    , mServer(aServer)
+    , mService(aService)
+    , mCharacteristic(aCharacteristic)
+    , mDescriptor(aDescriptor)
+  { }
+
+  bool Execute() override
+  {
+    BluetoothService* bs = BluetoothService::Get();
+    if (NS_WARN_IF(!bs)) {
+      return false;
+    }
+
+    BluetoothUuid uuid;
+    mDescriptor->GetUuid(uuid);
+    bs->GattServerAddDescriptorInternal(
+      mServer->mAppUuid,
+      mService->GetServiceHandle(),
+      mCharacteristic->GetCharacteristicHandle(),
+      uuid,
+      mDescriptor->GetPermissions(),
+      GetReply());
+
+    return true;
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+  nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+  nsRefPtr<BluetoothGattDescriptor> mDescriptor;
+};
+
+class BluetoothGattServer::StartServiceTask final
+  : public BluetoothReplyTaskQueue::SubTask
+{
+public:
+  StartServiceTask(BluetoothReplyTaskQueue* aRootQueue,
+                   BluetoothGattServer* aServer,
+                   BluetoothGattService* aService)
+    : BluetoothReplyTaskQueue::SubTask(aRootQueue, nullptr)
+    , mServer(aServer)
+    , mService(aService)
+  { }
+
+  bool Execute() override
+  {
+    BluetoothService* bs = BluetoothService::Get();
+    if (NS_WARN_IF(!bs)) {
+      return false;
+    }
+
+    bs->GattServerStartServiceInternal(
+      mServer->mAppUuid,
+      mService->GetServiceHandle(),
+      GetReply());
+
+    return true;
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+};
+
+/*
+ * CancelAddServiceTask is used when failing to completely add a service. No
+ * matter CancelAddServiceTask executes successfully or not, the promose should
+ * be rejected because we fail to adding the service eventually.
+ */
+class BluetoothGattServer::CancelAddServiceTask final
+  : public BluetoothVoidReplyRunnable
+{
+public:
+  CancelAddServiceTask(BluetoothGattServer* aServer,
+                       BluetoothGattService* aService,
+                       Promise* aPromise)
+    : BluetoothVoidReplyRunnable(nullptr, nullptr)
+      /* aPromise is not managed by BluetoothVoidReplyRunnable. It would be
+       * rejected after this task has been executed anyway. */
+    , mServer(aServer)
+    , mService(aService)
+    , mPromise(aPromise)
+  {
+    MOZ_ASSERT(mPromise);
+  }
+
+  void ReleaseMembers() override
+  {
+    BluetoothVoidReplyRunnable::ReleaseMembers();
+    mServer = nullptr;
+    mService = nullptr;
+    mPromise = nullptr;
+  }
+
+private:
+  void OnSuccessFired() override
+  {
+    mServer->mPendingService = nullptr;
+    mPromise->MaybeReject(NS_ERROR_FAILURE);
+  }
+
+  void OnErrorFired() override
+  {
+    mServer->mPendingService = nullptr;
+    mPromise->MaybeReject(NS_ERROR_FAILURE);
+  }
+
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+  nsRefPtr<Promise> mPromise;
+};
+
+class BluetoothGattServer::AddServiceTaskQueue final
+  : public BluetoothReplyTaskQueue
+{
+public:
+  AddServiceTaskQueue(BluetoothGattServer* aServer,
+                      BluetoothGattService* aService,
+                      Promise* aPromise)
+    : BluetoothReplyTaskQueue(nullptr)
+    , mServer(aServer)
+    , mService(aService)
+    , mPromise(aPromise)
+  {
+    /* add included services */
+    nsTArray<nsRefPtr<BluetoothGattService>> includedServices;
+    mService->GetIncludedServices(includedServices);
+    for (size_t i = 0; i < includedServices.Length(); ++i) {
+      nsRefPtr<SubTask> includedServiceTask =
+        new AddIncludedServiceTask(this, mServer, mService, includedServices[i]);
+      AppendTask(includedServiceTask.forget());
+    }
+
+    /* add characteristics */
+    nsTArray<nsRefPtr<BluetoothGattCharacteristic>> characteristics;
+    mService->GetCharacteristics(characteristics);
+    for (size_t i = 0; i < characteristics.Length(); ++i) {
+      nsRefPtr<SubTask> characteristicTask =
+        new AddCharacteristicTask(this, mServer, mService, characteristics[i]);
+      AppendTask(characteristicTask.forget());
+
+      /* add descriptors */
+      nsTArray<nsRefPtr<BluetoothGattDescriptor>> descriptors;
+      characteristics[i]->GetDescriptors(descriptors);
+      for (size_t j = 0; j < descriptors.Length(); ++j) {
+        nsRefPtr<SubTask> descriptorTask =
+          new AddDescriptorTask(this,
+                                mServer,
+                                mService,
+                                characteristics[i],
+                                descriptors[j]);
+
+        AppendTask(descriptorTask.forget());
+      }
+    }
+
+    /* start service */
+    nsRefPtr<SubTask> startTask = new StartServiceTask(this, mServer, mService);
+    AppendTask(startTask.forget());
+  }
+
+protected:
+  virtual ~AddServiceTaskQueue()
+  { }
+
+private:
+  void OnSuccessFired() override
+  {
+    mServer->mPendingService = nullptr;
+    mServer->mServices.AppendElement(mService);
+    mPromise->MaybeResolve(JS::UndefinedHandleValue);
+  }
+
+  void OnErrorFired() override
+  {
+    BluetoothService* bs = BluetoothService::Get();
+    BT_ENSURE_TRUE_REJECT_VOID(bs, mPromise, NS_ERROR_NOT_AVAILABLE);
+
+    bs->GattServerRemoveServiceInternal(
+      mServer->mAppUuid,
+      mService->GetServiceHandle(),
+      new CancelAddServiceTask(mServer, mService, mPromise));
+  }
+
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+  nsRefPtr<Promise> mPromise;
+};
+
+class BluetoothGattServer::AddServiceTask final
+  : public BluetoothVoidReplyRunnable
+{
+public:
+  AddServiceTask(BluetoothGattServer* aServer,
+                 BluetoothGattService* aService,
+                 Promise* aPromise)
+    : BluetoothVoidReplyRunnable(nullptr, nullptr)
+      /* aPromise is not managed by BluetoothVoidReplyRunnable. It would be
+       * passed to other tasks after this task executes successfully. */
+    , mServer(aServer)
+    , mService(aService)
+    , mPromise(aPromise)
+  {
+    MOZ_ASSERT(mServer);
+    MOZ_ASSERT(mService);
+    MOZ_ASSERT(mPromise);
+  }
+
+  void ReleaseMembers() override
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mServer = nullptr;
+    mService = nullptr;
+    mPromise = nullptr;
+  }
+
+private:
+  virtual void OnSuccessFired() override
+  {
+    mService->AssignAppUuid(mServer->mAppUuid);
+
+    nsRefPtr<nsRunnable> runnable = new AddServiceTaskQueue(mServer,
+                                                            mService,
+                                                            mPromise);
+    nsresult rv = NS_DispatchToMainThread(runnable.forget());
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      mServer->mPendingService = nullptr;
+      mPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+    }
+  }
+
+  virtual void OnErrorFired() override
+  {
+    mServer->mPendingService = nullptr;
+    mPromise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  }
+
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+  nsRefPtr<Promise> mPromise;
+};
+
+already_AddRefed<Promise>
+BluetoothGattServer::AddService(BluetoothGattService& aService,
+                                ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mValid, promise, NS_ERROR_NOT_AVAILABLE);
+  BT_ENSURE_TRUE_REJECT(!aService.IsActivated(),
+                        promise,
+                        NS_ERROR_INVALID_ARG);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  mPendingService = &aService;
+
+  bs->GattServerAddServiceInternal(mAppUuid,
+                                   mPendingService->GetServiceId(),
+                                   mPendingService->GetHandleCount(),
+                                   new AddServiceTask(this,
+                                                      mPendingService,
+                                                      promise));
+
+  return promise.forget();
+}
+
+class BluetoothGattServer::RemoveServiceTask final
+  : public BluetoothReplyRunnable
+{
+public:
+  RemoveServiceTask(BluetoothGattServer* aServer,
+                    BluetoothGattService* aService,
+                    Promise* aPromise)
+    : BluetoothReplyRunnable(nullptr, aPromise)
+    , mServer(aServer)
+    , mService(aService)
+  {
+    MOZ_ASSERT(mServer);
+    MOZ_ASSERT(mService);
+  }
+
+  void ReleaseMembers() override
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mServer = nullptr;
+    mService = nullptr;
+  }
+
+protected:
+  bool ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue) override
+  {
+    aValue.setUndefined();
+
+    mServer->mServices.RemoveElement(mService);
+
+    return true;
+  }
+
+private:
+  nsRefPtr<BluetoothGattServer> mServer;
+  nsRefPtr<BluetoothGattService> mService;
+};
+
+already_AddRefed<Promise>
+BluetoothGattServer::RemoveService(BluetoothGattService& aService,
+                                   ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mValid, promise, NS_ERROR_NOT_AVAILABLE);
+  BT_ENSURE_TRUE_REJECT(mServices.Contains(&aService),
+                        promise,
+                        NS_ERROR_INVALID_ARG);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  bs->GattServerRemoveServiceInternal(
+    mAppUuid, aService.GetServiceHandle(), new RemoveServiceTask(this,
+                                                                 &aService,
+                                                                 promise));
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGattServer::NotifyCharacteristicChanged(
+  const nsAString& aAddress,
+  BluetoothGattCharacteristic& aCharacteristic,
+  bool aConfirm,
+  ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mValid, promise, NS_ERROR_NOT_AVAILABLE);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothGattService> service = aCharacteristic.Service();
+  BT_ENSURE_TRUE_REJECT(service, promise, NS_ERROR_NOT_AVAILABLE);
+  BT_ENSURE_TRUE_REJECT(mServices.Contains(service),
+                        promise,
+                        NS_ERROR_NOT_AVAILABLE);
+
+  bs->GattServerSendIndicationInternal(
+    mAppUuid, aAddress, aCharacteristic.GetCharacteristicHandle(), aConfirm,
+    aCharacteristic.GetValue(),
+    new BluetoothVoidReplyRunnable(nullptr, promise));
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGattServer::SendResponse(const nsAString& aAddress,
+                                  uint16_t aStatus,
+                                  int32_t aRequestId,
+                                  ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mValid, promise, NS_ERROR_NOT_AVAILABLE);
+
+  RequestData* requestData;
+  mRequestMap.Get(aRequestId, &requestData);
+  BT_ENSURE_TRUE_REJECT(requestData, promise, NS_ERROR_UNEXPECTED);
+
+  BluetoothGattResponse response;
+  memset(&response, 0, sizeof(response));
+  response.mHandle = requestData->mHandle;
+
+  if (requestData->mCharacteristic) {
+    const nsTArray<uint8_t>& value = requestData->mCharacteristic->GetValue();
+    response.mLength = value.Length();
+    memcpy(&response.mValue, value.Elements(), response.mLength);
+  } else if (requestData->mDescriptor) {
+    const nsTArray<uint8_t>& value = requestData->mDescriptor->GetValue();
+    response.mLength = value.Length();
+    memcpy(&response.mValue, value.Elements(), response.mLength);
+  } else {
+    MOZ_ASSERT_UNREACHABLE(
+      "There should be at least one characteristic or descriptor in the "
+      "request data.");
+
+    promise->MaybeReject(NS_ERROR_INVALID_ARG);
+    return promise.forget();
+  }
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, promise, NS_ERROR_NOT_AVAILABLE);
+
+  bs->GattServerSendResponseInternal(
+    mAppUuid,
+    aAddress,
+    aStatus,
+    aRequestId,
+    response,
+    new BluetoothVoidReplyRunnable(nullptr, promise));
+
+  return promise.forget();
+}
--- a/dom/bluetooth/common/webapi/BluetoothGattServer.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattServer.h
@@ -5,16 +5,19 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_BluetoothGattServer_h
 #define mozilla_dom_bluetooth_BluetoothGattServer_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/BluetoothGattServerBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothGattService.h"
+#include "mozilla/dom/Promise.h"
+#include "nsClassHashtable.h"
 #include "nsCOMPtr.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 class Promise;
 }
 }
@@ -29,29 +32,50 @@ class BluetoothGattServer final : public
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothGattServer,
                                            DOMEventTargetHelper)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
+  void GetServices(
+    nsTArray<nsRefPtr<BluetoothGattService>>& aServices) const
+  {
+    aServices = mServices;
+  }
 
   /****************************************************************************
    * Event Handlers
    ***************************************************************************/
   IMPL_EVENT_HANDLER(connectionstatechanged);
+  IMPL_EVENT_HANDLER(attributereadreq);
+  IMPL_EVENT_HANDLER(attributewritereq);
 
   /****************************************************************************
    * Methods (Web API Implementation)
    ***************************************************************************/
   already_AddRefed<Promise> Connect(
     const nsAString& aAddress, ErrorResult& aRv);
   already_AddRefed<Promise> Disconnect(
     const nsAString& aAddress, ErrorResult& aRv);
+  already_AddRefed<Promise> AddService(BluetoothGattService& aService,
+                                       ErrorResult& aRv);
+  already_AddRefed<Promise> RemoveService(BluetoothGattService& aService,
+                                          ErrorResult& aRv);
+  already_AddRefed<Promise> NotifyCharacteristicChanged(
+    const nsAString& aAddress,
+    BluetoothGattCharacteristic& aCharacteristic,
+    bool aConfirm,
+    ErrorResult& aRv);
+
+  already_AddRefed<Promise> SendResponse(const nsAString& aAddress,
+                                         uint16_t aStatus,
+                                         int32_t aRequestId,
+                                         ErrorResult& aRv);
 
   /****************************************************************************
    * Others
    ***************************************************************************/
   void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
 
   nsPIDOMWindow* GetParentObject() const
   {
@@ -68,16 +92,58 @@ public:
    * If the BluetoothAdapter turns off, existing BluetoothGattServer instances
    * should stop working till the end of life.
    */
   void Invalidate();
 
 private:
   ~BluetoothGattServer();
 
+  class AddIncludedServiceTask;
+  class AddCharacteristicTask;
+  class AddDescriptorTask;
+  class StartServiceTask;
+  class CancelAddServiceTask;
+  class AddServiceTaskQueue;
+  class AddServiceTask;
+  class RemoveServiceTask;
+
+  friend class AddIncludedServiceTask;
+  friend class AddCharacteristicTask;
+  friend class AddDescriptorTask;
+  friend class StartServiceTask;
+  friend class CancelAddServiceTask;
+  friend class AddServiceTaskQueue;
+  friend class AddServiceTask;
+  friend class RemoveServiceTask;
+
+  struct RequestData
+  {
+    RequestData(const BluetoothAttributeHandle& aHandle,
+                BluetoothGattCharacteristic* aCharacteristic,
+                BluetoothGattDescriptor* aDescriptor)
+    : mHandle(aHandle)
+    , mCharacteristic(aCharacteristic)
+    , mDescriptor(aDescriptor)
+    { }
+
+    BluetoothAttributeHandle mHandle;
+    nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+    nsRefPtr<BluetoothGattDescriptor> mDescriptor;
+  };
+
+  void HandleServerRegistered(const BluetoothValue& aValue);
+  void HandleServerUnregistered(const BluetoothValue& aValue);
+  void HandleConnectionStateChanged(const BluetoothValue& aValue);
+  void HandleServiceHandleUpdated(const BluetoothValue& aValue);
+  void HandleCharacteristicHandleUpdated(const BluetoothValue& aValue);
+  void HandleDescriptorHandleUpdated(const BluetoothValue& aValue);
+  void HandleReadWriteRequest(const BluetoothValue& aValue,
+                              const nsAString& aString);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * Random generated UUID of this GATT client.
    */
@@ -85,13 +151,28 @@ private:
 
   /**
    * Id of the GATT server interface given by bluetooth stack.
    * 0 if the interface is not registered yet, nonzero otherwise.
    */
   int mServerIf;
 
   bool mValid;
+
+  /**
+   * Array of services for this server.
+   */
+  nsTArray<nsRefPtr<BluetoothGattService>> mServices;
+
+  /**
+   * The service that is being added to this server.
+   */
+  nsRefPtr<BluetoothGattService> mPendingService;
+
+  /**
+   * Map request information from the request ID.
+   */
+  nsClassHashtable<nsUint32HashKey, RequestData> mRequestMap;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_BluetoothGattServer_h
--- a/dom/bluetooth/common/webapi/BluetoothGattService.cpp
+++ b/dom/bluetooth/common/webapi/BluetoothGattService.cpp
@@ -24,29 +24,48 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Bl
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattService)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattService)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattService)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+const uint16_t BluetoothGattService::sHandleCount = 1;
+
+// Constructor of BluetoothGattService in ATT client role
 BluetoothGattService::BluetoothGattService(
   nsPIDOMWindow* aOwner, const nsAString& aAppUuid,
   const BluetoothGattServiceId& aServiceId)
   : mOwner(aOwner)
   , mAppUuid(aAppUuid)
   , mServiceId(aServiceId)
+  , mAttRole(ATT_CLIENT_ROLE)
+  , mActive(true)
 {
   MOZ_ASSERT(aOwner);
   MOZ_ASSERT(!mAppUuid.IsEmpty());
 
   UuidToString(mServiceId.mId.mUuid, mUuidStr);
 }
 
+// Constructor of BluetoothGattService in ATT server role
+BluetoothGattService::BluetoothGattService(
+  nsPIDOMWindow* aOwner,
+  const BluetoothGattServiceInit& aInit)
+  : mOwner(aOwner)
+  , mUuidStr(aInit.mUuid)
+  , mAttRole(ATT_SERVER_ROLE)
+  , mActive(false)
+{
+  memset(&mServiceId, 0, sizeof(mServiceId));
+  StringToUuid(aInit.mUuid, mServiceId.mId.mUuid);
+  mServiceId.mIsPrimary = aInit.mIsPrimary;
+}
+
 BluetoothGattService::~BluetoothGattService()
 {
 }
 
 void
 BluetoothGattService::AssignIncludedServices(
   const nsTArray<BluetoothGattServiceId>& aServiceIds)
 {
@@ -80,14 +99,161 @@ BluetoothGattService::AssignDescriptors(
   size_t index = mCharacteristics.IndexOf(aCharacteristicId);
   NS_ENSURE_TRUE_VOID(index != mCharacteristics.NoIndex);
 
   nsRefPtr<BluetoothGattCharacteristic> characteristic =
     mCharacteristics.ElementAt(index);
   characteristic->AssignDescriptors(aDescriptorIds);
 }
 
+void
+BluetoothGattService::AssignAppUuid(const nsAString& aAppUuid)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+
+  mAppUuid = aAppUuid;
+}
+
+void
+BluetoothGattService::AssignServiceHandle(
+  const BluetoothAttributeHandle& aServiceHandle)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+  MOZ_ASSERT(!mActive);
+  MOZ_ASSERT(!mServiceHandle.mHandle);
+
+  mServiceHandle = aServiceHandle;
+  mActive = true;
+}
+
+void
+BluetoothGattService::AssignCharacteristicHandle(
+  const BluetoothUuid& aCharacteristicUuid,
+  const BluetoothAttributeHandle& aCharacteristicHandle)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+  MOZ_ASSERT(mActive);
+
+  size_t index = mCharacteristics.IndexOf(aCharacteristicUuid);
+  NS_ENSURE_TRUE_VOID(index != mCharacteristics.NoIndex);
+  mCharacteristics[index]->AssignCharacteristicHandle(aCharacteristicHandle);
+}
+
+void
+BluetoothGattService::AssignDescriptorHandle(
+  const BluetoothUuid& aDescriptorUuid,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  const BluetoothAttributeHandle& aDescriptorHandle)
+{
+  MOZ_ASSERT(mAttRole == ATT_SERVER_ROLE);
+  MOZ_ASSERT(mActive);
+
+  size_t index = mCharacteristics.IndexOf(aCharacteristicHandle);
+  NS_ENSURE_TRUE_VOID(index != mCharacteristics.NoIndex);
+  mCharacteristics[index]->AssignDescriptorHandle(aDescriptorUuid,
+                                                  aDescriptorHandle);
+}
+
+uint16_t
+BluetoothGattService::GetHandleCount() const
+{
+  uint16_t count = sHandleCount;
+  for (size_t i = 0; i < mCharacteristics.Length(); ++i) {
+    count += mCharacteristics[i]->GetHandleCount();
+  }
+  return count;
+}
+
 JSObject*
 BluetoothGattService::WrapObject(JSContext* aContext,
                                  JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothGattServiceBinding::Wrap(aContext, this, aGivenProto);
 }
+
+already_AddRefed<BluetoothGattService>
+BluetoothGattService::Constructor(const GlobalObject& aGlobal,
+                                  const BluetoothGattServiceInit& aInit,
+                                  ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  if (!window) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<BluetoothGattService> service = new BluetoothGattService(window,
+                                                                    aInit);
+
+  return service.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGattService::AddCharacteristic(
+  const nsAString& aCharacteristicUuid,
+  const GattPermissions& aPermissions,
+  const GattCharacteristicProperties& aProperties,
+  const ArrayBuffer& aValue,
+  ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mAttRole == ATT_SERVER_ROLE,
+                        promise,
+                        NS_ERROR_UNEXPECTED);
+
+  /* The service should not be actively acting with the Bluetooth backend.
+   * Otherwise, characteristics cannot be added into the service. */
+  BT_ENSURE_TRUE_REJECT(!mActive, promise, NS_ERROR_UNEXPECTED);
+
+  nsRefPtr<BluetoothGattCharacteristic> characteristic =
+    new BluetoothGattCharacteristic(GetParentObject(),
+                                    this,
+                                    aCharacteristicUuid,
+                                    aPermissions,
+                                    aProperties,
+                                    aValue);
+
+  mCharacteristics.AppendElement(characteristic);
+  promise->MaybeResolve(characteristic);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGattService::AddIncludedService(BluetoothGattService& aIncludedService,
+                                         ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  if (!global) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  NS_ENSURE_TRUE(!aRv.Failed(), nullptr);
+
+  BT_ENSURE_TRUE_REJECT(mAttRole == ATT_SERVER_ROLE,
+                        promise,
+                        NS_ERROR_UNEXPECTED);
+
+  /* The service should not be actively acting with the Bluetooth backend.
+   * Otherwise, included services cannot be added into the service. */
+  BT_ENSURE_TRUE_REJECT(!mActive, promise, NS_ERROR_UNEXPECTED);
+
+  /* The included service itself should be actively acting with the Bluetooth
+   * backend. Otherwise, that service cannot be included by any services. */
+  BT_ENSURE_TRUE_REJECT(aIncludedService.mActive,
+                        promise,
+                        NS_ERROR_UNEXPECTED);
+
+  mIncludedServices.AppendElement(&aIncludedService);
+  promise->MaybeResolve(JS::UndefinedHandleValue);
+
+  return promise.forget();
+}
--- a/dom/bluetooth/common/webapi/BluetoothGattService.h
+++ b/dom/bluetooth/common/webapi/BluetoothGattService.h
@@ -20,16 +20,17 @@ BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothGatt;
 class BluetoothSignal;
 class BluetoothValue;
 
 class BluetoothGattService final : public nsISupports
                                  , public nsWrapperCache
 {
   friend class BluetoothGatt;
+  friend class BluetoothGattServer;
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattService)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
   bool IsPrimary() const
@@ -55,39 +56,67 @@ public:
 
   void GetCharacteristics(
     nsTArray<nsRefPtr<BluetoothGattCharacteristic>>& aCharacteristics) const
   {
     aCharacteristics = mCharacteristics;
   }
 
   /****************************************************************************
+   * Methods (Web API Implementation)
+   ***************************************************************************/
+  static already_AddRefed<BluetoothGattService> Constructor(
+    const GlobalObject& aGlobal,
+    const BluetoothGattServiceInit& aInit,
+    ErrorResult& aRv);
+  already_AddRefed<Promise> AddCharacteristic(
+    const nsAString& aCharacteristicUuid,
+    const GattPermissions& aPermissions,
+    const GattCharacteristicProperties& aProperties,
+    const ArrayBuffer& aValue,
+    ErrorResult& aRv);
+  already_AddRefed<Promise> AddIncludedService(
+    BluetoothGattService& aIncludedService,
+    ErrorResult& aRv);
+
+  /****************************************************************************
    * Others
    ***************************************************************************/
   const nsAString& GetAppUuid() const
   {
     return mAppUuid;
   }
 
   const BluetoothGattServiceId& GetServiceId() const
   {
     return mServiceId;
   }
 
+  const BluetoothAttributeHandle& GetServiceHandle() const
+  {
+    return mServiceHandle;
+  }
+
   nsPIDOMWindow* GetParentObject() const
   {
      return mOwner;
   }
 
+  uint16_t GetHandleCount() const;
+
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
+  // Constructor of BluetoothGattService in ATT client role
   BluetoothGattService(nsPIDOMWindow* aOwner,
                        const nsAString& aAppUuid,
                        const BluetoothGattServiceId& aServiceId);
+  // Constructor of BluetoothGattService in ATT server role
+  BluetoothGattService(nsPIDOMWindow* aOwner,
+                       const BluetoothGattServiceInit& aInit);
 
 private:
   ~BluetoothGattService();
 
   /**
    * Add newly discovered GATT included services into mIncludedServices and
    * update the cache value of mIncludedServices.
    *
@@ -117,16 +146,71 @@ private:
    * @param aDescriptorIds [in] An array of BluetoothGattId for each descriptor
    *                            that belongs to the characteristic referred by
    *                            aCharacteristicId.
    */
   void AssignDescriptors(
     const BluetoothGattId& aCharacteristicId,
     const nsTArray<BluetoothGattId>& aDescriptorIds);
 
+  /**
+   * Assign AppUuid of this GATT service.
+   *
+   * @param aAppUuid The value of AppUuid.
+   */
+  void AssignAppUuid(const nsAString& aAppUuid);
+
+  /**
+   * Assign the handle value for this GATT service. This function would be
+   * called only after a valid handle value is retrieved from the Bluetooth
+   * backend.
+   *
+   * @param aServiceHandle [in] The handle value of this GATT service.
+   */
+  void AssignServiceHandle(const BluetoothAttributeHandle& aServiceHandle);
+
+  /**
+   * Assign the handle value for one of the characteristic within this GATT
+   * service. This function would be called only after a valid handle value is
+   * retrieved from the Bluetooth backend.
+   *
+   * @param aCharacteristicUuid [in] BluetoothUuid of this GATT characteristic.
+   * @param aCharacteristicHandle [in] The handle value of this GATT
+   *                                   characteristic.
+   */
+  void AssignCharacteristicHandle(
+    const BluetoothUuid& aCharacteristicUuid,
+    const BluetoothAttributeHandle& aCharacteristicHandle);
+
+  /**
+   * Assign the handle value for one of the descriptor within this GATT
+   * service. This function would be called only after a valid handle value is
+   * retrieved from the Bluetooth backend.
+   *
+   * @param aDescriptorUuid [in] BluetoothUuid of this GATT descriptor.
+   * @param aCharacteristicHandle [in] The handle value of this GATT
+   *                                   characteristic.
+   * @param aDescriptorHandle [in] The handle value of this GATT descriptor.
+   */
+  void AssignDescriptorHandle(
+    const BluetoothUuid& aDescriptorUuid,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    const BluetoothAttributeHandle& aDescriptorHandle);
+
+  /**
+   * Examine whether this GATT service can react with the Bluetooth backend.
+   *
+   * @return true if this service can react with the Bluetooth backend; false
+   *         if this service cannot react with the Bluetooth backend.
+   */
+  bool IsActivated() const
+  {
+    return mActive;
+  }
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * UUID of the GATT client.
    */
@@ -149,16 +233,46 @@ private:
    * Array of discovered included services for this service.
    */
   nsTArray<nsRefPtr<BluetoothGattService>> mIncludedServices;
 
   /**
    * Array of discovered characteristics for this service.
    */
   nsTArray<nsRefPtr<BluetoothGattCharacteristic>> mCharacteristics;
+
+  /**
+   * ATT role of this GATT service.
+   */
+  const BluetoothAttRole mAttRole;
+
+  /**
+   * Activeness of this GATT service.
+   *
+   * True means this service does react with the Bluetooth backend. False means
+   * this service doesn't react with the Bluetooth backend. The value should be
+   * true if |mAttRole| equals |ATT_CLIENT_ROLE| because the service instance
+   * could be created only when the Bluetooth backend has found one GATT
+   * service. The value would be false at the beginning if |mAttRole| equals
+   * |ATT_SERVER_ROLE|. Then the value would become true later if this GATT
+   * service has been added into Bluetooth backend.
+   */
+  bool mActive;
+
+  /**
+   * Handle of this GATT service.
+   *
+   * The value is only valid if |mAttRole| equals |ATT_SERVER_ROLE|.
+   */
+  BluetoothAttributeHandle mServiceHandle;
+
+  /**
+   * Total count of handles of this GATT service itself.
+   */
+  static const uint16_t sHandleCount;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 /**
  * Explicit Specialization of Function Templates
  *
  * Allows customizing the template code for a given set of template arguments.
@@ -174,9 +288,31 @@ public:
   bool Equals(
     const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattService>& aService,
     const mozilla::dom::bluetooth::BluetoothGattServiceId& aServiceId) const
   {
     return aService->GetServiceId() == aServiceId;
   }
 };
 
+/**
+ * Explicit Specialization of Function Templates
+ *
+ * Allows customizing the template code for a given set of template arguments.
+ * With this function template, nsTArray can handle comparison between
+ * 'nsRefPtr<BluetoothGattService>' and 'BluetoothAttributeHandle' properly,
+ * including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattService>,
+  mozilla::dom::bluetooth::BluetoothAttributeHandle> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattService>& aService,
+    const mozilla::dom::bluetooth::BluetoothAttributeHandle& aServiceHandle)
+    const
+  {
+    return aService->GetServiceHandle() == aServiceHandle;
+  }
+};
+
 #endif // mozilla_dom_bluetooth_BluetoothGattService_h
--- a/dom/bluetooth/ipc/BluetoothMessageUtils.h
+++ b/dom/bluetooth/ipc/BluetoothMessageUtils.h
@@ -40,16 +40,24 @@ template <>
 struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattWriteType>
   : public ContiguousEnumSerializer<
              mozilla::dom::bluetooth::BluetoothGattWriteType,
              mozilla::dom::bluetooth::GATT_WRITE_TYPE_NO_RESPONSE,
              mozilla::dom::bluetooth::GATT_WRITE_TYPE_END_GUARD>
 { };
 
 template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattAuthReq>
+  : public ContiguousEnumSerializer<
+             mozilla::dom::bluetooth::BluetoothGattAuthReq,
+             mozilla::dom::bluetooth::GATT_AUTH_REQ_NONE,
+             mozilla::dom::bluetooth::GATT_AUTH_REQ_END_GUARD>
+{ };
+
+template <>
 struct ParamTraits<mozilla::dom::bluetooth::BluetoothUuid>
 {
   typedef mozilla::dom::bluetooth::BluetoothUuid paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     for (uint8_t i = 0; i < 16; i++) {
       WriteParam(aMsg, aParam.mUuid[i]);
@@ -131,11 +139,65 @@ struct ParamTraits<mozilla::dom::bluetoo
         !ReadParam(aMsg, aIter, &(aResult->mWriteType))) {
       return false;
     }
 
     return true;
   }
 };
 
+template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothAttributeHandle>
+{
+  typedef mozilla::dom::bluetooth::BluetoothAttributeHandle paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mHandle);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mHandle))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattResponse>
+{
+  typedef mozilla::dom::bluetooth::BluetoothGattResponse paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mHandle);
+    WriteParam(aMsg, aParam.mOffset);
+    WriteParam(aMsg, aParam.mLength);
+    WriteParam(aMsg, aParam.mAuthReq);
+    for (uint16_t i = 0; i < aParam.mLength; i++) {
+      WriteParam(aMsg, aParam.mValue[i]);
+    }
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mHandle)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mOffset)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mLength)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mAuthReq))) {
+      return false;
+    }
+
+    for (uint16_t i = 0; i < aResult->mLength; i++) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mValue[i]))) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
 } // namespace IPC
 
 #endif // mozilla_dom_bluetooth_ipc_BluetoothMessageUtils_h
--- a/dom/bluetooth/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/ipc/BluetoothParent.cpp
@@ -295,16 +295,37 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TGattServerConnectPeripheralRequest:
       return actor->DoRequest(
         aRequest.get_GattServerConnectPeripheralRequest());
     case Request::TGattServerDisconnectPeripheralRequest:
       return actor->DoRequest(
         aRequest.get_GattServerDisconnectPeripheralRequest());
     case Request::TUnregisterGattServerRequest:
       return actor->DoRequest(aRequest.get_UnregisterGattServerRequest());
+    case Request::TGattServerAddServiceRequest:
+      return actor->DoRequest(aRequest.get_GattServerAddServiceRequest());
+    case Request::TGattServerAddIncludedServiceRequest:
+      return actor->DoRequest(
+               aRequest.get_GattServerAddIncludedServiceRequest());
+    case Request::TGattServerAddCharacteristicRequest:
+      return actor->DoRequest(
+               aRequest.get_GattServerAddCharacteristicRequest());
+    case Request::TGattServerAddDescriptorRequest:
+      return actor->DoRequest(aRequest.get_GattServerAddDescriptorRequest());
+    case Request::TGattServerRemoveServiceRequest:
+      return actor->DoRequest(aRequest.get_GattServerRemoveServiceRequest());
+    case Request::TGattServerStartServiceRequest:
+      return actor->DoRequest(aRequest.get_GattServerStartServiceRequest());
+    case Request::TGattServerStopServiceRequest:
+      return actor->DoRequest(aRequest.get_GattServerStopServiceRequest());
+    case Request::TGattServerSendResponseRequest:
+      return actor->DoRequest(aRequest.get_GattServerSendResponseRequest());
+    case Request::TGattServerSendIndicationRequest:
+      return actor->DoRequest(
+        aRequest.get_GattServerSendIndicationRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   MOZ_CRASH("Should never get here!");
 }
 
 PBluetoothRequestParent*
@@ -1018,8 +1039,163 @@ BluetoothRequestParent::DoRequest(const 
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TUnregisterGattServerRequest);
 
   mService->UnregisterGattServerInternal(aRequest.serverIf(),
                                          mReplyRunnable.get());
 
   return true;
 }
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerAddServiceRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerAddServiceRequest);
+
+  mService->GattServerAddServiceInternal(aRequest.appUuid(),
+                                         aRequest.serviceId(),
+                                         aRequest.handleCount(),
+                                         mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerAddIncludedServiceRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerAddIncludedServiceRequest);
+
+  mService->GattServerAddIncludedServiceInternal(
+    aRequest.appUuid(),
+    aRequest.serviceHandle(),
+    aRequest.includedServiceHandle(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerAddCharacteristicRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerAddCharacteristicRequest);
+
+  mService->GattServerAddCharacteristicInternal(
+    aRequest.appUuid(),
+    aRequest.serviceHandle(),
+    aRequest.characteristicUuid(),
+    aRequest.permissions(),
+    aRequest.properties(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerAddDescriptorRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerAddDescriptorRequest);
+
+  mService->GattServerAddDescriptorInternal(
+    aRequest.appUuid(),
+    aRequest.serviceHandle(),
+    aRequest.characteristicHandle(),
+    aRequest.descriptorUuid(),
+    aRequest.permissions(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerRemoveServiceRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerRemoveServiceRequest);
+
+  mService->GattServerRemoveServiceInternal(
+    aRequest.appUuid(),
+    aRequest.serviceHandle(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerStartServiceRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerStartServiceRequest);
+
+  mService->GattServerStartServiceInternal(
+    aRequest.appUuid(),
+    aRequest.serviceHandle(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerStopServiceRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerStopServiceRequest);
+
+  mService->GattServerStopServiceInternal(
+    aRequest.appUuid(),
+    aRequest.serviceHandle(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerSendIndicationRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TGattServerSendIndicationRequest);
+
+  mService->GattServerSendIndicationInternal(
+    aRequest.appUuid(),
+    aRequest.address(),
+    aRequest.characteristicHandle(),
+    aRequest.confirm(),
+    aRequest.value(),
+    mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattServerSendResponseRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattServerSendResponseRequest);
+
+  mService->GattServerSendResponseInternal(
+    aRequest.appUuid(),
+    aRequest.address(),
+    aRequest.status(),
+    aRequest.requestId(),
+    aRequest.response(),
+    mReplyRunnable.get());
+
+  return true;
+}
--- a/dom/bluetooth/ipc/BluetoothParent.h
+++ b/dom/bluetooth/ipc/BluetoothParent.h
@@ -274,13 +274,40 @@ protected:
   bool
   DoRequest(const GattServerConnectPeripheralRequest& aRequest);
 
   bool
   DoRequest(const GattServerDisconnectPeripheralRequest& aRequest);
 
   bool
   DoRequest(const UnregisterGattServerRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerAddServiceRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerAddIncludedServiceRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerAddCharacteristicRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerAddDescriptorRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerRemoveServiceRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerStartServiceRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerStopServiceRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerSendResponseRequest& aRequest);
+
+  bool
+  DoRequest(const GattServerSendIndicationRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_BluetoothParent_h
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.cpp
@@ -622,16 +622,135 @@ BluetoothServiceChildProcess::GattServer
 
 void
 BluetoothServiceChildProcess::UnregisterGattServerInternal(
   int aServerIf, BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, UnregisterGattServerRequest(aServerIf));
 }
 
+void
+BluetoothServiceChildProcess::GattServerAddServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  uint16_t aHandleCount,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerAddServiceRequest(nsString(aAppUuid), aServiceId, aHandleCount));
+}
+
+void
+BluetoothServiceChildProcess::GattServerAddIncludedServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aIncludedServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerAddIncludedServiceRequest(nsString(aAppUuid),
+                                        aServiceHandle,
+                                        aIncludedServiceHandle));
+}
+
+void
+BluetoothServiceChildProcess::GattServerAddCharacteristicInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothUuid& aCharacteristicUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothGattCharProp aProperties,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerAddCharacteristicRequest(nsString(aAppUuid),
+                                       aServiceHandle,
+                                       aCharacteristicUuid,
+                                       aPermissions,
+                                       aProperties));
+}
+
+void
+BluetoothServiceChildProcess::GattServerAddDescriptorInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  const BluetoothUuid& aDescriptorUuid,
+  BluetoothGattAttrPerm aPermissions,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerAddDescriptorRequest(nsString(aAppUuid),
+                                   aServiceHandle,
+                                   aCharacteristicHandle,
+                                   aDescriptorUuid,
+                                   aPermissions));
+}
+
+void
+BluetoothServiceChildProcess::GattServerRemoveServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerRemoveServiceRequest(nsString(aAppUuid), aServiceHandle));
+}
+
+void
+BluetoothServiceChildProcess::GattServerStartServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerStartServiceRequest(nsString(aAppUuid), aServiceHandle));
+}
+
+void
+BluetoothServiceChildProcess::GattServerStopServiceInternal(
+  const nsAString& aAppUuid,
+  const BluetoothAttributeHandle& aServiceHandle,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerStopServiceRequest(nsString(aAppUuid), aServiceHandle));
+}
+
+void
+BluetoothServiceChildProcess::GattServerSendResponseInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  uint16_t aStatus,
+  int32_t aRequestId,
+  const BluetoothGattResponse& aRsp,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerSendResponseRequest(
+      nsString(aAppUuid), nsString(aAddress), aStatus, aRequestId, aRsp));
+}
+
+void
+BluetoothServiceChildProcess::GattServerSendIndicationInternal(
+  const nsAString& aAppUuid,
+  const nsAString& aAddress,
+  const BluetoothAttributeHandle& aCharacteristicHandle,
+  bool aConfirm,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattServerSendIndicationRequest(nsString(aAppUuid),
+                                    nsString(aAddress),
+                                    aCharacteristicHandle,
+                                    aConfirm,
+                                    aValue));
+}
+
 nsresult
 BluetoothServiceChildProcess::HandleStartup()
 {
   // Don't need to do anything here for startup since our Create function takes
   // care of the actor machinery.
   return NS_OK;
 }
 
--- a/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/ipc/BluetoothServiceChildProcess.h
@@ -282,43 +282,111 @@ public:
     BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadDescriptorValueInternal(
     const nsAString& aAppUuid,
     const BluetoothGattServiceId& aServiceId,
     const BluetoothGattId& aCharacteristicId,
     const BluetoothGattId& aDescriptorId,
-    BluetoothReplyRunnable* aRunnable);
+    BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientWriteDescriptorValueInternal(
     const nsAString& aAppUuid,
     const BluetoothGattServiceId& aServiceId,
     const BluetoothGattId& aCharacteristicId,
     const BluetoothGattId& aDescriptorId,
     const nsTArray<uint8_t>& aValue,
-    BluetoothReplyRunnable* aRunnable);
+    BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattServerConnectPeripheralInternal(
     const nsAString& aAppUuid,
     const nsAString& aAddress,
-    BluetoothReplyRunnable* aRunnable);
+    BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattServerDisconnectPeripheralInternal(
     const nsAString& aAppUuid,
     const nsAString& aAddress,
-    BluetoothReplyRunnable* aRunnable);
+    BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   UnregisterGattServerInternal(int aServerIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattServerAddServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    uint16_t aHandleCount,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddIncludedServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aIncludedServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddCharacteristicInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothUuid& aCharacteristicUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothGattCharProp aProperties,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerAddDescriptorInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    const BluetoothUuid& aDescriptorUuid,
+    BluetoothGattAttrPerm aPermissions,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerRemoveServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerStartServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerStopServiceInternal(
+    const nsAString& aAppUuid,
+    const BluetoothAttributeHandle& aServiceHandle,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerSendResponseInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    uint16_t aStatus,
+    int32_t aRequestId,
+    const BluetoothGattResponse& aRsp,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattServerSendIndicationInternal(
+    const nsAString& aAppUuid,
+    const nsAString& aAddress,
+    const BluetoothAttributeHandle& aCharacteristicHandle,
+    bool aConfirm,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) override;
+
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
   void
   NoteDeadActor();
 
   void
--- a/dom/bluetooth/ipc/BluetoothTypes.ipdlh
+++ b/dom/bluetooth/ipc/BluetoothTypes.ipdlh
@@ -1,26 +1,36 @@
 /* -*- 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/. */
 
+using mozilla::dom::bluetooth::BluetoothAttributeHandle
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
+using mozilla::dom::bluetooth::BluetoothGattAttrPerm
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattCharAttribute
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
+using mozilla::dom::bluetooth::BluetoothGattCharProp
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattId
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
+using mozilla::dom::bluetooth::BluetoothGattResponse
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattServiceId
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattWriteType
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothSspVariant
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothStatus
   from "mozilla/dom/bluetooth/BluetoothCommon.h";
+using mozilla::dom::bluetooth::BluetoothUuid
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 
 namespace mozilla {
 namespace dom {
 namespace bluetooth {
 
 /**
  * Value structure for returns from bluetooth. Currently modeled after dbus
  * returns, which can be a 32-bit int, an UTF16 string, a bool, or an array of
@@ -37,16 +47,18 @@ union BluetoothValue
   uint8_t[];
   uint32_t[];
   BluetoothNamedValue[];
   BluetoothGattId;
   BluetoothGattId[];
   BluetoothGattServiceId;
   BluetoothGattServiceId[];
   BluetoothGattCharAttribute[];
+  BluetoothAttributeHandle;
+  BluetoothUuid;
 };
 
 /**
  * Key-value pair for dicts returned by the bluetooth backend. Used for things
  * like property updates, where the property will have a name and a type.
  */
 struct BluetoothNamedValue
 {
--- a/dom/bluetooth/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/ipc/PBluetooth.ipdl
@@ -304,16 +304,84 @@ struct GattServerDisconnectPeripheralReq
   nsString address;
 };
 
 struct UnregisterGattServerRequest
 {
   int serverIf;
 };
 
+struct GattServerAddServiceRequest
+{
+  nsString appUuid;
+  BluetoothGattServiceId serviceId;
+  uint16_t handleCount;
+};
+
+struct GattServerAddIncludedServiceRequest
+{
+  nsString appUuid;
+  BluetoothAttributeHandle serviceHandle;
+  BluetoothAttributeHandle includedServiceHandle;
+};
+
+struct GattServerAddCharacteristicRequest
+{
+  nsString appUuid;
+  BluetoothAttributeHandle serviceHandle;
+  BluetoothUuid characteristicUuid;
+  BluetoothGattAttrPerm permissions;
+  BluetoothGattCharProp properties;
+};
+
+struct GattServerAddDescriptorRequest
+{
+  nsString appUuid;
+  BluetoothAttributeHandle serviceHandle;
+  BluetoothAttributeHandle characteristicHandle;
+  BluetoothUuid descriptorUuid;
+  BluetoothGattAttrPerm permissions;
+};
+
+struct GattServerRemoveServiceRequest
+{
+  nsString appUuid;
+  BluetoothAttributeHandle serviceHandle;
+};
+
+struct GattServerStartServiceRequest
+{
+  nsString appUuid;
+  BluetoothAttributeHandle serviceHandle;
+};
+
+struct GattServerStopServiceRequest
+{
+  nsString appUuid;
+  BluetoothAttributeHandle serviceHandle;
+};
+
+struct GattServerSendResponseRequest
+{
+  nsString appUuid;
+  nsString address;
+  uint16_t status;
+  int32_t requestId;
+  BluetoothGattResponse response;
+};
+
+struct GattServerSendIndicationRequest
+{
+  nsString appUuid;
+  nsString address;
+  BluetoothAttributeHandle characteristicHandle;
+  bool confirm;
+  uint8_t[] value;
+};
+
 union Request
 {
   GetAdaptersRequest;
   StartBluetoothRequest;
   StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
@@ -357,16 +425,25 @@ union Request
   GattClientReadRemoteRssiRequest;
   GattClientReadCharacteristicValueRequest;
   GattClientWriteCharacteristicValueRequest;
   GattClientReadDescriptorValueRequest;
   GattClientWriteDescriptorValueRequest;
   GattServerConnectPeripheralRequest;
   GattServerDisconnectPeripheralRequest;
   UnregisterGattServerRequest;
+  GattServerAddServiceRequest;
+  GattServerAddIncludedServiceRequest;
+  GattServerAddCharacteristicRequest;
+  GattServerAddDescriptorRequest;
+  GattServerRemoveServiceRequest;
+  GattServerStartServiceRequest;
+  GattServerStopServiceRequest;
+  GattServerSendResponseRequest;
+  GattServerSendIndicationRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
 
   /**
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -24,16 +24,17 @@ if CONFIG['MOZ_B2G_BT']:
         'common/BluetoothUtils.cpp',
         'common/BluetoothUuid.cpp',
         'common/ObexBase.cpp',
         'common/webapi/BluetoothAdapter.cpp',
         'common/webapi/BluetoothClassOfDevice.cpp',
         'common/webapi/BluetoothDevice.cpp',
         'common/webapi/BluetoothDiscoveryHandle.cpp',
         'common/webapi/BluetoothGatt.cpp',
+        'common/webapi/BluetoothGattAttributeEvent.cpp',
         'common/webapi/BluetoothGattCharacteristic.cpp',
         'common/webapi/BluetoothGattDescriptor.cpp',
         'common/webapi/BluetoothGattServer.cpp',
         'common/webapi/BluetoothGattService.cpp',
         'common/webapi/BluetoothLeDeviceEvent.cpp',
         'common/webapi/BluetoothManager.cpp',
         'common/webapi/BluetoothPairingHandle.cpp',
         'common/webapi/BluetoothPairingListener.cpp',
@@ -136,16 +137,17 @@ EXPORTS.mozilla.dom.bluetooth.ipc += [
 ]
 EXPORTS.mozilla.dom.bluetooth += [
     'common/BluetoothCommon.h',
     'common/webapi/BluetoothAdapter.h',
     'common/webapi/BluetoothClassOfDevice.h',
     'common/webapi/BluetoothDevice.h',
     'common/webapi/BluetoothDiscoveryHandle.h',
     'common/webapi/BluetoothGatt.h',
+    'common/webapi/BluetoothGattAttributeEvent.h',
     'common/webapi/BluetoothGattCharacteristic.h',
     'common/webapi/BluetoothGattDescriptor.h',
     'common/webapi/BluetoothGattServer.h',
     'common/webapi/BluetoothGattService.h',
     'common/webapi/BluetoothLeDeviceEvent.h',
     'common/webapi/BluetoothManager.h',
     'common/webapi/BluetoothPairingHandle.h',
     'common/webapi/BluetoothPairingListener.h',
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -1256,33 +1256,37 @@ BrowserElementChild.prototype = {
       this._ctxHandlers = {};
     } else {
       // We silently ignore if the embedder uses an incorrect id in the callback
       debug("Ignored invalid contextmenu invocation");
     }
   },
 
   _buildMenuObj: function(menu, idPrefix, copyableElements) {
-    var menuObj = {type: 'menu', items: []};
+    var menuObj = {type: 'menu', customized: false, items: []};
     // Customized context menu
     if (menu) {
       this._maybeCopyAttribute(menu, menuObj, 'label');
 
       for (var i = 0, child; child = menu.children[i++];) {
         if (child.nodeName === 'MENU') {
           menuObj.items.push(this._buildMenuObj(child, idPrefix + i + '_', false));
         } else if (child.nodeName === 'MENUITEM') {
           var id = this._ctxCounter + '_' + idPrefix + i;
           var menuitem = {id: id, type: 'menuitem'};
           this._maybeCopyAttribute(child, menuitem, 'label');
           this._maybeCopyAttribute(child, menuitem, 'icon');
           this._ctxHandlers[id] = child;
           menuObj.items.push(menuitem);
         }
       }
+
+      if (menuObj.items.length > 0) {
+        menuObj.customized = true;
+      }
     }
     // Note: Display "Copy Link" first in order to make sure "Copy Image" is
     //       put together with other image options if elem is an image link.
     // "Copy Link" menu item
     if (copyableElements.link) {
       menuObj.items.push({id: 'copy-link'});
     }
     // "Copy Image" menu item
--- a/dom/browser-element/mochitest/browserElement_ContextmenuEvents.js
+++ b/dom/browser-element/mochitest/browserElement_ContextmenuEvents.js
@@ -19,17 +19,22 @@ function checkEmptyContextMenu() {
 
     checkInnerContextMenu();
   });
 }
 
 function checkInnerContextMenu() {
   sendContextMenuTo('#inner-link', function onContextMenu(detail) {
     is(detail.systemTargets.length, 1, 'Includes anchor data');
-    is(detail.contextmenu.items.length, 3, 'Inner clicks trigger correct menu');
+    is(detail.contextmenu.items.length, 3, 'Inner clicks trigger correct customized menu');
+    is(detail.contextmenu.items[0].label, 'foo', 'Customized menu has a "foo" menu item');
+    is(detail.contextmenu.items[1].label, 'bar', 'Customized menu has a "bar" menu item');
+    is(detail.contextmenu.items[2].id, 'copy-link', '#inner-link has a copy-link menu item');
+    is(detail.contextmenu.customized, true, 'Make sure contextmenu has customized items');
+
     var target = detail.systemTargets[0];
     is(target.nodeName, 'A', 'Reports correct nodeName');
     is(target.data.uri, 'foo.html', 'Reports correct uri');
     is(target.data.text, 'Menu 1', 'Reports correct link text');
 
     checkCustomContextMenu();
   });
 }
@@ -42,19 +47,29 @@ function checkCustomContextMenu() {
   });
 }
 
 function checkNestedContextMenu() {
   sendContextMenuTo('#menu2-trigger', function onContextMenu(detail) {
     var innerMenu = detail.contextmenu.items.filter(function(x) {
       return x.type === 'menu';
     });
-    is(detail.systemTargets.length, 2, 'Includes anchor and img data');
+    is(detail.systemTargets.length, 2, 'Includes two systemTargets');
+    is(detail.systemTargets[0].nodeName, 'IMG', 'Includes "IMG" node');
+    is(detail.systemTargets[0].data.uri, 'example.png', 'Img data has the correct uri');
+    is(detail.systemTargets[1].nodeName, 'A', 'Includes "A" node');
+    is(detail.systemTargets[1].data.uri, 'bar.html', 'Anchor has the correct uri');
     ok(innerMenu.length > 0, 'Menu contains a nested menu');
 
+    is(detail.contextmenu.items.length, 4, 'We have correct # of menu items')
+    is(detail.contextmenu.customized, true, 'Make sure contextmenu has customized items');
+    is(detail.contextmenu.items[0].label, 'outer', 'Customized menu has an "outer" menu item');
+    is(detail.contextmenu.items[1].label, 'submenu', 'Customized menu has an "submenu" menu item');
+    is(detail.contextmenu.items[2].id, 'copy-link', 'Has a copy-link menu item');
+    is(detail.contextmenu.items[3].id, 'copy-image', 'Has a copy-image menu item');
     checkPreviousContextMenuHandler();
   });
 }
 
  // Finished testing the data passed to the contextmenu handler,
  // now we start selecting contextmenu items
 function checkPreviousContextMenuHandler() {
   // This is previously triggered contextmenu data, since we have
@@ -126,16 +141,19 @@ function checkCallbackWithoutPreventDefa
   }, /* ignorePreventDefault */ true);
 }
 
 function checkImageContextMenu() {
   sendContextMenuTo('#menu3-trigger', function onContextMenu(detail) {
     var target = detail.systemTargets[0];
     is(target.nodeName, 'IMG', 'Reports correct nodeName');
     is(target.data.uri, 'example.png', 'Reports correct uri');
+    is(detail.contextmenu.items.length, 1, 'Reports correct # of menu items');
+    is(detail.contextmenu.items[0].id, 'copy-image', 'IMG has a copy-image menu item');
+    is(detail.contextmenu.customized, false, 'Make sure we do not have customized items');
 
     checkVideoContextMenu();
   }, /* ignorePreventDefault */ true);
 }
 
 function checkVideoContextMenu() {
   sendContextMenuTo('#menu4-trigger', function onContextMenu(detail) {
     var target = detail.systemTargets[0];
@@ -260,17 +278,17 @@ function createIframe(callback) {
   iframe.src = 'data:text/html,<html>' +
     '<body>' +
     '<menu type="context" id="menu1" label="firstmenu">' +
       '<menuitem label="foo" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
       '<menuitem label="bar" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
     '</menu>' +
     '<menu type="context" id="menu2" label="secondmenu">' +
       '<menuitem label="outer" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
-      '<menu>' +
+      '<menu label="submenu">' +
         '<menuitem label="inner 1"></menuitem>' +
         '<menuitem label="inner 2" onclick="window.onContextMenuCallbackFired(event)"></menuitem>' +
       '</menu>' +
     '</menu>' +
     '<div id="menu1-trigger" contextmenu="menu1"><a id="inner-link" href="foo.html">Menu 1</a></div>' +
     '<a href="bar.html" contextmenu="menu2"><img id="menu2-trigger" src="example.png" /></a>' +
     '<img id="menu3-trigger" src="example.png" />' +
     '<video id="menu4-trigger" src="' + videoUrl + '"></video>' +
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -82,16 +82,23 @@ public:
   void SetPath(const nsAString& aPath);
   void SetEditable(bool aEditable);
 
   static already_AddRefed<DeviceStorageFile>
   CreateUnique(nsAString& aFileName,
                uint32_t aFileType,
                uint32_t aFileAttributes);
 
+  static already_AddRefed<DeviceStorageFile>
+  CreateUnique(const nsAString& aStorageType,
+               const nsAString& aStorageName,
+               nsAString& aFileName,
+               uint32_t aFileType,
+               uint32_t aFileAttributes);
+
   NS_DECL_THREADSAFE_ISUPPORTS
 
   bool IsAvailable();
   void GetFullPath(nsAString& aFullPath);
 
   // we want to make sure that the names of file can't reach
   // outside of the type of storage the user asked for.
   bool IsSafePath();
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -688,18 +688,30 @@ DeviceStorageFile::CreateUnique(nsAStrin
   nsString storageName;
   nsString storagePath;
   if (!nsDOMDeviceStorage::ParseFullPath(aFileName, storageName, storagePath)) {
     return nullptr;
   }
   if (storageName.IsEmpty()) {
     nsDOMDeviceStorage::GetDefaultStorageName(storageType, storageName);
   }
+  return CreateUnique(storageType, storageName, storagePath,
+                      aFileType, aFileAttributes);
+}
+
+//static
+already_AddRefed<DeviceStorageFile>
+DeviceStorageFile::CreateUnique(const nsAString& aStorageType,
+                                const nsAString& aStorageName,
+                                nsAString& aFileName,
+                                uint32_t aFileType,
+                                uint32_t aFileAttributes)
+{
   nsRefPtr<DeviceStorageFile> dsf =
-    new DeviceStorageFile(storageType, storageName, storagePath);
+    new DeviceStorageFile(aStorageType, aStorageName, aFileName);
   if (!dsf->mFile) {
     return nullptr;
   }
 
   nsresult rv = dsf->mFile->CreateUnique(aFileType, aFileAttributes);
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   // CreateUnique may cause the filename to change. So we need to update mPath
@@ -1494,16 +1506,17 @@ nsDOMDeviceStorageCursor::Continue(Error
   mOkToCallContinue = false;
   aRv = mRequest->Continue();
 }
 
 DeviceStorageRequest::DeviceStorageRequest()
   : mId(DeviceStorageRequestManager::INVALID_ID)
   , mAccess(DEVICE_STORAGE_ACCESS_UNDEFINED)
   , mSendToParent(true)
+  , mUseMainThread(false)
   , mUseStreamTransport(false)
   , mCheckFile(false)
   , mCheckBlob(false)
   , mMultipleResolve(false)
   , mPermissionCached(true)
 {
   DS_LOG_DEBUG("%p", this);
 }
@@ -1573,21 +1586,40 @@ nsresult
 DeviceStorageRequest::Cancel()
 {
   return Reject(POST_ERROR_EVENT_PERMISSION_DENIED);
 }
 
 nsresult
 DeviceStorageRequest::Allow()
 {
+  if (mUseMainThread && !NS_IsMainThread()) {
+    nsRefPtr<DeviceStorageRequest> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+    {
+      self->Allow();
+    });
+    return NS_DispatchToMainThread(r);
+  }
+
   nsresult rv = AllowInternal();
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return Reject(rv == NS_ERROR_ILLEGAL_VALUE
-                  ? POST_ERROR_EVENT_ILLEGAL_TYPE
-                  : POST_ERROR_EVENT_UNKNOWN);
+    const char *reason;
+    switch (rv) {
+      case NS_ERROR_ILLEGAL_VALUE:
+        reason = POST_ERROR_EVENT_ILLEGAL_TYPE;
+        break;
+      case NS_ERROR_DOM_SECURITY_ERR:
+        reason = POST_ERROR_EVENT_PERMISSION_DENIED;
+        break;
+      default:
+        reason = POST_ERROR_EVENT_UNKNOWN;
+        break;
+    }
+    return Reject(reason);
   }
   return rv;
 }
 
 DeviceStorageFile*
 DeviceStorageRequest::GetFile() const
 {
   MOZ_ASSERT(mFile);
@@ -1602,33 +1634,38 @@ DeviceStorageRequest::GetFileDescriptor(
 }
 
 DeviceStorageRequestManager*
 DeviceStorageRequest::GetManager() const
 {
   return mManager;
 }
 
-void
+nsresult
 DeviceStorageRequest::Prepare()
 {
+  return NS_OK;
 }
 
 nsresult
 DeviceStorageRequest::CreateSendParams(DeviceStorageParams& aParams)
 {
   MOZ_ASSERT_UNREACHABLE("Cannot send to parent, missing param creator");
   return NS_ERROR_UNEXPECTED;
 }
 
 nsresult
 DeviceStorageRequest::AllowInternal()
 {
   MOZ_ASSERT(mManager->IsOwningThread() || NS_IsMainThread());
-  Prepare();
+
+  nsresult rv = Prepare();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
   DeviceStorageTypeChecker* typeChecker
     = DeviceStorageTypeChecker::CreateOrGet();
   if (!typeChecker) {
     return NS_ERROR_UNEXPECTED;
   }
   if (mCheckBlob && (!mBlob ||
       !typeChecker->Check(mFile->mStorageType, mBlob))) {
@@ -1864,16 +1901,18 @@ protected:
     return NS_OK;
   }
 };
 
 class DeviceStorageCreateRequest final
   : public DeviceStorageRequest
 {
 public:
+  using DeviceStorageRequest::Initialize;
+
   DeviceStorageCreateRequest()
   {
     mAccess = DEVICE_STORAGE_ACCESS_CREATE;
     mUseStreamTransport = true;
     DS_LOG_INFO("");
   }
 
   NS_IMETHOD Run() override
@@ -1898,17 +1937,72 @@ public:
       return Reject(POST_ERROR_EVENT_UNKNOWN);
     }
 
     nsString fullPath;
     mFile->GetFullPath(fullPath);
     return Resolve(fullPath);
   }
 
+  void Initialize(DeviceStorageRequestManager* aManager,
+                  DeviceStorageFile* aFile,
+                  uint32_t aRequest) override
+  {
+    DeviceStorageRequest::Initialize(aManager, aFile, aRequest);
+    mUseMainThread = aFile->mPath.IsEmpty();
+  }
+
 protected:
+  nsresult Prepare() override
+  {
+    if (!mFile->mPath.IsEmpty()) {
+      // Checks have already been performed when request was created
+      return NS_OK;
+    }
+
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
+    if (!mimeSvc) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // if mimeType or extension are null, the request will be rejected
+    // in DeviceStorageRequest::AllowInternal when the type checker
+    // verifies the file path
+    nsString mimeType;
+    mBlob->GetType(mimeType);
+
+    nsCString extension;
+    mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
+                                 EmptyCString(), extension);
+
+    char buffer[32];
+    NS_MakeRandomString(buffer, ArrayLength(buffer) - 1);
+
+    nsAutoString path;
+    path.AssignLiteral(buffer);
+    path.Append('.');
+    path.AppendASCII(extension.get());
+
+    nsRefPtr<DeviceStorageFile> file
+      = DeviceStorageFile::CreateUnique(mFile->mStorageType,
+                                        mFile->mStorageName, path,
+                                        nsIFile::NORMAL_FILE_TYPE, 00600);
+    if (!file) {
+      return NS_ERROR_FAILURE;
+    }
+    if (!file->IsSafePath()) {
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+
+    mFile = file.forget();
+    return NS_OK;
+  }
+
   nsresult CreateSendParams(DeviceStorageParams& aParams) override
   {
     BlobChild* actor
       = ContentChild::GetSingleton()->GetOrCreateActorForBlobImpl(mBlob);
     if (!actor) {
       return NS_ERROR_FAILURE;
     }
 
@@ -1981,16 +2075,17 @@ protected:
 class DeviceStorageOpenRequest final
   : public DeviceStorageRequest
 {
 public:
   using DeviceStorageRequest::Initialize;
 
   DeviceStorageOpenRequest()
   {
+    mUseMainThread = true;
     mUseStreamTransport = true;
     mCheckFile = true;
     DS_LOG_INFO("");
   }
 
   void Initialize(DeviceStorageRequestManager* aManager,
                   DeviceStorageFile* aFile,
                   uint32_t aRequest) override
@@ -2014,20 +2109,21 @@ public:
     if (NS_FAILED(rv)) {
       return Reject(POST_ERROR_EVENT_UNKNOWN);
     }
 
     return Resolve(mFile);
   }
 
 protected:
-  void Prepare() override
+  nsresult Prepare() override
   {
     MOZ_ASSERT(NS_IsMainThread());
     mFile->CalculateMimeType();
+    return NS_OK;
   }
 
   nsresult CreateSendParams(DeviceStorageParams& aParams) override
   {
     DeviceStorageGetParams params(mFile->mStorageType,
                                   mFile->mStorageName,
                                   mFile->mRootDir,
                                   mFile->mPath);
@@ -2921,62 +3017,39 @@ nsDOMDeviceStorage::IsAvailable()
 {
   nsRefPtr<DeviceStorageFile> dsf(new DeviceStorageFile(mStorageType, mStorageName));
   return dsf->IsAvailable();
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::Add(Blob* aBlob, ErrorResult& aRv)
 {
-  if (!aBlob) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
-  if (!mimeSvc) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-
-  // if mimeType isn't set, we will not get a correct
-  // extension, and AddNamed() will fail.  This will post an
-  // onerror to the requestee.
-  nsString mimeType;
-  aBlob->GetType(mimeType);
-
-  nsCString extension;
-  mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
-                               EmptyCString(), extension);
-  // if extension is null here, we will ignore it for now.
-  // AddNamed() will check the file path and fail.  This
-  // will post an onerror to the requestee.
-
-  // possible race here w/ unique filename
-  char buffer[32];
-  NS_MakeRandomString(buffer, ArrayLength(buffer) - 1);
-
-  nsAutoCString path;
-  path.Assign(nsDependentCString(buffer));
-  path.Append('.');
-  path.Append(extension);
-
-  return AddNamed(aBlob, NS_ConvertASCIItoUTF16(path), aRv);
+  nsString path;
+  return AddOrAppendNamed(aBlob, path, true, aRv);
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::AddNamed(Blob* aBlob, const nsAString& aPath,
                              ErrorResult& aRv)
 {
+  if (aPath.IsEmpty()) {
+    aRv.Throw(NS_ERROR_ILLEGAL_VALUE);
+    return nullptr;
+  }
   return AddOrAppendNamed(aBlob, aPath, true, aRv);
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::AppendNamed(Blob* aBlob, const nsAString& aPath,
                                 ErrorResult& aRv)
 {
+  if (aPath.IsEmpty()) {
+    aRv.Throw(NS_ERROR_ILLEGAL_VALUE);
+    return nullptr;
+  }
   return AddOrAppendNamed(aBlob, aPath, false, aRv);
 }
 
 uint32_t
 nsDOMDeviceStorage::CreateDOMRequest(DOMRequest** aRequest, ErrorResult& aRv)
 {
   if (!mManager) {
     DS_LOG_WARN("shutdown");
@@ -3012,29 +3085,23 @@ nsDOMDeviceStorage::CreateAndRejectDOMRe
   return request.forget();
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::AddOrAppendNamed(Blob* aBlob, const nsAString& aPath,
                                      bool aCreate, ErrorResult& aRv)
 {
   MOZ_ASSERT(IsOwningThread());
+  MOZ_ASSERT(aCreate || !aPath.IsEmpty());
 
   // if the blob is null here, bail
   if (!aBlob) {
     return nullptr;
   }
 
-  DeviceStorageTypeChecker* typeChecker
-    = DeviceStorageTypeChecker::CreateOrGet();
-  if (!typeChecker) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-
   nsCOMPtr<nsIRunnable> r;
 
   if (IsFullPath(aPath)) {
     nsString storagePath;
     nsRefPtr<nsDOMDeviceStorage> ds = GetStorage(aPath, storagePath);
     if (!ds) {
       return CreateAndRejectDOMRequest(POST_ERROR_EVENT_UNKNOWN, aRv);
     }
@@ -3043,35 +3110,35 @@ nsDOMDeviceStorage::AddOrAppendNamed(Blo
   }
 
   nsRefPtr<DOMRequest> domRequest;
   uint32_t id = CreateDOMRequest(getter_AddRefs(domRequest), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mStorageType,
-                                                          mStorageName,
-                                                          aPath);
-  if (!dsf->IsSafePath()) {
-    aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
-  } else if (!typeChecker->Check(mStorageType, dsf->mFile) ||
-      !typeChecker->Check(mStorageType, aBlob->Impl())) {
-    aRv = mManager->Reject(id, POST_ERROR_EVENT_ILLEGAL_TYPE);
+  nsRefPtr<DeviceStorageFile> dsf;
+  if (aPath.IsEmpty()) {
+    dsf = new DeviceStorageFile(mStorageType, mStorageName);
   } else {
-    nsRefPtr<DeviceStorageRequest> request;
-    if (aCreate) {
-      request = new DeviceStorageCreateRequest();
-    } else {
-      request = new DeviceStorageAppendRequest();
+    dsf = new DeviceStorageFile(mStorageType, mStorageName, aPath);
+    if (!dsf->IsSafePath()) {
+      aRv = mManager->Reject(id, POST_ERROR_EVENT_PERMISSION_DENIED);
+      return domRequest.forget();
     }
-    request->Initialize(mManager, dsf, id, aBlob->Impl());
-    aRv = CheckPermission(request);
-  }
-
+  }
+
+  nsRefPtr<DeviceStorageRequest> request;
+  if (aCreate) {
+    request = new DeviceStorageCreateRequest();
+  } else {
+    request = new DeviceStorageAppendRequest();
+  }
+  request->Initialize(mManager, dsf, id, aBlob->Impl());
+  aRv = CheckPermission(request);
   return domRequest.forget();
 }
 
 already_AddRefed<DOMRequest>
 nsDOMDeviceStorage::GetInternal(const nsAString& aPath, bool aEditable,
                                 ErrorResult& aRv)
 {
   MOZ_ASSERT(IsOwningThread());
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -373,28 +373,29 @@ public:
 
 protected:
   bool ForceDispatch() const
   {
     return !mSendToParent && mPermissionCached;
   }
 
   virtual ~DeviceStorageRequest();
-  virtual void Prepare();
+  virtual nsresult Prepare();
   virtual nsresult CreateSendParams(mozilla::dom::DeviceStorageParams& aParams);
   nsresult AllowInternal();
   nsresult SendToParentProcess();
 
   nsRefPtr<DeviceStorageRequestManager> mManager;
   nsRefPtr<DeviceStorageFile> mFile;
   uint32_t mId;
   nsRefPtr<mozilla::dom::BlobImpl> mBlob;
   nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
   DeviceStorageAccessType mAccess;
   bool mSendToParent;
+  bool mUseMainThread;
   bool mUseStreamTransport;
   bool mCheckFile;
   bool mCheckBlob;
   bool mMultipleResolve;
   bool mPermissionCached;
 
 private:
   DeviceStorageRequest(const DeviceStorageRequest&) = delete;
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -59,16 +59,20 @@ const kEventConstructors = {
   BluetoothAttributeEvent:                   { create: function (aName, aProps) {
                                                           return new BluetoothAttributeEvent(aName, aProps);
                                                        },
                                              },
   BluetoothDeviceEvent:                      { create: function (aName, aProps) {
                                                           return new BluetoothDeviceEvent(aName, aProps);
                                                        },
                                              },
+  BluetoothGattAttributeEvent:               { create: function (aName, aProps) {
+                                                          return new BluetoothGattAttributeEvent(aName, aProps);
+                                                       },
+                                             },
   BluetoothGattCharacteristicEvent:          { create: function (aName, aProps) {
                                                           return new BluetoothGattCharacteristicEvent(aName, aProps);
                                                        },
                                              },
   BluetoothLeDeviceEvent:                    { create: function (aName, aProps) {
                                                           return new BluetoothLeDeviceEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/system/gonk/DataCallInterfaceService.js
+++ b/dom/system/gonk/DataCallInterfaceService.js
@@ -67,17 +67,18 @@ DataCall.prototype = {
   suggestedRetryTime: -1,
   cid: -1,
   active: -1,
   pdpType: -1,
   ifname: null,
   addreses: null,
   dnses: null,
   gateways: null,
-  pcscf: null
+  pcscf: null,
+  mtu: -1
 };
 
 function DataCallInterfaceService() {
   this._dataCallInterfaces = [];
 
   let numClients = gRil.numRadioInterfaces;
   for (let i = 0; i < numClients; i++) {
     this._dataCallInterfaces.push(new DataCallInterface(i));
--- a/dom/system/gonk/DataCallManager.js
+++ b/dom/system/gonk/DataCallManager.js
@@ -956,17 +956,18 @@ function DataCall(aClientId, aApnSetting
     roaming_protocol: aApnSetting.roaming_protocol
   };
   this.linkInfo = {
     cid: null,
     ifname: null,
     addresses: [],
     dnses: [],
     gateways: [],
-    pcscf: []
+    pcscf: [],
+    mtu: null
   };
   this.state = NETWORK_STATE_UNKNOWN;
   this.requestedNetworkIfaces = [];
 }
 DataCall.prototype = {
   /**
    * Standard values for the APN connection retry process
    * Retry funcion: time(secs) = A * numer_of_retries^2 + B
@@ -1022,16 +1023,20 @@ DataCall.prototype = {
       }
       for (let i = 0; i < lhs.length; i++) {
         if (lhs[i] != rhs[i]) {
           return "changed";
         }
       }
     }
 
+    if (aCurrentDataCall.mtu != aUpdatedDataCall.mtu) {
+      return "changed";
+    }
+
     return "identical";
   },
 
   _getGeckoDataCallState:function (aDataCall) {
     if (aDataCall.active == Ci.nsIDataCallInterface.DATACALL_STATE_ACTIVE_UP ||
         aDataCall.active == Ci.nsIDataCallInterface.DATACALL_STATE_ACTIVE_DOWN) {
       return NETWORK_STATE_CONNECTED;
     }
@@ -1088,16 +1093,17 @@ DataCall.prototype = {
       return;
     }
 
     this.linkInfo.ifname = aDataCall.ifname;
     this.linkInfo.addresses = aDataCall.addresses ? aDataCall.addresses.split(" ") : [];
     this.linkInfo.gateways = aDataCall.gateways ? aDataCall.gateways.split(" ") : [];
     this.linkInfo.dnses = aDataCall.dnses ? aDataCall.dnses.split(" ") : [];
     this.linkInfo.pcscf = aDataCall.pcscf ? aDataCall.pcscf.split(" ") : [];
+    this.linkInfo.mtu = aDataCall.mtu > 0 ? aDataCall.mtu : 0;
     this.state = this._getGeckoDataCallState(aDataCall);
 
     // Notify DataCallHandler about data call connected.
     this.dataCallHandler.notifyDataCallChanged(this);
 
     for (let i = 0; i < this.requestedNetworkIfaces.length; i++) {
       this.requestedNetworkIfaces[i].notifyRILNetworkInterface();
     }
@@ -1141,17 +1147,18 @@ DataCall.prototype = {
       return;
     }
 
     let newLinkInfo = {
       ifname: aUpdatedDataCall.ifname,
       addresses: aUpdatedDataCall.addresses ? aUpdatedDataCall.addresses.split(" ") : [],
       dnses: aUpdatedDataCall.dnses ? aUpdatedDataCall.dnses.split(" ") : [],
       gateways: aUpdatedDataCall.gateways ? aUpdatedDataCall.gateways.split(" ") : [],
-      pcscf: aUpdatedDataCall.pcscf ? aUpdatedDataCall.pcscf.split(" ") : []
+      pcscf: aUpdatedDataCall.pcscf ? aUpdatedDataCall.pcscf.split(" ") : [],
+      mtu: aUpdatedDataCall.mtu > 0 ? aUpdatedDataCall.mtu : 0
     };
 
     switch (dataCallState) {
       case NETWORK_STATE_CONNECTED:
         if (this.state == NETWORK_STATE_CONNECTED) {
           let result =
             this._compareDataCallLink(newLinkInfo, this.linkInfo);
 
@@ -1168,16 +1175,17 @@ DataCall.prototype = {
           if (DEBUG) {
             this.debug("Data link minor change, just update and notify.");
           }
 
           this.linkInfo.addresses = newLinkInfo.addresses.slice();
           this.linkInfo.gateways = newLinkInfo.gateways.slice();
           this.linkInfo.dnses = newLinkInfo.dnses.slice();
           this.linkInfo.pcscf = newLinkInfo.pcscf.slice();
+          this.linkInfo.mtu = newLinkInfo.mtu;
         }
         break;
       case NETWORK_STATE_DISCONNECTED:
       case NETWORK_STATE_UNKNOWN:
         if (this.state == NETWORK_STATE_CONNECTED) {
           // Notify first on unexpected data call disconnection.
           this.state = dataCallState;
           for (let i = 0; i < this.requestedNetworkIfaces.length; i++) {
@@ -1267,16 +1275,17 @@ DataCall.prototype = {
 
   resetLinkInfo: function() {
     this.linkInfo.cid = null;
     this.linkInfo.ifname = null;
     this.linkInfo.addresses = [];
     this.linkInfo.dnses = [];
     this.linkInfo.gateways = [];
     this.linkInfo.pcscf = [];
+    this.linkInfo.mtu = null;
   },
 
   reset: function() {
     this.resetLinkInfo();
 
     this.state = NETWORK_STATE_UNKNOWN;
   },
 
@@ -1661,16 +1670,21 @@ RILNetworkInterface.prototype = {
   get httpProxyHost() {
     return this.apnSetting.proxy || "";
   },
 
   get httpProxyPort() {
     return this.apnSetting.port || "";
   },
 
+  get mtu() {
+    // Value provided by network has higher priority than apn settings.
+    return this.dataCall.linkInfo.mtu || this.apnSetting.mtu || -1;
+  },
+
   // Helpers
 
   debug: function(aMsg) {
     dump("-*- RILNetworkInterface[" + this.dataCallHandler.clientId + ":" +
          this.info.type + "]: " + aMsg + "\n");
   },
 
   get connected() {
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -94,16 +94,17 @@ function ExtraNetworkInfo(aNetwork) {
   this.type = aNetwork.info.type;
   this.name = aNetwork.info.name;
   this.ips = ips.value;
   this.prefixLengths = prefixLengths.value;
   this.gateways = aNetwork.info.getGateways();
   this.dnses = aNetwork.info.getDnses();
   this.httpProxyHost = aNetwork.httpProxyHost;
   this.httpProxyPort = aNetwork.httpProxyPort;
+  this.mtu = aNetwork.mtu;
 }
 ExtraNetworkInfo.prototype = {
   getAddresses: function(aIps, aPrefixLengths) {
     aIps.value = this.ips.slice();
     aPrefixLengths.value = this.prefixLengths.slice();
 
     return this.ips.length;
   },
@@ -364,16 +365,23 @@ NetworkManager.prototype = {
                 Ci.nsINetworkInfo.NETWORK_TYPE_MOBILE_DUN) {
               return;
             }
             // Dun type is a special case where we add the default route to a
             // secondary table.
             return this.setSecondaryDefaultRoute(extNetworkInfo);
           })
           .then(() => this._addSubnetRoutes(extNetworkInfo))
+          .then(() => {
+            if (extNetworkInfo.mtu <= 0) {
+              return;
+            }
+
+            return this._setMtu(extNetworkInfo);
+          })
           .then(() => this.setAndConfigureActive())
           .then(() => {
             // Update data connection when Wifi connected/disconnected
             if (extNetworkInfo.type ==
                 Ci.nsINetworkInfo.NETWORK_TYPE_WIFI && this.mRil) {
               for (let i = 0; i < this.mRil.numRadioInterfaces; i++) {
                 this.mRil.getRadioInterface(i).updateRILNetworkInterface();
               }
@@ -938,16 +946,28 @@ NetworkManager.prototype = {
           aReject("setDNS failed");
           return;
         }
         aResolve();
       });
     });
   },
 
+  _setMtu: function(aNetworkInfo) {
+    return new Promise((aResolve, aReject) => {
+      gNetworkService.setMtu(aNetworkInfo.name, aNetworkInfo.mtu, (aSuccess) => {
+        if (!aSuccess) {
+          debug("setMtu failed");
+        }
+        // Always resolve.
+        aResolve();
+      });
+    });
+  },
+
   _createNetwork: function(aInterfaceName) {
     return new Promise((aResolve, aReject) => {
       gNetworkService.createNetwork(aInterfaceName, (aSuccess) => {
         if (!aSuccess) {
           aReject("createNetwork failed");
           return;
         }
         aResolve();
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -796,11 +796,25 @@ NetworkService.prototype = {
         if (result.error) {
           aReject(result.reason);
           return;
         }
         aResolve(result.netId);
       });
     });
   },
+
+  setMtu: function (aInterfaceName, aMtu, aCallback) {
+    debug("Set MTU on " + aInterfaceName + ": " + aMtu);
+
+    let params = {
+      cmd: "setMtu",
+      ifname: aInterfaceName,
+      mtu: aMtu
+    };
+
+    this.controlMessage(params, function(aResult) {
+      aCallback.nativeCommandResult(!aResult.error);
+    });
+  }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkService]);
--- a/dom/system/gonk/NetworkUtils.cpp
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -1433,16 +1433,27 @@ void NetworkUtils::enableIpv6(CommandCha
 
 void NetworkUtils::disableIpv6(CommandChain* aChain,
                                CommandCallback aCallback,
                                NetworkResultOptions& aResult)
 {
   setIpv6Enabled(aChain, aCallback, aResult, false);
 }
 
+void NetworkUtils::setMtu(CommandChain* aChain,
+                          CommandCallback aCallback,
+                          NetworkResultOptions& aResult)
+{
+  char command[MAX_COMMAND_SIZE];
+  PR_snprintf(command, MAX_COMMAND_SIZE - 1, "interface setmtu %s %d",
+              GET_CHAR(mIfname), GET_FIELD(mMtu));
+
+  doCommand(command, aChain, aCallback);
+}
+
 #undef GET_CHAR
 #undef GET_FIELD
 
 /*
  * Netd command success/fail function
  */
 #define ASSIGN_FIELD(prop)  aResult.prop = aChain->getParams().prop;
 #define ASSIGN_FIELD_VALUE(prop, value)  aResult.prop = value;
@@ -1671,16 +1682,17 @@ void NetworkUtils::ExecuteCommand(Networ
     BUILD_ENTRY(dhcpRequest),
     BUILD_ENTRY(stopDhcp),
     BUILD_ENTRY(enableInterface),
     BUILD_ENTRY(disableInterface),
     BUILD_ENTRY(resetConnections),
     BUILD_ENTRY(createNetwork),
     BUILD_ENTRY(destroyNetwork),
     BUILD_ENTRY(getNetId),
+    BUILD_ENTRY(setMtu),
 
     #undef BUILD_ENTRY
   };
 
   // Loop until we find the command name which matches aOptions.mCmd.
   CommandHandler handler = nullptr;
   for (size_t i = 0; i < mozilla::ArrayLength(COMMAND_HANDLER_TABLE); i++) {
     if (aOptions.mCmd.EqualsASCII(COMMAND_HANDLER_TABLE[i].mCommandName)) {
@@ -2709,16 +2721,33 @@ CommandResult NetworkUtils::getNetId(Net
   NetIdManager::NetIdInfo netIdInfo;
   if (-1 == mNetIdManager.lookup(GET_FIELD(mIfname), &netIdInfo)) {
     return ESRCH;
   }
   result.mNetId.AppendInt(netIdInfo.mNetId, 10);
   return result;
 }
 
+CommandResult NetworkUtils::setMtu(NetworkParams& aOptions)
+{
+  // Setting/getting mtu is supported since Kitkat.
+  if (SDK_VERSION < 19) {
+    ERROR("setMtu is not supported in current SDK_VERSION.");
+    return -1;
+  }
+
+  static CommandFunc COMMAND_CHAIN[] = {
+    setMtu,
+    defaultAsyncSuccessHandler,
+  };
+
+  runChain(aOptions, COMMAND_CHAIN, defaultAsyncFailureHandler);
+  return CommandResult::Pending();
+}
+
 void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
 {
   NetworkResultOptions result;
   switch(code) {
     case NETD_COMMAND_INTERFACE_CHANGE:
       result.mTopic = NS_ConvertUTF8toUTF16("netd-interface-change");
       break;
     case NETD_COMMAND_BANDWIDTH_CONTROLLER:
--- a/dom/system/gonk/NetworkUtils.h
+++ b/dom/system/gonk/NetworkUtils.h
@@ -139,16 +139,17 @@ public:
     COPY_OPT_STRING_FIELD(mCurInternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mCurExternalIfname, EmptyString())
     COPY_OPT_FIELD(mThreshold, -1)
     COPY_OPT_FIELD(mIpaddr, 0)
     COPY_OPT_FIELD(mMask, 0)
     COPY_OPT_FIELD(mGateway_long, 0)
     COPY_OPT_FIELD(mDns1_long, 0)
     COPY_OPT_FIELD(mDns2_long, 0)
+    COPY_OPT_FIELD(mMtu, 0)
 
     mLoopIndex = 0;
 
 #undef COPY_SEQUENCE_FIELD
 #undef COPY_OPT_STRING_FIELD
 #undef COPY_OPT_FIELD
 #undef COPY_FIELD
   }
@@ -191,16 +192,17 @@ public:
   nsString mCurInternalIfname;
   nsString mCurExternalIfname;
   long mThreshold;
   long mIpaddr;
   long mMask;
   long mGateway_long;
   long mDns1_long;
   long mDns2_long;
+  long mMtu;