Merge m-c to inbound a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Mon, 23 Mar 2015 16:51:22 -0700
changeset 264019 7f5abc27fd5366c596a98b6b06169542b89dccb9
parent 264018 812ea3cdd997ed7f58238c3ff267f302a31bd5a3 (current diff)
parent 263970 235a9cb26548a76b85a67af8845746ac27ca2e7a (diff)
child 264020 3f4f37b1b910e835e9eec172de4028a4ffe0b178
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound a=merge CLOSED TREE
security/sandbox/linux/SandboxFilter.cpp
--- 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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
--- 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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <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="527d1c939ee57deb7192166e56e2a3fffa8cb087"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
   <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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
--- 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="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
--- 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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <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="527d1c939ee57deb7192166e56e2a3fffa8cb087"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
--- 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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
   <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": "8eac260ee81a8aca05770d18c5736536d44ee7a7", 
+        "git_revision": "efebbafd12fc42ddcd378948b683a51106517660", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "1400d176ecef76d06b012fb082c246eb17d1d30f", 
+    "revision": "d8e53e5d917b1ce79aea842e8340ce82799cac3e", 
     "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="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
   <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="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="8eac260ee81a8aca05770d18c5736536d44ee7a7"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="efebbafd12fc42ddcd378948b683a51106517660"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="86cd7486d8e50eaac8ef6fe2f51f09d25194577b"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <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="b685e3aab4fde7624d78993877a8f7910f2a5f06"/>
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -54,16 +54,17 @@ support-files =
   doc_conditional-breakpoints.html
   doc_domnode-variables.html
   doc_editor-mode.html
   doc_empty-tab-01.html
   doc_empty-tab-02.html
   doc_event-listeners-01.html
   doc_event-listeners-02.html
   doc_event-listeners-03.html
+  doc_event-listeners-04.html
   doc_frame-parameters.html
   doc_function-display-name.html
   doc_function-search.html
   doc_global-method-override.html
   doc_iframes.html
   doc_included-script.html
   doc_inline-debugger-statement.html
   doc_inline-script.html
@@ -129,16 +130,18 @@ skip-if = e10s || true # bug 1113935
 [browser_dbg_break-on-dom-05.js]
 [browser_dbg_break-on-dom-06.js]
 [browser_dbg_break-on-dom-07.js]
 [browser_dbg_break-on-dom-08.js]
 [browser_dbg_break-on-dom-event-01.js]
 skip-if = e10s || os == "mac" || e10s # Bug 895426
 [browser_dbg_break-on-dom-event-02.js]
 skip-if = e10s # TODO
