Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 17 Apr 2015 13:10:10 +0200
changeset 239600 ade4423fb17de9a31ed359d79079ffd02cbb0b7e
parent 239599 7a458d7e3e4f9e8a91b6c48445c12acd4abebaf4 (diff)
parent 239551 725af2a560d806778b8b1e535bba3e23d1d99f31 (current diff)
child 239601 9be6e8044ca3dd8427ec29dae9da7334a436c35f
push id12444
push userryanvm@gmail.com
push dateFri, 17 Apr 2015 20:04:42 +0000
treeherderfx-team@560a202db924 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone40.0a1
Merge mozilla-central to b2g-inbound
--- 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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
--- 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="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="46e1877c0d88b085f7ebc5f432d5bb8f1e2d1f3b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- 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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
--- 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="6b0721ca0e92788df30daf8f7a5fb2863544f9c8">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
--- 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="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="46e1877c0d88b085f7ebc5f432d5bb8f1e2d1f3b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <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="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "3cd0a9facce26c2acc7be3755a17131a6358e33f", 
+        "git_revision": "3b38ebb44d3313bd39e7378b21d004c6b84eecf2", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "c5c31cccfb22048626664d063643ae1fb1e2facc", 
+    "revision": "665ff1dedd6cd1f379cdcfa618d16edf40948016", 
     "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="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- 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="6b0721ca0e92788df30daf8f7a5fb2863544f9c8">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="3cd0a9facce26c2acc7be3755a17131a6358e33f"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="3b38ebb44d3313bd39e7378b21d004c6b84eecf2"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="f2f2f0cbee2f2517070dd194051d509c07cdacff"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a1ba73572c713e298ae53821d000fc3a5087b5f"/>
--- a/dom/apps/AppsService.js
+++ b/dom/apps/AppsService.js
@@ -142,13 +142,23 @@ AppsService.prototype = {
           return Services.io.newURI(to, null, null);
         }
       }
     }
     // No matching redirect.
     return null;
   },
 
+  getScopeByLocalId: function(aLocalId) {
+    debug("getScopeByLocalId( " + aLocalId + " )");
+    if (this.isInvalidId(aLocalId)) {
+      return null;
+    }
+    // TODO : implement properly!
+    // We just return null for now to not break PushService.jsm
+    return null;
+  },
+
   classID : APPS_SERVICE_CID,
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -687,16 +687,17 @@ GK_ATOM(onblocked, "onblocked")
 GK_ATOM(onblur, "onblur")
 GK_ATOM(onbroadcast, "onbroadcast")
 GK_ATOM(onbusy, "onbusy")
 GK_ATOM(oncached, "oncached")
 GK_ATOM(oncallschanged, "oncallschanged")
 GK_ATOM(oncardstatechange, "oncardstatechange")
 GK_ATOM(oncfstatechange, "oncfstatechange")
 GK_ATOM(onchange, "onchange")
+GK_ATOM(oncharacteristicchanged, "oncharacteristicchanged")
 GK_ATOM(onchargingchange, "onchargingchange")
 GK_ATOM(onchargingtimechange, "onchargingtimechange")
 GK_ATOM(onchecking, "onchecking")
 GK_ATOM(onclick, "onclick")
 GK_ATOM(onclirmodechange, "onclirmodechange")
 GK_ATOM(onclose, "onclose")
 GK_ATOM(oncommand, "oncommand")
 GK_ATOM(oncommandupdate, "oncommandupdate")
--- a/dom/bluetooth/BluetoothCommon.h
+++ b/dom/bluetooth/BluetoothCommon.h
@@ -223,16 +223,22 @@ extern bool gBluetoothDebugFlag;
 
 /**
  * When receiving a query about current play status from remote device, we'll
  * dispatch an event.
  */
 #define REQUEST_MEDIA_PLAYSTATUS_ID          "requestmediaplaystatus"
 
 /**
+ * When the value of a characteristic of a remote BLE device changes, we'll
+ * dispatch an event
+ */
+#define GATT_CHARACTERISTIC_CHANGED_ID       "characteristicchanged"
+
+/**
  * When a remote BLE device gets connected / disconnected, we'll dispatch an
  * event
  */
 #define GATT_CONNECTION_STATE_CHANGED_ID     "connectionstatechanged"
 
 // 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"
@@ -600,19 +606,73 @@ struct BluetoothAvrcpNotificationParam {
 struct BluetoothAvrcpPlayerSettings {
   uint8_t mNumAttr;
   uint8_t mIds[256];
   uint8_t mValues[256];
 };
 
 enum BluetoothGattStatus {
   GATT_STATUS_SUCCESS,
-  GATT_STATUS_ERROR
+  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,
+  GATT_STATUS_INVALID_OFFSET,
+  GATT_STATUS_INSUFFICIENT_AUTHORIZATION,
+  GATT_STATUS_PREPARE_QUEUE_FULL,
+  GATT_STATUS_ATTRIBUTE_NOT_FOUND,
+  GATT_STATUS_ATTRIBUTE_NOT_LONG,
+  GATT_STATUS_INSUFFICIENT_ENCRYPTION_KEY_SIZE,
+  GATT_STATUS_INVALID_ATTRIBUTE_LENGTH,
+  GATT_STATUS_UNLIKELY_ERROR,
+  GATT_STATUS_INSUFFICIENT_ENCRYPTION,
+  GATT_STATUS_UNSUPPORTED_GROUP_TYPE,
+  GATT_STATUS_INSUFFICIENT_RESOURCES,
+  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
 };
 
+enum BluetoothGattWriteType {
+  GATT_WRITE_TYPE_NO_RESPONSE,
+  GATT_WRITE_TYPE_NORMAL,
+  GATT_WRITE_TYPE_PREPARE,
+  GATT_WRITE_TYPE_SIGNED,
+  GATT_WRITE_TYPE_END_GUARD
+};
+
+/*
+ * Bluetooth GATT Characteristic Properties bit field
+ */
+enum BluetoothGattCharPropBit {
+  GATT_CHAR_PROP_BIT_BROADCAST            = (1 << 0),
+  GATT_CHAR_PROP_BIT_READ                 = (1 << 1),
+  GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE    = (1 << 2),
+  GATT_CHAR_PROP_BIT_WRITE                = (1 << 3),
+  GATT_CHAR_PROP_BIT_NOTIFY               = (1 << 4),
+  GATT_CHAR_PROP_BIT_INDICATE             = (1 << 5),
+  GATT_CHAR_PROP_BIT_SIGNED_WRITE         = (1 << 6),
+  GATT_CHAR_PROP_BIT_EXTENDED_PROPERTIES  = (1 << 7)
+};
+
+/*
+ * BluetoothGattCharProp is used to store a bit mask value which contains
+ * each corresponding bit value of each BluetoothGattCharPropBit.
+ */
+typedef uint8_t BluetoothGattCharProp;
+#define BLUETOOTH_EMPTY_GATT_CHAR_PROP  static_cast<BluetoothGattCharProp>(0x00)
+
 struct BluetoothGattAdvData {
   uint8_t mAdvData[62];
 };
 
 struct BluetoothGattId {
   BluetoothUuid mUuid;
   uint8_t mInstanceId;
 
@@ -627,16 +687,29 @@ struct BluetoothGattServiceId {
   uint8_t mIsPrimary;
 
   bool operator==(const BluetoothGattServiceId& aOther) const
   {
     return mId == aOther.mId && mIsPrimary == aOther.mIsPrimary;
   }
 };
 
+struct BluetoothGattCharAttribute {
+  BluetoothGattId mId;
+  BluetoothGattCharProp mProperties;
+  BluetoothGattWriteType mWriteType;
+
+  bool operator==(const BluetoothGattCharAttribute& aOther) const
+  {
+    return mId == aOther.mId &&
+           mProperties == aOther.mProperties &&
+           mWriteType == aOther.mWriteType;
+  }
+};
+
 struct BluetoothGattReadParam {
   BluetoothGattServiceId mServiceId;
   BluetoothGattId mCharId;
   BluetoothGattId mDescriptorId;
   uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
   uint16_t mValueLength;
   uint16_t mValueType;
   uint8_t mStatus;
@@ -650,14 +723,14 @@ struct BluetoothGattWriteParam {
 };
 
 struct BluetoothGattNotifyParam {
   uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
   nsString mBdAddr;
   BluetoothGattServiceId mServiceId;
   BluetoothGattId mCharId;
   uint16_t mLength;
-  uint8_t mIsNotify;
+  bool mIsNotify;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_bluetoothcommon_h__
--- a/dom/bluetooth/BluetoothInterface.h
+++ b/dom/bluetooth/BluetoothInterface.h
@@ -530,17 +530,17 @@ public:
                            const BluetoothGattServiceId& aServiceId)
   { }
 
   virtual void
   GetCharacteristicNotification(int aConnId,
                                 BluetoothGattStatus aStatus,
                                 const BluetoothGattServiceId& aServiceId,
                                 const BluetoothGattId& aCharId,
-                                int aCharProperty)
+                                const BluetoothGattCharProp& aCharProperty)
   { }
 
   virtual void
   GetDescriptorNotification(int aConnId,
                             BluetoothGattStatus aStatus,
                             const BluetoothGattServiceId& aServiceId,
                             const BluetoothGattId& aCharId,
                             const BluetoothGattId& aDescriptorId)
@@ -750,40 +750,38 @@ public:
                              bool aFirst,
                              const BluetoothGattId& aDescriptorId,
                              BluetoothGattClientResultHandler* aRes) = 0;
 
   /* Read / Write An Attribute */
   virtual void ReadCharacteristic(int aConnId,
                                   const BluetoothGattServiceId& aServiceId,
                                   const BluetoothGattId& aCharId,
-                                  int aAuthReq,
+                                  BluetoothGattAuthReq aAuthReq,
                                   BluetoothGattClientResultHandler* aRes) = 0;
   virtual void WriteCharacteristic(int aConnId,
                                    const BluetoothGattServiceId& aServiceId,
                                    const BluetoothGattId& aCharId,
-                                   int aWriteType,
-                                   int aLen,
-                                   int aAuthReq,
-                                   const ArrayBuffer& aValue,
+                                   BluetoothGattWriteType aWriteType,
+                                   BluetoothGattAuthReq aAuthReq,
+                                   const nsTArray<uint8_t>& aValue,
                                    BluetoothGattClientResultHandler* aRes) = 0;
   virtual void ReadDescriptor(int aConnId,
                               const BluetoothGattServiceId& aServiceId,
                               const BluetoothGattId& aCharId,
                               const BluetoothGattId& aDescriptorId,
-                              int aAuthReq,
+                              BluetoothGattAuthReq aAuthReq,
                               BluetoothGattClientResultHandler* aRes) = 0;
   virtual void WriteDescriptor(int aConnId,
                                const BluetoothGattServiceId& aServiceId,
                                const BluetoothGattId& aCharId,
                                const BluetoothGattId& aDescriptorId,
-                               int aWriteType,
-                               int aLen,
-                               int aAuthReq,
-                               const ArrayBuffer& aValue,
+                               BluetoothGattWriteType aWriteType,
+                               BluetoothGattAuthReq aAuthReq,
+                               const nsTArray<uint8_t>& aValue,
                                BluetoothGattClientResultHandler* aRes) = 0;
 
   /* Execute / Abort Prepared Write*/
   virtual void ExecuteWrite(int aConnId,
                             int aIsExecute,
                             BluetoothGattClientResultHandler* aRes) = 0;
 
 
--- a/dom/bluetooth/bluedroid/BluetoothGattHALInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattHALInterface.cpp
@@ -129,19 +129,19 @@ struct BluetoothGattClientCallback
     GattClientNotificationHandlerWrapper, void,
     int, BluetoothGattServiceId,
     int, const BluetoothGattServiceId&>
     SearchResultNotification;
 
   typedef BluetoothNotificationHALRunnable5<
     GattClientNotificationHandlerWrapper, void,
     int, BluetoothGattStatus, BluetoothGattServiceId,
-    BluetoothGattId, int,
+    BluetoothGattId, BluetoothGattCharProp,
     int, BluetoothGattStatus, const BluetoothGattServiceId&,
-    const BluetoothGattId&>
+    const BluetoothGattId&, const BluetoothGattCharProp&>
     GetCharacteristicNotification;
 
   typedef BluetoothNotificationHALRunnable5<
     GattClientNotificationHandlerWrapper, void,
     int, BluetoothGattStatus, BluetoothGattServiceId,
     BluetoothGattId, BluetoothGattId,
     int, BluetoothGattStatus, const BluetoothGattServiceId&,
     const BluetoothGattId&, const BluetoothGattId&>
@@ -740,28 +740,30 @@ BluetoothGattClientHALInterface::GetDesc
       aRes, &BluetoothGattClientResultHandler::GetDescriptor,
       ConvertDefault(status, STATUS_FAIL));
   }
 }
 
 void
 BluetoothGattClientHALInterface::ReadCharacteristic(
   int aConnId, const BluetoothGattServiceId& aServiceId,
-  const BluetoothGattId& aCharId, int aAuthReq,
+  const BluetoothGattId& aCharId, BluetoothGattAuthReq aAuthReq,
   BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   btgatt_srvc_id_t serviceId;
   btgatt_gatt_id_t charId;
+  int authReq;
 
   if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
-      NS_SUCCEEDED(Convert(aCharId, charId))) {
+      NS_SUCCEEDED(Convert(aCharId, charId)) &&
+      NS_SUCCEEDED(Convert(aAuthReq, authReq))) {
     status = mInterface->read_characteristic(aConnId, &serviceId, &charId,
-                                             aAuthReq);
+                                             authReq);
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
 
   if (aRes) {
@@ -769,32 +771,35 @@ BluetoothGattClientHALInterface::ReadCha
       aRes, &BluetoothGattClientResultHandler::ReadCharacteristic,
       ConvertDefault(status, STATUS_FAIL));
   }
 }
 
 void
 BluetoothGattClientHALInterface::WriteCharacteristic(
   int aConnId, const BluetoothGattServiceId& aServiceId,
-  const BluetoothGattId& aCharId, int aWriteType, int aLen,
-  int aAuthReq, const ArrayBuffer& aValue,
+  const BluetoothGattId& aCharId, BluetoothGattWriteType aWriteType,
+  BluetoothGattAuthReq aAuthReq, const nsTArray<uint8_t>& aValue,
   BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   btgatt_srvc_id_t serviceId;
   btgatt_gatt_id_t charId;
-  char value[aLen + 1];
+  int writeType;
+  int authReq;
 
   if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
       NS_SUCCEEDED(Convert(aCharId, charId)) &&
-      NS_SUCCEEDED(Convert(aValue, value))) {
-    status = mInterface->write_characteristic(aConnId, &serviceId, &charId,
-                                              aWriteType, aLen, aAuthReq,
-                                              value);
+      NS_SUCCEEDED(Convert(aWriteType, writeType)) &&
+      NS_SUCCEEDED(Convert(aAuthReq, authReq))) {
+    status = mInterface->write_characteristic(
+      aConnId, &serviceId, &charId, writeType,
+      aValue.Length() * sizeof(uint8_t), authReq,
+      reinterpret_cast<char*>(const_cast<uint8_t*>(aValue.Elements())));
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
 
   if (aRes) {
@@ -804,29 +809,31 @@ BluetoothGattClientHALInterface::WriteCh
   }
 }
 
 void
 BluetoothGattClientHALInterface::ReadDescriptor(
   int aConnId, const BluetoothGattServiceId& aServiceId,
   const BluetoothGattId& aCharId,
   const BluetoothGattId& aDescriptorId,
-  int aAuthReq, BluetoothGattClientResultHandler* aRes)
+  BluetoothGattAuthReq aAuthReq, BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   btgatt_srvc_id_t serviceId;
   btgatt_gatt_id_t charId;
   btgatt_gatt_id_t descriptorId;
+  int authReq;
 
   if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
       NS_SUCCEEDED(Convert(aCharId, charId)) &&
