author | Wes Kocher <wkocher@mozilla.com> |
Mon, 23 Mar 2015 16:51:22 -0700 | |
changeset 235180 | 7f5abc27fd5366c596a98b6b06169542b89dccb9 |
parent 235140 | 812ea3cdd997ed7f58238c3ff267f302a31bd5a3 (current diff) |
parent 235179 | 235a9cb26548a76b85a67af8845746ac27ca2e7a (diff) |
child 235181 | 3f4f37b1b910e835e9eec172de4028a4ffe0b178 |
push id | 57353 |
push user | kwierso@gmail.com |
push date | Mon, 23 Mar 2015 23:51:33 +0000 |
treeherder | mozilla-inbound@7f5abc27fd53 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 39.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
|
--- 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.