+[browser_dbg_break-on-dom-event-03.js]
+skip-if = e10s # TODO
 [browser_dbg_breakpoints-actual-location.js]
 [browser_dbg_breakpoints-actual-location2.js]
 [browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js]
 skip-if = e10s # Bug 1093535
 [browser_dbg_breakpoints-button-01.js]
 [browser_dbg_breakpoints-button-02.js]
 [browser_dbg_breakpoints-contextmenu-add.js]
 [browser_dbg_breakpoints-contextmenu.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_break-on-dom-event-03.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the break-on-dom-events request works for load event listeners.
+ */
+
+const TAB_URL = EXAMPLE_URL + "doc_event-listeners-04.html";
+
+let gClient, gThreadClient;
+
+function test() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init();
+    DebuggerServer.addBrowserActors();
+  }
+
+  let transport = DebuggerServer.connectPipe();
+  gClient = new DebuggerClient(transport);
+  gClient.connect((aType, aTraits) => {
+    is(aType, "browser",
+      "Root actor should identify itself as a browser.");
+
+    addTab(TAB_URL)
+      .then(() => attachThreadActorForUrl(gClient, TAB_URL))
+      .then(aThreadClient => gThreadClient = aThreadClient)
+      .then(pauseDebuggee)
+      .then(testBreakOnLoad)
+      .then(closeConnection)
+      .then(finish)
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+}
+
+function pauseDebuggee() {
+  let deferred = promise.defer();
+
+  gClient.addOneTimeListener("paused", (aEvent, aPacket) => {
+    is(aPacket.type, "paused",
+      "We should now be paused.");
+    is(aPacket.why.type, "debuggerStatement",
+      "The debugger statement was hit.");
+
+    gThreadClient.resume(deferred.resolve);
+  });
+
+  // Spin the event loop before causing the debuggee to pause, to allow
+  // this function to return first.
+  executeSoon(() => triggerButtonClick());
+
+  return deferred.promise;
+}
+
+// Test pause on a load event.
+function testBreakOnLoad() {
+  let deferred = promise.defer();
+
+  // Test calling pauseOnDOMEvents from a running state.
+  gThreadClient.pauseOnDOMEvents(["load"], (aPacket) => {
+    is(aPacket.error, undefined,
+      "The pause-on-load request completed successfully.");
+    let handlers = ["loadHandler"];
+
+    gClient.addListener("paused", function tester(aEvent, aPacket) {
+      is(aPacket.why.type, "pauseOnDOMEvents",
+        "A hidden breakpoint was hit.");
+
+      is(aPacket.frame.where.line, 15, "Found the load event listener.");
+      gClient.removeListener("paused", tester);
+      deferred.resolve();
+
+      gThreadClient.resume(() => triggerButtonClick(handlers.slice(-1)));
+    });
+
+    getTabActorForUrl(gClient, TAB_URL).then(aGrip => {
+      gClient.attachTab(aGrip.actor, (aResponse, aTabClient) => {
+        aTabClient.reload();
+      });
+    });
+  });
+
+  return deferred.promise;
+}
+
+function triggerButtonClick() {
+  let button  = content.document.querySelector("button");
+  EventUtils.sendMouseEvent({ type: "click" }, button);
+}
+
+function closeConnection() {
+  let deferred = promise.defer();
+  gClient.close(deferred.resolve);
+  return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+  gClient = null;
+  gThreadClient = null;
+});
--- a/browser/devtools/debugger/test/browser_dbg_source-maps-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_source-maps-04.js
@@ -84,21 +84,21 @@ function disableIgnoreCaughtExceptions()
   return deferred.promise;
 }
 
 function testSetBreakpoint() {
   let deferred = promise.defer();
   let sourceForm = getSourceForm(gSources, JS_URL);
   let source = gDebugger.gThreadClient.source(sourceForm);
 
-  source.setBreakpoint({ line: 3, column: 61 }, aResponse => {
+  source.setBreakpoint({ line: 3, column: 18 }, aResponse => {
     ok(!aResponse.error,
       "Should be able to set a breakpoint in a js file.");
     ok(!aResponse.actualLocation,
-      "Should be able to set a breakpoint on line 3 and column 61.");
+      "Should be able to set a breakpoint on line 3 and column 18.");
 
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 function reloadPage() {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/doc_event-listeners-04.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger test page</title>
+  </head>
+
+  <body>
+    <button>Click me!</button>
+
+    <script type="text/javascript">
+      window.addEventListener("load", function onload() {
+        var button = document.querySelector("button");
+        button.onclick = function () {
+          debugger;
+        };
+      });
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/shared/widgets/Tooltip.js
+++ b/browser/devtools/shared/widgets/Tooltip.js
@@ -184,23 +184,27 @@ function Tooltip(doc, options) {
     noAutoFocus: true,
     closeOnEvents: []
   }, options);
   this.panel = PanelFactory.get(doc, this.options);
 
   // Used for namedTimeouts in the mouseover handling
   this.uid = "tooltip-" + Date.now();
 
-  // Emit show/hide events
-  for (let event of POPUP_EVENTS) {
-    this["_onPopup" + event] = ((e) => {
-      return () => this.emit(e);
-    })(event);
-    this.panel.addEventListener("popup" + event,
-      this["_onPopup" + event], false);
+  // Emit show/hide events when the panel does.
+  for (let eventName of POPUP_EVENTS) {
+    this["_onPopup" + eventName] = (name => {
+      return e => {
+        if (e.target === this.panel) {
+          this.emit(name);
+        }
+      };
+    })(eventName);
+    this.panel.addEventListener("popup" + eventName,
+      this["_onPopup" + eventName], false);
   }
 
   // Listen to keypress events to close the tooltip if configured to do so
   let win = this.doc.querySelector("window");
   this._onKeyPress = event => {
     if (this.panel.hidden) {
       return;
     }
@@ -298,19 +302,19 @@ Tooltip.prototype = {
   },
 
   /**
    * Get rid of references and event listeners
    */
   destroy: function () {
     this.hide();
 
-    for (let event of POPUP_EVENTS) {
-      this.panel.removeEventListener("popup" + event,
-        this["_onPopup" + event], false);
+    for (let eventName of POPUP_EVENTS) {
+      this.panel.removeEventListener("popup" + eventName,
+        this["_onPopup" + eventName], false);
     }
 
     let win = this.doc.querySelector("window");
     win.removeEventListener("keypress", this._onKeyPress, false);
 
     let closeOnEvents = this.options.get("closeOnEvents");
     for (let {emitter, event, useCapture} of closeOnEvents) {
       for (let remove of ["removeEventListener", "off"]) {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -154,16 +154,28 @@ DOMInterfaces = {
 'BluetoothDiscoveryHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothDiscoveryHandle',
 },
 
 'BluetoothGatt': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothGatt',
 },
 
+'BluetoothGattCharacteristic': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothGattCharacteristic',
+},
+
+'BluetoothGattDescriptor': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothGattDescriptor',
+},
+
+'BluetoothGattService': {
+    'nativeType': 'mozilla::dom::bluetooth::BluetoothGattService',
+},
+
 'BluetoothManager': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothManager',
 },
 
 'BluetoothPairingHandle': {
     'nativeType': 'mozilla::dom::bluetooth::BluetoothPairingHandle',
 },
 
--- a/dom/bluetooth2/BluetoothCommon.h
+++ b/dom/bluetooth2/BluetoothCommon.h
@@ -280,16 +280,26 @@ enum BluetoothSspVariant {
   SSP_VARIANT_PASSKEY_CONFIRMATION,
   SSP_VARIANT_PASSKEY_ENTRY,
   SSP_VARIANT_CONSENT,
   SSP_VARIANT_PASSKEY_NOTIFICATION
 };
 
 struct BluetoothUuid {
   uint8_t mUuid[16];
+
+  bool operator==(const BluetoothUuid& aOther) const
+  {
+    for (uint8_t i = 0; i < sizeof(mUuid); i++) {
+      if (mUuid[i] != aOther.mUuid[i]) {
+        return false;
+      }
+    }
+    return true;
+  }
 };
 
 struct BluetoothServiceRecord {
   BluetoothUuid mUuid;
   uint16_t mChannel;
   char mName[256];
 };
 
@@ -542,21 +552,31 @@ enum BluetoothGattStatus {
 
 struct BluetoothGattAdvData {
   uint8_t mAdvData[62];
 };
 
 struct BluetoothGattId {
   BluetoothUuid mUuid;
   uint8_t mInstanceId;
+
+  bool operator==(const BluetoothGattId& aOther) const
+  {
+    return mUuid == aOther.mUuid && mInstanceId == aOther.mInstanceId;
+  }
 };
 
 struct BluetoothGattServiceId {
   BluetoothGattId mId;
   uint8_t mIsPrimary;
+
+  bool operator==(const BluetoothGattServiceId& aOther) const
+  {
+    return mId == aOther.mId && mIsPrimary == aOther.mIsPrimary;
+  }
 };
 
 struct BluetoothGattReadParam {
   BluetoothGattServiceId mServiceId;
   BluetoothGattId mCharId;
   BluetoothGattId mDescriptorId;
   uint8_t mValue[BLUETOOTH_GATT_MAX_ATTR_LEN];
   uint16_t mValueLength;
--- a/dom/bluetooth2/BluetoothGatt.cpp
+++ b/dom/bluetooth2/BluetoothGatt.cpp
@@ -1,52 +1,48 @@
 /* -*- 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/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/bluetooth/BluetoothGatt.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/dom/BluetoothGattBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 USING_BLUETOOTH_NAMESPACE
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(BluetoothGatt)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(BluetoothGatt,
-                                                DOMEventTargetHelper)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(BluetoothGatt,
-                                                  DOMEventTargetHelper)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BluetoothGatt,
+                                   DOMEventTargetHelper,
+                                   mServices)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(BluetoothGatt)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(BluetoothGatt, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(BluetoothGatt, DOMEventTargetHelper)
 
 BluetoothGatt::BluetoothGatt(nsPIDOMWindow* aWindow,
                              const nsAString& aDeviceAddr)
   : DOMEventTargetHelper(aWindow)
   , mAppUuid(EmptyString())
   , mClientIf(0)
   , mConnectionState(BluetoothConnectionState::Disconnected)
   , mDeviceAddr(aDeviceAddr)
+  , mDiscoveringServices(false)
 {
   MOZ_ASSERT(aWindow);
   MOZ_ASSERT(!mDeviceAddr.IsEmpty());
 }
 
 BluetoothGatt::~BluetoothGatt()
 {
   BluetoothService* bs = BluetoothService::Get();
@@ -211,16 +207,46 @@ BluetoothGatt::ReadRemoteRssi(ErrorResul
 
   nsRefPtr<BluetoothReplyRunnable> result =
     new ReadRemoteRssiTask(promise);
   bs->GattClientReadRemoteRssiInternal(mClientIf, mDeviceAddr, result);
 
   return promise.forget();
 }
 
+already_AddRefed<Promise>
+BluetoothGatt::DiscoverServices(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(
+    mConnectionState == BluetoothConnectionState::Connected &&
+    !mDiscoveringServices,
+    NS_ERROR_DOM_INVALID_STATE_ERR);
+
+  BluetoothService* bs = BluetoothService::Get();
+  BT_ENSURE_TRUE_REJECT(bs, NS_ERROR_NOT_AVAILABLE);
+
+  mDiscoveringServices = true;
+  nsRefPtr<BluetoothReplyRunnable> result =
+    new BluetoothVoidReplyRunnable(nullptr /* DOMRequest */,
+                                   promise,
+                                   NS_LITERAL_STRING("DiscoverGattServices"));
+  bs->DiscoverGattServicesInternal(mAppUuid, result);
+
+  return promise.forget();
+}
+
 void
 BluetoothGatt::UpdateConnectionState(BluetoothConnectionState aState)
 {
   BT_API2_LOGR("GATT connection state changes to: %d", int(aState));
   mConnectionState = aState;
 
   // Dispatch connectionstatechanged event to application
   nsCOMPtr<nsIDOMEvent> event;
@@ -231,16 +257,32 @@ BluetoothGatt::UpdateConnectionState(Blu
                         false,
                         false);
   NS_ENSURE_SUCCESS_VOID(rv);
 
   DispatchTrustedEvent(event);
 }
 
 void
+BluetoothGatt::HandleServicesDiscovered(const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattServiceId);
+
+  const InfallibleTArray<BluetoothGattServiceId>& serviceIds =
+    aValue.get_ArrayOfBluetoothGattServiceId();
+
+  for (uint32_t i = 0; i < serviceIds.Length(); i++) {
+    mServices.AppendElement(new BluetoothGattService(
+      GetParentObject(), mAppUuid, serviceIds[i]));
+  }
+
+  BluetoothGattBinding::ClearCachedServicesValue(this);
+}
+
+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();
@@ -248,16 +290,28 @@ BluetoothGatt::Notify(const BluetoothSig
     mClientIf = 0;
   } else if (aData.name().EqualsLiteral(GATT_CONNECTION_STATE_CHANGED_ID)) {
     MOZ_ASSERT(v.type() == BluetoothValue::Tbool);
 
     BluetoothConnectionState state =
       v.get_bool() ? BluetoothConnectionState::Connected
                    : BluetoothConnectionState::Disconnected;
     UpdateConnectionState(state);
+  } else if (aData.name().EqualsLiteral("ServicesDiscovered")) {
+    HandleServicesDiscovered(v);
+  } else if (aData.name().EqualsLiteral("DiscoverCompleted")) {
+    MOZ_ASSERT(v.type() == BluetoothValue::Tbool);
+
+    bool isDiscoverSuccess = v.get_bool();
+    if (!isDiscoverSuccess) { // Clean all discovered attributes if failed
+      mServices.Clear();
+      BluetoothGattBinding::ClearCachedServicesValue(this);
+    }
+
+    mDiscoveringServices = false;
   } else {
     BT_WARNING("Not handling GATT signal: %s",
                NS_ConvertUTF16toUTF8(aData.name()).get());
   }
 }
 
 JSObject*
 BluetoothGatt::WrapObject(JSContext* aContext, JS::Handle<JSObject*> aGivenProto)
--- a/dom/bluetooth2/BluetoothGatt.h
+++ b/dom/bluetooth2/BluetoothGatt.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_bluetooth_bluetoothgatt_h__
 #define mozilla_dom_bluetooth_bluetoothgatt_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/BluetoothGattBinding.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothGattService.h"
 #include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace dom {
 class Promise;
 }
 }
 
@@ -36,26 +37,32 @@ public:
   /****************************************************************************
    * Attribute Getters
    ***************************************************************************/
   BluetoothConnectionState ConnectionState() const
   {
     return mConnectionState;
   }
 
+  void GetServices(nsTArray<nsRefPtr<BluetoothGattService>>& aServices) const
+  {
+    aServices = mServices;
+  }
+
   /****************************************************************************
    * Event Handlers
    ***************************************************************************/
   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);
   already_AddRefed<Promise> ReadRemoteRssi(ErrorResult& aRv);
 
   /****************************************************************************
    * Others
    ***************************************************************************/
   void Notify(const BluetoothSignal& aParam); // BluetoothSignalObserver
 
   nsPIDOMWindow* GetParentObject() const
@@ -82,16 +89,25 @@ private:
 
   /**
    * Generate a random uuid.
    *
    * @param aUuidString [out] String to store the generated uuid.
    */
   void GenerateUuid(nsAString &aUuidString);
 
+  /**
+   * 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);
+
   /****************************************************************************
    * Variables
    ***************************************************************************/
   /**
    * Random generated UUID of this GATT client.
    */
   nsString mAppUuid;
 
@@ -105,13 +121,23 @@ private:
    * Connection state of this remote device.
    */
   BluetoothConnectionState mConnectionState;
 
   /**
    * Address of the remote device.
    */
   nsString mDeviceAddr;
+
+  /**
+   * Array of discovered services from the remote GATT server.
+   */
+  nsTArray<nsRefPtr<BluetoothGattService>> mServices;
+
+  /**
+   * Indicate whether there is ongoing discoverServices request or not.
+   */
+  bool mDiscoveringServices;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGattCharacteristic.cpp
@@ -0,0 +1,97 @@
+/* -*- 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 "BluetoothService.h"
+#include "BluetoothUtils.h"
+#include "mozilla/dom/BluetoothGattCharacteristicBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
+#include "mozilla/dom/bluetooth/BluetoothGattDescriptor.h"
+#include "mozilla/dom/bluetooth/BluetoothGattService.h"
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(
+  BluetoothGattCharacteristic, mOwner, mService, mDescriptors)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattCharacteristic)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattCharacteristic)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattCharacteristic)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+BluetoothGattCharacteristic::BluetoothGattCharacteristic(
+  nsPIDOMWindow* aOwner,
+  BluetoothGattService* aService,
+  const BluetoothGattId& aCharId)
+  : mOwner(aOwner)
+  , mService(aService)
+  , mCharId(aCharId)
+{
+  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
+  // of this characteristic to applications
+  nsString path;
+  GeneratePathFromGattId(mCharId, path, mUuidStr);
+  bs->RegisterBluetoothSignalHandler(path, this);
+}
+
+BluetoothGattCharacteristic::~BluetoothGattCharacteristic()
+{
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  nsString path;
+  GeneratePathFromGattId(mCharId, path);
+  bs->UnregisterBluetoothSignalHandler(path, this);
+}
+
+void
+BluetoothGattCharacteristic::HandleDescriptorsDiscovered(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattId);
+
+  const InfallibleTArray<BluetoothGattId>& descriptorIds =
+    aValue.get_ArrayOfBluetoothGattId();
+
+  for (uint32_t i = 0; i < descriptorIds.Length(); i++) {
+    mDescriptors.AppendElement(new BluetoothGattDescriptor(
+      GetParentObject(), this, descriptorIds[i]));
+  }
+
+  BluetoothGattCharacteristicBinding::ClearCachedDescriptorsValue(this);
+}
+
+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 {
+    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);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGattCharacteristic.h
@@ -0,0 +1,120 @@
+/* -*- 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/. */
+
+#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 "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsPIDOMWindow.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothGattService;
+class BluetoothSignal;
+class BluetoothValue;
+
+class BluetoothGattCharacteristic final : public nsISupports
+                                        , public nsWrapperCache
+                                        , public BluetoothSignalObserver
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattCharacteristic)
+
+  /****************************************************************************
+   * Attribute Getters
+   ***************************************************************************/
+  BluetoothGattService* Service() const
+  {
+    return mService;
+  }
+
+  void GetDescriptors(
+    nsTArray<nsRefPtr<BluetoothGattDescriptor>>& aDescriptors) const
+  {
+    aDescriptors = mDescriptors;
+  }
+
+  void GetUuid(nsString& aUuidStr) const
+  {
+    aUuidStr = mUuidStr;
+  }
+
+  int InstanceId() const
+  {
+    return mCharId.mInstanceId;
+  }
+
+  /****************************************************************************
+   * Others
+   ***************************************************************************/
+  const BluetoothGattId& GetCharacteristicId() const
+  {
+    return mCharId;
+  }
+
+  void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+     return mOwner;
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  BluetoothGattCharacteristic(nsPIDOMWindow* aOwner,
+                              BluetoothGattService* aService,
+                              const BluetoothGattId& aCharId);
+
+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);
+
+  /****************************************************************************
+   * Variables
+   ***************************************************************************/
+  nsCOMPtr<nsPIDOMWindow> mOwner;
+
+  /**
+   * Service that this characteristic belongs to.
+   */
+  nsRefPtr<BluetoothGattService> mService;
+
+  /**
+   * Array of discovered descriptors for this characteristic.
+   */
+  nsTArray<nsRefPtr<BluetoothGattDescriptor>> mDescriptors;
+
+  /**
+   * GattId of this GATT characteristic which contains
+   * 1) mUuid: UUID of this characteristic in byte array format.
+   * 2) mInstanceId: Instance id of this characteristic.
+   */
+  BluetoothGattId mCharId;
+
+  /**
+   * UUID string of this GATT characteristic.
+   */
+  nsString mUuidStr;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGattDescriptor.cpp
@@ -0,0 +1,54 @@
+/* -*- 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 "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/BluetoothTypes.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+USING_BLUETOOTH_NAMESPACE
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(
+  BluetoothGattDescriptor, mOwner, mCharacteristic)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattDescriptor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattDescriptor)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattDescriptor)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+BluetoothGattDescriptor::BluetoothGattDescriptor(
+  nsPIDOMWindow* aOwner,
+  BluetoothGattCharacteristic* aCharacteristic,
+  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);
+}
+
+BluetoothGattDescriptor::~BluetoothGattDescriptor()
+{
+}
+
+JSObject*
+BluetoothGattDescriptor::WrapObject(JSContext* aContext,
+                                    JS::Handle<JSObject*> aGivenProto)
+{
+  return BluetoothGattDescriptorBinding::Wrap(aContext, this, aGivenProto);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGattDescriptor.h
@@ -0,0 +1,88 @@
+/* -*- 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/. */
+
+#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 "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:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattDescriptor)
+
+  /****************************************************************************
+   * Attribute Getters
+   ***************************************************************************/
+  BluetoothGattCharacteristic* Characteristic() const
+  {
+    return mCharacteristic;
+  }
+
+  void GetUuid(nsString& aUuidStr) const
+  {
+    aUuidStr = mUuidStr;
+  }
+
+  /****************************************************************************
+   * Others
+   ***************************************************************************/
+  void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+     return mOwner;
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  BluetoothGattDescriptor(nsPIDOMWindow* aOwner,
+                          BluetoothGattCharacteristic* aCharacteristic,
+                          const BluetoothGattId& aDescriptorId);
+
+private:
+  ~BluetoothGattDescriptor();
+
+  /****************************************************************************
+   * Variables
+   ***************************************************************************/
+  nsCOMPtr<nsPIDOMWindow> mOwner;
+
+  /**
+   * Characteristic that this descriptor belongs to.
+   */
+  nsRefPtr<BluetoothGattCharacteristic> mCharacteristic;
+
+  /**
+   * GattId of this GATT descriptor which contains
+   * 1) mUuid: UUID of this descriptor in byte array format.
+   * 2) mInstanceId: Instance id of this descriptor.
+   */
+  BluetoothGattId mDescriptorId;
+
+  /**
+   * UUID string of this GATT descriptor.
+   */
+  nsString mUuidStr;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGattService.cpp
@@ -0,0 +1,114 @@
+/* -*- 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 "BluetoothService.h"
+#include "BluetoothUtils.h"
+#include "mozilla/dom/BluetoothGattServiceBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.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(
+  BluetoothGattService, mOwner, mIncludedServices, mCharacteristics)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(BluetoothGattService)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(BluetoothGattService)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BluetoothGattService)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+BluetoothGattService::BluetoothGattService(
+  nsPIDOMWindow* aOwner, const nsAString& aAppUuid,
+  const BluetoothGattServiceId& aServiceId)
+  : mOwner(aOwner)
+  , mAppUuid(aAppUuid)
+  , mServiceId(aServiceId)
+{
+  MOZ_ASSERT(aOwner);
+  MOZ_ASSERT(!mAppUuid.IsEmpty());
+
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  // Generate bluetooth signal path and a string representation to provide
+  // uuid of this service to applications
+  nsString path;
+  GeneratePathFromGattId(mServiceId.mId, path, mUuidStr);
+  bs->RegisterBluetoothSignalHandler(path, this);
+}
+
+BluetoothGattService::~BluetoothGattService()
+{
+  BluetoothService* bs = BluetoothService::Get();
+  NS_ENSURE_TRUE_VOID(bs);
+
+  nsString path;
+  GeneratePathFromGattId(mServiceId.mId, path);
+  bs->UnregisterBluetoothSignalHandler(path, this);
+}
+
+void
+BluetoothGattService::HandleIncludedServicesDiscovered(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattServiceId);
+
+  const InfallibleTArray<BluetoothGattServiceId>& includedServIds =
+    aValue.get_ArrayOfBluetoothGattServiceId();
+
+  for (uint32_t i = 0; i < includedServIds.Length(); i++) {
+    mIncludedServices.AppendElement(new BluetoothGattService(
+      GetParentObject(), mAppUuid, includedServIds[i]));
+  }
+
+  BluetoothGattServiceBinding::ClearCachedIncludedServicesValue(this);
+}
+
+void
+BluetoothGattService::HandleCharacteristicsDiscovered(
+  const BluetoothValue& aValue)
+{
+  MOZ_ASSERT(aValue.type() == BluetoothValue::TArrayOfBluetoothGattId);
+
+  const InfallibleTArray<BluetoothGattId>& characteristicIds =
+    aValue.get_ArrayOfBluetoothGattId();
+
+  for (uint32_t i = 0; i < characteristicIds.Length(); i++) {
+    mCharacteristics.AppendElement(new BluetoothGattCharacteristic(
+      GetParentObject(), this, characteristicIds[i]));
+  }
+
+  BluetoothGattServiceBinding::ClearCachedCharacteristicsValue(this);
+}
+
+void
+BluetoothGattService::Notify(const BluetoothSignal& aData)
+{
+  BT_LOGD("[D] %s", NS_ConvertUTF16toUTF8(aData.name()).get());
+
+  BluetoothValue v = aData.value();
+  if (aData.name().EqualsLiteral("IncludedServicesDiscovered")) {
+    HandleIncludedServicesDiscovered(v);
+  } else if (aData.name().EqualsLiteral("CharacteristicsDiscovered")) {
+    HandleCharacteristicsDiscovered(v);
+  } else {
+    BT_WARNING("Not handling GATT Service signal: %s",
+               NS_ConvertUTF16toUTF8(aData.name()).get());
+  }
+}
+
+JSObject*
+BluetoothGattService::WrapObject(JSContext* aContext,
+                                 JS::Handle<JSObject*> aGivenProto)
+{
+  return BluetoothGattServiceBinding::Wrap(aContext, this, aGivenProto);
+}
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/BluetoothGattService.h
@@ -0,0 +1,146 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_bluetooth_bluetoothgattservice_h__
+#define mozilla_dom_bluetooth_bluetoothgattservice_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/BluetoothGattServiceBinding.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
+#include "mozilla/dom/bluetooth/BluetoothGattCharacteristic.h"
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+#include "nsPIDOMWindow.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothSignal;
+class BluetoothValue;
+
+class BluetoothGattService final : public nsISupports
+                                 , public nsWrapperCache
+                                 , public BluetoothSignalObserver
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattService)
+
+  /****************************************************************************
+   * Attribute Getters
+   ***************************************************************************/
+  bool IsPrimary() const
+  {
+    return mServiceId.mIsPrimary;
+  }
+
+  void GetUuid(nsString& aUuidStr) const
+  {
+    aUuidStr = mUuidStr;
+  }
+
+  int InstanceId() const
+  {
+    return mServiceId.mId.mInstanceId;
+  }
+
+  void GetIncludedServices(
+    nsTArray<nsRefPtr<BluetoothGattService>>& aIncludedServices) const
+  {
+    aIncludedServices = mIncludedServices;
+  }
+
+  void GetCharacteristics(
+    nsTArray<nsRefPtr<BluetoothGattCharacteristic>>& aCharacteristics) const
+  {
+    aCharacteristics = mCharacteristics;
+  }
+
+  /****************************************************************************
+   * Others
+   ***************************************************************************/
+  const nsAString& GetAppUuid() const
+  {
+    return mAppUuid;
+  }
+
+  const BluetoothGattServiceId& GetServiceId() const
+  {
+    return mServiceId;
+  }
+
+  void Notify(const BluetoothSignal& aData); // BluetoothSignalObserver
+
+  nsPIDOMWindow* GetParentObject() const
+  {
+     return mOwner;
+  }
+
+  virtual JSObject* WrapObject(JSContext* aCx,
+                               JS::Handle<JSObject*> aGivenProto) override;
+
+  BluetoothGattService(nsPIDOMWindow* aOwner,
+                       const nsAString& aAppUuid,
+                       const BluetoothGattServiceId& aServiceId);
+
+private:
+  ~BluetoothGattService();
+
+  /**
+   * Add newly discovered GATT included services into mIncludedServices and
+   * update the cache value of mIncludedServices.
+   *
+   * @param aValue [in] BluetoothValue which contains an array of
+   *                    BluetoothGattServiceId of all discovered included
+   *                    services.
+   */
+  void HandleIncludedServicesDiscovered(const BluetoothValue& aValue);
+
+  /**
+   * Add newly discovered GATT characteristics into mCharacteristics and
+   * update the cache value of mCharacteristics.
+   *
+   * @param aValue [in] BluetoothValue which contains an array of
+   *                    BluetoothGattId of all discovered characteristics.
+   */
+  void HandleCharacteristicsDiscovered(const BluetoothValue& aValue);
+
+  /****************************************************************************
+   * Variables
+   ***************************************************************************/
+  nsCOMPtr<nsPIDOMWindow> mOwner;
+
+  /**
+   * UUID of the GATT client.
+   */
+  nsString mAppUuid;
+
+  /**
+   * ServiceId of this GATT service which contains
+   * 1) mId.mUuid: UUID of this service in byte array format.
+   * 2) mId.mInstanceId: Instance id of this service.
+   * 3) mIsPrimary: Indicate whether this is a primary service or not.
+   */
+  BluetoothGattServiceId mServiceId;
+
+  /**
+   * UUID string of this GATT service.
+   */
+  nsString mUuidStr;
+
+  /**
+   * Array of discovered included services for this service.
+   */
+  nsTArray<nsRefPtr<BluetoothGattService>> mIncludedServices;
+
+  /**
+   * Array of discovered characteristics for this service.
+   */
+  nsTArray<nsRefPtr<BluetoothGattCharacteristic>> mCharacteristics;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth2/BluetoothInterface.h
+++ b/dom/bluetooth2/BluetoothInterface.h
@@ -686,30 +686,34 @@ public:
 
   /* Clear the attribute cache for a given device*/
   virtual void Refresh(int aClientIf,
                        const nsAString& aBdAddr,
                        BluetoothGattClientResultHandler* aRes) = 0;
 
   /* Enumerate Attributes */
   virtual void SearchService(int aConnId,
+                             bool aSearchAll,
                              const BluetoothUuid& aUuid,
                              BluetoothGattClientResultHandler* aRes) = 0;
   virtual void GetIncludedService(
     int aConnId,
     const BluetoothGattServiceId& aServiceId,
+    bool aFirst,
     const BluetoothGattServiceId& aStartServiceId,
     BluetoothGattClientResultHandler* aRes) = 0;
   virtual void GetCharacteristic(int aConnId,
                                  const BluetoothGattServiceId& aServiceId,
+                                 bool aFirst,
                                  const BluetoothGattId& aStartCharId,
                                  BluetoothGattClientResultHandler* aRes) = 0;
   virtual void GetDescriptor(int aConnId,
                              const BluetoothGattServiceId& aServiceId,
                              const BluetoothGattId& aCharId,
+                             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,
--- a/dom/bluetooth2/BluetoothService.h
+++ b/dom/bluetooth2/BluetoothService.h
@@ -338,16 +338,24 @@ public:
    * (platform specific implementation)
    */
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
                                BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
+   * Discover GATT services, characteristic, descriptors from a remote GATT
+   * server. (platform specific implementation)
+   */
+  virtual void
+  DiscoverGattServicesInternal(const nsAString& aAppUuid,
+                               BluetoothReplyRunnable* aRunnable) = 0;
+
+  /**
    * Unregister a GATT client. (platform specific implementation)
    */
   virtual void
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) = 0;
 
   /**
    * Request RSSI for a remote GATT server. (platform specific implementation)
--- a/dom/bluetooth2/BluetoothUtils.cpp
+++ b/dom/bluetooth2/BluetoothUtils.cpp
@@ -36,16 +36,27 @@ UuidToString(const BluetoothUuid& aUuid,
           ntohs(uuid2), ntohs(uuid3),
           ntohl(uuid4), ntohs(uuid5));
 
   aString.Truncate();
   aString.AssignLiteral(uuidStr);
 }
 
 void
+ReversedUuidToString(const BluetoothUuid& aUuid, nsAString& aString)
+{
+  BluetoothUuid uuid;
+  for (uint8_t i = 0; i < 16; i++) {
+    uuid.mUuid[i] = aUuid.mUuid[15 - i];
+  }
+
+  UuidToString(uuid, aString);
+}
+
+void
 StringToUuid(const char* aString, BluetoothUuid& aUuid)
 {
   uint32_t uuid0, uuid4;
   uint16_t uuid1, uuid2, uuid3, uuid5;
 
   sscanf(aString, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
          &uuid0, &uuid1, &uuid2, &uuid3, &uuid4, &uuid5);
 
@@ -59,16 +70,36 @@ StringToUuid(const char* aString, Blueto
   memcpy(&aUuid.mUuid[0], &uuid0, sizeof(uint32_t));
   memcpy(&aUuid.mUuid[4], &uuid1, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[6], &uuid2, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[8], &uuid3, sizeof(uint16_t));
   memcpy(&aUuid.mUuid[10], &uuid4, sizeof(uint32_t));
   memcpy(&aUuid.mUuid[14], &uuid5, sizeof(uint16_t));
 }
 
+void
+GeneratePathFromGattId(const BluetoothGattId& aId,
+                       nsAString& aPath,
+                       nsAString& aUuidStr)
+{
+  ReversedUuidToString(aId.mUuid, aUuidStr);
+
+  aPath.Assign(aUuidStr);
+  aPath.AppendLiteral("_");
+  aPath.AppendInt(aId.mInstanceId);
+}
+
+void
+GeneratePathFromGattId(const BluetoothGattId& aId,
+                       nsAString& aPath)
+{
+  nsString uuidStr;
+  GeneratePathFromGattId(aId, aPath, uuidStr);
+}
+
 /**
  * |SetJsObject| is an internal function used by |BroadcastSystemMessage| only
  */
 bool
 SetJsObject(JSContext* aContext,
             const BluetoothValue& aValue,
             JS::Handle<JSObject*> aObj)
 {
--- a/dom/bluetooth2/BluetoothUtils.h
+++ b/dom/bluetooth2/BluetoothUtils.h
@@ -17,31 +17,72 @@ class BluetoothReplyRunnable;
 class BluetoothValue;
 
 //
 // BluetoothUuid <-> uuid string conversion
 //
 
 /**
  * Convert BluetoothUuid object to xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string.
- * This utility function is used by gecko internal only to convert BluetoothUuid
- * created by bluetooth stack to uuid string representation.
+ *
+ * Note: This utility function is used by gecko internal only to convert
+ * BluetoothUuid created by bluetooth stack to uuid string representation.
  */
 void
 UuidToString(const BluetoothUuid& aUuid, nsAString& aString);
 
 /**
+ * Convert BluetoothUuid object in a reversed byte order to
+ * xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string.
+ * Bluedroid stack reports the BluetoothUuid in a reversed byte order for
+ * GATT service, characteristic, descriptor uuids.
+ *
+ * Note: This utility function is used by gecko internal only to convert
+ * BluetoothUuid in a reversed byte order created by bluetooth stack to uuid
+ * string representation.
+ */
+void
+ReversedUuidToString(const BluetoothUuid& aUuid, nsAString& aString);
+
+/**
  * Convert xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx uuid string to BluetoothUuid object.
- * This utility function is used by gecko internal only to convert uuid string
- * created by gecko back to BluetoothUuid representation.
+ *
+ * Note: This utility function is used by gecko internal only to convert uuid
+ * string created by gecko back to BluetoothUuid representation.
  */
 void
 StringToUuid(const char* aString, BluetoothUuid& aUuid);
 
 //
+// Generate bluetooth signal path from GattId
+//
+
+/**
+ * Generate bluetooth signal path and UUID string from a GattId.
+ *
+ * @param aId      [in] GattId value to convert.
+ * @param aPath    [out] Bluetooth signal path generated from aId.
+ * @param aUuidStr [out] UUID string generated from aId.
+ */
+void
+GeneratePathFromGattId(const BluetoothGattId& aId,
+                       nsAString& aPath,
+                       nsAString& aUuidStr);
+
+/**
+ * Generate bluetooth signal path from a GattId.
+ *
+ * @param aId   [in] GattId value to convert.
+ * @param aPath [out] Bluetooth signal path generated from aId.
+ */
+void
+GeneratePathFromGattId(const BluetoothGattId& aId,
+                       nsAString& aPath);
+
+//
 // Broadcast system message
 //
 
 bool
 BroadcastSystemMessage(const nsAString& aType,
                        const BluetoothValue& aData);
 
 bool
--- a/dom/bluetooth2/bluedroid/BluetoothGattHALInterface.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothGattHALInterface.cpp
@@ -616,24 +616,26 @@ BluetoothGattClientHALInterface::Refresh
     DispatchBluetoothGattClientHALResult(
       aRes, &BluetoothGattClientResultHandler::Refresh,
       ConvertDefault(status, STATUS_FAIL));
   }
 }
 
 void
 BluetoothGattClientHALInterface::SearchService(
-  int aConnId, const BluetoothUuid& aUuid,
+  int aConnId, bool aSearchAll, const BluetoothUuid& aUuid,
   BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   bt_uuid_t uuid;
 
-  if (NS_SUCCEEDED(Convert(aUuid, uuid))) {
+  if (aSearchAll) {
+    status = mInterface->search_service(aConnId, 0);
+  } else if (NS_SUCCEEDED(Convert(aUuid, uuid))) {
     status = mInterface->search_service(aConnId, &uuid);
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
 
@@ -642,26 +644,28 @@ BluetoothGattClientHALInterface::SearchS
       aRes, &BluetoothGattClientResultHandler::SearchService,
       ConvertDefault(status, STATUS_FAIL));
   }
 }
 
 void
 BluetoothGattClientHALInterface::GetIncludedService(
   int aConnId, const BluetoothGattServiceId& aServiceId,
-  const BluetoothGattServiceId& aStartServiceId,
+  bool aFirst, const BluetoothGattServiceId& aStartServiceId,
   BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   btgatt_srvc_id_t serviceId;
   btgatt_srvc_id_t startServiceId;
 
-  if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
-      NS_SUCCEEDED(Convert(aStartServiceId, startServiceId))) {
+  if (aFirst && NS_SUCCEEDED(Convert(aServiceId, serviceId))) {
+    status = mInterface->get_included_service(aConnId, &serviceId, 0);
+  } else if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
+             NS_SUCCEEDED(Convert(aStartServiceId, startServiceId))) {
     status = mInterface->get_included_service(aConnId, &serviceId,
                                               &startServiceId);
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
@@ -671,26 +675,28 @@ BluetoothGattClientHALInterface::GetIncl
       aRes, &BluetoothGattClientResultHandler::GetIncludedService,
       ConvertDefault(status, STATUS_FAIL));
   }
 }
 
 void
 BluetoothGattClientHALInterface::GetCharacteristic(
   int aConnId, const BluetoothGattServiceId& aServiceId,
-  const BluetoothGattId& aStartCharId,
+  bool aFirst, const BluetoothGattId& aStartCharId,
   BluetoothGattClientResultHandler* aRes)
 {
   bt_status_t status;
 #if ANDROID_VERSION >= 19
   btgatt_srvc_id_t serviceId;
   btgatt_gatt_id_t startCharId;
 
-  if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
-      NS_SUCCEEDED(Convert(aStartCharId, startCharId))) {
+  if (aFirst && NS_SUCCEEDED(Convert(aServiceId, serviceId))) {
+    status = mInterface->get_characteristic(aConnId, &serviceId, 0);
+  } else if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
+             NS_SUCCEEDED(Convert(aStartCharId, startCharId))) {
     status = mInterface->get_characteristic(aConnId, &serviceId, &startCharId);
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
 
@@ -699,29 +705,32 @@ BluetoothGattClientHALInterface::GetChar
       aRes, &BluetoothGattClientResultHandler::GetCharacteristic,
       ConvertDefault(status, STATUS_FAIL));
   }
 }
 
 void
 BluetoothGattClientHALInterface::GetDescriptor(
   int aConnId, const BluetoothGattServiceId& aServiceId,
-  const BluetoothGattId& aCharId,
-  const BluetoothGattId& aDescriptorId,
-  BluetoothGattClientResultHandler* aRes)
+  const BluetoothGattId& aCharId, bool aFirst,
+  const BluetoothGattId& aDescriptorId, 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;
 