-      NS_SUCCEEDED(Convert(aDescriptorId, descriptorId))) {
+      NS_SUCCEEDED(Convert(aDescriptorId, descriptorId)) &&
+      NS_SUCCEEDED(Convert(aAuthReq, authReq))) {
     status = mInterface->read_descriptor(aConnId, &serviceId, &charId,
-                                         &descriptorId, aAuthReq);
+                                         &descriptorId, authReq);
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
 
   if (aRes) {
@@ -836,34 +843,38 @@ BluetoothGattClientHALInterface::ReadDes
   }
 }
 
 void
 BluetoothGattClientHALInterface::WriteDescriptor(
   int aConnId, const BluetoothGattServiceId& aServiceId,
   const BluetoothGattId& aCharId,
   const BluetoothGattId& aDescriptorId,
-  int aWriteType, int aLen, int aAuthReq,
-  const ArrayBuffer& aValue,
+  BluetoothGattWriteType aWriteType,
+  BluetoothGattAuthReq aAuthReq,
+  const nsTArray<uint8_t>& aValue,
   BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   btgatt_srvc_id_t serviceId;
   btgatt_gatt_id_t charId;
   btgatt_gatt_id_t descriptorId;
-  char value[aLen + 1];
+  int writeType;
+  int authReq;
 
   if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
       NS_SUCCEEDED(Convert(aCharId, charId)) &&
       NS_SUCCEEDED(Convert(aDescriptorId, descriptorId)) &&
-      NS_SUCCEEDED(Convert(aValue, value))) {
-    status = mInterface->write_descriptor(aConnId, &serviceId, &charId,
-                                          &descriptorId, aWriteType, aLen,
-                                          aAuthReq, value);
+      NS_SUCCEEDED(Convert(aWriteType, writeType)) &&
+      NS_SUCCEEDED(Convert(aAuthReq, authReq))) {
+    status = mInterface->write_descriptor(
+      aConnId, &serviceId, &charId, &descriptorId, writeType,
+      aValue.Length() * sizeof(uint8_t), authReq,
+      reinterpret_cast<char*>(const_cast<uint8_t*>(aValue.Elements())));
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
 
   if (aRes) {
--- a/dom/bluetooth/bluedroid/BluetoothGattHALInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothGattHALInterface.h
@@ -75,40 +75,38 @@ public:
                      bool aFirst,
                      const BluetoothGattId& aDescriptorId,
                      BluetoothGattClientResultHandler* aRes);
 
   /* Read / Write An Attribute */
   void ReadCharacteristic(int aConnId,
                           const BluetoothGattServiceId& aServiceId,
                           const BluetoothGattId& aCharId,
-                          int aAuthReq,
+                          BluetoothGattAuthReq aAuthReq,
                           BluetoothGattClientResultHandler* aRes);
   void WriteCharacteristic(int aConnId,
                            const BluetoothGattServiceId& aServiceId,
                            const BluetoothGattId& aCharId,
-                           int aWriteType,
-                           int aLen,
-                           int aAuthReq,
-                           const ArrayBuffer& aValue,
+                           BluetoothGattWriteType aWriteType,
+                           BluetoothGattAuthReq aAuthReq,
+                           const nsTArray<uint8_t>& aValue,
                            BluetoothGattClientResultHandler* aRes);
   void ReadDescriptor(int aConnId,
                       const BluetoothGattServiceId& aServiceId,
                       const BluetoothGattId& aCharId,
                       const BluetoothGattId& aDescriptorId,
-                      int aAuthReq,
+                      BluetoothGattAuthReq aAuthReq,
                       BluetoothGattClientResultHandler* aRes);
   void WriteDescriptor(int aConnId,
                        const BluetoothGattServiceId& aServiceId,
                        const BluetoothGattId& aCharId,
                        const BluetoothGattId& aDescriptorId,
-                       int aWriteType,
-                       int aLen,
-                       int aAuthReq,
-                       const ArrayBuffer& aValue,
+                       BluetoothGattWriteType aWriteType,
+                       BluetoothGattAuthReq aAuthReq,
+                       const nsTArray<uint8_t>& aValue,
                        BluetoothGattClientResultHandler* aRes);
 
   /* Execute / Abort Prepared Write*/
   void ExecuteWrite(int aConnId,
                     int aIsExecute,
                     BluetoothGattClientResultHandler* aRes);
 
 
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.cpp
@@ -34,16 +34,104 @@ namespace {
   static BluetoothGattInterface* sBluetoothGattInterface;
   static BluetoothGattClientInterface* sBluetoothGattClientInterface;
 } // anonymous namespace
 
 bool BluetoothGattManager::mInShutdown = false;
 
 static StaticAutoPtr<nsTArray<nsRefPtr<BluetoothGattClient> > > sClients;
 
+struct BluetoothGattClientReadCharState
+{
+  bool mAuthRetry;
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  void Assign(bool aAuthRetry,
+              BluetoothReplyRunnable* aRunnable)
+  {
+    mAuthRetry = aAuthRetry;
+    mRunnable = aRunnable;
+  }
+
+  void Reset()
+  {
+    mAuthRetry = false;
+    mRunnable = nullptr;
+  }
+};
+
+struct BluetoothGattClientWriteCharState
+{
+  BluetoothGattWriteType mWriteType;
+  nsTArray<uint8_t> mWriteValue;
+  bool mAuthRetry;
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  void Assign(BluetoothGattWriteType aWriteType,
+              const nsTArray<uint8_t>& aWriteValue,
+              bool aAuthRetry,
+              BluetoothReplyRunnable* aRunnable)
+  {
+    mWriteType = aWriteType;
+    mWriteValue = aWriteValue;
+    mAuthRetry = aAuthRetry;
+    mRunnable = aRunnable;
+  }
+
+  void Reset()
+  {
+    mWriteType = GATT_WRITE_TYPE_NORMAL;
+    mWriteValue.Clear();
+    mAuthRetry = false;
+    mRunnable = nullptr;
+  }
+};
+
+struct BluetoothGattClientReadDescState
+{
+  bool mAuthRetry;
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  void Assign(bool aAuthRetry,
+              BluetoothReplyRunnable* aRunnable)
+  {
+    mAuthRetry = aAuthRetry;
+    mRunnable = aRunnable;
+  }
+
+  void Reset()
+  {
+    mAuthRetry = false;
+    mRunnable = nullptr;
+  }
+};
+
+struct BluetoothGattClientWriteDescState
+{
+  nsTArray<uint8_t> mWriteValue;
+  bool mAuthRetry;
+  nsRefPtr<BluetoothReplyRunnable> mRunnable;
+
+  void Assign(const nsTArray<uint8_t>& aWriteValue,
+              bool aAuthRetry,
+              BluetoothReplyRunnable* aRunnable)
+  {
+    mWriteValue = aWriteValue;
+    mAuthRetry = aAuthRetry;
+    mRunnable = aRunnable;
+  }
+
+  void Reset()
+  {
+    mWriteValue.Clear();
+    mAuthRetry = false;
+    mRunnable = nullptr;
+  }
+};
+
 class mozilla::dom::bluetooth::BluetoothGattClient final : public nsISupports
 {
 public:
   NS_DECL_ISUPPORTS
 
   BluetoothGattClient(const nsAString& aAppUuid, const nsAString& aDeviceAddr)
   : mAppUuid(aAppUuid)
   , mDeviceAddr(aDeviceAddr)
@@ -55,16 +143,20 @@ public:
   {
     mConnectRunnable = nullptr;
     mDisconnectRunnable = nullptr;
     mDiscoverRunnable = nullptr;
     mUnregisterClientRunnable = nullptr;
     mReadRemoteRssiRunnable = nullptr;
     mRegisterNotificationsRunnable = nullptr;
     mDeregisterNotificationsRunnable = nullptr;
+    mReadCharacteristicState.Reset();
+    mWriteCharacteristicState.Reset();
+    mReadDescriptorState.Reset();
+    mWriteDescriptorState.Reset();
   }
 
   void NotifyDiscoverCompleted(bool aSuccess)
   {
     MOZ_ASSERT(!mAppUuid.IsEmpty());
     MOZ_ASSERT(mDiscoverRunnable);
 
     BluetoothService* bs = BluetoothService::Get();
@@ -99,23 +191,28 @@ public:
   nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDiscoverRunnable;
   nsRefPtr<BluetoothReplyRunnable> mReadRemoteRssiRunnable;
   nsRefPtr<BluetoothReplyRunnable> mRegisterNotificationsRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDeregisterNotificationsRunnable;
   nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
 
+  BluetoothGattClientReadCharState mReadCharacteristicState;
+  BluetoothGattClientWriteCharState mWriteCharacteristicState;
+  BluetoothGattClientReadDescState mReadDescriptorState;
+  BluetoothGattClientWriteDescState mWriteDescriptorState;
+
   /**
    * These temporary arrays are used only during discover operations.
    * All of them are empty if there are no ongoing discover operations.
    */
   nsTArray<BluetoothGattServiceId> mServices;
   nsTArray<BluetoothGattServiceId> mIncludedServices;
-  nsTArray<BluetoothGattId> mCharacteristics;
+  nsTArray<BluetoothGattCharAttribute> mCharacteristics;
   nsTArray<BluetoothGattId> mDescriptors;
 };
 
 NS_IMPL_ISUPPORTS0(BluetoothGattClient)
 
 class UuidComparator
 {
 public:
@@ -780,16 +877,347 @@ BluetoothGattManager::DeregisterNotifica
 
   client->mDeregisterNotificationsRunnable = aRunnable;
 
   sBluetoothGattClientInterface->DeregisterNotification(
     client->mClientIf, client->mDeviceAddr, aServId, aCharId,
     new DeregisterNotificationsResultHandler(client));
 }
 
+class BluetoothGattManager::ReadCharacteristicValueResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  ReadCharacteristicValueResultHandler(BluetoothGattClient* aClient)
+    : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::ReadCharacteristicValue failed" \
+               ": %d", (int)aStatus);
+    MOZ_ASSERT(mClient->mReadCharacteristicState.mRunnable);
+
+    nsRefPtr<BluetoothReplyRunnable> runnable =
+      mClient->mReadCharacteristicState.mRunnable;
+    mClient->mReadCharacteristicState.Reset();
+
+    // Reject the read characteristic value request
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("ReadCharacteristicValue failed"));
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::ReadCharacteristicValue(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    // Reject the read characteristic value request
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("ReadCharacteristicValue failed"));
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  /**
+   * Reject subsequent reading requests to follow ATT sequential protocol that
+   * handles one request at a time. Otherwise underlying layers would drop the
+   * subsequent requests silently.
+   *
+   * Bug 1147776 intends to solve a larger problem that other kind of requests
+   * may still interfere the ongoing request.
+   */
+  if (client->mReadCharacteristicState.mRunnable) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("ReadCharacteristicValue failed"));
+    return;
+  }
+
+  client->mReadCharacteristicState.Assign(false, aRunnable);
+
+  /**
+   * First, read the characteristic value through an unauthenticated physical
+   * link. If the operation fails due to insufficient authentication/encryption
+   * key size, retry to read through an authenticated physical link.
+   */
+  sBluetoothGattClientInterface->ReadCharacteristic(
+    client->mConnId,
+    aServiceId,
+    aCharacteristicId,
+    GATT_AUTH_REQ_NONE,
+    new ReadCharacteristicValueResultHandler(client));
+}
+
+class BluetoothGattManager::WriteCharacteristicValueResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  WriteCharacteristicValueResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::WriteCharacteristicValue failed" \
+               ": %d", (int)aStatus);
+    MOZ_ASSERT(mClient->mWriteCharacteristicState.mRunnable);
+
+    nsRefPtr<BluetoothReplyRunnable> runnable =
+      mClient->mWriteCharacteristicState.mRunnable;
+    mClient->mWriteCharacteristicState.Reset();
+
+    // Reject the write characteristic value request
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("WriteCharacteristicValue failed"));
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::WriteCharacteristicValue(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattWriteType& aWriteType,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    // Reject the write characteristic value request
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("WriteCharacteristicValue failed"));
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  /**
+   * Reject subsequent writing requests to follow ATT sequential protocol that
+   * handles one request at a time. Otherwise underlying layers would drop the
+   * subsequent requests silently.
+   *
+   * Bug 1147776 intends to solve a larger problem that other kind of requests
+   * may still interfere the ongoing request.
+   */
+  if (client->mWriteCharacteristicState.mRunnable) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("WriteCharacteristicValue failed"));
+    return;
+  }
+
+  client->mWriteCharacteristicState.Assign(aWriteType, aValue, false, aRunnable);
+
+  /**
+   * First, write the characteristic value through an unauthenticated physical
+   * link. If the operation fails due to insufficient authentication/encryption
+   * key size, retry to write through an authenticated physical link.
+   */
+  sBluetoothGattClientInterface->WriteCharacteristic(
+    client->mConnId,
+    aServiceId,
+    aCharacteristicId,
+    aWriteType,
+    GATT_AUTH_REQ_NONE,
+    aValue,
+    new WriteCharacteristicValueResultHandler(client));
+}
+
+class BluetoothGattManager::ReadDescriptorValueResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  ReadDescriptorValueResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::ReadDescriptorValue failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mReadDescriptorState.mRunnable);
+
+    nsRefPtr<BluetoothReplyRunnable> runnable =
+      mClient->mReadDescriptorState.mRunnable;
+    mClient->mReadDescriptorState.Reset();
+
+    // Reject the read descriptor value request
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("ReadDescriptorValue failed"));
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::ReadDescriptorValue(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    // Reject the read descriptor value request
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("ReadDescriptorValue failed"));
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  /**
+   * Reject subsequent reading requests to follow ATT sequential protocol that
+   * handles one request at a time. Otherwise underlying layers would drop the
+   * subsequent requests silently.
+   *
+   * Bug 1147776 intends to solve a larger problem that other kind of requests
+   * may still interfere the ongoing request.
+   */
+  if (client->mReadDescriptorState.mRunnable) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("ReadDescriptorValue failed"));
+    return;
+  }
+
+  client->mReadDescriptorState.Assign(false, aRunnable);
+
+  /**
+   * First, read the descriptor value through an unauthenticated physical
+   * link. If the operation fails due to insufficient authentication/encryption
+   * key size, retry to read through an authenticated physical link.
+   */
+  sBluetoothGattClientInterface->ReadDescriptor(
+    client->mConnId,
+    aServiceId,
+    aCharacteristicId,
+    aDescriptorId,
+    GATT_AUTH_REQ_NONE,
+    new ReadDescriptorValueResultHandler(client));
+}
+
+class BluetoothGattManager::WriteDescriptorValueResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  WriteDescriptorValueResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::WriteDescriptorValue failed: %d",
+               (int)aStatus);
+    MOZ_ASSERT(mClient->mWriteDescriptorState.mRunnable);
+
+    nsRefPtr<BluetoothReplyRunnable> runnable =
+      mClient->mWriteDescriptorState.mRunnable;
+    mClient->mWriteDescriptorState.Reset();
+
+    // Reject the write descriptor value request
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("WriteDescriptorValue failed"));
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::WriteDescriptorValue(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  if (NS_WARN_IF(index == sClients->NoIndex)) {
+    // Reject the write descriptor value request
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("WriteDescriptorValue failed"));
+    return;
+  }
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  /**
+   * Reject subsequent writing requests to follow ATT sequential protocol that
+   * handles one request at a time. Otherwise underlying layers would drop the
+   * subsequent requests silently.
+   *
+   * Bug 1147776 intends to solve a larger problem that other kind of requests
+   * may still interfere the ongoing request.
+   */
+  if (client->mWriteDescriptorState.mRunnable) {
+    DispatchReplyError(aRunnable,
+                       NS_LITERAL_STRING("WriteDescriptorValue failed"));
+    return;
+  }
+
+  /**
+   * First, write the descriptor value through an unauthenticated physical
+   * link. If the operation fails due to insufficient authentication/encryption
+   * key size, retry to write through an authenticated physical link.
+   */
+  client->mWriteDescriptorState.Assign(aValue, false, aRunnable);
+
+  sBluetoothGattClientInterface->WriteDescriptor(
+    client->mConnId,
+    aServiceId,
+    aCharacteristicId,
+    aDescriptorId,
+    GATT_WRITE_TYPE_NORMAL,
+    GATT_AUTH_REQ_NONE,
+    aValue,
+    new WriteDescriptorValueResultHandler(client));
+}
+
 //
 // Notification Handlers
 //
 void
 BluetoothGattManager::RegisterClientNotification(BluetoothGattStatus aStatus,
                                                  int aClientIf,
                                                  const BluetoothUuid& aAppUuid)
 {
@@ -1005,34 +1433,42 @@ BluetoothGattManager::SearchResultNotifi
   sClients->ElementAt(index)->mServices.AppendElement(aServiceId);
 }
 
 void
 BluetoothGattManager::GetCharacteristicNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattServiceId& aServiceId,
   const BluetoothGattId& aCharId,
-  int aCharProperty)
+  const BluetoothGattCharProp& aCharProperty)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
                                    ConnIdComparator());
   MOZ_ASSERT(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   MOZ_ASSERT(client->mDiscoverRunnable);
 
   if (aStatus == GATT_STATUS_SUCCESS) {
+    BluetoothGattCharAttribute attribute;
+    attribute.mId = aCharId;
+    attribute.mProperties = aCharProperty;
+    attribute.mWriteType =
+      aCharProperty & GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE
+        ? GATT_WRITE_TYPE_NO_RESPONSE
+        : GATT_WRITE_TYPE_NORMAL;
+
     // Save to mCharacteristics for distributing to applications and
     // discovering descriptors of this characteristic later
-    client->mCharacteristics.AppendElement(aCharId);
+    client->mCharacteristics.AppendElement(attribute);
 
     // Get next characteristic of this service
     sBluetoothGattClientInterface->GetCharacteristic(
       aConnId,
       aServiceId,
       false,
       aCharId,
       new DiscoverResultHandler(client));
@@ -1166,41 +1602,250 @@ BluetoothGattManager::RegisterNotificati
    *
    * Please see Bug 1149043 for more information.
    */
 }
 
 void
 BluetoothGattManager::NotifyNotification(
   int aConnId, const BluetoothGattNotifyParam& aNotifyParam)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  // Notify BluetoothGattCharacteristic to update characteristic value
+  nsString path;
+  GeneratePathFromGattId(aNotifyParam.mCharId, path);
+
+  nsTArray<uint8_t> value;
+  value.AppendElements(aNotifyParam.mValue, aNotifyParam.mLength);
+
+  bs->DistributeSignal(NS_LITERAL_STRING("CharacteristicValueUpdated"),
+                       path,
+                       BluetoothValue(value));
+
+  // Notify BluetoothGatt for characteristic changed
+  nsTArray<BluetoothNamedValue> ids;
+  ids.AppendElement(BluetoothNamedValue(NS_LITERAL_STRING("serviceId"),
+                                        aNotifyParam.mServiceId));
+  ids.AppendElement(BluetoothNamedValue(NS_LITERAL_STRING("charId"),
+                                        aNotifyParam.mCharId));
+
+  bs->DistributeSignal(NS_LITERAL_STRING(GATT_CHARACTERISTIC_CHANGED_ID),
+                       client->mAppUuid,
+                       BluetoothValue(ids));
+}
 
 void
 BluetoothGattManager::ReadCharacteristicNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattReadParam& aReadParam)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  MOZ_ASSERT(client->mReadCharacteristicState.mRunnable);
+  nsRefPtr<BluetoothReplyRunnable> runnable =
+    client->mReadCharacteristicState.mRunnable;
+
+  if (aStatus == GATT_STATUS_SUCCESS) {
+    client->mReadCharacteristicState.Reset();
+    // Notify BluetoothGattCharacteristic to update characteristic value
+    nsString path;
+    GeneratePathFromGattId(aReadParam.mCharId, path);
+
+    nsTArray<uint8_t> value;
+    value.AppendElements(aReadParam.mValue, aReadParam.mValueLength);
+
+    bs->DistributeSignal(NS_LITERAL_STRING("CharacteristicValueUpdated"),
+                         path,
+                         BluetoothValue(value));
+
+    // Notify BluetoothGatt for characteristic changed
+    nsTArray<BluetoothNamedValue> ids;
+    ids.AppendElement(BluetoothNamedValue(NS_LITERAL_STRING("serviceId"),
+                                          aReadParam.mServiceId));
+    ids.AppendElement(BluetoothNamedValue(NS_LITERAL_STRING("charId"),
+                                          aReadParam.mCharId));
+
+    bs->DistributeSignal(NS_LITERAL_STRING(GATT_CHARACTERISTIC_CHANGED_ID),
+                         client->mAppUuid,
+                         BluetoothValue(ids));
+
+    // Resolve the promise
+    DispatchReplySuccess(runnable, BluetoothValue(value));
+  } else if (!client->mReadCharacteristicState.mAuthRetry &&
+             (aStatus == GATT_STATUS_INSUFFICIENT_AUTHENTICATION ||
+              aStatus == GATT_STATUS_INSUFFICIENT_ENCRYPTION)) {
+    client->mReadCharacteristicState.mAuthRetry = true;
+    // Retry with another authentication requirement
+    sBluetoothGattClientInterface->ReadCharacteristic(
+      aConnId,
+      aReadParam.mServiceId,
+      aReadParam.mCharId,
+      GATT_AUTH_REQ_MITM,
+      new ReadCharacteristicValueResultHandler(client));
+  } else {
+    client->mReadCharacteristicState.Reset();
+    // Reject the promise
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("ReadCharacteristicValue failed"));
+  }
+}
 
 void
 BluetoothGattManager::WriteCharacteristicNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattWriteParam& aWriteParam)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  MOZ_ASSERT(client->mWriteCharacteristicState.mRunnable);
+  nsRefPtr<BluetoothReplyRunnable> runnable =
+    client->mWriteCharacteristicState.mRunnable;
+
+  if (aStatus == GATT_STATUS_SUCCESS) {
+    client->mWriteCharacteristicState.Reset();
+    // Resolve the promise
+    DispatchReplySuccess(runnable);
+  } else if (!client->mWriteCharacteristicState.mAuthRetry &&
+             (aStatus == GATT_STATUS_INSUFFICIENT_AUTHENTICATION ||
+              aStatus == GATT_STATUS_INSUFFICIENT_ENCRYPTION)) {
+    client->mWriteCharacteristicState.mAuthRetry = true;
+    // Retry with another authentication requirement
+    sBluetoothGattClientInterface->WriteCharacteristic(
+      aConnId,
+      aWriteParam.mServiceId,
+      aWriteParam.mCharId,
+      client->mWriteCharacteristicState.mWriteType,
+      GATT_AUTH_REQ_MITM,
+      client->mWriteCharacteristicState.mWriteValue,
+      new WriteCharacteristicValueResultHandler(client));
+  } else {
+    client->mWriteCharacteristicState.Reset();
+    // Reject the promise
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("WriteCharacteristicValue failed"));
+  }
+}
 
 void
 BluetoothGattManager::ReadDescriptorNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattReadParam& aReadParam)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  MOZ_ASSERT(client->mReadDescriptorState.mRunnable);
+  nsRefPtr<BluetoothReplyRunnable> runnable =
+    client->mReadDescriptorState.mRunnable;
+
+  if (aStatus == GATT_STATUS_SUCCESS) {
+    client->mReadDescriptorState.Reset();
+    // Notify BluetoothGattDescriptor to update descriptor value
+    nsString path;
+    GeneratePathFromGattId(aReadParam.mDescriptorId, path);
+
+    nsTArray<uint8_t> value;
+    value.AppendElements(aReadParam.mValue, aReadParam.mValueLength);
+
+    bs->DistributeSignal(NS_LITERAL_STRING("DescriptorValueUpdated"),
+                         path,
+                         BluetoothValue(value));
+
+    // Resolve the promise
+    DispatchReplySuccess(runnable, BluetoothValue(value));
+  } else if (!client->mReadDescriptorState.mAuthRetry &&
+             (aStatus == GATT_STATUS_INSUFFICIENT_AUTHENTICATION ||
+              aStatus == GATT_STATUS_INSUFFICIENT_ENCRYPTION)) {
+    client->mReadDescriptorState.mAuthRetry = true;
+    // Retry with another authentication requirement
+    sBluetoothGattClientInterface->ReadDescriptor(
+      aConnId,
+      aReadParam.mServiceId,
+      aReadParam.mCharId,
+      aReadParam.mDescriptorId,
+      GATT_AUTH_REQ_MITM,
+      new ReadDescriptorValueResultHandler(client));
+  } else {
+    client->mReadDescriptorState.Reset();
+    // Reject the promise
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("ReadDescriptorValue failed"));
+  }
+}
 
 void
 BluetoothGattManager::WriteDescriptorNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattWriteParam& aWriteParam)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sClients->IndexOf(aConnId, 0 /* Start */, ConnIdComparator());
+  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+
+  MOZ_ASSERT(client->mWriteDescriptorState.mRunnable);
+  nsRefPtr<BluetoothReplyRunnable> runnable =
+    client->mWriteDescriptorState.mRunnable;
+
+  if (aStatus == GATT_STATUS_SUCCESS) {
+    client->mWriteDescriptorState.Reset();
+    // Resolve the promise
+    DispatchReplySuccess(runnable);
+  } else if (!client->mWriteDescriptorState.mAuthRetry &&
+             (aStatus == GATT_STATUS_INSUFFICIENT_AUTHENTICATION ||
+              aStatus == GATT_STATUS_INSUFFICIENT_ENCRYPTION)) {
+    client->mWriteDescriptorState.mAuthRetry = true;
+    // Retry with another authentication requirement
+    sBluetoothGattClientInterface->WriteDescriptor(
+      aConnId,
+      aWriteParam.mServiceId,
+      aWriteParam.mCharId,
+      aWriteParam.mDescriptorId,
+      GATT_WRITE_TYPE_NORMAL,
+      GATT_AUTH_REQ_MITM,
+      client->mWriteDescriptorState.mWriteValue,
+      new WriteDescriptorValueResultHandler(client));
+  } else {
+    client->mWriteDescriptorState.Reset();
+    // Reject the promise
+    DispatchReplyError(runnable,
+                       NS_LITERAL_STRING("WriteDescriptorValue failed"));
+  }
+}
 
 void
 BluetoothGattManager::ExecuteWriteNotification(int aConnId,
                                                BluetoothGattStatus aStatus)
 { }
 
 void
 BluetoothGattManager::ReadRemoteRssiNotification(int aClientIf,
@@ -1301,17 +1946,17 @@ BluetoothGattManager::ProceedDiscoverPro
    *      discover included services of the next service.
    * 3) Both arrays are already empty:
    *      Discover is done, notify application.
    */
   if (!aClient->mCharacteristics.IsEmpty()) {
     sBluetoothGattClientInterface->GetDescriptor(
       aClient->mConnId,
       aServiceId,
-      aClient->mCharacteristics[0],
+      aClient->mCharacteristics[0].mId,
       true, // first descriptor
       BluetoothGattId(),
       new DiscoverResultHandler(aClient));
     aClient->mCharacteristics.RemoveElementAt(0);
   } else if (!aClient->mServices.IsEmpty()) {
     sBluetoothGattClientInterface->GetIncludedService(
       aClient->mConnId,
       aClient->mServices[0],
--- a/dom/bluetooth/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothGattManager.h
@@ -51,28 +51,61 @@ public:
                              const BluetoothGattId& aCharId,
                              BluetoothReplyRunnable* aRunnable);
 
   void DeregisterNotifications(const nsAString& aAppUuid,
                                const BluetoothGattServiceId& aServId,
                                const BluetoothGattId& aCharId,
                                BluetoothReplyRunnable* aRunnable);
 
+  void ReadCharacteristicValue(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    BluetoothReplyRunnable* aRunnable);
+
+  void WriteCharacteristicValue(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattWriteType& aWriteType,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable);
+
+  void ReadDescriptorValue(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    BluetoothReplyRunnable* aRunnable);
+
+  void WriteDescriptorValue(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable);
+
 private:
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
   class RegisterClientResultHandler;
   class UnregisterClientResultHandler;
   class ConnectResultHandler;
   class DisconnectResultHandler;
   class DiscoverResultHandler;
   class ReadRemoteRssiResultHandler;
   class RegisterNotificationsResultHandler;
   class DeregisterNotificationsResultHandler;
+  class ReadCharacteristicValueResultHandler;
+  class WriteCharacteristicValueResultHandler;
+  class ReadDescriptorValueResultHandler;
+  class WriteDescriptorValueResultHandler;
 
   BluetoothGattManager();
 
   void HandleShutdown();
 
   void RegisterClientNotification(BluetoothGattStatus aStatus,
                                   int aClientIf,
                                   const BluetoothUuid& aAppUuid) override;
@@ -97,17 +130,17 @@ private:
   void SearchResultNotification(int aConnId,
                                 const BluetoothGattServiceId& aServiceId)
                                 override;
 
   void GetCharacteristicNotification(
     int aConnId, BluetoothGattStatus aStatus,
     const BluetoothGattServiceId& aServiceId,
     const BluetoothGattId& aCharId,
-    int aCharProperty) override;
+    const BluetoothGattCharProp& aCharProperty) override;
 
   void GetDescriptorNotification(
     int aConnId, BluetoothGattStatus aStatus,
     const BluetoothGattServiceId& aServiceId,
     const BluetoothGattId& aCharId,
     const BluetoothGattId& aDescriptorId) override;
 
   void GetIncludedServiceNotification(
--- a/dom/bluetooth/bluedroid/BluetoothHALHelpers.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothHALHelpers.cpp
@@ -316,17 +316,17 @@ Convert(const btgatt_notify_params_t& aI
   rv = Convert(aIn.srvc_id, aOut.mServiceId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = Convert(aIn.char_id, aOut.mCharId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   memcpy(aOut.mValue, aIn.value, aIn.len);
   aOut.mLength = aIn.len;
-  aOut.mIsNotify = aIn.is_notify;
+  aOut.mIsNotify = (aIn.is_notify != 0);
 
   return NS_OK;
 }
 #endif // ANDROID_VERSION >= 19
 #else
 // TODO: Support GATT
 #endif
 
--- a/dom/bluetooth/bluedroid/BluetoothHALHelpers.h
+++ b/dom/bluetooth/bluedroid/BluetoothHALHelpers.h
@@ -778,33 +778,108 @@ Convert(btrc_remote_features_t aIn, unsi
   return NS_OK;
 }
 #endif // ANDROID_VERSION >= 19
 
 #ifdef MOZ_B2G_BT_API_V2
 inline nsresult
 Convert(int aIn, BluetoothGattStatus& aOut)
 {
-  /**
-   * Currently we only map bluedroid's GATT status into GATT_STATUS_SUCCESS and
-   * GATT_STATUS_ERROR. This function needs to be revised if we want to support
-   * specific error status.
-   */
-  if (!aIn) {
-    aOut = GATT_STATUS_SUCCESS;
+  /* Reference: $B2G/external/bluetooth/bluedroid/stack/include/gatt_api.h */
+  static const BluetoothGattStatus sGattStatus[] = {
+    CONVERT(0x0000, GATT_STATUS_SUCCESS),
+    CONVERT(0x0001, GATT_STATUS_INVALID_HANDLE),
+    CONVERT(0x0002, GATT_STATUS_READ_NOT_PERMITTED),
+    CONVERT(0x0003, GATT_STATUS_WRITE_NOT_PERMITTED),
+    CONVERT(0x0004, GATT_STATUS_INVALID_PDU),
+    CONVERT(0x0005, GATT_STATUS_INSUFFICIENT_AUTHENTICATION),
+    CONVERT(0x0006, GATT_STATUS_REQUEST_NOT_SUPPORTED),
+    CONVERT(0x0007, GATT_STATUS_INVALID_OFFSET),
+    CONVERT(0x0008, GATT_STATUS_INSUFFICIENT_AUTHORIZATION),
+    CONVERT(0x0009, GATT_STATUS_PREPARE_QUEUE_FULL),
+    CONVERT(0x000a, GATT_STATUS_ATTRIBUTE_NOT_FOUND),
+    CONVERT(0x000b, GATT_STATUS_ATTRIBUTE_NOT_LONG),
+    CONVERT(0x000c, GATT_STATUS_INSUFFICIENT_ENCRYPTION_KEY_SIZE),
+    CONVERT(0x000d, GATT_STATUS_INVALID_ATTRIBUTE_LENGTH),
+    CONVERT(0x000e, GATT_STATUS_UNLIKELY_ERROR),
+    CONVERT(0x000f, GATT_STATUS_INSUFFICIENT_ENCRYPTION),
+    CONVERT(0x0010, GATT_STATUS_UNSUPPORTED_GROUP_TYPE),
+    CONVERT(0x0011, GATT_STATUS_INSUFFICIENT_RESOURCES)
+  };
+  if (static_cast<uint32_t>(aIn) >= MOZ_ARRAY_LENGTH(sGattStatus)) {
+    aOut = GATT_STATUS_UNKNOWN_ERROR;
   } else {
-    aOut = GATT_STATUS_ERROR;
+    aOut = sGattStatus[aIn];
   }
-
   return NS_OK;
 }
 
 nsresult
 Convert(const uint8_t* aIn, BluetoothGattAdvData& aOut);
 
+inline nsresult
+Convert(int aIn, BluetoothGattCharProp& aOut)
+{
+  /* Reference: $B2G/external/bluetooth/bluedroid/stack/include/gatt_api.h */
+  static const uint8_t sGattCharPropBit[] = {
+    CONVERT(0, GATT_CHAR_PROP_BIT_BROADCAST),
+    CONVERT(1, GATT_CHAR_PROP_BIT_READ),
+    CONVERT(2, GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE),
+    CONVERT(3, GATT_CHAR_PROP_BIT_WRITE),
+    CONVERT(4, GATT_CHAR_PROP_BIT_NOTIFY),
+    CONVERT(5, GATT_CHAR_PROP_BIT_INDICATE),
+    CONVERT(6, GATT_CHAR_PROP_BIT_SIGNED_WRITE),
+    CONVERT(7, GATT_CHAR_PROP_BIT_EXTENDED_PROPERTIES)
+  };
+  aOut = BLUETOOTH_EMPTY_GATT_CHAR_PROP;
+  for (uint8_t i = 0; i < MOZ_ARRAY_LENGTH(sGattCharPropBit); i++) {
+    if (aIn & (1 << i)) {
+      aOut |= sGattCharPropBit[i];
+    }
+  }
+  return NS_OK;
+}
+
+inline nsresult
+Convert(BluetoothGattAuthReq aIn, int& aOut)
+{
+  /* Reference: $B2G/external/bluetooth/bluedroid/stack/include/gatt_api.h */
+  static const int sGattAuthReq[] = {
+    CONVERT(GATT_AUTH_REQ_NONE, 0),
+    CONVERT(GATT_AUTH_REQ_NO_MITM, 1),
+    CONVERT(GATT_AUTH_REQ_MITM, 2),
+    CONVERT(GATT_AUTH_REQ_SIGNED_NO_MITM, 3),
+    CONVERT(GATT_AUTH_REQ_SIGNED_MITM, 4)
+  };
+  if (aIn >= MOZ_ARRAY_LENGTH(sGattAuthReq)) {
+    aOut = GATT_AUTH_REQ_NONE; // silences compiler warning
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  aOut = sGattAuthReq[aIn];
+  return NS_OK;
+}
+
+inline nsresult
+Convert(BluetoothGattWriteType aIn, int& aOut)
+{
+  /* Reference: $B2G/external/bluetooth/bluedroid/stack/include/gatt_api.h */
+  static const int sGattWriteType[] = {
+    CONVERT(GATT_WRITE_TYPE_NO_RESPONSE, 1),
+    CONVERT(GATT_WRITE_TYPE_NORMAL, 2),
+    CONVERT(GATT_WRITE_TYPE_PREPARE, 3),
+    CONVERT(GATT_WRITE_TYPE_SIGNED, 4)
+  };
+  if (aIn >= MOZ_ARRAY_LENGTH(sGattWriteType)) {
+    aOut = GATT_WRITE_TYPE_NORMAL; // silences compiler warning
+    return NS_ERROR_ILLEGAL_VALUE;
+  }
+  aOut = sGattWriteType[aIn];
+  return NS_OK;
+}
+
 #if ANDROID_VERSION >= 19
 nsresult
 Convert(const BluetoothGattId& aIn, btgatt_gatt_id_t& aOut);
 
 nsresult
 Convert(const btgatt_gatt_id_t& aIn, BluetoothGattId& aOut);
 
 nsresult
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -484,16 +484,93 @@ BluetoothServiceBluedroid::GattClientRea
 
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
   BluetoothGattManager* gatt = BluetoothGattManager::Get();
   ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
 
   gatt->ReadRemoteRssi(aClientIf, aDeviceAddress, aRunnable);
 }
+
+void
+BluetoothServiceBluedroid::GattClientReadCharacteristicValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  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->ReadCharacteristicValue(aAppUuid, aServiceId, aCharacteristicId,
+                                aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattClientWriteCharacteristicValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattWriteType& aWriteType,
+  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->WriteCharacteristicValue(aAppUuid, aServiceId, aCharacteristicId,
+                                 aWriteType, aValue, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattClientReadDescriptorValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  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->ReadDescriptorValue(aAppUuid, aServiceId, aCharacteristicId,
+                            aDescriptorId, aRunnable);
+}
+
+void
+BluetoothServiceBluedroid::GattClientWriteDescriptorValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  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->WriteDescriptorValue(aAppUuid, aServiceId, aCharacteristicId,
+                             aDescriptorId, aValue, aRunnable);
+}
 #else
 
 #define ENSURE_BLUETOOTH_IS_READY(runnable, result)                    \
   do {                                                                 \
     if (!sBtInterface || !IsEnabled()) {                               \
       NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth is not ready");     \
       DispatchBluetoothReply(runnable, BluetoothValue(), errorStr);    \
       return result;                                                   \
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -223,16 +223,49 @@ public:
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadRemoteRssiInternal(
     int aClientIf, const nsAString& aDeviceAddress,
     BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattClientReadCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientWriteCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattWriteType& aWriteType,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientReadDescriptorValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    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) override;
+
   //
   // Bluetooth notifications
   //
 
   virtual void AdapterStateChangedNotification(bool aState) override;
   virtual void AdapterPropertiesNotification(
     BluetoothStatus aStatus, int aNumProperties,
     const BluetoothProperty* aProperties) override;
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.cpp
@@ -6,16 +6,17 @@
 
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGatt.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothGattBinding.h"
+#include "mozilla/dom/BluetoothGattCharacteristicEvent.h"
 #include "mozilla/dom/Promise.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
@@ -273,16 +274,52 @@ BluetoothGatt::HandleServicesDiscovered(
     mServices.AppendElement(new BluetoothGattService(
       GetParentObject(), mAppUuid, serviceIds[i]));
   }
 
   BluetoothGattBinding::ClearCachedServicesValue(this);
 }
 
 void
+BluetoothGatt::HandleCharacteristicChanged(const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const InfallibleTArray<BluetoothNamedValue>& ids =
+    aValue.get_ArrayOfBluetoothNamedValue();
+  MOZ_ASSERT(ids.Length() == 2); // ServiceId, CharId
+  MOZ_ASSERT(ids[0].name().EqualsLiteral("serviceId"));
+  MOZ_ASSERT(ids[0].value().type() == BluetoothValue::TBluetoothGattServiceId);
+  MOZ_ASSERT(ids[1].name().EqualsLiteral("charId"));
+  MOZ_ASSERT(ids[1].value().type() == BluetoothValue::TBluetoothGattId);
+
+  size_t index = mServices.IndexOf(ids[0].value().get_BluetoothGattServiceId());
+  NS_ENSURE_TRUE_VOID(index != mServices.NoIndex);
+
+  nsRefPtr<BluetoothGattService> service = mServices.ElementAt(index);
+  nsTArray<nsRefPtr<BluetoothGattCharacteristic>> chars;
+  service->GetCharacteristics(chars);
+
+  index = chars.IndexOf(ids[1].value().get_BluetoothGattId());
+  NS_ENSURE_TRUE_VOID(index != chars.NoIndex);
+  nsRefPtr<BluetoothGattCharacteristic> characteristic = chars.ElementAt(index);
+
+  // Dispatch characteristicchanged event to application
+  BluetoothGattCharacteristicEventInit init;
+  init.mCharacteristic = characteristic;
+  nsRefPtr<BluetoothGattCharacteristicEvent> event =
+    BluetoothGattCharacteristicEvent::Constructor(
+      this,
+      NS_LITERAL_STRING(GATT_CHARACTERISTIC_CHANGED_ID),
+      init);
+
+  DispatchTrustedEvent(event);
+}
+
+void
 BluetoothGatt::Notify(const BluetoothSignal& aData)
 {
   BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("ClientRegistered")) {
     MOZ_ASSERT(v.type() == BluetoothValue::Tuint32_t);
     mClientIf = v.get_uint32_t();
@@ -302,16 +339,18 @@ BluetoothGatt::Notify(const BluetoothSig
 
     bool isDiscoverSuccess = v.get_bool();
     if (!isDiscoverSuccess) { // Clean all discovered attributes if failed
       mServices.Clear();
       BluetoothGattBinding::ClearCachedServicesValue(this);
     }
 
     mDiscoveringServices = false;
+  } else if (aData.name().EqualsLiteral(GATT_CHARACTERISTIC_CHANGED_ID)) {
+    HandleCharacteristicChanged(v);
   } else {
     BT_WARNING("Not handling GATT signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
 JSObject*
 BluetoothGatt::WrapObject(JSContext* aContext,
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.h
@@ -45,16 +45,17 @@ public:
   void GetServices(nsTArray<nsRefPtr<BluetoothGattService>>& aServices) const
   {
     aServices = mServices;
   }
 
   /****************************************************************************
    * Event Handlers
    ***************************************************************************/
+  IMPL_EVENT_HANDLER(characteristicchanged);
   IMPL_EVENT_HANDLER(connectionstatechanged);
 
   /****************************************************************************
    * Methods (Web API Implementation)
    ***************************************************************************/
   already_AddRefed<Promise> Connect(ErrorResult& aRv);
   already_AddRefed<Promise> Disconnect(ErrorResult& aRv);
   already_AddRefed<Promise> DiscoverServices(ErrorResult& aRv);
@@ -99,16 +100,30 @@ private:
    * Add newly discovered GATT services into mServices and update the cache
    * value of mServices.
    *
    * @param aValue [in] BluetoothValue which contains an array of
    *                    BluetoothGattServiceId of all discovered services.
    */
   void HandleServicesDiscovered(const BluetoothValue& aValue);
 
+  /**
+   * The value of a GATT characteristic has changed. In the mean time, the
+   * cached value of this GATT characteristic has already been updated. An
+   * 'characteristicchanged' event will be fired by this function.
+   *
+   * @param aValue [in] BluetoothValue which contains an array of
+   *                    BluetoothNamedValue. There are exact two elements in
+   *                    the array. The first element uses 'serviceId' as the
+   *                    name and uses BluetoothGattServiceId as the value. The
+   *                    second element uses 'charId' as the name and uses
+   *                    BluetoothGattId as the value.
+   */
+  void HandleCharacteristicChanged(const BluetoothValue& aValue);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   /**
    * Random generated UUID of this GATT client.
    */
   nsString mAppUuid;
 
--- a/dom/bluetooth/bluetooth2/BluetoothGattCharacteristic.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothGattCharacteristic.cpp
@@ -27,20 +27,22 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Bluetoo
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattCharacteristic)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 BluetoothGattCharacteristic::BluetoothGattCharacteristic(
   nsPIDOMWindow* aOwner,
   BluetoothGattService* aService,
-  const BluetoothGattId& aCharId)
+  const BluetoothGattCharAttribute& aChar)
   : mOwner(aOwner)
   , mService(aService)
-  , mCharId(aCharId)
+  , mCharId(aChar.mId)
+  , mProperties(aChar.mProperties)
+  , mWriteType(aChar.mWriteType)
 {
   MOZ_ASSERT(aOwner);
   MOZ_ASSERT(mService);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   // Generate bluetooth signal path and a string representation to provide uuid
@@ -131,27 +133,173 @@ BluetoothGattCharacteristic::HandleDescr
     mDescriptors.AppendElement(new BluetoothGattDescriptor(
       GetParentObject(), this, descriptorIds[i]));
   }
 
   BluetoothGattCharacteristicBinding::ClearCachedDescriptorsValue(this);
 }
 
 void
+BluetoothGattCharacteristic::HandleCharacteristicValueUpdated(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfuint8_t);
+
+  mValue = aValue.get_ArrayOfuint8_t();
+}
+
+void
 BluetoothGattCharacteristic::Notify(const BluetoothSignal& aData)
 {
   BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
 
   BluetoothValue v = aData.value();
   if (aData.name().EqualsLiteral("DescriptorsDiscovered")) {
     HandleDescriptorsDiscovered(v);
+  } else if (aData.name().EqualsLiteral("CharacteristicValueUpdated")) {
+    HandleCharacteristicValueUpdated(v);
   } else {
     BT_WARNING("Not handling GATT Characteristic signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
 JSObject*
 BluetoothGattCharacteristic::WrapObject(JSContext* aContext,
                                         JS::Handle<JSObject*> aGivenProto)
 {
   return BluetoothGattCharacteristicBinding::Wrap(aContext, this, aGivenProto);
 }
+
+void
+BluetoothGattCharacteristic::GetValue(JSContext* cx,
+                                      JS::MutableHandle<JSObject*> aValue) const
+{
+  MOZ_ASSERT(aValue);
+
+  aValue.set(mValue.IsEmpty()
+             ? nullptr
+             : ArrayBuffer::Create(cx, mValue.Length(), mValue.Elements()));
+}
+
+void
+BluetoothGattCharacteristic::GetProperties(
+  mozilla::dom::GattCharacteristicProperties& aProperties) 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;
+}
+
+class ReadValueTask final : public BluetoothReplyRunnable
+{
+public:
+  ReadValueTask(BluetoothGattCharacteristic* aCharacteristic, Promise* aPromise)
+    : BluetoothReplyRunnable(
+        nullptr, aPromise,
+        NS_LITERAL_STRING("GattClientReadCharacteristicValue"))
+    , mCharacteristic(aCharacteristic)
+  {
+    MOZ_ASSERT(aCharacteristic);
+    MOZ_ASSERT(aPromise);
+  }
+
+  bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
+  {
+    aValue.setUndefined();
+
+    const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::TArrayOfuint8_t, false);
+
+    AutoJSAPI jsapi;
+    NS_ENSURE_TRUE(jsapi.Init(mCharacteristic->GetParentObject()), false);
+
+    JSContext* cx = jsapi.cx();
+    if (!ToJSValue(cx, v.get_ArrayOfuint8_t(), aValue)) {
+      JS_ClearPendingException(cx);
+      return false;
+    }
+
+    return true;
+  }
+
+  void
+  ReleaseMembers()
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mCharacteristic = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+};
+
+already_AddRefed<Promise>
+BluetoothGattCharacteristic::ReadValue(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(mProperties & GATT_CHAR_PROP_BIT_READ,
+                        NS_ERROR_NOT_AVAILABLE);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result = new ReadValueTask(this, promise);
+  bs->GattClientReadCharacteristicValueInternal(mService->GetAppUuid(),
+                                                mService->GetServiceId(),
+                                                mCharId,
+                                                result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGattCharacteristic::WriteValue(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(mProperties &
+                          (GATT_CHAR_PROP_BIT_WRITE_NO_RESPONSE ||
+                           GATT_CHAR_PROP_BIT_WRITE ||
+                           GATT_CHAR_PROP_BIT_SIGNED_WRITE),
+                        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, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result = new BluetoothVoidReplyRunnable(
+    nullptr, promise, NS_LITERAL_STRING("GattClientWriteCharacteristicValue"));
+  bs->GattClientWriteCharacteristicValueInternal(mService->GetAppUuid(),
+                                                 mService->GetServiceId(),
+                                                 mCharId,
+                                                 mWriteType,
+                                                 value,
+                                                 result);
+
+  return promise.forget();
+}
--- a/dom/bluetooth/bluetooth2/BluetoothGattCharacteristic.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGattCharacteristic.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_bluetooth_bluetoothgattcharacteristic_h__
 #define mozilla_dom_bluetooth_bluetoothgattcharacteristic_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BluetoothGattCharacteristicBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
+#include "mozilla/dom/TypedArray.h"
 #include "nsCOMPtr.h"
 #include "nsWrapperCache.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 class Promise;
 }
@@ -54,16 +55,27 @@ public:
     aUuidStr = mUuidStr;
   }
 
   int InstanceId() const
   {
     return mCharId.mInstanceId;
   }
 
+  void GetValue(JSContext* cx, JS::MutableHandle<JSObject*> aValue) 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);
 
   /****************************************************************************
    * Others
@@ -80,30 +92,37 @@ public:
      return mOwner;
   }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   BluetoothGattCharacteristic(nsPIDOMWindow* aOwner,
                               BluetoothGattService* aService,
-                              const BluetoothGattId& aCharId);
+                              const BluetoothGattCharAttribute& aChar);
 
 private:
   ~BluetoothGattCharacteristic();
 
   /**
    * Add newly discovered GATT descriptors into mDescriptors and update the
    * cache value of mDescriptors.
    *
    * @param aValue [in] BluetoothValue which contains an array of
    *                    BluetoothGattId of all discovered descriptors.
    */
   void HandleDescriptorsDiscovered(const BluetoothValue& aValue);
 
+  /**
+   * Update the value of this characteristic.
+   *
+   * @param aValue [in] BluetoothValue which contains an uint8_t array.
+   */
+  void HandleCharacteristicValueUpdated(const BluetoothValue& aValue);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * Service that this characteristic belongs to.
    */
@@ -120,13 +139,49 @@ private:
    * 2) mInstanceId: Instance id of this characteristic.
    */
   BluetoothGattId mCharId;
 
   /**
    * UUID string of this GATT characteristic.
    */
   nsString mUuidStr;
+
+  /**
+   * Value of this GATT characteristic.
+   */
+  nsTArray<uint8_t> mValue;
+
+  /**
+   * Properties of this GATT characteristic.
+   */
+  BluetoothGattCharProp mProperties;
+
+  /**
+   * Write type of this GATT characteristic.
+   */
+  BluetoothGattWriteType mWriteType;
 };
 
 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<BluetoothGattCharacteristic>' and 'BluetoothGattId' properly,
+ * including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>,
+  mozilla::dom::bluetooth::BluetoothGattId> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattCharacteristic>& aChar,
+    const mozilla::dom::bluetooth::BluetoothGattId& aCharId) const
+  {
+    return aChar->GetCharacteristicId() == aCharId;
+  }
+};
+
 #endif
--- a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.cpp
@@ -1,20 +1,22 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.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;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(
@@ -32,23 +34,172 @@ BluetoothGattDescriptor::BluetoothGattDe
   const BluetoothGattId& aDescriptorId)
   : mOwner(aOwner)
   , mCharacteristic(aCharacteristic)
   , mDescriptorId(aDescriptorId)
 {
   MOZ_ASSERT(aOwner);
   MOZ_ASSERT(aCharacteristic);
 
-  // Generate a string representation to provide uuid of this descriptor to
-  // applications
-  ReversedUuidToString(aDescriptorId.mUuid, mUuidStr);
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  // Generate bluetooth signal path and a string representation to provide uuid
+  // of this descriptor to applications
+  nsString path;
+  GeneratePathFromGattId(mDescriptorId, path, mUuidStr);
+  bs->RegisterBluetoothSignalHandler(path, this);
 }
 
 BluetoothGattDescriptor::~BluetoothGattDescriptor()
 {
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  nsString path;
+  GeneratePathFromGattId(mDescriptorId, path);
+  bs->UnregisterBluetoothSignalHandler(path, this);
+}
+
+void
+BluetoothGattDescriptor::HandleDescriptorValueUpdated(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfuint8_t);
+
+  mValue = aValue.get_ArrayOfuint8_t();
+}
+
+void
+BluetoothGattDescriptor::Notify(const BluetoothSignal& aData)
+{
+  BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
+
+  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());
+  }
 }
 
 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