-  if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
-      NS_SUCCEEDED(Convert(aCharId, charId)) &&
-      NS_SUCCEEDED(Convert(aDescriptorId, descriptorId))) {
+  if (aFirst &&
+      NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
+      NS_SUCCEEDED(Convert(aCharId, charId))) {
+    status = mInterface->get_descriptor(aConnId, &serviceId, &charId, 0);
+  } else if (NS_SUCCEEDED(Convert(aServiceId, serviceId)) &&
+             NS_SUCCEEDED(Convert(aCharId, charId)) &&
+             NS_SUCCEEDED(Convert(aDescriptorId, descriptorId))) {
     status = mInterface->get_descriptor(aConnId, &serviceId, &charId,
                                         &descriptorId);
   } else {
     status = BT_STATUS_PARM_INVALID;
   }
 #else
   status = BT_STATUS_UNSUPPORTED;
 #endif
--- a/dom/bluetooth2/bluedroid/BluetoothGattHALInterface.h
+++ b/dom/bluetooth2/bluedroid/BluetoothGattHALInterface.h
@@ -51,29 +51,33 @@ public:
 
   /* Clear the attribute cache for a given device*/
   void Refresh(int aClientIf,
                const nsAString& aBdAddr,
                BluetoothGattClientResultHandler* aRes);
 
   /* Enumerate Attributes */
   void SearchService(int aConnId,
+                     bool aSearchAll,
                      const BluetoothUuid& aUuid,
                      BluetoothGattClientResultHandler* aRes);
   void GetIncludedService(int aConnId,
                           const BluetoothGattServiceId& aServiceId,
+                          bool aFirst,
                           const BluetoothGattServiceId& aStartServiceId,
                           BluetoothGattClientResultHandler* aRes);
   void GetCharacteristic(int aConnId,
                          const BluetoothGattServiceId& aServiceId,
+                         bool aFirst,
                          const BluetoothGattId& aStartCharId,
                          BluetoothGattClientResultHandler* aRes);
   void GetDescriptor(int aConnId,
                      const BluetoothGattServiceId& aServiceId,
                      const BluetoothGattId& aCharId,
+                     bool aFirst,
                      const BluetoothGattId& aDescriptorId,
                      BluetoothGattClientResultHandler* aRes);
 
   /* Read / Write An Attribute */
   void ReadCharacteristic(int aConnId,
                           const BluetoothGattServiceId& aServiceId,
                           const BluetoothGattId& aCharId,
                           int aAuthReq,
--- a/dom/bluetooth2/bluedroid/BluetoothGattManager.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothGattManager.cpp
@@ -1,22 +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 "BluetoothGattManager.h"
 
-#include "BluetoothCommon.h"
 #include "BluetoothInterface.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothService.h"
 #include "BluetoothUtils.h"
 #include "MainThreadUtils.h"
+#include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "nsIObserverService.h"
 #include "nsThreadUtils.h"
 
 #define ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(runnable)                       \
   do {                                                                        \
     if (!sBluetoothGattInterface) {                                           \
@@ -32,47 +32,87 @@ USING_BLUETOOTH_NAMESPACE
 namespace {
   StaticRefPtr<BluetoothGattManager> sBluetoothGattManager;
   static BluetoothGattInterface* sBluetoothGattInterface;
   static BluetoothGattClientInterface* sBluetoothGattClientInterface;
 } // anonymous namespace
 
 bool BluetoothGattManager::mInShutdown = false;
 
-class BluetoothGattClient;
 static StaticAutoPtr<nsTArray<nsRefPtr<BluetoothGattClient> > > sClients;
 
-class BluetoothGattClient final : public nsISupports
+class mozilla::dom::bluetooth::BluetoothGattClient final : public nsISupports
 {
 public:
   NS_DECL_ISUPPORTS
 
   BluetoothGattClient(const nsAString& aAppUuid, const nsAString& aDeviceAddr)
   : mAppUuid(aAppUuid)
   , mDeviceAddr(aDeviceAddr)
   , mClientIf(0)
   , mConnId(0)
   { }
 
   ~BluetoothGattClient()
   {
     mConnectRunnable = nullptr;
     mDisconnectRunnable = nullptr;
+    mDiscoverRunnable = nullptr;
     mUnregisterClientRunnable = nullptr;
     mReadRemoteRssiRunnable = nullptr;
   }
 
+  void NotifyDiscoverCompleted(bool aSuccess)
+  {
+    MOZ_ASSERT(!mAppUuid.IsEmpty());
+    MOZ_ASSERT(mDiscoverRunnable);
+
+    BluetoothService* bs = BluetoothService::Get();
+    NS_ENSURE_TRUE_VOID(bs);
+
+    // Notify application to clear the cache values of
+    // service/characteristic/descriptor.
+    bs->DistributeSignal(NS_LITERAL_STRING("DiscoverCompleted"),
+                         mAppUuid,
+                         BluetoothValue(aSuccess));
+
+    // Resolve/Reject the Promise.
+    if (aSuccess) {
+      DispatchReplySuccess(mDiscoverRunnable);
+    } else {
+      DispatchReplyError(mDiscoverRunnable,
+                         NS_LITERAL_STRING("Discover failed"));
+    }
+
+    // Cleanup
+    mServices.Clear();
+    mIncludedServices.Clear();
+    mCharacteristics.Clear();
+    mDescriptors.Clear();
+    mDiscoverRunnable = nullptr;
+  }
+
   nsString mAppUuid;
   nsString mDeviceAddr;
   int mClientIf;
   int mConnId;
   nsRefPtr<BluetoothReplyRunnable> mConnectRunnable;
   nsRefPtr<BluetoothReplyRunnable> mDisconnectRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mDiscoverRunnable;
+  nsRefPtr<BluetoothReplyRunnable> mReadRemoteRssiRunnable;
   nsRefPtr<BluetoothReplyRunnable> mUnregisterClientRunnable;
-  nsRefPtr<BluetoothReplyRunnable> mReadRemoteRssiRunnable;
+
+  /**
+   * 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<BluetoothGattId> mDescriptors;
 };
 
 NS_IMPL_ISUPPORTS0(BluetoothGattClient)
 
 class UuidComparator
 {
 public:
   bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
@@ -87,16 +127,26 @@ class ClientIfComparator
 public:
   bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
               int aClientIf) const
   {
     return aClient->mClientIf == aClientIf;
   }
 };
 
+class ConnIdComparator
+{
+public:
+  bool Equals(const nsRefPtr<BluetoothGattClient>& aClient,
+              int aConnId) const
+  {
+    return aClient->mConnId == aConnId;
+  }
+};
+
 BluetoothGattManager*
 BluetoothGattManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // If sBluetoothGattManager already exists, exit early
   if (sBluetoothGattManager) {
     return sBluetoothGattManager;
@@ -333,23 +383,17 @@ BluetoothGattManager::UnregisterClient(i
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-
-  // Reject the unregister request if the client is not found
-  if (index == sClients->NoIndex) {
-    DispatchReplyError(aRunnable,
-                       NS_LITERAL_STRING("Unregister GATT client failed"));
-    return;
-  }
+  MOZ_ASSERT(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mUnregisterClientRunnable = aRunnable;
 
   sBluetoothGattClientInterface->UnregisterClient(
     aClientIf,
     new UnregisterClientResultHandler(client));
 }
@@ -464,33 +508,88 @@ BluetoothGattManager::Disconnect(const n
                                  BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
-
-  // Reject the disconnect request if the client is not found
-  if (index == sClients->NoIndex) {
-    DispatchReplyError(aRunnable, NS_LITERAL_STRING("Disconnect failed"));
-    return;
-  }
+  MOZ_ASSERT(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mDisconnectRunnable = aRunnable;
 
   sBluetoothGattClientInterface->Disconnect(
     client->mClientIf,
     aDeviceAddr,
     client->mConnId,
     new DisconnectResultHandler(client));
 }
 
+class BluetoothGattManager::DiscoverResultHandler final
+  : public BluetoothGattClientResultHandler
+{
+public:
+  DiscoverResultHandler(BluetoothGattClient* aClient)
+  : mClient(aClient)
+  {
+    MOZ_ASSERT(mClient);
+  }
+
+  void OnError(BluetoothStatus aStatus) override
+  {
+    BT_WARNING("BluetoothGattClientInterface::Discover failed: %d",
+               (int)aStatus);
+
+    mClient->NotifyDiscoverCompleted(false);
+  }
+
+private:
+  nsRefPtr<BluetoothGattClient> mClient;
+};
+
+void
+BluetoothGattManager::Discover(const nsAString& aAppUuid,
+                               BluetoothReplyRunnable* aRunnable)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aRunnable);
+
+  ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
+
+  size_t index = sClients->IndexOf(aAppUuid, 0 /* Start */, UuidComparator());
+  MOZ_ASSERT(index != sClients->NoIndex);
+
+  nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
+  MOZ_ASSERT(client->mConnId > 0);
+  MOZ_ASSERT(!client->mDiscoverRunnable);
+
+  client->mDiscoverRunnable = aRunnable;
+
+  /**
+   * Discover all services/characteristics/descriptors offered by the remote
+   * GATT server.
+   *
+   * The discover procesure includes following steps.
+   * 1) Discover all services.
+   * 2) After all services are discovered, for each service S, we will do
+   *    following actions.
+   *    2-1) Discover all included services of service S.
+   *    2-2) Discover all characteristics of service S.
+   *    2-3) Discover all descriptors of those characteristics discovered in
+   *         2-2).
+   */
+  sBluetoothGattClientInterface->SearchService(
+    client->mConnId,
+    true, // search all services
+    BluetoothUuid(),
+    new DiscoverResultHandler(client));
+}
+
 class BluetoothGattManager::ReadRemoteRssiResultHandler final
   : public BluetoothGattClientResultHandler
 {
 public:
   ReadRemoteRssiResultHandler(BluetoothGattClient* aClient)
   : mClient(aClient)
   {
     MOZ_ASSERT(mClient);
@@ -522,23 +621,17 @@ BluetoothGattManager::ReadRemoteRssi(int
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aRunnable);
 
   ENSURE_GATT_CLIENT_INTF_IS_READY_VOID(aRunnable);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-
-  // Reject the read remote rssi request if the client is not found
-  if (index == sClients->NoIndex) {
-    DispatchReplyError(aRunnable,
-                       NS_LITERAL_STRING("Read remote RSSI failed"));
-    return;
-  }
+  MOZ_ASSERT(index != sClients->NoIndex);
 
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
   client->mReadRemoteRssiRunnable = aRunnable;
 
   sBluetoothGattClientInterface->ReadRemoteRssi(
     aClientIf, aDeviceAddr,
     new ReadRemoteRssiResultHandler(client));
 }
@@ -553,17 +646,17 @@ BluetoothGattManager::RegisterClientNoti
 {
   BT_API2_LOGR("Client Registered, clientIf = %d", aClientIf);
   MOZ_ASSERT(NS_IsMainThread());
 
   nsString uuid;
   UuidToString(aAppUuid, uuid);
 
   size_t index = sClients->IndexOf(uuid, 0 /* Start */, UuidComparator());
-  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+  MOZ_ASSERT(index != sClients->NoIndex);
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     BT_API2_LOGR(
       "RegisterClient failed, clientIf = %d, status = %d, appUuid = %s",
@@ -616,17 +709,17 @@ BluetoothGattManager::ConnectNotificatio
   BT_API2_LOGR();
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+  MOZ_ASSERT(index != sClients->NoIndex);
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     BT_API2_LOGR("Connect failed, clientIf = %d, connId = %d, status = %d",
                  aClientIf, aConnId, aStatus);
 
     // Notify BluetoothGatt that the client remains disconnected
     bs->DistributeSignal(
@@ -668,17 +761,17 @@ BluetoothGattManager::DisconnectNotifica
   BT_API2_LOGR();
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
   size_t index = sClients->IndexOf(aClientIf, 0 /* Start */,
                                    ClientIfComparator());
-  NS_ENSURE_TRUE_VOID(index != sClients->NoIndex);
+  MOZ_ASSERT(index != sClients->NoIndex);
   nsRefPtr<BluetoothGattClient> client = sClients->ElementAt(index);
 
   if (aStatus != GATT_STATUS_SUCCESS) {
     // Notify BluetoothGatt that the client remains connected
     bs->DistributeSignal(
       NS_LITERAL_STRING(GATT_CONNECTION_STATE_CHANGED_ID),
       client->mAppUuid,
       BluetoothValue(true)); // Connected
@@ -706,45 +799,205 @@ BluetoothGattManager::DisconnectNotifica
     DispatchReplySuccess(client->mDisconnectRunnable);
     client->mDisconnectRunnable = nullptr;
   }
 }
 
 void
 BluetoothGattManager::SearchCompleteNotification(int aConnId,
                                                  BluetoothGattStatus aStatus)
-{ }
+{
+  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) {
+    client->NotifyDiscoverCompleted(false);
+    return;
+  }
+
+  // Notify BluetoothGatt to create all services
+  bs->DistributeSignal(NS_LITERAL_STRING("ServicesDiscovered"),
+                       client->mAppUuid,
+                       BluetoothValue(client->mServices));
+
+  // All services are discovered, continue to search included services of each
+  // service if existed, otherwise, notify application that discover completed
+  if (!client->mServices.IsEmpty()) {
+    sBluetoothGattClientInterface->GetIncludedService(
+      aConnId,
+      client->mServices[0], // start from first service
+      true, // first included service
+      BluetoothGattServiceId(),
+      new DiscoverResultHandler(client));
+  } else {
+    client->NotifyDiscoverCompleted(true);
+  }
+}
 
 void
 BluetoothGattManager::SearchResultNotification(
   int aConnId, const BluetoothGattServiceId& aServiceId)
-{ }
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  size_t index = sClients->IndexOf(aConnId, 0 /* Start */,
+                                   ConnIdComparator());
+  MOZ_ASSERT(index != sClients->NoIndex);
+
+  // Save to mServices for distributing to application and discovering
+  // included services, characteristics of this service later
+  sClients->ElementAt(index)->mServices.AppendElement(aServiceId);
+}
 
 void
 BluetoothGattManager::GetCharacteristicNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattServiceId& aServiceId,
   const BluetoothGattId& aCharId,
   int 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) {
+    // Save to mCharacteristics for distributing to applications and
+    // discovering descriptors of this characteristic later
+    client->mCharacteristics.AppendElement(aCharId);
+
+    // Get next characteristic of this service
+    sBluetoothGattClientInterface->GetCharacteristic(
+      aConnId,
+      aServiceId,
+      false,
+      aCharId,
+      new DiscoverResultHandler(client));
+  } else { // all characteristics of this service are discovered
+    // Notify BluetoothGattService to create characteristics then proceed
+    nsString path;
+    GeneratePathFromGattId(aServiceId.mId, path);
+
+    bs->DistributeSignal(NS_LITERAL_STRING("CharacteristicsDiscovered"),
+                         path,
+                         BluetoothValue(client->mCharacteristics));
+
+    ProceedDiscoverProcess(client, aServiceId);
+  }
+}
 
 void
 BluetoothGattManager::GetDescriptorNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattServiceId& aServiceId,
   const BluetoothGattId& aCharId,
   const BluetoothGattId& aDescriptorId)
-{ }
+{
+  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) {
+    // Save to mDescriptors for distributing to applications
+    client->mDescriptors.AppendElement(aDescriptorId);
+
+    // Get next descriptor of this characteristic
+    sBluetoothGattClientInterface->GetDescriptor(
+      aConnId,
+      aServiceId,
+      aCharId,
+      false,
+      aDescriptorId,
+      new DiscoverResultHandler(client));
+  } else { // all descriptors of this characteristic are discovered
+    // Notify BluetoothGattCharacteristic to create descriptors then proceed
+    nsString path;
+    GeneratePathFromGattId(aCharId, path);
+
+    bs->DistributeSignal(NS_LITERAL_STRING("DescriptorsDiscovered"),
+                         path,
+                         BluetoothValue(client->mDescriptors));
+    client->mDescriptors.Clear();
+
+    ProceedDiscoverProcess(client, aServiceId);
+  }
+}
 
 void
 BluetoothGattManager::GetIncludedServiceNotification(
   int aConnId, BluetoothGattStatus aStatus,
   const BluetoothGattServiceId& aServiceId,
   const BluetoothGattServiceId& aIncludedServId)