+{
+  MOZ_ASSERT(aValue);
+
+  aValue.set(mValue.IsEmpty()
+             ? nullptr
+             : ArrayBuffer::Create(cx, mValue.Length(), mValue.Elements()));
+}
+
+class ReadValueTask final : public BluetoothReplyRunnable
+{
+public:
+  ReadValueTask(BluetoothGattDescriptor* aDescriptor, Promise* aPromise)
+    : BluetoothReplyRunnable(
+        nullptr, aPromise,
+        NS_LITERAL_STRING("GattClientReadDescriptorValue"))
+    , mDescriptor(aDescriptor)
+  {
+    MOZ_ASSERT(aDescriptor);
+    MOZ_ASSERT(aPromise);
+  }
+
+  bool
+  ParseSuccessfulReply(JS::MutableHandle<JS::Value> aValue)
+  {
+    aValue.setUndefined();
+
+    const BluetoothValue& v = mReply->get_BluetoothReplySuccess().value();
+    NS_ENSURE_TRUE(v.type() == BluetoothValue::TArrayOfuint8_t, false);
+
+    AutoJSAPI jsapi;
+    NS_ENSURE_TRUE(jsapi.Init(mDescriptor->GetParentObject()), false);
+
+    JSContext* cx = jsapi.cx();
+    if (!ToJSValue(cx, v.get_ArrayOfuint8_t(), aValue)) {
+      JS_ClearPendingException(cx);
+      return false;
+    }
+
+    return true;
+  }
+
+  void
+  ReleaseMembers()
+  {
+    BluetoothReplyRunnable::ReleaseMembers();
+    mDescriptor = nullptr;
+  }
+
+private:
+  nsRefPtr<BluetoothGattDescriptor> mDescriptor;
+};
+
+already_AddRefed<Promise>
+BluetoothGattDescriptor::ReadValue(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);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result = new ReadValueTask(this, promise);
+  bs->GattClientReadDescriptorValueInternal(
+    mCharacteristic->Service()->GetAppUuid(),
+    mCharacteristic->Service()->GetServiceId(),
+    mCharacteristic->GetCharacteristicId(),
+    mDescriptorId,
+    result);
+
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+BluetoothGattDescriptor::WriteValue(
+  const RootedTypedArray<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);
+
+  aValue.ComputeLengthAndData();
+
+  nsTArray<uint8_t> value;
+  value.AppendElements(aValue.Data(), aValue.Length());
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  nsRefPtr<BluetoothReplyRunnable> result = new BluetoothVoidReplyRunnable(
+    nullptr, promise, NS_LITERAL_STRING("GattClientWriteDescriptorValue"));
+  bs->GattClientWriteDescriptorValueInternal(
+    mCharacteristic->Service()->GetAppUuid(),
+    mCharacteristic->Service()->GetServiceId(),
+    mCharacteristic->GetCharacteristicId(),
+    mDescriptorId,
+    value,
+    result);
+
+  return promise.forget();
+}
--- a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h
@@ -5,28 +5,31 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothgattdescriptor_h__
 #define mozilla_dom_bluetooth_bluetoothgattdescriptor_h__
 
 #include "mozilla/Attributes.h"
 #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"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothGattCharacteristic;
 class BluetoothSignal;
 class BluetoothValue;
 
 class BluetoothGattDescriptor final : public nsISupports
                                     , public nsWrapperCache
+                                    , public BluetoothSignalObserver
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattDescriptor)
 
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
@@ -35,16 +38,25 @@ public:
     return mCharacteristic;
   }
 
   void GetUuid(nsString& aUuidStr) const
   {
     aUuidStr = mUuidStr;
   }
 
+  void GetValue(JSContext* cx, JS::MutableHandle<JSObject*> aValue) 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
 
   nsPIDOMWindow* GetParentObject() const
   {
      return mOwner;
@@ -55,16 +67,23 @@ public:
 
   BluetoothGattDescriptor(nsPIDOMWindow* aOwner,
                           BluetoothGattCharacteristic* aCharacteristic,
                           const BluetoothGattId& aDescriptorId);
 
 private:
   ~BluetoothGattDescriptor();
 
+  /**
+   * Update the value of this descriptor.
+   *
+   * @param aValue [in] BluetoothValue which contains an uint8_t array.
+   */
+  void HandleDescriptorValueUpdated(const BluetoothValue& aValue);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   nsCOMPtr<nsPIDOMWindow> mOwner;
 
   /**
    * Characteristic that this descriptor belongs to.
    */
@@ -76,13 +95,18 @@ private:
    * 2) mInstanceId: Instance id of this descriptor.
    */
   BluetoothGattId mDescriptorId;
 
   /**
    * UUID string of this GATT descriptor.
    */
   nsString mUuidStr;
+
+  /**
+   * Value of this GATT descriptor.
+   */
+  nsTArray<uint8_t> mValue;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluetooth2/BluetoothGattService.cpp
+++ b/dom/bluetooth/bluetooth2/BluetoothGattService.cpp
@@ -72,24 +72,25 @@ BluetoothGattService::HandleIncludedServ
 
   BluetoothGattServiceBinding::ClearCachedIncludedServicesValue(this);
 }
 
 void
 BluetoothGattService::HandleCharacteristicsDiscovered(
   const BluetoothValue& aValue)
 {
-  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattId);
+  MOZ_ASSERT(aValue.type() ==
+             BluetoothValue::TArrayOfBluetoothGattCharAttribute);
 
-  const InfallibleTArray<BluetoothGattId>& characteristicIds =
-    aValue.get_ArrayOfBluetoothGattId();
+  const InfallibleTArray<BluetoothGattCharAttribute>& characteristics =
+    aValue.get_ArrayOfBluetoothGattCharAttribute();
 