-{ }
+{
+  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) {
+    // Save to mIncludedServices for distributing to applications
+    client->mIncludedServices.AppendElement(aIncludedServId);
+
+    // Get next included service of this service
+    sBluetoothGattClientInterface->GetIncludedService(
+      aConnId,
+      aServiceId,
+      false,
+      aIncludedServId,
+      new DiscoverResultHandler(client));
+  } else { // all included services of this service are discovered
+    // Notify BluetoothGattService to create included services
+    nsString path;
+    GeneratePathFromGattId(aServiceId.mId, path);
+
+    bs->DistributeSignal(NS_LITERAL_STRING("IncludedServicesDiscovered"),
+                         path,
+                         BluetoothValue(client->mIncludedServices));
+    client->mIncludedServices.Clear();
+
+    // Start to discover characteristics of this service
+    sBluetoothGattClientInterface->GetCharacteristic(
+      aConnId,
+      aServiceId,
+      true, // first characteristic
+      BluetoothGattId(),
+      new DiscoverResultHandler(client));
+  }
+}
 
 void
 BluetoothGattManager::RegisterNotificationNotification(
   int aConnId, int aIsRegister, BluetoothGattStatus aStatus,
   const BluetoothGattServiceId& aServiceId,
   const BluetoothGattId& aCharId)
 { }
 