-  for (uint32_t i = 0; i < characteristicIds.Length(); i++) {
+  for (uint32_t i = 0; i < characteristics.Length(); i++) {
     mCharacteristics.AppendElement(new BluetoothGattCharacteristic(
-      GetParentObject(), this, characteristicIds[i]));
+      GetParentObject(), this, characteristics[i]));
   }
 
   BluetoothGattServiceBinding::ClearCachedCharacteristicsValue(this);
 }
 
 void
 BluetoothGattService::Notify(const BluetoothSignal& aData)
 {
--- a/dom/bluetooth/bluetooth2/BluetoothGattService.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGattService.h
@@ -138,9 +138,30 @@ private:
   /**
    * Array of discovered characteristics for this service.
    */
   nsTArray<nsRefPtr<BluetoothGattCharacteristic>> mCharacteristics;
 };
 
 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<BluetoothGattService>' and 'BluetoothGattServiceId' properly,
+ * including IndexOf() and Contains();
+ */
+template <>
+class nsDefaultComparator <
+  nsRefPtr<mozilla::dom::bluetooth::BluetoothGattService>,
+  mozilla::dom::bluetooth::BluetoothGattServiceId> {
+public:
+  bool Equals(
+    const nsRefPtr<mozilla::dom::bluetooth::BluetoothGattService>& aService,
+    const mozilla::dom::bluetooth::BluetoothGattServiceId& aServiceId) const
+  {
+    return aService->GetServiceId() == aServiceId;
+  }
+};
+
 #endif
--- a/dom/bluetooth/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth/bluetooth2/BluetoothService.h
@@ -402,16 +402,65 @@ public:
   /**
    * Request RSSI for a remote GATT server. (platform specific implementation)
    */
   virtual void
   GattClientReadRemoteRssiInternal(int aClientIf,
                                    const nsAString& aDeviceAddress,
                                    BluetoothReplyRunnable* aRunnable) = 0;
 
+  /**
+   * Read the value of a characteristic on a GATT client.
+   * (platform specific implementation)
+   */
+  virtual void
+  GattClientReadCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Write the value of a characteristic on a GATT client.
+   * (platform specific implementation)
+   */
+  virtual void
+  GattClientWriteCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattWriteType& aWriteType,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Read the value of a descriptor of a characteristic on a GATT client.
+   * (platform specific implementation)
+   */
+  virtual void
+  GattClientReadDescriptorValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
+   * Write the value of a descriptor of a characteristic on a GATT client.
+   * (platform specific implementation)
+   */
+  virtual void
+  GattClientWriteDescriptorValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) = 0;
+
   bool
   IsEnabled() const
   {
     return mEnabled;
   }
 
   bool
   IsToggling() const;
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothMessageUtils.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothMessageUtils.h
@@ -32,16 +32,24 @@ template <>
 struct ParamTraits<mozilla::dom::bluetooth::BluetoothStatus>
   : public ContiguousEnumSerializer<
              mozilla::dom::bluetooth::BluetoothStatus,
              mozilla::dom::bluetooth::STATUS_SUCCESS,
              mozilla::dom::bluetooth::NUM_STATUS>
 { };
 
 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::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]);
@@ -98,11 +106,33 @@ struct ParamTraits<mozilla::dom::bluetoo
     if (!ReadParam(aMsg, aIter, &(aResult->mId)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIsPrimary))) {
       return false;
     }
 
     return true;
   }
 };
+
+template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattCharAttribute>
+{
+  typedef mozilla::dom::bluetooth::BluetoothGattCharAttribute paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mId);
+    WriteParam(aMsg, aParam.mProperties);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mId)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mProperties))) {
+      return false;
+    }
+
+    return true;
+  }
+};
 } // namespace IPC
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothmessageutils_h__
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.cpp
@@ -265,16 +265,28 @@ BluetoothParent::RecvPBluetoothRequestCo
         aRequest.get_GattClientStartNotificationsRequest());
     case Request::TGattClientStopNotificationsRequest:
       return actor->DoRequest(
         aRequest.get_GattClientStopNotificationsRequest());
     case Request::TUnregisterGattClientRequest:
       return actor->DoRequest(aRequest.get_UnregisterGattClientRequest());
     case Request::TGattClientReadRemoteRssiRequest:
       return actor->DoRequest(aRequest.get_GattClientReadRemoteRssiRequest());
+    case Request::TGattClientReadCharacteristicValueRequest:
+      return actor->DoRequest(
+        aRequest.get_GattClientReadCharacteristicValueRequest());
+    case Request::TGattClientWriteCharacteristicValueRequest:
+      return actor->DoRequest(
+        aRequest.get_GattClientWriteCharacteristicValueRequest());
+    case Request::TGattClientReadDescriptorValueRequest:
+      return actor->DoRequest(
+        aRequest.get_GattClientReadDescriptorValueRequest());
+    case Request::TGattClientWriteDescriptorValueRequest:
+      return actor->DoRequest(
+        aRequest.get_GattClientWriteDescriptorValueRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
   MOZ_CRASH("Should never get here!");
 }
 
 PBluetoothRequestParent*
@@ -821,8 +833,77 @@ BluetoothRequestParent::DoRequest(
   MOZ_ASSERT(mRequestType == Request::TGattClientReadRemoteRssiRequest);
 
   mService->GattClientReadRemoteRssiInternal(aRequest.clientIf(),
                                              aRequest.deviceAddress(),
                                              mReplyRunnable.get());
 
   return true;
 }
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattClientReadCharacteristicValueRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattClientReadCharacteristicValueRequest);
+
+  mService->GattClientReadCharacteristicValueInternal(aRequest.appUuid(),
+                                                      aRequest.serviceId(),
+                                                      aRequest.charId(),
+                                                      mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattClientWriteCharacteristicValueRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattClientWriteCharacteristicValueRequest);
+
+  mService->GattClientWriteCharacteristicValueInternal(aRequest.appUuid(),
+                                                       aRequest.serviceId(),
+                                                       aRequest.charId(),
+                                                       aRequest.writeType(),
+                                                       aRequest.value(),
+                                                       mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattClientReadDescriptorValueRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattClientReadDescriptorValueRequest);
+
+  mService->GattClientReadDescriptorValueInternal(aRequest.appUuid(),
+                                                  aRequest.serviceId(),
+                                                  aRequest.charId(),
+                                                  aRequest.descId(),
+                                                  mReplyRunnable.get());
+
+  return true;
+}
+
+bool
+BluetoothRequestParent::DoRequest(
+  const GattClientWriteDescriptorValueRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType ==
+             Request::TGattClientWriteDescriptorValueRequest);
+
+  mService->GattClientWriteDescriptorValueInternal(aRequest.appUuid(),
+                                                   aRequest.serviceId(),
+                                                   aRequest.charId(),
+                                                   aRequest.descId(),
+                                                   aRequest.value(),
+                                                   mReplyRunnable.get());
+
+  return true;
+}
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothParent.h
@@ -238,13 +238,25 @@ protected:
   bool
   DoRequest(const GattClientStopNotificationsRequest& aRequest);
 
   bool
   DoRequest(const UnregisterGattClientRequest& aRequest);
 
   bool
   DoRequest(const GattClientReadRemoteRssiRequest& aRequest);
+
+  bool
+  DoRequest(const GattClientReadCharacteristicValueRequest& aRequest);
+
+  bool
+  DoRequest(const GattClientWriteCharacteristicValueRequest& aRequest);
+
+  bool
+  DoRequest(const GattClientReadDescriptorValueRequest& aRequest);
+
+  bool
+  DoRequest(const GattClientWriteDescriptorValueRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothparent_h__
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -457,16 +457,78 @@ BluetoothServiceChildProcess::GattClient
   int aClientIf, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable,
               GattClientReadRemoteRssiRequest(aClientIf,
                                               nsString(aDeviceAddress)));
 }
 
+void
+BluetoothServiceChildProcess::GattClientReadCharacteristicValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattClientReadCharacteristicValueRequest(nsString(aAppUuid),
+                                             aServiceId,
+                                             aCharacteristicId));
+}
+
+void
+BluetoothServiceChildProcess::GattClientWriteCharacteristicValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattWriteType& aWriteType,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattClientWriteCharacteristicValueRequest(nsString(aAppUuid),
+                                              aServiceId,
+                                              aCharacteristicId,
+                                              aWriteType,
+                                              aValue));
+}
+
+void
+BluetoothServiceChildProcess::GattClientReadDescriptorValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattClientReadDescriptorValueRequest(nsString(aAppUuid),
+                                         aServiceId,
+                                         aCharacteristicId,
+                                         aDescriptorId));
+}
+
+void
+BluetoothServiceChildProcess::GattClientWriteDescriptorValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    GattClientWriteDescriptorValueRequest(nsString(aAppUuid),
+                                          aServiceId,
+                                          aCharacteristicId,
+                                          aDescriptorId,
+                                          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/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -236,16 +236,49 @@ public:
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadRemoteRssiInternal(int aClientIf,
                                    const nsAString& aDeviceAddress,
                                    BluetoothReplyRunnable* aRunnable) override;
 
+  virtual void
+  GattClientReadCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientWriteCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattWriteType& aWriteType,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientReadDescriptorValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    BluetoothReplyRunnable* aRunnable);
+
+  virtual void
+  GattClientWriteDescriptorValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable);
+
 protected:
   BluetoothServiceChildProcess();
   virtual ~BluetoothServiceChildProcess();
 
   void
   NoteDeadActor();
 
   void
--- a/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
+++ b/dom/bluetooth/bluetooth2/ipc/BluetoothTypes.ipdlh
@@ -1,18 +1,22 @@
 /* -*- 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::BluetoothGattCharAttribute
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 using mozilla::dom::bluetooth::BluetoothGattId
   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";
 
 namespace mozilla {
 namespace dom {
 namespace bluetooth {
@@ -29,16 +33,17 @@ union BluetoothValue
   bool;
   nsString[];
   uint8_t[];
   BluetoothNamedValue[];
   BluetoothGattId;
   BluetoothGattId[];
   BluetoothGattServiceId;
   BluetoothGattServiceId[];
+  BluetoothGattCharAttribute[];
 };
 
 /**
  * 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/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth/bluetooth2/ipc/PBluetooth.ipdl
@@ -227,16 +227,49 @@ struct UnregisterGattClientRequest
 };
 
 struct GattClientReadRemoteRssiRequest
 {
   int clientIf;
   nsString deviceAddress;
 };
 
+struct GattClientReadCharacteristicValueRequest
+{
+  nsString appUuid;
+  BluetoothGattServiceId serviceId;
+  BluetoothGattId charId;
+};
+
+struct GattClientWriteCharacteristicValueRequest
+{
+  nsString appUuid;
+  BluetoothGattServiceId serviceId;
+  BluetoothGattId charId;
+  BluetoothGattWriteType writeType;
+  uint8_t[] value;
+};
+
+struct GattClientReadDescriptorValueRequest
+{
+  nsString appUuid;
+  BluetoothGattServiceId serviceId;
+  BluetoothGattId charId;
+  BluetoothGattId descId;
+};
+
+struct GattClientWriteDescriptorValueRequest
+{
+  nsString appUuid;
+  BluetoothGattServiceId serviceId;
+  BluetoothGattId charId;
+  BluetoothGattId descId;
+  uint8_t[] value;
+};
+
 union Request
 {
   GetAdaptersRequest;
   StartBluetoothRequest;
   StopBluetoothRequest;
   SetPropertyRequest;
   GetPropertyRequest;
   StartDiscoveryRequest;
@@ -268,16 +301,20 @@ union Request
   SendPlayStatusRequest;
   ConnectGattClientRequest;
   DisconnectGattClientRequest;
   DiscoverGattServicesRequest;
   GattClientStartNotificationsRequest;
   GattClientStopNotificationsRequest;
   UnregisterGattClientRequest;
   GattClientReadRemoteRssiRequest;
+  GattClientReadCharacteristicValueRequest;
+  GattClientWriteCharacteristicValueRequest;
+  GattClientReadDescriptorValueRequest;
+  GattClientWriteDescriptorValueRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
 
   /**
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -4736,11 +4736,52 @@ BluetoothDBusService::UnregisterGattClie
 }
 
 void
 BluetoothDBusService::GattClientReadRemoteRssiInternal(
   int aClientIf, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
 }
+
+void
+BluetoothDBusService::GattClientReadCharacteristicValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattClientWriteCharacteristicValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattWriteType& aWriteType,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattClientReadDescriptorValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
+BluetoothDBusService::GattClientWriteDescriptorValueInternal(
+  const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId,
+  const BluetoothGattId& aCharacteristicId,
+  const BluetoothGattId& aDescriptorId,
+  const nsTArray<uint8_t>& aValue,
+  BluetoothReplyRunnable* aRunnable)
+{
+}
 #else
 // Missing in bluetooth1
 #endif
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -265,16 +265,49 @@ public:
   virtual void
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadRemoteRssiInternal(
     int aClientIf, const nsAString& aDeviceAddress,
     BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientReadCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientWriteCharacteristicValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattWriteType& aWriteType,
+    const nsTArray<uint8_t>& aValue,
+    BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  GattClientReadDescriptorValueInternal(
+    const nsAString& aAppUuid,
+    const BluetoothGattServiceId& aServiceId,
+    const BluetoothGattId& aCharacteristicId,
+    const BluetoothGattId& aDescriptorId,
+    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) override;
 #else
 // Missing in bluetooth1
 #endif
 
 private:
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
--- a/dom/camera/DOMCameraCapabilities.cpp
+++ b/dom/camera/DOMCameraCapabilities.cpp
@@ -318,16 +318,17 @@ CameraCapabilities::HasSupport(JSContext
 }
 
 CameraCapabilities::CameraCapabilities(nsPIDOMWindow* aWindow,
                                        ICameraControl* aCameraControl)
   : mWindow(aWindow)
   , mCameraControl(aCameraControl)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  MOZ_COUNT_CTOR(CameraCapabilities);
   if (mCameraControl) {
     mListener = new CameraClosedListenerProxy<CameraCapabilities>(this);
     mCameraControl->AddListener(mListener);
   }
 }
 
 CameraCapabilities::~CameraCapabilities()
 {
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -301,17 +301,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
   if (NS_FAILED(rv)) {
     mListener->OnUserError(DOMCameraControlListener::kInStartCamera, rv);
   }
 }
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
-  /*invoke DOMMdediastream destroy*/
+  /*invoke DOMMediaStream destroy*/
   Destroy();
 }
 
 JSObject*
 nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return CameraControlBinding::Wrap(aCx, this, aGivenProto);
 }
@@ -1368,20 +1368,19 @@ nsDOMCameraControl::OnFacesDetected(cons
 {
   DOM_CAMERA_LOGI("DOM OnFacesDetected %zu face(s)\n", aFaces.Length());
   MOZ_ASSERT(NS_IsMainThread());
 
   Sequence<OwningNonNull<DOMCameraDetectedFace> > faces;
   uint32_t len = aFaces.Length();
 
   if (faces.SetCapacity(len)) {
-    nsRefPtr<DOMCameraDetectedFace> f;
     for (uint32_t i = 0; i < len; ++i) {
-      f = new DOMCameraDetectedFace(static_cast<DOMMediaStream*>(this), aFaces[i]);
-      *faces.AppendElement() = f.forget().take();
+      *faces.AppendElement() =
+        new DOMCameraDetectedFace(static_cast<DOMMediaStream*>(this), aFaces[i]);
     }
   }
 
   CameraFacesDetectedEventInit eventInit;
   eventInit.mFaces.SetValue(faces);
 
   nsRefPtr<CameraFacesDetectedEvent> event =
     CameraFacesDetectedEvent::Constructor(this,
@@ -1422,16 +1421,20 @@ nsDOMCameraControl::OnUserError(CameraCo
     this, aContext, aError);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<Promise> promise;
 
   switch (aContext) {
     case CameraControlListener::kInStartCamera:
       promise = mGetCameraPromise.forget();
+      // If we failed to open the camera, we never actually provided a reference
+      // for the application to release explicitly. Thus we must clear our handle
+      // here to ensure everything is freed.
+      mCameraControl = nullptr;
       break;
 
     case CameraControlListener::kInStopCamera:
       promise = mReleasePromise.forget();
       if (aError == NS_ERROR_NOT_INITIALIZED) {
         // This value indicates that the hardware is already closed; which for
         // kInStopCamera, is not actually an error.
         if (promise) {
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -26,16 +26,17 @@
 #ifdef MOZ_WIDGET_GONK
 #include <media/mediaplayer.h>
 #include <media/MediaProfiles.h>
 #include "GrallocImages.h"
 #endif
 #include "nsCOMPtr.h"
 #include "nsMemory.h"
 #include "nsThread.h"
+#include "nsITimer.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "nsAlgorithm.h"
 #include "nsPrintfCString.h"
 #include "AutoRwLock.h"
 #include "GonkCameraHwMgr.h"
@@ -54,37 +55,47 @@ using namespace android;
   do {                                                                    \
     if (!mCameraHw.get()) {                                               \
       NS_WARNING("Camera hardware is not initialized");                   \
       DOM_CAMERA_LOGE("%s:%d : mCameraHw is null\n", __func__, __LINE__); \
       return NS_ERROR_NOT_INITIALIZED;                                    \
     }                                                                     \
   } while(0)
 
+static const unsigned long kAutoFocusCompleteTimeoutMs = 1000;
+static const int32_t kAutoFocusCompleteTimeoutLimit = 3;
+
 // Construct nsGonkCameraControl on the main thread.
 nsGonkCameraControl::nsGonkCameraControl(uint32_t aCameraId)
   : mCameraId(aCameraId)
   , mLastThumbnailSize({0, 0})
   , mPreviewFps(30)
   , mResumePreviewAfterTakingPicture(false) // XXXmikeh - see bug 950102
   , mFlashSupported(false)
   , mLuminanceSupported(false)
   , mAutoFlashModeOverridden(false)
   , mSeparateVideoAndPreviewSizesSupported(false)
   , mDeferConfigUpdate(0)
 #ifdef MOZ_WIDGET_GONK
   , mRecorder(nullptr)
 #endif
   , mRecorderMonitor("GonkCameraControl::mRecorder.Monitor")
   , mVideoFile(nullptr)
+  , mAutoFocusPending(false)
+  , mAutoFocusCompleteExpired(0)
   , mReentrantMonitor("GonkCameraControl::OnTakePicture.Monitor")
 {
   // Constructor runs on the main thread...
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mImageContainer = LayerManager::CreateImageContainer();
+
+  mAutoFocusCompleteTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+  if (NS_WARN_IF(!mAutoFocusCompleteTimer)) {
+    mAutoFocusCompleteExpired = kAutoFocusCompleteTimeoutLimit;
+  }
 }
 
 nsresult
 nsGonkCameraControl::StartImpl(const Configuration* aInitialConfig)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
 
   nsresult rv = StartInternal(aInitialConfig);
@@ -808,16 +819,22 @@ nsGonkCameraControl::AutoFocusImpl()
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   RETURN_IF_NO_CAMERA_HW();
 
   DOM_CAMERA_LOGI("Starting auto focus\n");
 
   if (mCameraHw->AutoFocus() != OK) {
     return NS_ERROR_FAILURE;
   }
+
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+  mAutoFocusPending = true;
+  if (mAutoFocusCompleteTimer) {
+    mAutoFocusCompleteTimer->Cancel();
+  }
   return NS_OK;
 }
 
 nsresult
 nsGonkCameraControl::StartFaceDetectionImpl()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mCameraThread);
   RETURN_IF_NO_CAMERA_HW();
@@ -1300,54 +1317,142 @@ nsGonkCameraControl::ResumeContinuousFoc
   // http://developer.android.com/reference/android/hardware/Camera.Parameters.html#FOCUS_MODE_CONTINUOUS_PICTURE
   if (NS_WARN_IF(mCameraHw->CancelAutoFocus() != OK)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
+class AutoFocusMovingTimerCallback : public nsITimerCallback
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  AutoFocusMovingTimerCallback(nsGonkCameraControl* aCameraControl)
+    : mCameraControl(aCameraControl)
+  { }
+
+  NS_IMETHODIMP
+  Notify(nsITimer* aTimer)
+  {
+    mCameraControl->OnAutoFocusComplete(true, true);
+    return NS_OK;
+  }
+
+protected:
+  virtual ~AutoFocusMovingTimerCallback()
+  { }
+
+  nsRefPtr<nsGonkCameraControl> mCameraControl;
+};
+
+NS_IMPL_ISUPPORTS(AutoFocusMovingTimerCallback, nsITimerCallback);
+
 void
-nsGonkCameraControl::OnAutoFocusComplete(bool aSuccess)
+nsGonkCameraControl::OnAutoFocusMoving(bool aIsMoving)
+{
+  CameraControlImpl::OnAutoFocusMoving(aIsMoving);
+
+  if (!aIsMoving) {
+    /* Some drivers do not signal us with the status of the continuous auto focus
+       operation, only the moving signal which comes first. As a result we need to
+       arm a timer to detect the driver behaviour and if necessary generate the
+       signal ourselves to update the application state. */
+    int32_t expiredCount = 0;
+
+    {
+      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+      if (mAutoFocusCompleteTimer) {
+        mAutoFocusCompleteTimer->Cancel();
+
+        if (!mAutoFocusPending) {
+          nsRefPtr<nsITimerCallback> timerCb = new AutoFocusMovingTimerCallback(this);
+          nsresult rv = mAutoFocusCompleteTimer->InitWithCallback(timerCb,
+                                                                  kAutoFocusCompleteTimeoutMs,
+                                                                  nsITimer::TYPE_ONE_SHOT);
+          NS_WARN_IF(NS_FAILED(rv));
+        }
+        return;
+      }
+
+      if (!mAutoFocusPending) {
+        expiredCount = mAutoFocusCompleteExpired;
+      }
+    }
+
+    if (expiredCount == kAutoFocusCompleteTimeoutLimit) {
+      OnAutoFocusComplete(true, true);
+    }
+  }
+}
+
+void
+nsGonkCameraControl::OnAutoFocusComplete(bool aSuccess, bool aExpired)
 {
   class AutoFocusComplete : public nsRunnable
   {
   public:
-    AutoFocusComplete(nsGonkCameraControl* aCameraControl, bool aSuccess)
+    AutoFocusComplete(nsGonkCameraControl* aCameraControl, bool aSuccess, bool aExpired)
       : mCameraControl(aCameraControl)
       , mSuccess(aSuccess)
+      , mExpired(aExpired)
     { }
 
     NS_IMETHODIMP
     Run() override
     {
-      mCameraControl->OnAutoFocusComplete(mSuccess);
+      mCameraControl->OnAutoFocusComplete(mSuccess, mExpired);
       return NS_OK;
     }
 
   protected:
     nsRefPtr<nsGonkCameraControl> mCameraControl;
     bool mSuccess;
+    bool mExpired;
   };
 
   if (NS_GetCurrentThread() == mCameraThread) {
+    {
+      ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+      if (mAutoFocusPending) {
+        mAutoFocusPending = false;
+      } else if (mAutoFocusCompleteTimer) {
+        if (aExpired) {
+          NS_WARNING("Camera timed out waiting for OnAutoFocusComplete");
+          ++mAutoFocusCompleteExpired;
+        } else {
+          mAutoFocusCompleteTimer->Cancel();
+          --mAutoFocusCompleteExpired;
+        }
+
+        if (mAutoFocusCompleteExpired == kAutoFocusCompleteTimeoutLimit ||
+            mAutoFocusCompleteExpired == -kAutoFocusCompleteTimeoutLimit)
+        {
+          mAutoFocusCompleteTimer = nullptr;
+        }
+      }
+    }
+
     /**
      * Auto focusing can change some of the camera's parameters, so
      * we need to pull a new set before notifying any clients.
      */
     PullParametersImpl();
     CameraControlImpl::OnAutoFocusComplete(aSuccess);
     return;
   }
 
   /**
    * Because the callback needs to call PullParametersImpl(),
    * we need to dispatch this callback through the Camera Thread.
    */
-  mCameraThread->Dispatch(new AutoFocusComplete(this, aSuccess), NS_DISPATCH_NORMAL);
+  mCameraThread->Dispatch(new AutoFocusComplete(this, aSuccess, aExpired), NS_DISPATCH_NORMAL);
 }
 
 bool
 FeatureDetected(int32_t feature[])
 {
   /**
    * For information on what constitutes a valid feature, see:
    * http://androidxref.com/4.0.4/xref/system/core/include/system/camera.h#202
@@ -2100,17 +2205,17 @@ void
 OnTakePictureError(nsGonkCameraControl* gc)
 {
   gc->OnTakePictureError();
 }
 
 void
 OnAutoFocusComplete(nsGonkCameraControl* gc, bool aSuccess)
 {
-  gc->OnAutoFocusComplete(aSuccess);
+  gc->OnAutoFocusComplete(aSuccess, false);
 }
 
 void
 OnAutoFocusMoving(nsGonkCameraControl* gc, bool aIsMoving)
 {
   gc->OnAutoFocusMoving(aIsMoving);
 }
 
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -29,16 +29,17 @@
 #ifdef MOZ_WIDGET_GONK
 #include <media/MediaProfiles.h>
 #include <camera/Camera.h>
 #include "GonkRecorder.h"
 #else
 #include "FallbackCameraPlatform.h"
 #endif
 
+class nsITimer;
 
 namespace android {
   class GonkCameraHardware;
   class GonkRecorder;
   class GonkCameraSource;
 }
 
 namespace mozilla {
@@ -51,17 +52,18 @@ namespace layers {
 class GonkRecorderProfile;
 class GonkRecorderProfileManager;
 
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
   nsGonkCameraControl(uint32_t aCameraId);
 
-  void OnAutoFocusComplete(bool aSuccess);
+  void OnAutoFocusMoving(bool aIsMoving);
+  void OnAutoFocusComplete(bool aSuccess, bool aExpired);
   void OnFacesDetected(camera_frame_metadata_t* aMetaData);
   void OnTakePictureComplete(uint8_t* aData, uint32_t aLength);
   void OnTakePictureError();
   void OnRateLimitPreview(bool aLimit);
   void OnNewPreviewFrame(layers::TextureClient* aBuffer);
 #ifdef MOZ_WIDGET_GONK
   void OnRecorderEvent(int msg, int ext1, int ext2);
 #endif
@@ -194,16 +196,20 @@ protected:
   ReentrantMonitor          mRecorderMonitor;
 
   // Supported recorder profiles
   nsRefPtrHashtable<nsStringHashKey, RecorderProfile> mRecorderProfiles;
 
   nsRefPtr<DeviceStorageFile> mVideoFile;
   nsString                  mFileFormat;
 
+  bool                      mAutoFocusPending;
+  nsCOMPtr<nsITimer>        mAutoFocusCompleteTimer;
+  int32_t                   mAutoFocusCompleteExpired;
+
   // Guards against calling StartPreviewImpl() while in OnTakePictureComplete().
   ReentrantMonitor          mReentrantMonitor;
 
 private:
   nsGonkCameraControl(const nsGonkCameraControl&) = delete;
   nsGonkCameraControl& operator=(const nsGonkCameraControl&) = delete;
 };
 
--- a/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
+++ b/dom/camera/test/test_camera_hardware_auto_focus_moving_cb.html
@@ -29,18 +29,44 @@ suite.test('auto-focus-moving', function
       }
       suite.camera.addEventListener('focus', onEvent);
     });
 
     suite.hw.fireAutoFocusMoving(true);
     return sync;
   }
 
+  function waitAutoFocusComplete(p) {
+    var sync = new Promise(function(resolve, reject) {
+      function onEvent(e) {
+        suite.camera.removeEventListener('focus', onEvent);
+        ok(e.newState === 'focused', 'autofocus event state focused == ' + e.newState);
+        resolve();
+      }
+      suite.camera.addEventListener('focus', onEvent);
+    });
+
+    // Missing the fireAutoFocusComplete but it should timeout on its own
+    suite.hw.fireAutoFocusMoving(false);
+    return sync;
+  }
+
+  function runAutoFocusCycle(p) {
+    return triggerAutoFocusMoving(p)
+      .then(waitAutoFocusComplete);
+  }
+
+  /* If the driver doesn't supply an onAutoFocusComplete notification,
+     gecko will timeout and provide it. After three times, it will no
+     longer rely upon a timeout and fire it immediately. */
   return suite.getCamera()
-    .then(triggerAutoFocusMoving);
+    .then(runAutoFocusCycle)
+    .then(runAutoFocusCycle)
+    .then(runAutoFocusCycle)
+    .then(runAutoFocusCycle);
 });
 
 suite.setup()
   .then(suite.run);
 
 </script>
 </body>
 
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -191,16 +191,18 @@ public:
 
   virtual void RemoveEventListener(const nsAString& aType,
                                    mozilla::dom::EventListener* aListener,
                                    bool aUseCapture,
                                    ErrorResult& aRv) override;
 
   explicit nsDOMDeviceStorage(nsPIDOMWindow* aWindow);
 
+  static int InstanceCount() { return sInstanceCount; }
+
   nsresult Init(nsPIDOMWindow* aWindow, const nsAString& aType,
                 const nsAString& aVolName);
 
   bool IsAvailable();
   bool IsFullPath(const nsAString& aPath)
   {
     return aPath.Length() > 0 && aPath.CharAt(0) == '/';
   }
@@ -314,16 +316,18 @@ private:
   DeleteInternal(nsPIDOMWindow* aWin, const nsAString& aPath,
                  DOMRequest* aRequest);
 
   already_AddRefed<DOMCursor>
   EnumerateInternal(const nsAString& aName,
                     const EnumerationParameters& aOptions, bool aEditable,
                     ErrorResult& aRv);
 
+  static int sInstanceCount;
+
   nsString mStorageType;
   nsCOMPtr<nsIFile> mRootDirectory;
   nsString mStorageName;
   bool mIsShareable;
   bool mIsRemovable;
 
   already_AddRefed<nsDOMDeviceStorage> GetStorage(const nsAString& aFullPath,
                                                   nsAString& aOutStoragePath);
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3356,23 +3356,27 @@ NS_IMPL_CYCLE_COLLECTION(DeviceStorageRe
 NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorage)
   NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorage)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, DOMEventTargetHelper)
 
+int nsDOMDeviceStorage::sInstanceCount = 0;
+
 nsDOMDeviceStorage::nsDOMDeviceStorage(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
   , mIsShareable(false)
   , mIsRemovable(false)
   , mIsWatchingFile(false)
   , mAllowedToWatchFile(false)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  sInstanceCount++;
 }
 
 /* virtual */ JSObject*
 nsDOMDeviceStorage::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DeviceStorageBinding::Wrap(aCx, this, aGivenProto);
 }
 