@@ -858,9 +1111,50 @@ BluetoothGattManager::Observe(nsISupport
 void
 BluetoothGattManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mInShutdown = true;
   sBluetoothGattManager = nullptr;
 }
 
+void
+BluetoothGattManager::ProceedDiscoverProcess(
+  BluetoothGattClient* aClient,
+  const BluetoothGattServiceId& aServiceId)
+{
+  /**
+   * This function will be called to decide how to proceed the discover process
+   * after discovering all characteristics of a given service, or after
+   * discovering all descriptors of a given characteristic.
+   *
+   * There are three cases here,
+   * 1) mCharacteristics is not empty:
+   *      Proceed to discover descriptors of the first saved characteristic.
+   * 2) mCharacteristics is empty but mServices is not empty:
+   *      This service does not have any saved characteristics left, proceed to
+   *      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],
+      true, // first descriptor
+      BluetoothGattId(),
+      new DiscoverResultHandler(aClient));
+    aClient->mCharacteristics.RemoveElementAt(0);
+  } else if (!aClient->mServices.IsEmpty()) {
+    sBluetoothGattClientInterface->GetIncludedService(
+      aClient->mConnId,
+      aClient->mServices[0],
+      true, // first included service
+      BluetoothGattServiceId(),
+      new DiscoverResultHandler(aClient));
+    aClient->mServices.RemoveElementAt(0);
+  } else {
+    aClient->NotifyDiscoverCompleted(true);
+  }
+}
+
 NS_IMPL_ISUPPORTS(BluetoothGattManager, nsIObserver)
--- a/dom/bluetooth2/bluedroid/BluetoothGattManager.h
+++ b/dom/bluetooth2/bluedroid/BluetoothGattManager.h
@@ -8,16 +8,17 @@
 #define mozilla_dom_bluetooth_bluetoothgattmanager_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothInterface.h"
 #include "BluetoothProfileManagerBase.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
+class BluetoothGattClient;
 class BluetoothReplyRunnable;
 
 class BluetoothGattManager final : public nsIObserver
                                      , public BluetoothGattNotificationHandler
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
@@ -30,31 +31,35 @@ public:
   void Connect(const nsAString& aAppUuid,
                const nsAString& aDeviceAddr,
                BluetoothReplyRunnable* aRunnable);
 
   void Disconnect(const nsAString& aAppUuid,
                   const nsAString& aDeviceAddr,
                   BluetoothReplyRunnable* aRunnable);
 
+  void Discover(const nsAString& aAppUuid,
+                BluetoothReplyRunnable* aRunnable);
+
   void UnregisterClient(int aClientIf,
                         BluetoothReplyRunnable* aRunnable);
 
   void ReadRemoteRssi(int aClientIf,
                       const nsAString& aDeviceAddr,
                       BluetoothReplyRunnable* aRunnable);
 
 private:
   class CleanupResultHandler;
   class CleanupResultHandlerRunnable;
   class InitGattResultHandler;
   class RegisterClientResultHandler;
   class UnregisterClientResultHandler;
   class ConnectResultHandler;
   class DisconnectResultHandler;
+  class DiscoverResultHandler;
   class ReadRemoteRssiResultHandler;
 
   BluetoothGattManager();
 
   void HandleShutdown();
 
   void RegisterClientNotification(BluetoothGattStatus aStatus,
                                   int aClientIf,
@@ -132,14 +137,17 @@ private:
   void ReadRemoteRssiNotification(int aClientIf,
                                   const nsAString& aBdAddr,
                                   int aRssi,
                                   BluetoothGattStatus aStatus) override;
 
   void ListenNotification(BluetoothGattStatus aStatus,
                           int aServerIf) override;
 
+  void ProceedDiscoverProcess(BluetoothGattClient* aClient,
+                              const BluetoothGattServiceId& aServiceId);
+
   static bool mInShutdown;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.cpp
@@ -1113,16 +1113,30 @@ BluetoothServiceBluedroid::DisconnectGat
 
   BluetoothGattManager* gatt = BluetoothGattManager::Get();
   ENSURE_GATT_MGR_IS_READY_VOID(gatt, aRunnable);
 
   gatt->Disconnect(aAppUuid, aDeviceAddress, aRunnable);
 }
 
 void
+BluetoothServiceBluedroid::DiscoverGattServicesInternal(
+  const nsAString& aAppUuid, 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->Discover(aAppUuid, aRunnable);
+}
+
+void
 BluetoothServiceBluedroid::UnregisterGattClientInternal(
   int aClientIf, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
   BluetoothGattManager* gatt = BluetoothGattManager::Get();
--- a/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth2/bluedroid/BluetoothServiceBluedroid.h
@@ -180,16 +180,20 @@ public:
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
+  DiscoverGattServicesInternal(const nsAString& aAppUuid,
+                               BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadRemoteRssiInternal(
     int aClientIf, const nsAString& aDeviceAddress,
     BluetoothReplyRunnable* aRunnable) override;
 
--- a/dom/bluetooth2/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.cpp
@@ -4287,16 +4287,22 @@ BluetoothDBusService::ConnectGattClientI
 void
 BluetoothDBusService::DisconnectGattClientInternal(
   const nsAString& aAppUuid, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
 }
 
 void
+BluetoothDBusService::DiscoverGattServicesInternal(
+  const nsAString& aAppUuid, BluetoothReplyRunnable* aRunnable)
+{
+}
+
+void
 BluetoothDBusService::UnregisterGattClientInternal(
   int aClientIf, BluetoothReplyRunnable* aRunnable)
 {
 }
 
 void
 BluetoothDBusService::GattClientReadRemoteRssiInternal(
   int aClientIf, const nsAString& aDeviceAddress,
--- a/dom/bluetooth2/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth2/bluez/BluetoothDBusService.h
@@ -189,16 +189,21 @@ public:
   ConnectGattClientInternal(const nsAString& aAppUuid,
                             const nsAString& aDeviceAddress,
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
                                BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
+  DiscoverGattServicesInternal(const nsAString& aAppUuid,
+                               BluetoothReplyRunnable* aRunnable) override;
+
   virtual void
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadRemoteRssiInternal(
     int aClientIf, const nsAString& aDeviceAddress,
     BluetoothReplyRunnable* aRunnable) override;
--- a/dom/bluetooth2/ipc/BluetoothMessageUtils.h
+++ b/dom/bluetooth2/ipc/BluetoothMessageUtils.h
@@ -23,11 +23,78 @@ struct ParamTraits<mozilla::dom::bluetoo
 template <>
 struct ParamTraits<mozilla::dom::bluetooth::BluetoothStatus>
   : public ContiguousEnumSerializer<
              mozilla::dom::bluetooth::BluetoothStatus,
              mozilla::dom::bluetooth::STATUS_SUCCESS,
              mozilla::dom::bluetooth::STATUS_RMT_DEV_DOWN>
 { };
 
+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]);
+    }
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    for (uint8_t i = 0; i < 16; i++) {
+      if (!ReadParam(aMsg, aIter, &(aResult->mUuid[i]))) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattId>
+{
+  typedef mozilla::dom::bluetooth::BluetoothGattId paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mUuid);
+    WriteParam(aMsg, aParam.mInstanceId);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mUuid)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mInstanceId))) {
+      return false;
+    }
+
+    return true;
+  }
+};
+
+template <>
+struct ParamTraits<mozilla::dom::bluetooth::BluetoothGattServiceId>
+{
+  typedef mozilla::dom::bluetooth::BluetoothGattServiceId paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mId);
+    WriteParam(aMsg, aParam.mIsPrimary);
+  }
+
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mId)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mIsPrimary))) {
+      return false;
+    }
+
+    return true;
+  }
+};
 } // namespace IPC
 
 #endif // mozilla_dom_bluetooth_ipc_bluetoothmessageutils_h__
--- a/dom/bluetooth2/ipc/BluetoothParent.cpp
+++ b/dom/bluetooth2/ipc/BluetoothParent.cpp
@@ -249,16 +249,18 @@ BluetoothParent::RecvPBluetoothRequestCo
     case Request::TSendMetaDataRequest:
       return actor->DoRequest(aRequest.get_SendMetaDataRequest());
     case Request::TSendPlayStatusRequest:
       return actor->DoRequest(aRequest.get_SendPlayStatusRequest());
     case Request::TConnectGattClientRequest:
       return actor->DoRequest(aRequest.get_ConnectGattClientRequest());
     case Request::TDisconnectGattClientRequest:
       return actor->DoRequest(aRequest.get_DisconnectGattClientRequest());
+    case Request::TDiscoverGattServicesRequest:
+      return actor->DoRequest(aRequest.get_DiscoverGattServicesRequest());
     case Request::TUnregisterGattClientRequest:
       return actor->DoRequest(aRequest.get_UnregisterGattClientRequest());
     case Request::TGattClientReadRemoteRssiRequest:
       return actor->DoRequest(aRequest.get_GattClientReadRemoteRssiRequest());
     default:
       MOZ_CRASH("Unknown type!");
   }
 
@@ -715,16 +717,28 @@ BluetoothRequestParent::DoRequest(const 
   mService->DisconnectGattClientInternal(aRequest.appUuid(),
                                          aRequest.deviceAddress(),
                                          mReplyRunnable.get());
 
   return true;
 }
 
 bool
+BluetoothRequestParent::DoRequest(const DiscoverGattServicesRequest& aRequest)
+{
+  MOZ_ASSERT(mService);
+  MOZ_ASSERT(mRequestType == Request::TDiscoverGattServicesRequest);
+
+  mService->DiscoverGattServicesInternal(aRequest.appUuid(),
+                                         mReplyRunnable.get());
+
+  return true;
+}
+
+bool
 BluetoothRequestParent::DoRequest(const UnregisterGattClientRequest& aRequest)
 {
   MOZ_ASSERT(mService);
   MOZ_ASSERT(mRequestType == Request::TUnregisterGattClientRequest);
 
   mService->UnregisterGattClientInternal(aRequest.clientIf(),
                                          mReplyRunnable.get());
 
--- a/dom/bluetooth2/ipc/BluetoothParent.h
+++ b/dom/bluetooth2/ipc/BluetoothParent.h
@@ -219,16 +219,19 @@ protected:
 
   bool
   DoRequest(const ConnectGattClientRequest& aRequest);
 
   bool
   DoRequest(const DisconnectGattClientRequest& aRequest);
 
   bool
+  DoRequest(const DiscoverGattServicesRequest& aRequest);
+
+  bool
   DoRequest(const UnregisterGattClientRequest& aRequest);
 
   bool
   DoRequest(const GattClientReadRemoteRssiRequest& aRequest);
 };
 
 END_BLUETOOTH_NAMESPACE
 
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.cpp
@@ -393,16 +393,24 @@ BluetoothServiceChildProcess::Disconnect
   const nsAString& aAppUuid, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable,
     DisconnectGattClientRequest(nsString(aAppUuid), nsString(aDeviceAddress)));
 }
 
 void
+BluetoothServiceChildProcess::DiscoverGattServicesInternal(
+  const nsAString& aAppUuid, BluetoothReplyRunnable* aRunnable)
+{
+  SendRequest(aRunnable,
+    DiscoverGattServicesRequest(nsString(aAppUuid)));
+}
+
+void
 BluetoothServiceChildProcess::UnregisterGattClientInternal(
   int aClientIf, BluetoothReplyRunnable* aRunnable)
 {
   SendRequest(aRunnable, UnregisterGattClientRequest(aClientIf));
 }
 
 void
 BluetoothServiceChildProcess::GattClientReadRemoteRssiInternal(
--- a/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
+++ b/dom/bluetooth2/ipc/BluetoothServiceChildProcess.h
@@ -198,16 +198,20 @@ public:
                             BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   DisconnectGattClientInternal(const nsAString& aAppUuid,
                                const nsAString& aDeviceAddress,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
+  DiscoverGattServicesInternal(const nsAString& aAppUuid,
+                               BluetoothReplyRunnable* aRunnable) override;
+
+  virtual void
   UnregisterGattClientInternal(int aClientIf,
                                BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   GattClientReadRemoteRssiInternal(int aClientIf,
                                    const nsAString& aDeviceAddress,
                                    BluetoothReplyRunnable* aRunnable) override;
 
--- a/dom/bluetooth2/ipc/BluetoothTypes.ipdlh
+++ b/dom/bluetooth2/ipc/BluetoothTypes.ipdlh
@@ -1,15 +1,20 @@
 /* -*- 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::BluetoothStatus 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::BluetoothStatus
+  from "mozilla/dom/bluetooth/BluetoothCommon.h";
 
 namespace mozilla {
 namespace dom {
 namespace bluetooth {
 
 /**
  * Value structure for returns from bluetooth. Currently modeled after dbus
  * returns, which can be a 32-bit int, an UTF16 string, a bool, or an array of
@@ -18,16 +23,18 @@ namespace bluetooth {
 union BluetoothValue
 {
   uint32_t;
   nsString;
   bool;
   nsString[];
   uint8_t[];
   BluetoothNamedValue[];
+  BluetoothGattId[];
+  BluetoothGattServiceId[];
 };
 
 /**
  * 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/bluetooth2/ipc/PBluetooth.ipdl
+++ b/dom/bluetooth2/ipc/PBluetooth.ipdl
@@ -183,16 +183,21 @@ struct ConnectGattClientRequest
 };
 
 struct DisconnectGattClientRequest
 {
   nsString appUuid;
   nsString deviceAddress;
 };
 
+struct DiscoverGattServicesRequest
+{
+  nsString appUuid;
+};
+
 struct UnregisterGattClientRequest
 {
   int clientIf;
 };
 
 struct GattClientReadRemoteRssiRequest
 {
   int clientIf;
@@ -228,16 +233,17 @@ union Request
   IsScoConnectedRequest;
   AnswerWaitingCallRequest;
   IgnoreWaitingCallRequest;
   ToggleCallsRequest;
   SendMetaDataRequest;
   SendPlayStatusRequest;
   ConnectGattClientRequest;
   DisconnectGattClientRequest;
+  DiscoverGattServicesRequest;
   UnregisterGattClientRequest;
   GattClientReadRemoteRssiRequest;
 };
 
 protocol PBluetooth
 {
   manager PContent;
   manages PBluetoothRequest;
--- a/dom/bluetooth2/moz.build
+++ b/dom/bluetooth2/moz.build
@@ -6,16 +6,19 @@
 
 if CONFIG['MOZ_B2G_BT']:
     SOURCES += [
         'BluetoothAdapter.cpp',
         'BluetoothClassOfDevice.cpp',
         'BluetoothDevice.cpp',
         'BluetoothDiscoveryHandle.cpp',
         'BluetoothGatt.cpp',
+        'BluetoothGattCharacteristic.cpp',
+        'BluetoothGattDescriptor.cpp',
+        'BluetoothGattService.cpp',
         'BluetoothHidManager.cpp',
         'BluetoothInterface.cpp',
         'BluetoothManager.cpp',
         'BluetoothPairingHandle.cpp',
         'BluetoothPairingListener.cpp',
         'BluetoothProfileController.cpp',
         'BluetoothReplyRunnable.cpp',
         'BluetoothService.cpp',
@@ -119,16 +122,19 @@ EXPORTS.mozilla.dom.bluetooth.ipc += [
 
 EXPORTS.mozilla.dom.bluetooth += [
     'BluetoothAdapter.h',
     'BluetoothClassOfDevice.h',
     'BluetoothCommon.h',
     'BluetoothDevice.h',
     'BluetoothDiscoveryHandle.h',
     'BluetoothGatt.h',
+    'BluetoothGattCharacteristic.h',
+    'BluetoothGattDescriptor.h',
+    'BluetoothGattService.h',
     'BluetoothManager.h',
     'BluetoothPairingHandle.h',
     'BluetoothPairingListener.h',
 ]
 
 IPDL_SOURCES += [
     'ipc/BluetoothTypes.ipdlh',
     'ipc/PBluetooth.ipdl',
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -519,17 +519,16 @@ skip-if = buildapp == 'mulet' || buildap
 [test_track.html]
 [test_track_disabled.html]
 [test_ul_attributes_reflection.html]
 [test_undoManager.html]
 [test_video_wakelock.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && debug) #bug 871015, bug 881443
 [test_input_files_not_nsIFile.html]
 [test_ignoreuserfocus.html]
-skip-if = (toolkit == 'gonk' && debug)
 [test_fragment_form_pointer.html]
 [test_bug1682.html]
 [test_bug1823.html]
 [test_bug57600.html]
 [test_bug196523.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || e10s #Bug 931116, b2g desktop specific, initial triage
 [test_bug199692.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' || e10s #bug 811644 #Bug 931116, b2g desktop specific, initial triage
--- a/dom/webidl/BluetoothGatt.webidl
+++ b/dom/webidl/BluetoothGatt.webidl
@@ -2,16 +2,18 @@
 /* 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/. */
 
 [CheckPermissions="bluetooth"]
 interface BluetoothGatt : EventTarget
 {
+  [Cached, Pure]
+  readonly attribute sequence<BluetoothGattService> services;
   readonly attribute BluetoothConnectionState       connectionState;
 
   // 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.
@@ -24,16 +26,24 @@ interface BluetoothGatt : EventTarget
    *   3) Promise is resolved or rejected according to the operation result.
    */
   [NewObject]
   Promise<void>                                     connect();
   [NewObject]
   Promise<void>                                     disconnect();
 
   /**
+   * Discover services, characteristics, descriptors offered by the remote GATT
+   * server. The promise will be rejected if the connState is not connected or
+   * operation fails.
+   */
+  [NewObject]
+  Promise<void>                                     discoverServices();
+
+  /**
    * Read RSSI for the remote BLE device if the connectState is connected.
    * Otherwise, the Promise will be rejected directly.
    */
   [NewObject]
   Promise<short>                                    readRemoteRssi();
 };
 
 enum BluetoothConnectionState
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGattCharacteristic.webidl
@@ -0,0 +1,16 @@
+/* -*- 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/. */
+
+[CheckPermissions="bluetooth"]
+interface BluetoothGattCharacteristic
+{
+  readonly attribute BluetoothGattService                   service;
+  [Cached, Pure]
+  readonly attribute sequence<BluetoothGattDescriptor>      descriptors;
+
+  readonly attribute DOMString                              uuid;
+  readonly attribute unsigned short                         instanceId;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGattDescriptor.webidl
@@ -0,0 +1,12 @@
+/* -*- 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/. */
+
+[CheckPermissions="bluetooth"]
+interface BluetoothGattDescriptor
+{
+  readonly attribute BluetoothGattCharacteristic            characteristic;
+  readonly attribute DOMString                              uuid;
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/BluetoothGattService.webidl
@@ -0,0 +1,18 @@
+/* -*- 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/. */
+
+[CheckPermissions="bluetooth"]
+interface BluetoothGattService
+{
+  [Cached, Pure]
+  readonly attribute sequence<BluetoothGattCharacteristic>  characteristics;
+  [Cached, Pure]
+  readonly attribute sequence<BluetoothGattService>         includedServices;
+
+  readonly attribute boolean                                isPrimary;
+  readonly attribute DOMString                              uuid;
+  readonly attribute unsigned short                         instanceId;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -632,16 +632,19 @@ if CONFIG['MOZ_DEBUG']:
 if CONFIG['MOZ_B2G_BT']:
     if CONFIG['MOZ_B2G_BT_API_V2']:
         WEBIDL_FILES += [
             'BluetoothAdapter2.webidl',
             'BluetoothClassOfDevice.webidl',
             'BluetoothDevice2.webidl',
             'BluetoothDiscoveryHandle.webidl',
             'BluetoothGatt.webidl',
+            'BluetoothGattCharacteristic.webidl',
+            'BluetoothGattDescriptor.webidl',
+            'BluetoothGattService.webidl',
             'BluetoothManager2.webidl',
             'BluetoothPairingHandle.webidl',
             'BluetoothPairingListener.webidl',
         ]
     else:
         WEBIDL_FILES += [
             'BluetoothAdapter.webidl',
             'BluetoothDevice.webidl',
--- a/mobile/android/base/overlays/ui/ShareDialog.java
+++ b/mobile/android/base/overlays/ui/ShareDialog.java
@@ -101,17 +101,18 @@ public class ShareDialog extends Locales
     protected void handleSendTabUIEvent(Intent intent) {
         sendTabOverrideIntent = intent.getParcelableExtra(SendTab.OVERRIDE_INTENT);
 
         ParcelableClientRecord[] clientrecords = (ParcelableClientRecord[]) intent.getParcelableArrayExtra(SendTab.EXTRA_CLIENT_RECORDS);
 
         // Escape hatch: we don't show the option to open this dialog in this state so this should
         // never be run. However, due to potential inconsistencies in synced client state
         // (e.g. bug 1122302 comment 47), we might fail.
-        if (state == State.DEVICES_ONLY && clientrecords.length == 0) {
+        if (state == State.DEVICES_ONLY &&
+                (clientrecords == null || clientrecords.length == 0)) {
             Log.e(LOGTAG, "In state: " + State.DEVICES_ONLY + " and received 0 synced clients. Finishing...");
             Toast.makeText(this, getResources().getText(R.string.overlay_no_synced_devices), Toast.LENGTH_SHORT)
                  .show();
             finish();
             return;
         }
 
         sendTabList.setSyncClients(clientrecords);
--- a/mobile/android/base/widget/ActivityChooserModel.java
+++ b/mobile/android/base/widget/ActivityChooserModel.java
@@ -23,16 +23,17 @@ package org.mozilla.gecko.widget;
 // Mozilla: New import
 import android.accounts.Account;
 import android.content.pm.PackageManager;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.overlays.ui.ShareDialog;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
+import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.R;
 import java.io.File;
 
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -1298,16 +1299,23 @@ public class ActivityChooserModel extend
             mReloadActivities = true;
         }
     }
 
     /**
      * Mozilla: Return whether or not there are other synced clients.
      */
     private boolean hasOtherSyncClients() {
+        // ClientsDatabaseAccessor returns stale data (bug 1145896) so we work around this by
+        // checking if we have accounts set up - if not, we can't have any clients.
+        if (!FirefoxAccounts.firefoxAccountsExist(mContext) &&
+                !SyncAccounts.syncAccountsExist(mContext))  {
+            return false;
+        }
+
         final ClientsDatabaseAccessor db = new ClientsDatabaseAccessor(mContext);
         return db.clientsCount() > 0;
     }
 
     /**
      * Mozilla: Reload activities on sync.
      */
     private class SyncStatusListener implements FirefoxAccounts.SyncStatusListener {
--- a/mozglue/build/Nuwa.cpp
+++ b/mozglue/build/Nuwa.cpp
@@ -101,22 +101,20 @@ static size_t getPageSize(void) {
   return PAGE_SIZE;
 #else
   #warning "Hard-coding page size to 4096 bytes"
   return 4096
 #endif
 }
 
 /**
- * The stack size is chosen carefully so the frozen threads doesn't consume too
- * much memory in the Nuwa process. The threads shouldn't run deep recursive
- * methods or do large allocations on the stack to avoid stack overflow.
+ * Use 1 MiB stack size as Android does.
  */
 #ifndef NUWA_STACK_SIZE
-#define NUWA_STACK_SIZE (1024 * 128)
+#define NUWA_STACK_SIZE (1024 * 1024)
 #endif
 
 #define NATIVE_THREAD_NAME_LENGTH 16
 
 typedef struct nuwa_construct {
   typedef void(*construct_t)(void*);
 
   construct_t construct;
@@ -168,16 +166,18 @@ struct thread_info : public mozilla::Lin
    * (if the pthread_mutex_trylock succeeded) or another thread may have already
    * held the lock.  If the recreated thread did lock the mutex we must balance
    * that with another unlock on the main thread, which is signaled by
    * condMutexNeedsBalancing.
    */
   pthread_mutex_t *condMutex;
   bool condMutexNeedsBalancing;
 
+  size_t stackSize;
+  size_t guardSize;
   void *stk;
 
   pid_t origNativeThreadID;
   pid_t recreatedNativeThreadID;
   char nativeThreadName[NATIVE_THREAD_NAME_LENGTH];
 };
 
 typedef struct thread_info thread_info_t;
@@ -541,53 +541,81 @@ thread_info_new(void) {
   /* link tinfo to sAllThreads */
   thread_info_t *tinfo = new thread_info_t();
   tinfo->flags = 0;
   tinfo->recrFunctions = nullptr;
   tinfo->recreatedThreadID = 0;
   tinfo->recreatedNativeThreadID = 0;
   tinfo->condMutex = nullptr;
   tinfo->condMutexNeedsBalancing = false;
-  tinfo->stk = MozTaggedAnonymousMmap(nullptr,
-                                      NUWA_STACK_SIZE + getPageSize(),
-                                      PROT_READ | PROT_WRITE,
-                                      MAP_PRIVATE | MAP_ANONYMOUS,
-                                      /* fd */ -1,
-                                      /* offset */ 0,
-                                      "nuwa-thread-stack");
 
-  // We use a smaller stack size. Add protection to stack overflow: mprotect()
-  // stack top (the page at the lowest address) so we crash instead of corrupt
-  // other content that is malloc()'d.
-  mprotect(tinfo->stk, getPageSize(), PROT_NONE);
-
-  pthread_attr_init(&tinfo->threadAttr);
+  // Default stack and guard size.
+  tinfo->stackSize = NUWA_STACK_SIZE;
+  tinfo->guardSize = getPageSize();
 
   REAL(pthread_mutex_lock)(&sThreadCountLock);
   // Insert to the tail.
   sAllThreads.insertBack(tinfo);
 
   sThreadCount++;
   pthread_cond_signal(&sThreadChangeCond);
   pthread_mutex_unlock(&sThreadCountLock);
 
   return tinfo;
 }
 
 static void
+thread_attr_init(thread_info_t *tinfo, const pthread_attr_t *tattr)
+{
+  pthread_attr_init(&tinfo->threadAttr);
+
+  if (tattr) {
+    // Override default thread stack and guard size with tattr.
+    pthread_attr_getstacksize(tattr, &tinfo->stackSize);
+    pthread_attr_getguardsize(tattr, &tinfo->guardSize);
+
+    size_t pageSize = getPageSize();
+
+    tinfo->stackSize = (tinfo->stackSize + pageSize - 1) % pageSize;
+    tinfo->guardSize = (tinfo->guardSize + pageSize - 1) % pageSize;
+
+    int detachState = 0;
+    pthread_attr_getdetachstate(tattr, &detachState);
+    pthread_attr_setdetachstate(&tinfo->threadAttr, detachState);
+  }
+
+  tinfo->stk = MozTaggedAnonymousMmap(nullptr,
+                                      tinfo->stackSize + tinfo->guardSize,
+                                      PROT_READ | PROT_WRITE,
+                                      MAP_PRIVATE | MAP_ANONYMOUS,
+                                      /* fd */ -1,
+                                      /* offset */ 0,
+                                      "nuwa-thread-stack");
+
+  // Add protection to stack overflow: mprotect() stack top (the page at the
+  // lowest address) so we crash instead of corrupt other content that is
+  // malloc()'d.
+  mprotect(tinfo->stk, tinfo->guardSize, PROT_NONE);
+
+  pthread_attr_setstack(&tinfo->threadAttr,
+                        (char*)tinfo->stk + tinfo->guardSize,
+                        tinfo->stackSize);
+}
+
+static void
 thread_info_cleanup(void *arg) {
   if (sNuwaForking) {
     // We shouldn't have any thread exiting when we are forking a new process.
     abort();
   }
 
   thread_info_t *tinfo = (thread_info_t *)arg;
   pthread_attr_destroy(&tinfo->threadAttr);
 
-  munmap(tinfo->stk, NUWA_STACK_SIZE + getPageSize());
+  munmap(tinfo->stk, tinfo->stackSize + tinfo->guardSize);
 
   REAL(pthread_mutex_lock)(&sThreadCountLock);
   /* unlink tinfo from sAllThreads */
   tinfo->remove();
   pthread_mutex_unlock(&sThreadCountLock);
 
   if (tinfo->recrFunctions) {
     delete tinfo->recrFunctions;
@@ -736,21 +764,19 @@ extern "C" MFBT_API int
                       const pthread_attr_t *attr,
                       void *(*start_routine) (void *),
                       void *arg) {
   if (!sIsNuwaProcess) {
     return REAL(pthread_create)(thread, attr, start_routine, arg);
   }
 
   thread_info_t *tinfo = thread_info_new();
+  thread_attr_init(tinfo, attr);
   tinfo->startupFunc = start_routine;
   tinfo->startupArg = arg;
-  pthread_attr_setstack(&tinfo->threadAttr,
-                        (char*)tinfo->stk + getPageSize(),
-                        NUWA_STACK_SIZE);
 
   int rv = REAL(pthread_create)(thread,
                                 &tinfo->threadAttr,
                                 thread_create_startup,
                                 tinfo);
   if (rv) {
     thread_info_cleanup(tinfo);
   } else {
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -149,16 +149,17 @@ SandboxFilterImplContent::Build() {
   /* ioctl() is for GL. Remove when GL proxy is implemented.
    * Additionally ioctl() might be a place where we want to have
    * argument filtering */
   Allow(SYSCALL(ioctl));
   Allow(SYSCALL(close));
   Allow(SYSCALL(munmap));
   Allow(SYSCALL(mprotect));
   Allow(SYSCALL(writev));
+  Allow(SYSCALL(pread64));
   AllowThreadClone();
   Allow(SYSCALL(brk));
 #if SYSCALL_EXISTS(set_thread_area)
   Allow(SYSCALL(set_thread_area));
 #endif
 
   Allow(SYSCALL(getpid));
   Allow(SYSCALL(gettid));
--- a/testing/taskcluster/taskcluster_graph/try_test_parser.py
+++ b/testing/taskcluster/taskcluster_graph/try_test_parser.py
@@ -12,16 +12,18 @@ def parse_test_opts(input_str):
     token = ''
     in_platforms = False
 
     def add_test(value):
         cur_test['test'] = value.strip()
         tests.insert(0, cur_test)
 
     def add_platform(value):
+        # Ensure platforms exists...
+        cur_test['platforms'] = cur_test.get('platforms', [])
         cur_test['platforms'].insert(0, value.strip())
 
     # This might be somewhat confusing but we parse the string _backwards_ so
     # there is no ambiguity over what state we are in.
     for char in reversed(input_str):
 
         # , indicates exiting a state
         if char == ',':
@@ -40,17 +42,16 @@ def parse_test_opts(input_str):
         elif char == '[':
             # Exiting platform state entering test state.
             add_platform(token)
             token = ''
             in_platforms = False
         elif char == ']':
             # Entering platform state.
             in_platforms = True
-            cur_test['platforms'] = []
         else:
             # Accumulator.
             token = char + token
 
     # Handle any left over tokens.
     if token:
         add_test(token)
 
--- a/testing/taskcluster/tests/test_try_test_parser.py
+++ b/testing/taskcluster/tests/test_try_test_parser.py
@@ -25,15 +25,27 @@ class TryTestParserTest(unittest.TestCas
                 { 'test': 'bar', 'platforms': ['b'] },
                 { 'test': 'baz' },
                 { 'test': 'qux', 'platforms': ['z'] },
                 { 'test': 'a' }
             ]
         )
 
         self.assertEquals(
+            parse_test_opts('mochitest-3[Ubuntu,10.6,10.8,Windows XP,Windows 7,Windows 8]'),
+            [
+                {
+                    'test': 'mochitest-3',
+                    'platforms': [
+                        'Ubuntu', '10.6', '10.8', 'Windows XP', 'Windows 7', 'Windows 8'
+                    ]
+                }
+            ]
+        )
+
+        self.assertEquals(
             parse_test_opts(''),
             []
         )
 
 if __name__ == '__main__':
     mozunit.main()
 
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -24,16 +24,17 @@ const { defer, resolve, reject, all } = 
 loader.lazyGetter(this, "Debugger", () => {
   let Debugger = require("Debugger");
   hackDebugger(Debugger);
   return Debugger;
 });
 loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
 loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
 loader.lazyRequireGetter(this, "CssLogic", "devtools/styleinspector/css-logic", true);
+loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 let TYPED_ARRAY_CLASSES = ["Uint8Array", "Uint8ClampedArray", "Uint16Array",
       "Uint32Array", "Int8Array", "Int16Array", "Int32Array", "Float32Array",
       "Float64Array"];
 
 // Number of items to preview in objects, arrays, maps, sets, lists,
 // collections, etc.
 let OBJECT_PREVIEW_MAX_ITEMS = 10;
@@ -439,16 +440,17 @@ function ThreadActor(aParent, aGlobal)
   this._frameActors = [];
   this._parent = aParent;
   this._dbg = null;
   this._gripDepth = 0;
   this._threadLifetimePool = null;
   this._tabClosed = false;
   this._scripts = null;
   this._sources = null;
+  this._pauseOnDOMEvents = null;
 
   this._options = {
     useSourceMaps: false,
     autoBlackBox: false
   };
 
   this.breakpointActorMap = new BreakpointActorMap;
   this.sourceActorStore = new SourceActorStore;
@@ -462,16 +464,18 @@ function ThreadActor(aParent, aGlobal)
   this.global = aGlobal;
 
   this._allEventsListener = this._allEventsListener.bind(this);
   this.onNewGlobal = this.onNewGlobal.bind(this);
   this.onNewSource = this.onNewSource.bind(this);
   this.uncaughtExceptionHook = this.uncaughtExceptionHook.bind(this);
   this.onDebuggerStatement = this.onDebuggerStatement.bind(this);
   this.onNewScript = this.onNewScript.bind(this);
+  this._onWindowReady = this._onWindowReady.bind(this);
+  events.on(this._parent, "window-ready", this._onWindowReady);
   // Set a wrappedJSObject property so |this| can be sent via the observer svc
   // for the xpcshell harness.
   this.wrappedJSObject = this;
 }
 
 ThreadActor.prototype = {
   // Used by the ObjectActor to keep track of the depth of grip() calls.
   _gripDepth: null,
@@ -615,16 +619,17 @@ ThreadActor.prototype = {
       this.onResume();
     }
 
     // Blow away our source actor ID store because those IDs are only
     // valid for this connection. This is ok because we never keep
     // things like breakpoints across connections.
     this._sourceActorStore = null;
 
+    events.off(this._parent, "window-ready", this._onWindowReady);
     this.clearDebuggees();
     this.conn.removeActorPool(this._threadLifetimePool);
     this._threadLifetimePool = null;
 
     if (this._prettyPrintWorker) {
       this._prettyPrintWorker.removeEventListener(
         "error", this._onPrettyPrintError, false);
       this._prettyPrintWorker.removeEventListener(
@@ -1005,16 +1010,26 @@ ThreadActor.prototype = {
       this._pauseOnDOMEvents = events;
       let els = Cc["@mozilla.org/eventlistenerservice;1"]
                 .getService(Ci.nsIEventListenerService);
       els.addListenerForAllEvents(this.global, this._allEventsListener, true);
     }
   },
 
   /**
+   * If we are tasked with breaking on the load event, we have to add the
+   * listener early enough.
+   */
+  _onWindowReady: function () {
+    this._maybeListenToEvents({
+      pauseOnDOMEvents: this._pauseOnDOMEvents
+    });
+  },
+
+  /**
    * Handle a protocol request to resume execution of the debuggee.
    */
   onResume: function (aRequest) {
     if (this._state !== "paused") {
       return {
         error: "wrongState",
         message: "Can't resume when debuggee isn't paused. Current state is '"
           + this._state + "'"
@@ -2786,68 +2801,238 @@ SourceActor.prototype = {
 
     return this._setBreakpointForActor(actor);
   },
 
   /*
    * Ensure the given BreakpointActor is set as a breakpoint handler on all
    * scripts that match its location in the original source.
    *
-   * It is possible that no scripts match the given location, because they have
-   * all been garbage collected. In that case, the BreakpointActor is not set as
-   * a breakpoint handler for any script, but is still inserted in the
-   * BreakpointActorMap as a pending breakpoint. Whenever a new script is
-   * introduced, we call this method again to see if there are now any scripts
-   * that matches the given location.
+   * If there are no scripts that match the location of the BreakpointActor,
+   * we slide its location to the next closest line (for line breakpoints) or
+   * column (for column breakpoint) that does.
    *
-   * The first time we find one or more scripts that matches the given location,
-   * we check if any of these scripts has any entry points for the given
-   * location. If not, we assume that the given location does not have any code.
-   *
-   * If the given location does not contain any code, we slide the breakpoint
-   * down to the next closest line that does, and update the BreakpointActorMap
-   * accordingly. Note that we only do so if the BreakpointActor is still
-   * pending (i.e. is not set as a breakpoint handler for any script).
+   * If breakpoint sliding fails, then either there are no scripts that contain
+   * any code for the given location, or they were all garbage collected before
+   * the debugger started running. We cannot distinguish between these two
+   * cases, so we insert the BreakpointActor in the BreakpointActorMap as
+   * a pending breakpoint. Whenever a new script is introduced, this method is
+   * called again for each pending breakpoint.
    *
    * @param BreakpointActor actor
    *        The BreakpointActor to be set as a breakpoint handler.
    *
    * @returns A Promise that resolves to the given BreakpointActor.
    */
   _setBreakpointForActor: function (actor) {
+    let { originalLocation } = actor;
+
     if (this.isSourceMapped) {
-      return this.threadActor.sources.getGeneratedLocation(
-        actor.originalLocation
-      ).then((generatedLocation) => {
+      // TODO: Refactor breakpoint sliding for source mapped sources.
+      return this.threadActor.sources.getGeneratedLocation(originalLocation)
+                                     .then((generatedLocation) => {
         return generatedLocation.generatedSourceActor
-                                ._setBreakpointForActorAtLocation(
+                                ._setBreakpointForActorAtLocationWithSliding(
           actor,
           generatedLocation
         );
       });
     } else {
-      return Promise.resolve(this._setBreakpointForActorAtLocation(
-        actor,
-        GeneratedLocation.fromOriginalLocation(actor.originalLocation)
-      ));
+      // If this is a non-source mapped source, the original location and
+      // generated location are the same, so we can safely convert between them.
+      let generatedLocation = GeneratedLocation.fromOriginalLocation(originalLocation);
+      let { generatedColumn } = generatedLocation;
+
+      // Try to set the breakpoint on the generated location directly. If this
+      // succeeds, we can avoid the more expensive breakpoint sliding algorithm
+      // below.
+      if (this._setBreakpointForActorAtLocation(actor, generatedLocation)) {
+        return Promise.resolve(actor);
+      }
+
+      // There were no scripts that matched the given location, so we need to
+      // perform breakpoint sliding.
+      if (generatedColumn === undefined) {
+        // To perform breakpoint sliding for line breakpoints, we need to build
+        // a map from line numbers to a list of entry points for each line,
+        // implemented as a sparse array. An entry point is a (script, offsets)
+        // pair, and represents all offsets in that script that are entry points
+        // for the corresponding line.
+        let lineToEntryPointsMap = [];
+
+        // Iterate over all scripts that correspond to this source actor.
+        let scripts = this.scripts.getScriptsBySourceActor(this);
+        for (let script of scripts) {
+          // Get all offsets for each line in the current script. This returns
+          // a map from line numbers fo a list of offsets for each line,
+          // implemented as a sparse array.
+          let lineToOffsetsMap = script.getAllOffsets();
+
+          // Iterate over each line, and add their list of offsets to the map
+          // from line numbers to entry points by forming a (script, offsets)
+          // pair, where script is the current script, and offsets is the list
+          // of offsets for the current line.
+          for (let line = 0; line < lineToOffsetsMap.length; ++line) {
+            let offsets = lineToOffsetsMap[line];
+            if (offsets) {
+              let entryPoints = lineToEntryPointsMap[line];
+              if (!entryPoints) {
+                // We dont have a list of entry points for the current line
+                // number yet, so create it and add it to the map.
+                entryPoints = [];
+                lineToEntryPointsMap[line] = entryPoints;
+              }
+              entryPoints.push({ script, offsets });
+            }
+          }
+        }
+
+        let {
+          originalSourceActor,
+          originalLine,
+          originalColumn
+        } = originalLocation;
+
+        // Now that we have a map from line numbers to a list of entry points
+        // for each line, we can use it to perform breakpoint sliding. Start
+        // at the original line of the breakpoint actor, and keep incrementing
+        // it by one, until either we find a line that has at least one entry
+        // point, or we go past the last line in the map.
+        //
+        // Note that by computing the entire map up front, and implementing it
+        // as a sparse array, we can easily tell when we went past the last line
+        // in the map.
+        let actualLine = originalLine;
+        while (actualLine < lineToEntryPointsMap.length) {
+          let entryPoints = lineToEntryPointsMap[actualLine];
+          if (entryPoints) {
+            setBreakpointForActorAtEntryPoints(actor, entryPoints);
+            break;
+          }
+          ++actualLine;
+        }
+        if (actualLine === lineToEntryPointsMap.length) {
+          // We went past the last line in the map, so breakpoint sliding
+          // failed. Keep the BreakpointActor in the BreakpointActorMap as a
+          // pending breakpoint, so we can try again whenever a new script is
+          // introduced.
+          return Promise.resolve(actor);
+        }
+
+        // If the actual line on which the BreakpointActor was set differs from
+        // the original line that was requested, the BreakpointActor and the
+        // BreakpointActorMap need to be updated accordingly.
+        if (actualLine !== originalLine) {
+          let actualLocation = new OriginalLocation(
+            originalSourceActor,
+            actualLine
+          );
+          let existingActor = this.breakpointActorMap.getActor(actualLocation);
+          if (existingActor) {
+            actor.onDelete();
+            this.breakpointActorMap.deleteActor(originalLocation);
+            actor = existingActor;
+          } else {
+            this.breakpointActorMap.deleteActor(originalLocation);
+            actor.originalLocation = actualLocation;
+            this.breakpointActorMap.setActor(actualLocation, actor);
+          }
+        }
+
+        return Promise.resolve(actor);
+      } else {
+        // TODO: Implement breakpoint sliding for column breakpoints
+        return Promise.resolve(actor);
+      }
     }
   },
 
   /*
    * Ensure the given BreakpointActor is set as breakpoint handler on all
    * scripts that match the given location in the generated source.
    *
    * @param BreakpointActor actor
    *        The BreakpointActor to be set as a breakpoint handler.
    * @param GeneratedLocation generatedLocation
    *        A GeneratedLocation representing the location in the generated
    *        source for which the given BreakpointActor is to be set as a
    *        breakpoint handler.
+   *
+   * @returns A Boolean that is true if the BreakpointActor was set as a
+   *          breakpoint handler on at least one script, and false otherwise.
    */
   _setBreakpointForActorAtLocation: function (actor, generatedLocation) {
+    let { generatedLine, generatedColumn } = generatedLocation;
+
+    // Find all scripts that match the given source actor and line number.
+    let scripts = this.scripts.getScriptsBySourceActorAndLine(
+      this,
+      generatedLine
+    ).filter((script) => !actor.hasScript(script));
+
+    // Find all entry points that correspond to the given location.
+    let entryPoints = [];
+    if (generatedColumn === undefined) {
+      // This is a line breakpoint, so we are interested in all offsets
+      // that correspond to the given line number.
+      for (let script of scripts) {
+        let offsets = script.getLineOffsets(generatedLine);
+        if (offsets.length > 0) {
+          entryPoints.push({ script, offsets });
+        }
+      }
+    } else {
+      // This is a column breakpoint, so we are interested in all column
+      // offsets that correspond to the given line *and* column number.
+      for (let script of scripts) {
+        let columnToOffsetMap = script.getAllColumnOffsets()
+                                      .filter(({ lineNumber }) => {
+          return lineNumber === generatedLine;
+        });
+        for (let { columnNumber: column, offset } of columnToOffsetMap) {
+          // TODO: What we are actually interested in here is a range of
+          // columns, rather than a single one.
+          if (column == generatedColumn) {
+            entryPoints.push({ script, offsets: [offset] });
+          }
+        }
+      }
+    }
+
+    if (entryPoints.length === 0) {
+      return false;
+    }
+    setBreakpointForActorAtEntryPoints(actor, entryPoints);
+    return true;
+  },
+
+  /*
+   * Ensure the given BreakpointActor is set as breakpoint handler on all
+   * scripts that match the given location in the generated source.
+   *
+   * TODO: This method is bugged, because it performs breakpoint sliding on
+   * generated locations. Breakpoint sliding should be performed on original
+   * locations, because there is no guarantee that the next line in the
+   * generated source corresponds to the next line in an original source.
+   *
+   * The only place this method is still used is from setBreakpointForActor
+   * when called for a source mapped source. Once that code has been refactored,
+   * this method can be removed.
+   *
+   * @param BreakpointActor actor
+   *        The BreakpointActor to be set as a breakpoint handler.
+   * @param GeneratedLocation generatedLocation
+   *        A GeneratedLocation representing the location in the generated
+   *        source for which the given BreakpointActor is to be set as a
+   *        breakpoint handler.
+   *
+   * @returns A Boolean that is true if the BreakpointActor was set as a
+   *          breakpoint handler on at least one script, and false otherwise.
+   */
+  _setBreakpointForActorAtLocationWithSliding: function (actor, generatedLocation) {
     let originalLocation = actor.originalLocation;
     let { generatedLine, generatedColumn } = generatedLocation;
 
     // Find all scripts matching the given location. We will almost always have
     // a `source` object to query, but multiple inline HTML scripts are all
     // represented by a single SourceActor even though they have separate source
     // objects, so we need to query based on the url of the page for them.
     let scripts = this.scripts.getScriptsBySourceActorAndLine(this, generatedLine);
@@ -2962,20 +3147,16 @@ SourceActor.prototype = {
     }
 
     for (let [script, mappings] of scriptsAndOffsetMappings) {
       for (let offsetMapping of mappings) {
         script.setBreakpoint(offsetMapping.offset, actor);
       }
       actor.addScript(script, this.threadActor);
     }
-
-    return {
-      actor: actor.actorID
-    };
   },
 
   /**
    * Find the first line that is associated with bytecode offsets, and is
    * greater than or equal to the given start line.
    *
    * @param Array scripts
    *        The set of Debugger.Script instances to consider.
--- a/toolkit/devtools/server/actors/utils/ScriptStore.js
+++ b/toolkit/devtools/server/actors/utils/ScriptStore.js
@@ -74,16 +74,22 @@ ScriptStore.prototype = {
    *
    * NB: The ScriptStore retains ownership of the returned array, and the
    * ScriptStore's consumers MUST NOT MODIFY its contents!
    */
   getAllScripts() {
     return this._scripts.items;
   },
 
+  getScriptsBySourceActor(sourceActor) {
+    return sourceActor.source ?
+           this.getScriptsBySource(sourceActor.source) :
+           this.getScriptsByURL(sourceActor._originalUrl);
+  },
+
   getScriptsBySourceActorAndLine(sourceActor, line) {
     return sourceActor.source ?
            this.getScriptsBySourceAndLine(sourceActor.source, line) :
            this.getScriptsByURLAndLine(sourceActor._originalUrl, line);
   },
 
   /**
    * Get all scripts produced from the given source.