@@ -3448,16 +3452,18 @@ nsDOMDeviceStorage::Init(nsPIDOMWindow* 
     }
   }
 
   return NS_OK;
 }
 
 nsDOMDeviceStorage::~nsDOMDeviceStorage()
 {
+  MOZ_ASSERT(NS_IsMainThread());
+  sInstanceCount--;
 }
 
 void
 nsDOMDeviceStorage::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mFileSystem) {
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -59,16 +59,20 @@ const kEventConstructors = {
   BluetoothDeviceEvent:                      { create: function (aName, aProps) {
                                                           return new BluetoothDeviceEvent(aName, aProps);
                                                        },
                                              },
   BluetoothDiscoveryStateChangedEvent:       { create: function (aName, aProps) {
                                                           return new BluetoothDiscoveryStateChangedEvent(aName, aProps);
                                                        },
                                              },
+  BluetoothGattCharacteristicEvent:          { create: function (aName, aProps) {
+                                                          return new BluetoothGattCharacteristicEvent(aName, aProps);
+                                                       },
+                                             },
   BluetoothPairingEvent:                     { create: function (aName, aProps) {
                                                           return new BluetoothPairingEvent(aName, aProps);
                                                        },
                                              },
   BluetoothStatusChangedEvent:               { create: function (aName, aProps) {
                                                           return new BluetoothStatusChangedEvent(aName, aProps);
                                                        },
                                              },
--- a/dom/interfaces/apps/nsIAppsService.idl
+++ b/dom/interfaces/apps/nsIAppsService.idl
@@ -11,17 +11,17 @@ interface nsIURI;
 #define APPS_SERVICE_CID { 0x05072afa, 0x92fe, 0x45bf, { 0xae, 0x22, 0x39, 0xb6, 0x9c, 0x11, 0x70, 0x58 } }
 #define APPS_SERVICE_CONTRACTID "@mozilla.org/AppsService;1"
 %}
 
 /*
  * This service allows accessing some DOMApplicationRegistry methods from
  * non-javascript code.
  */
-[scriptable, uuid(76ced447-6f92-48fb-b4e6-690e4859bc7f)]
+[scriptable, uuid(03f1e30e-a1aa-4e18-ab3f-cd04b3b35a54)]
 interface nsIAppsService : nsISupports
 {
   mozIApplication getAppByManifestURL(in DOMString manifestURL);
 
   /**
    * Returns a Promise for the manifest for a given manifestURL.
    * This is only supported in the parent process: the promise will be rejected
    * in content processes.
@@ -73,9 +73,14 @@ interface nsIAppsService : nsISupports
    * Returns null if no redirection is declared for this uri.
    */
   nsIURI getRedirect(in unsigned long localId, in nsIURI uri);
 
   /**
    * Returns the localId if the app was installed from a store
    */
   DOMString getAppLocalIdByStoreId(in DOMString storeID);
+
+  /**
+   * Returns the scope for app to use with service workers.
+   */
+  DOMString getScopeByLocalId(in unsigned long localId);
 };
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2246,16 +2246,23 @@ ContentChild::RecvVolumes(nsTArray<Volum
 }
 
 bool
 ContentChild::RecvFilePathUpdate(const nsString& aStorageType,
                                  const nsString& aStorageName,
                                  const nsString& aPath,
                                  const nsCString& aReason)
 {
+    if (nsDOMDeviceStorage::InstanceCount() == 0) {
+        // No device storage instances in this process. Don't try and
+        // and create a DeviceStorageFile since it will fail.
+
+        return true;
+    }
+
     nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(aStorageType, aStorageName, aPath);
 
     nsString reason;
     CopyASCIItoUTF16(aReason, reason);
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(dsf, "file-watcher-update", reason.get());
     return true;
 }
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -2260,17 +2260,18 @@ RadioInterface.prototype = {
     }
 
     // RIL:IccInfoChanged corresponds to a DOM event that gets fired only
     // when iccInfo has changed.
     // TODO: Bug 815526, deprecate RILContentHelper.
     gMessageManager.sendIccMessage("RIL:IccInfoChanged",
                                    this.clientId,
                                    message.iccid ? message : null);
-    gIccService.notifyIccInfoChanged(this.clientId, this.rilContext.iccInfo);
+    gIccService.notifyIccInfoChanged(this.clientId,
+                                     message.iccid ? message : null);
 
     // Update lastKnownHomeNetwork.
     if (message.mcc && message.mnc) {
       let lastKnownHomeNetwork = message.mcc + "-" + message.mnc;
       // Append spn information if available.
       if (message.spn) {
         lastKnownHomeNetwork += "-" + message.spn;
       }
--- a/dom/system/gonk/nsIVolumeService.idl
+++ b/dom/system/gonk/nsIVolumeService.idl
@@ -3,27 +3,29 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIVolume.idl"
 #include "nsIVolumeMountLock.idl"
 
 interface nsIArray;
 
-[scriptable, uuid(c31b182c-61a3-449c-bba8-fd45044499c2)]
+[scriptable, uuid(879874c6-5532-437a-bf76-703d0c2e7e77)]
 interface nsIVolumeService : nsISupports
 {
     nsIVolume getVolumeByName(in DOMString volName);
     nsIVolume getVolumeByPath(in DOMString path);
     nsIVolume createOrGetVolumeByPath(in DOMString path);
 
     nsIVolumeMountLock createMountLock(in DOMString volName);
 
     nsIArray getVolumeNames();
 
+    void Dump(in DOMString label);
+
     /* for test case only to simulate sdcard insertion/removal */
     void createFakeVolume(in DOMString name, in DOMString path);
     void SetFakeVolumeState(in DOMString name, in long state);
 };
 
 %{C++
 #define NS_VOLUMESERVICE_CID \
   {0x7c179fb7, 0x67a0, 0x43a3, {0x93, 0x37, 0x29, 0x4e, 0x03, 0x60, 0xb8, 0x58}}
--- a/dom/system/gonk/nsVolume.cpp
+++ b/dom/system/gonk/nsVolume.cpp
@@ -61,16 +61,33 @@ nsVolume::nsVolume(const Volume* aVolume
     mIsSharing(aVolume->IsSharing()),
     mIsFormatting(aVolume->IsFormatting()),
     mIsUnmounting(aVolume->IsUnmounting()),
     mIsRemovable(aVolume->IsRemovable()),
     mIsHotSwappable(aVolume->IsHotSwappable())
 {
 }
 
+void nsVolume::Dump(const char* aLabel) const
+{
+  LOG("%s: Volume: %s is %s and %s @ %s gen %d locked %d",
+      aLabel,
+      NameStr().get(),
+      StateStr(),
+      IsMediaPresent() ? "inserted" : "missing",
+      MountPointStr().get(),
+      MountGeneration(),
+      (int)IsMountLocked());
+  LOG("%s:   IsSharing %s IsFormating %s IsUnmounting %s",
+      aLabel,
+      (IsSharing() ? "y" : "n"),
+      (IsFormatting() ? "y" : "n"),
+      (IsUnmounting() ? "y" : "n"));
+}
+
 bool nsVolume::Equals(nsIVolume* aVolume)
 {
   nsString volName;
   aVolume->GetName(volName);
   if (!mName.Equals(volName)) {
     return false;
   }
 
--- a/dom/system/gonk/nsVolume.h
+++ b/dom/system/gonk/nsVolume.h
@@ -68,16 +68,18 @@ public:
   bool Equals(nsIVolume* aVolume);
   void Set(nsIVolume* aVolume);
 
   void LogState() const;
 
   const nsString& Name() const        { return mName; }
   nsCString NameStr() const           { return NS_LossyConvertUTF16toASCII(mName); }
 
+  void Dump(const char* aLabel) const;
+
   int32_t MountGeneration() const     { return mMountGeneration; }
   bool IsMountLocked() const          { return mMountLocked; }
 
   const nsString& MountPoint() const  { return mMountPoint; }
   nsCString MountPointStr() const     { return NS_LossyConvertUTF16toASCII(mMountPoint); }
 
   int32_t State() const               { return mState; }
   const char* StateStr() const        { return NS_VolumeStateStr(mState); }
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -119,16 +119,41 @@ nsVolumeService::~nsVolumeService()
 // Callback for nsIDOMMozWakeLockListener
 NS_IMETHODIMP
 nsVolumeService::Callback(const nsAString& aTopic, const nsAString& aState)
 {
   CheckMountLock(aTopic, aState);
   return NS_OK;
 }
 
+void nsVolumeService::DumpNoLock(const char* aLabel)
+{
+  mArrayMonitor.AssertCurrentThreadOwns();
+
+  nsVolume::Array::size_type numVolumes = mVolumeArray.Length();
+
+  if (numVolumes == 0) {
+    LOG("%s: No Volumes!", aLabel);
+    return;
+  }
+  nsVolume::Array::index_type volIndex;
+  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+    nsRefPtr<nsVolume> vol = mVolumeArray[volIndex];
+    vol->Dump(aLabel);
+  }
+}
+
+NS_IMETHODIMP
+nsVolumeService::Dump(const nsAString& aLabel)
+{
+  MonitorAutoLock autoLock(mArrayMonitor);
+  DumpNoLock(NS_LossyConvertUTF16toASCII(aLabel).get());
+  return NS_OK;
+}
+
 NS_IMETHODIMP nsVolumeService::GetVolumeByName(const nsAString& aVolName, nsIVolume **aResult)
 {
   MonitorAutoLock autoLock(mArrayMonitor);
 
   nsRefPtr<nsVolume> vol = FindVolumeByName(aVolName);
   if (!vol) {
     return NS_ERROR_NOT_AVAILABLE;
   }
@@ -277,16 +302,17 @@ nsVolumeService::RecvVolumesFromParent(c
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     // We are the parent. Therefore our volumes are already correct.
     return;
   }
   if (mGotVolumesFromParent) {
     // We've already done this, no need to do it again.
     return;
   }
+
   for (uint32_t i = 0; i < aVolumes.Length(); i++) {
     const VolumeInfo& volInfo(aVolumes[i]);
     nsRefPtr<nsVolume> vol = new nsVolume(volInfo.name(),
                                           volInfo.mountPoint(),
                                           volInfo.volState(),
                                           volInfo.mountGeneration(),
                                           volInfo.isMediaPresent(),
                                           volInfo.isSharing(),
--- a/dom/system/gonk/nsVolumeService.h
+++ b/dom/system/gonk/nsVolumeService.h
@@ -40,16 +40,18 @@ public:
   NS_DECL_NSIDOMMOZWAKELOCKLISTENER
 
   nsVolumeService();
 
   static already_AddRefed<nsVolumeService> GetSingleton();
   //static nsVolumeService* GetSingleton();
   static void Shutdown();
 
+  void DumpNoLock(const char* aLabel);
+
   void UpdateVolume(nsIVolume* aVolume, bool aNotifyObservers = true);
   void UpdateVolumeIOThread(const Volume* aVolume);
 
   void RecvVolumesFromParent(const nsTArray<dom::VolumeInfo>& aVolumes);
   void GetVolumesForIPC(nsTArray<dom::VolumeInfo>* aResult);
 
 private:
   ~nsVolumeService();
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -9369,16 +9369,161 @@ ICCPDUHelperObject.prototype = {
 
     // trailing 0xff
     while (j++ < numOctets) {
       GsmPDUHelper.writeHexOctet(0xff);
     }
   },
 
   /**
+   * Write UCS2 String on UICC.
+   * The default choose 0x81 or 0x82 encode, otherwise use 0x80 encode.
+   *
+   * @see TS 102.221, Annex A.
+   * @param numOctets
+   *        Total number of octets to be written. This includes the length of
+   *        alphaId and the length of trailing unused octets(0xff).
+   * @param str
+   *        String to be written.
+   *
+   */
+  writeICCUCS2String: function(numOctets, str) {
+    let GsmPDUHelper = this.context.GsmPDUHelper;
+    let scheme = 0x80;
+    let basePointer;
+
+    if (str.length > 2) {
+      let min = 0xFFFF;
+      let max = 0;
+      for (let i = 0; i < str.length; i++) {
+        let code = str.charCodeAt(i);
+        // filter out GSM Default Alphabet character
+        if (code & 0xFF80) {
+          if (min > code) {
+            min = code;
+          }
+          if (max < code) {
+            max = code;
+          }
+        }
+      }
+
+      // 0x81 and 0x82 only support 'half-page', i.e., 128 characters.
+      if ((max - min) >= 0 && (max - min) < 128) {
+        // 0x81 base pointer is 0hhh hhhh h000 0000, and bit 16 is set to zero,
+        // therefore it can't compute 0x8000~0xFFFF.
+        // Since 0x81 only support 128 characters,
+        // either XX00~XX7f(bit 8 are 0) or XX80~XXff(bit 8 are 1)
+        if (((min & 0x7f80) == (max & 0x7f80)) &&
+            ((max & 0x8000) == 0)) {
+          scheme = 0x81;
+          basePointer = min & 0x7f80;
+        } else {
+          scheme = 0x82;
+          basePointer = min;
+        }
+      }
+    }
+
+    switch (scheme) {
+      /**
+       * +------+---------+---------+---------+---------+------+------+
+       * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
+       * +------+---------+---------+---------+---------+------+------+
+       */
+      case 0x80: {
+        // 0x80 support UCS2 0000~ffff
+        GsmPDUHelper.writeHexOctet(0x80);
+        numOctets--;
+        // Now the str is UCS2 string, each character will take 2 octets.
+        if (str.length * 2 > numOctets) {
+          str = str.substring(0, Math.floor(numOctets / 2));
+        }
+        GsmPDUHelper.writeUCS2String(str);
+
+        // trailing 0xff
+        for (let i = str.length * 2; i < numOctets; i++) {
+          GsmPDUHelper.writeHexOctet(0xff);
+        }
+        return;
+      }
+      /**
+       * +------+-----+--------------+-----+-----+-----+--------+------+
+       * | 0x81 | len | base_pointer | Ch1 | Ch2 | ... | Ch_len | 0xff |
+       * +------+-----+--------------+-----+-----+-----+--------+------+
+       *
+       * len: The length of characters.
+       * base_pointer: 0hhh hhhh h000 0000
+       * Ch_n: bit 8 = 0
+       *       GSM default alphabets
+       *       bit 8 = 1
+       *       UCS2 character whose char code is (Ch_n - base_pointer) | 0x80
+       *
+       */
+      case 0x81: {
+        GsmPDUHelper.writeHexOctet(0x81);
+
+        if (str.length > (numOctets - 3)) {
+          str = str.substring(0, numOctets - 3);
+        }
+
+        GsmPDUHelper.writeHexOctet(str.length);
+        GsmPDUHelper.writeHexOctet((basePointer >> 7) & 0xff);
+        numOctets -= 3;
+        break;
+      }
+      /* +------+-----+------------------+------------------+-----+-----+-----+--------+
+       * | 0x82 | len | base_pointer_msb | base_pointer_lsb | Ch1 | Ch2 | ... | Ch_len |
+       * +------+-----+------------------+------------------+-----+-----+-----+--------+
+       *
+       * len: The length of characters.
+       * base_pointer_msb, base_pointer_lsn: base_pointer
+       * Ch_n: bit 8 = 0
+       *       GSM default alphabets
+       *       bit 8 = 1
+       *       UCS2 character whose char code is (Ch_n - base_pointer) | 0x80
+       */
+      case 0x82: {
+        GsmPDUHelper.writeHexOctet(0x82);
+
+        if (str.length > (numOctets - 4)) {
+          str = str.substring(0, numOctets - 4);
+        }
+
+        GsmPDUHelper.writeHexOctet(str.length);
+        GsmPDUHelper.writeHexOctet((basePointer >> 8) & 0xff);
+        GsmPDUHelper.writeHexOctet(basePointer & 0xff);
+        numOctets -= 4;
+        break;
+      }
+    }
+
+    if (scheme == 0x81 || scheme == 0x82) {
+      for (let i = 0; i < str.length; i++) {
+        let code = str.charCodeAt(i);
+
+        // bit 8 = 0,
+        // GSM default alphabets
+        if (code >> 8 == 0) {
+          GsmPDUHelper.writeHexOctet(code & 0x7F);
+        } else {
+          // bit 8 = 1,
+          // UCS2 character whose char code is (code - basePointer) | 0x80
+          GsmPDUHelper.writeHexOctet((code - basePointer) | 0x80);
+        }
+      }
+
+      // trailing 0xff
+      for (let i = 0; i < numOctets - str.length; i++) {
+        GsmPDUHelper.writeHexOctet(0xff);
+      }
+    }
+  },
+
+ /**
    * Read UCS2 String on UICC.
    *
    * @see TS 101.221, Annex A.
    * @param scheme
    *        Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
    * @param numOctets
    *        Number of octets to be read as UCS2 string.
    */
@@ -9572,29 +9717,17 @@ ICCPDUHelperObject.prototype = {
     if (numOctets === 0) {
       return;
     }
 
     // If alphaId is empty or it's of GSM 8 bit.
     if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
       this.writeStringTo8BitUnpacked(numOctets, alphaId);
     } else {
-      let GsmPDUHelper = this.context.GsmPDUHelper;
-
-      // Currently only support UCS2 coding scheme 0x80.
-      GsmPDUHelper.writeHexOctet(0x80);
-      numOctets--;
-      // Now the alphaId is UCS2 string, each character will take 2 octets.
-      if (alphaId.length * 2 > numOctets) {
-        alphaId = alphaId.substring(0, Math.floor(numOctets / 2));
-      }
-      GsmPDUHelper.writeUCS2String(alphaId);
-      for (let i = alphaId.length * 2; i < numOctets; i++) {
-        GsmPDUHelper.writeHexOctet(0xff);
-      }
+      this.writeICCUCS2String(numOctets, alphaId);
     }
   },
 
   /**
    * Read Dialling number.
    *
    * @see TS 131.102
    *
--- a/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc_ICCPDUHelper.js
@@ -43,16 +43,69 @@ add_test(function test_read_icc_ucs2_str
     helper.writeHexOctet(array2[i]);
   }
   equal(iccHelper.readICCUCS2String(0x82, len2), "Mozilla\u694a");
 
   run_next_test();
 });
 
 /**
+ * Verify ICCPDUHelper#writeICCUCS2String()
+ */
+add_test(function test_write_icc_ucs2_string() {
+  let worker = newUint8Worker();
+  let context = worker.ContextPool._contexts[0];
+  let helper = context.GsmPDUHelper;
+  let iccHelper = context.ICCPDUHelper;
+  let alphaLen = 18;
+  let test_data = [
+    {
+      encode: 0x80,
+      // string only contain one character.
+      data: "\u82b3"
+    }, {
+      encode: 0x80,
+      // 2 UCS2 character not located in the same half-page.
+      data: "Fire \u82b3\u8233"
+    }, {
+      encode: 0x80,
+      // 2 UCS2 character not located in the same half-page.
+      data: "\u694a\u704a"
+    }, {
+      encode: 0x81,
+      // 2 UCS2 character within same half-page.
+      data: "Fire \u6901\u697f"
+    }, {
+      encode: 0x81,
+      // 2 UCS2 character within same half-page.
+      data: "Fire \u6980\u69ff"
+    }, {
+      encode: 0x82,
+      // 2 UCS2 character within same half-page, but bit 8 is different.
+      data: "Fire \u0514\u0593"
+    }, {
+      encode: 0x82,
+      // 2 UCS2 character over 0x81 can encode range.
+      data: "Fire \u8000\u8001"
+    }, {
+      encode: 0x82,
+      // 2 UCS2 character over 0x81 can encode range.
+      data: "Fire \ufffd\ufffe"
+    }];
+
+  for (let i = 0; i < test_data.length; i++) {
+    let test = test_data[i];
+    iccHelper.writeICCUCS2String(alphaLen, test.data);
+    equal(helper.readHexOctet(), test.encode);
+    equal(iccHelper.readICCUCS2String(test.encode, alphaLen - 1), test.data);
+  }
+
+  run_next_test();
+});
+/**
  * Verify ICCPDUHelper#readDiallingNumber
  */
 add_test(function test_read_dialling_number() {
   let worker = newUint8Worker();
   let context = worker.ContextPool._contexts[0];
   let helper = context.GsmPDUHelper;
   let iccHelper = context.ICCPDUHelper;
   let str = "123456789";
@@ -287,30 +340,30 @@ add_test(function test_write_alpha_ident
   equal(iccHelper.readAlphaIdentifier(10), "");
 
   // GSM 8 bit
   let str = "Mozilla";
   iccHelper.writeAlphaIdentifier(str.length + ffLen, str);
   equal(iccHelper.readAlphaIdentifier(str.length + ffLen), str);
 
   // UCS2
-  str = "Mozilla\u694a";
+  str = "Mozilla\u8000";
   iccHelper.writeAlphaIdentifier(str.length * 2 + ffLen, str);
   // * 2 for each character will be encoded to UCS2 alphabets.
   equal(iccHelper.readAlphaIdentifier(str.length * 2 + ffLen), str);
 
   // Test with maximum octets written.
   // 1 coding scheme (0x80) and 1 UCS2 character, total 3 octets.
   str = "\u694a";
   iccHelper.writeAlphaIdentifier(3, str);
   equal(iccHelper.readAlphaIdentifier(3), str);
 
   // 1 coding scheme (0x80) and 2 UCS2 characters, total 5 octets.
   // numOctets is limited to 4, so only 1 UCS2 character can be written.
-  str = "\u694a\u694a";
+  str = "\u694a\u69ca";
   iccHelper.writeAlphaIdentifier(4, str);
   helper.writeHexOctet(0xff); // dummy octet.
   equal(iccHelper.readAlphaIdentifier(5), str.substring(0, 1));
 
   // Write 0 octet.
   iccHelper.writeAlphaIdentifier(0, "1");
   helper.writeHexOctet(0xff); // dummy octet.
   equal(iccHelper.readAlphaIdentifier(1), "");
--- a/dom/webidl/BluetoothGatt.webidl
+++ b/dom/webidl/BluetoothGatt.webidl
@@ -6,16 +6,18 @@
 
 [CheckPermissions="bluetooth"]
 interface BluetoothGatt : EventTarget
 {
   [Cached, Pure]
   readonly attribute sequence<BluetoothGattService> services;
   readonly attribute BluetoothConnectionState       connectionState;
 
+  // Fired when the value of any characteristic changed
+           attribute EventHandler                   oncharacteristicchanged;
   // Fired when attribute connectionState changed
            attribute EventHandler                   onconnectionstatechanged;
 
   /**
    * Connect/Disconnect to the remote BLE device if the connectionState is
    * disconnected/connected. Otherwise, the Promise will be rejected directly.
    *
    * If current connectionState is disconnected/connected,
--- a/dom/webidl/BluetoothGattCharacteristic.webidl
+++ b/dom/webidl/BluetoothGattCharacteristic.webidl
@@ -1,23 +1,43 @@
 /* -*- 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/. */
 
+dictionary GattCharacteristicProperties
+{
+  required boolean broadcast;
+  required boolean read;
+  required boolean writeNoResponse;
+  required boolean write;
+  required boolean notify;
+  required boolean indicate;
+  required boolean signedWrite;
+  required boolean extendedProps;
+};
+
 [CheckPermissions="bluetooth"]
 interface BluetoothGattCharacteristic
 {
   readonly attribute BluetoothGattService                   service;
   [Cached, Pure]
   readonly attribute sequence<BluetoothGattDescriptor>      descriptors;
 
   readonly attribute DOMString                              uuid;
   readonly attribute unsigned short                         instanceId;
+  readonly attribute ArrayBuffer?                           value;
+  [Cached, Constant]
+  readonly attribute GattCharacteristicProperties           properties;
+
+  [NewObject]
+  Promise<ArrayBuffer>  readValue();
+  [NewObject]
+  Promise<void>         writeValue(ArrayBuffer value);
 
   /**
    * Start or stop subscribing notifications of this characteristic from the
    * remote GATT server.
    */
   [NewObject]
   Promise<void> startNotifications();
   [NewObject]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGattCharacteristicEvent.webidl
@@ -0,0 +1,18 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[CheckPermissions="bluetooth",
+ Constructor(DOMString type,
+             optional BluetoothGattCharacteristicEventInit eventInitDict)]
+interface BluetoothGattCharacteristicEvent : Event
+{
+  readonly attribute BluetoothGattCharacteristic characteristic;
+};
+
+dictionary BluetoothGattCharacteristicEventInit : EventInit
+{
+  required BluetoothGattCharacteristic characteristic;
+};
--- a/dom/webidl/BluetoothGattDescriptor.webidl
+++ b/dom/webidl/BluetoothGattDescriptor.webidl
@@ -4,9 +4,15 @@
  * 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/. */
 
 [CheckPermissions="bluetooth"]
 interface BluetoothGattDescriptor
 {
   readonly attribute BluetoothGattCharacteristic            characteristic;
   readonly attribute DOMString                              uuid;
+  readonly attribute ArrayBuffer?                           value;
+
+  [NewObject]
+  Promise<ArrayBuffer>  readValue();
+  [NewObject]
+  Promise<void>         writeValue(ArrayBuffer value);
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -787,16 +787,17 @@ if CONFIG['MOZ_GAMEPAD']:
         'GamepadEvent.webidl',
     ]
 
 if CONFIG['MOZ_B2G_BT']:
     if CONFIG['MOZ_B2G_BT_API_V2']:
         GENERATED_EVENTS_WEBIDL_FILES += [
             'BluetoothAdapterEvent.webidl',
             'BluetoothAttributeEvent.webidl',
+            'BluetoothGattCharacteristicEvent.webidl',
             'BluetoothPairingEvent.webidl',
         ]
     else:
         GENERATED_EVENTS_WEBIDL_FILES += [
             'BluetoothDiscoveryStateChangedEvent.webidl',
         ]
 
     GENERATED_EVENTS_WEBIDL_FILES += [
--- a/gfx/2d/Matrix.cpp
+++ b/gfx/2d/Matrix.cpp
@@ -215,17 +215,17 @@ Rect Matrix4x4::ProjectRectBounds(const 
       if (intercept.y < 0.0f) {
         min_y = aClip.y;
       } else if (intercept.y > 0.0f) {
         max_y = aClip.YMost();
       }
     }
   }
 
-  if (max_x <= min_x || max_y <= min_y) {
+  if (max_x < min_x || max_y < min_y) {
     return Rect(0, 0, 0, 0);
   }
 
   return Rect(min_x, min_y, max_x - min_x, max_y - min_y);
 }
 
 bool
 Matrix4x4::Invert()
--- a/intl/lwbrk/rulebrk.c
+++ b/intl/lwbrk/rulebrk.c
@@ -1,14 +1,15 @@
 /* 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/. */
 #define TH_UNICODE
 
 #include <stdlib.h>
+#include <stdint.h>
 #include <assert.h>
 #include "th_char.h"
 #define th_isalpha(c)   (((c)>='a'&&(c)<='z')||((c)>='A'&&(c)<='Z'))
 #define th_isspace(c)   ((c)==' '||(c)=='\t')
 
 
 /*
 /////////////////////////////////////////////////
--- a/testing/mozbase/mozrunner/mozrunner/devices/base.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/base.py
@@ -136,17 +136,17 @@ class Device(object):
         self.connected = True
 
         if self.logdir:
             # save logcat
             logcat_log = os.path.join(self.logdir, '%s.log' % serial)
             if os.path.isfile(logcat_log):
                 self._rotate_log(logcat_log)
             logcat_args = [self.app_ctx.adb, '-s', '%s' % serial,
-                           'logcat', '-v', 'time']
+                           'logcat', '-v', 'time', '-b', 'main', '-b', 'radio']
             self.logcat_proc = ProcessHandler(logcat_args, logfile=logcat_log)
             self.logcat_proc.run()
 
     def reboot(self):
         """
         Reboots the device via adb.
         """
         self.dm.reboot(wait=True)
--- a/tools/profiler/GeckoTaskTracer.cpp
+++ b/tools/profiler/GeckoTaskTracer.cpp
@@ -2,16 +2,17 @@
 /* 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 "GeckoTaskTracer.h"
 #include "GeckoTaskTracerImpl.h"
 
+#include "mozilla/MathAlgorithms.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/unused.h"
 
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "prtime.h"
@@ -22,23 +23,42 @@
 // glibc doesn't implement gettid(2).
 #include <sys/syscall.h>
 static pid_t gettid()
 {
   return (pid_t) syscall(SYS_gettid);
 }
 #endif
 
+// NS_ENSURE_TRUE_VOID() without the warning on the debug build.
+#define ENSURE_TRUE_VOID(x)   \
+  do {                        \
+    if (MOZ_UNLIKELY(!(x))) { \
+       return;                \
+    }                         \
+  } while(0)
+
+// NS_ENSURE_TRUE() without the warning on the debug build.
+#define ENSURE_TRUE(x, ret)   \
+  do {                        \
+    if (MOZ_UNLIKELY(!(x))) { \
+       return ret;            \
+    }                         \
+  } while(0)
+
 namespace mozilla {
 namespace tasktracer {
 
-static mozilla::ThreadLocal<TraceInfo*>* sTraceInfoTLS = nullptr;
+static mozilla::ThreadLocal<TraceInfo*> sTraceInfoTLS;
 static mozilla::StaticMutex sMutex;
+
+// The generation of TraceInfo. It will be > 0 if the Task Tracer is started and
+// <= 0 if stopped.
+static mozilla::Atomic<bool> sStarted;
 static nsTArray<nsAutoPtr<TraceInfo>>* sTraceInfos = nullptr;
-static bool sIsLoggingStarted = false;
 static PRTime sStartTime;
 
 static const char sJSLabelPrefix[] = "#tt#";
 
 namespace {
 
 static PRTime
 GetTimestamp()
@@ -47,64 +67,54 @@ GetTimestamp()
 }
 
 static TraceInfo*
 AllocTraceInfo(int aTid)
 {
   StaticMutexAutoLock lock(sMutex);
 
   nsAutoPtr<TraceInfo>* info = sTraceInfos->AppendElement(
-                                 new TraceInfo(aTid, sIsLoggingStarted));
+                                 new TraceInfo(aTid));
 
   return info->get();
 }
 
-static bool
-IsInitialized()
-{
-  return sTraceInfoTLS ? sTraceInfoTLS->initialized() : false;
-}
-
 static void
 SaveCurTraceInfo()
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!info) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   info->mSavedCurTraceSourceId = info->mCurTraceSourceId;
   info->mSavedCurTraceSourceType = info->mCurTraceSourceType;
   info->mSavedCurTaskId = info->mCurTaskId;
 }
 
 static void
 RestoreCurTraceInfo()
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!info) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   info->mCurTraceSourceId = info->mSavedCurTraceSourceId;
   info->mCurTraceSourceType = info->mSavedCurTraceSourceType;
   info->mCurTaskId = info->mSavedCurTaskId;
 }
 
 static void
 CreateSourceEvent(SourceEventType aType)
 {
-  NS_ENSURE_TRUE_VOID(IsInitialized());
-
   // Save the currently traced source event info.
   SaveCurTraceInfo();
 
   // Create a new unique task id.
   uint64_t newId = GenNewUniqueTaskId();
   TraceInfo* info = GetOrCreateTraceInfo();
+  ENSURE_TRUE_VOID(info);
+
   info->mCurTraceSourceId = newId;
   info->mCurTraceSourceType = aType;
   info->mCurTaskId = newId;
 
   int* namePtr;
 #define SOURCE_EVENT_NAME(type)         \
   case SourceEventType::type:           \
   {                                     \
@@ -124,64 +134,67 @@ CreateSourceEvent(SourceEventType aType)
   LogDispatch(newId, newId, newId, aType);
   LogVirtualTablePtr(newId, newId, namePtr);
   LogBegin(newId, newId);
 }
 
 static void
 DestroySourceEvent()
 {
-  NS_ENSURE_TRUE_VOID(IsInitialized());
-
   // Log a fake end for this source event.
   TraceInfo* info = GetOrCreateTraceInfo();
+  ENSURE_TRUE_VOID(info);
+
   LogEnd(info->mCurTraceSourceId, info->mCurTraceSourceId);
 
   // Restore the previously saved source event info.
   RestoreCurTraceInfo();
 }
 
+inline static bool
+IsStartLogging()
+{
+  return sStarted;
+}
+
+static void
+SetLogStarted(bool aIsStartLogging)
+{
+  MOZ_ASSERT(aIsStartLogging != IsStartLogging());
+  sStarted = aIsStartLogging;
+
+  StaticMutexAutoLock lock(sMutex);
+  if (!aIsStartLogging) {
+    for (uint32_t i = 0; i < sTraceInfos->Length(); ++i) {
+      (*sTraceInfos)[i]->mObsolete = true;
+    }
+  }
+}
+
 static void
 CleanUp()
 {
+  SetLogStarted(false);
   StaticMutexAutoLock lock(sMutex);
 
   if (sTraceInfos) {
     delete sTraceInfos;
     sTraceInfos = nullptr;
   }
-
-  // pthread_key_delete() is not called at the destructor of
-  // mozilla::ThreadLocal (Bug 1064672).
-  if (sTraceInfoTLS) {
-    delete sTraceInfoTLS;
-    sTraceInfoTLS = nullptr;
-  }
 }
 
-static void
-SetLogStarted(bool aIsStartLogging)
+inline static void
+ObsoleteCurrentTraceInfos()
 {
-  // TODO: This is called from a signal handler. Use semaphore instead.
-  StaticMutexAutoLock lock(sMutex);
-
+  // Note that we can't and don't need to acquire sMutex here because this
+  // function is called before the other threads are recreated.
   for (uint32_t i = 0; i < sTraceInfos->Length(); ++i) {
-    (*sTraceInfos)[i]->mStartLogging = aIsStartLogging;
+    (*sTraceInfos)[i]->mObsolete = true;
   }
-
-  sIsLoggingStarted = aIsStartLogging;
 }
-
-static bool
-IsStartLogging(TraceInfo* aInfo)
-{
-  StaticMutexAutoLock lock(sMutex);
-  return aInfo ? aInfo->mStartLogging : false;
-}
-
 } // namespace anonymous
 
 nsCString*
 TraceInfo::AppendLog()
 {
   MutexAutoLock lock(mLogsMutex);
   return mLogs.AppendElement();
 }
@@ -192,55 +205,74 @@ TraceInfo::MoveLogsInto(TraceInfoLogsTyp
   MutexAutoLock lock(mLogsMutex);
   aResult.MoveElementsFrom(mLogs);
 }
 
 void
 InitTaskTracer(uint32_t aFlags)
 {
   if (aFlags & FORKED_AFTER_NUWA) {
-    CleanUp();
+    ObsoleteCurrentTraceInfos();
+    return;
   }
 
-  MOZ_ASSERT(!sTraceInfoTLS);
-  sTraceInfoTLS = new ThreadLocal<TraceInfo*>();
-
   MOZ_ASSERT(!sTraceInfos);
   sTraceInfos = new nsTArray<nsAutoPtr<TraceInfo>>();
 
-  if (!sTraceInfoTLS->initialized()) {
-    unused << sTraceInfoTLS->init();
+  if (!sTraceInfoTLS.initialized()) {
+    unused << sTraceInfoTLS.init();
   }
 }
 
 void
 ShutdownTaskTracer()
 {
   CleanUp();
 }
 
+static void
+FreeTraceInfo(TraceInfo* aTraceInfo)
+{
+  StaticMutexAutoLock lock(sMutex);
+  if (aTraceInfo) {
+    sTraceInfos->RemoveElement(aTraceInfo);
+  }
+}
+
+void FreeTraceInfo()
+{
+  FreeTraceInfo(sTraceInfoTLS.get());
+}
+
 TraceInfo*
 GetOrCreateTraceInfo()
 {
-  NS_ENSURE_TRUE(IsInitialized(), nullptr);
+  ENSURE_TRUE(sTraceInfoTLS.initialized(), nullptr);
+  ENSURE_TRUE(IsStartLogging(), nullptr);
 
-  TraceInfo* info = sTraceInfoTLS->get();
+  TraceInfo* info = sTraceInfoTLS.get();
+  if (info && info->mObsolete) {
+    // TraceInfo is obsolete: remove it.
+    FreeTraceInfo(info);
+    info = nullptr;
+  }
+
   if (!info) {
     info = AllocTraceInfo(gettid());
-    sTraceInfoTLS->set(info);
+    sTraceInfoTLS.set(info);
   }
 
   return info;
 }
 
 uint64_t
 GenNewUniqueTaskId()
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  NS_ENSURE_TRUE(info, 0);
+  ENSURE_TRUE(info, 0);
 
   pid_t tid = gettid();
   uint64_t taskid = ((uint64_t)tid << 32) | ++info->mLastUniqueTaskId;
   return taskid;
 }
 
 AutoSaveCurTraceInfo::AutoSaveCurTraceInfo()
 {
@@ -252,131 +284,109 @@ AutoSaveCurTraceInfo::~AutoSaveCurTraceI
   RestoreCurTraceInfo();
 }
 
 void
 SetCurTraceInfo(uint64_t aSourceEventId, uint64_t aParentTaskId,
                 SourceEventType aSourceEventType)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  NS_ENSURE_TRUE_VOID(info);
+  ENSURE_TRUE_VOID(info);
 
   info->mCurTraceSourceId = aSourceEventId;
   info->mCurTaskId = aParentTaskId;
   info->mCurTraceSourceType = aSourceEventType;
 }
 
 void
 GetCurTraceInfo(uint64_t* aOutSourceEventId, uint64_t* aOutParentTaskId,
                 SourceEventType* aOutSourceEventType)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  NS_ENSURE_TRUE_VOID(info);
+  ENSURE_TRUE_VOID(info);
 
   *aOutSourceEventId = info->mCurTraceSourceId;
   *aOutParentTaskId = info->mCurTaskId;
   *aOutSourceEventType = info->mCurTraceSourceType;
 }
 
 void
 LogDispatch(uint64_t aTaskId, uint64_t aParentTaskId, uint64_t aSourceEventId,
             SourceEventType aSourceEventType)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!IsStartLogging(info)) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   // Log format:
   // [0 taskId dispatchTime sourceEventId sourceEventType parentTaskId]
   nsCString* log = info->AppendLog();
   if (log) {
     log->AppendPrintf("%d %lld %lld %lld %d %lld",
                       ACTION_DISPATCH, aTaskId, GetTimestamp(), aSourceEventId,
                       aSourceEventType, aParentTaskId);
   }
 }
 
 void
 LogBegin(uint64_t aTaskId, uint64_t aSourceEventId)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!IsStartLogging(info)) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   // Log format:
   // [1 taskId beginTime processId threadId]
   nsCString* log = info->AppendLog();
   if (log) {
     log->AppendPrintf("%d %lld %lld %d %d",
                       ACTION_BEGIN, aTaskId, GetTimestamp(), getpid(), gettid());
   }
 }
 
 void
 LogEnd(uint64_t aTaskId, uint64_t aSourceEventId)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!IsStartLogging(info)) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   // Log format:
   // [2 taskId endTime]
   nsCString* log = info->AppendLog();
   if (log) {
     log->AppendPrintf("%d %lld %lld", ACTION_END, aTaskId, GetTimestamp());
   }
 }
 
 void
 LogVirtualTablePtr(uint64_t aTaskId, uint64_t aSourceEventId, int* aVptr)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!IsStartLogging(info)) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   // Log format:
   // [4 taskId address]
   nsCString* log = info->AppendLog();
   if (log) {
     log->AppendPrintf("%d %lld %p", ACTION_GET_VTABLE, aTaskId, aVptr);
   }
 }
 
-void
-FreeTraceInfo()
-{
-  NS_ENSURE_TRUE_VOID(IsInitialized());
-
-  StaticMutexAutoLock lock(sMutex);
-  TraceInfo* info = GetOrCreateTraceInfo();
-  if (info) {
-    sTraceInfos->RemoveElement(info);
-  }
-}
-
 AutoSourceEvent::AutoSourceEvent(SourceEventType aType)
 {
   CreateSourceEvent(aType);
 }
 
 AutoSourceEvent::~AutoSourceEvent()
 {
   DestroySourceEvent();
 }
 
 void AddLabel(const char* aFormat, ...)
 {
   TraceInfo* info = GetOrCreateTraceInfo();
-  if (!IsStartLogging(info)) {
-    return;
-  }
+  ENSURE_TRUE_VOID(info);
 
   va_list args;
   va_start(args, aFormat);
   nsAutoCString buffer;
   buffer.AppendPrintf(aFormat, args);
   va_end(args);
 
   // Log format:
@@ -399,17 +409,17 @@ StartLogging()
 
 void
 StopLogging()
 {
   SetLogStarted(false);
 }
 
 TraceInfoLogsType*
-GetLoggedData()
+GetLoggedData(TimeStamp aTimeStamp)
 {
   TraceInfoLogsType* result = new TraceInfoLogsType();
 
   // TODO: This is called from a signal handler. Use semaphore instead.
   StaticMutexAutoLock lock(sMutex);
 
   for (uint32_t i = 0; i < sTraceInfos->Length(); ++i) {
     (*sTraceInfos)[i]->MoveLogsInto(*result);
@@ -425,10 +435,13 @@ GetStartTime()
 }
 
 const char*
 GetJSLabelPrefix()
 {
   return sJSLabelPrefix;
 }
 
+#undef ENSURE_TRUE_VOID
+#undef ENSURE_TRUE
+
 } // namespace tasktracer
 } // namespace mozilla
--- a/tools/profiler/GeckoTaskTracerImpl.h
+++ b/tools/profiler/GeckoTaskTracerImpl.h
@@ -13,26 +13,26 @@
 
 namespace mozilla {
 namespace tasktracer {
 
 typedef nsTArray<nsCString> TraceInfoLogsType;
 
 struct TraceInfo
 {
-  TraceInfo(uint32_t aThreadId, bool aStartLogging)
+  TraceInfo(uint32_t aThreadId)
     : mCurTraceSourceId(0)
     , mCurTaskId(0)
     , mSavedCurTraceSourceId(0)
     , mSavedCurTaskId(0)
     , mCurTraceSourceType(Unknown)
     , mSavedCurTraceSourceType(Unknown)
     , mThreadId(aThreadId)
     , mLastUniqueTaskId(0)
-    , mStartLogging(aStartLogging)
+    , mObsolete(false)
     , mLogsMutex("TraceInfoMutex")
   {
     MOZ_COUNT_CTOR(TraceInfo);
   }
 
   ~TraceInfo() { MOZ_COUNT_DTOR(TraceInfo); }
 
   nsCString* AppendLog();
@@ -41,17 +41,17 @@ struct TraceInfo
   uint64_t mCurTraceSourceId;
   uint64_t mCurTaskId;
   uint64_t mSavedCurTraceSourceId;
   uint64_t mSavedCurTaskId;
   SourceEventType mCurTraceSourceType;
   SourceEventType mSavedCurTraceSourceType;
   uint32_t mThreadId;
   uint32_t mLastUniqueTaskId;
-  bool mStartLogging;
+  mozilla::Atomic<bool> mObsolete;
 
   // This mutex protects the following log array because MoveLogsInto() might
   // be called on another thread.
   mozilla::Mutex mLogsMutex;
   TraceInfoLogsType mLogs;
 };
 
 // Return the TraceInfo of current thread, allocate a new one if not exit.