Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 16 Jun 2015 16:24:17 +0200
changeset 267225 7299b8b5a8a165731ce3dd7dfc7a372c5bb9f68f
parent 267224 e46d0a7d8794c8f93f477889125600011f16dd46 (current diff)
parent 267161 abaec2f7ed2c10724f6b1d51a7d6c4a397a3daf8 (diff)
child 267226 d8783d8b557f24f35c57255867ad37176d341f13
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-esr52@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone41.0a1
Merge mozilla-central to mozilla-inbound
browser/themes/linux/webRTC-shareDevice-16.png
browser/themes/linux/webRTC-shareDevice-64.png
browser/themes/linux/webRTC-shareMicrophone-16.png
browser/themes/linux/webRTC-shareMicrophone-64.png
browser/themes/osx/webRTC-shareDevice-16.png
browser/themes/osx/webRTC-shareDevice-16@2x.png
browser/themes/osx/webRTC-shareDevice-64.png
browser/themes/osx/webRTC-shareDevice-64@2x.png
browser/themes/osx/webRTC-shareMicrophone-16.png
browser/themes/osx/webRTC-shareMicrophone-16@2x.png
browser/themes/osx/webRTC-shareMicrophone-64.png
browser/themes/osx/webRTC-shareMicrophone-64@2x.png
browser/themes/shared/webrtc/webRTC-shareDevice-16.png
browser/themes/shared/webrtc/webRTC-shareDevice-16@2x.png
browser/themes/shared/webrtc/webRTC-shareDevice-64.png
browser/themes/shared/webrtc/webRTC-shareDevice-64@2x.png
browser/themes/shared/webrtc/webRTC-shareMicrophone-16.png
browser/themes/shared/webrtc/webRTC-shareMicrophone-16@2x.png
browser/themes/shared/webrtc/webRTC-shareMicrophone-64.png
browser/themes/shared/webrtc/webRTC-shareMicrophone-64@2x.png
browser/themes/windows/webRTC-shareDevice-16.png
browser/themes/windows/webRTC-shareDevice-64.png
browser/themes/windows/webRTC-shareMicrophone-16.png
browser/themes/windows/webRTC-shareMicrophone-64.png
mobile/android/base/fennec-ids-generator.py
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1165422 - Updated the Android SDK versions
+Bug 1167064: Switch to bluetooth APIv2.
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- 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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- 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="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- 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="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
   <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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- 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="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- 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="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="87a2d8ab9248540910e56921654367b78a587095"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9d0e5057ee5404a31ec1bf76131cb11336a7c3b6"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- 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="e862ab9177af664f00b4522e2350f4cb13866d73">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "62ba52866f4e5ca9120dad5bfe62fc5df981dc39", 
+        "git_revision": "c92e9d14022f0191f8ccdaadf1752352cde97be0", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "e54f587baa2008376f2439fb6901fdb99a878a99", 
+    "revision": "d9efff50aff6c261c55cfdff66e4fedd81250e0b", 
     "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="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
   <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="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="62ba52866f4e5ca9120dad5bfe62fc5df981dc39"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="c92e9d14022f0191f8ccdaadf1752352cde97be0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3477513bcd385571aa01c0d074849e35bd5e2376"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="46da1a05ac04157669685246d70ac59d48699c9e"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="94516f787d477dc307e4566f415e0d2f0794f6b9"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1373,16 +1373,17 @@ pref("devtools.appmanager.manifestEditor
 
 // Enable DevTools WebIDE by default
 pref("devtools.webide.enabled", true);
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
+pref("devtools.toolbox.previousHost", "side");
 pref("devtools.toolbox.selectedTool", "webconsole");
 pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers"]');
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 pref("devtools.toolbox.splitconsoleEnabled", false);
 pref("devtools.toolbox.splitconsoleHeight", 100);
 
 // Toolbox Button preferences
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1236,24 +1236,19 @@
                          oncommand="DeveloperToolbar.hide();"
                          tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
 #endif
    </toolbar>
   </vbox>
 
   <svg:svg height="0">
 #include tab-shape.inc.svg
-#if defined(XP_UNIX) && !defined(XP_MACOSX)
-    <svg:clipPath id="urlbar-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="m 1,-5 l 0,50 l 10000,0 l 0,-50 z"/>
-    </svg:clipPath>
-#endif
     <svg:clipPath id="urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
 #ifndef XP_MACOSX
-      <svg:path d="m 1,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22 l 10000,0 l 0,-50 l -10000,0 z"/>
+      <svg:path d="m 1,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
 #else
       <svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
 #endif
     </svg:clipPath>
   </svg:svg>
 
 </vbox>
 # <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -97,22 +97,25 @@ let handleContentContextMenu = function 
   let charSet = doc.characterSet;
   let baseURI = doc.baseURI;
   let referrer = doc.referrer;
   let referrerPolicy = doc.referrerPolicy;
   let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                                           .getInterface(Ci.nsIDOMWindowUtils)
                                           .outerWindowID;
 
+  let disableSetDesktopBg = null;
   // Media related cache info parent needs for saving
   let contentType = null;
   let contentDisposition = null;
   if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
       event.target instanceof Ci.nsIImageLoadingContent &&
       event.target.currentURI) {
+    disableSetDesktopBg = disableSetDesktopBackground(event.target);
+
     try {
       let imageCache = 
         Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
                                         .getImgCacheForDocument(doc);
       let props =
         imageCache.findEntryProperties(event.target.currentURI);
       try {
         contentType = props.get("type", Ci.nsISupportsCString).data;
@@ -143,17 +146,17 @@ let handleContentContextMenu = function 
     event.target.ownerDocument.defaultView.updateCommands("contentcontextmenu");
 
     let customMenuItems = PageMenuChild.build(event.target);
     let principal = doc.nodePrincipal;
     sendSyncMessage("contextmenu",
                     { editFlags, spellInfo, customMenuItems, addonInfo,
                       principal, docLocation, charSet, baseURI, referrer,
                       referrerPolicy, contentType, contentDisposition,
-                      frameOuterWindowID, selectionInfo },
+                      frameOuterWindowID, selectionInfo, disableSetDesktopBg },
                     { event, popupNode: event.target });
   }
   else {
     // Break out to the parent window and pass the add-on info along
     let browser = docShell.chromeEventHandler;
     let mainWin = browser.ownerDocument.defaultView;
     mainWin.gContextMenuContentData = {
       isRemote: false,
@@ -164,16 +167,17 @@ let handleContentContextMenu = function 
       documentURIObject: doc.documentURIObject,
       docLocation: docLocation,
       charSet: charSet,
       referrer: referrer,
       referrerPolicy: referrerPolicy,
       contentType: contentType,
       contentDisposition: contentDisposition,
       selectionInfo: selectionInfo,
+      disableSetDesktopBackground: disableSetDesktopBg,
     };
   }
 }
 
 Cc["@mozilla.org/eventlistenerservice;1"]
   .getService(Ci.nsIEventListenerService)
   .addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
 
@@ -748,8 +752,57 @@ addMessageListener("ContextMenu:SearchFi
   else {
     let separator = spec.includes("?") ? "&" : "?";
     spec += separator + formData.join("&");
   }
 
   sendAsyncMessage("ContextMenu:SearchFieldBookmarkData:Result",
                    { spec, title, description, postData, charset });
 });
+
+function disableSetDesktopBackground(aTarget) {
+  // Disable the Set as Desktop Background menu item if we're still trying
+  // to load the image or the load failed.
+  if (!(aTarget instanceof Ci.nsIImageLoadingContent))
+    return true;
+
+  if (("complete" in aTarget) && !aTarget.complete)
+    return true;
+
+  if (aTarget.currentURI.schemeIs("javascript"))
+    return true;
+
+  let request = aTarget.QueryInterface(Ci.nsIImageLoadingContent)
+                       .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+  if (!request)
+    return true;
+
+  return false;
+}
+
+addMessageListener("ContextMenu:SetAsDesktopBackground", (message) => {
+  let target = message.objects.target;
+
+  // Paranoia: check disableSetDesktopBackground again, in case the
+  // image changed since the context menu was initiated.
+  let disable = disableSetDesktopBackground(target);
+
+  if (!disable) {
+    try {
+      BrowserUtils.urlSecurityCheck(target.currentURI.spec, target.ownerDocument.nodePrincipal);
+      let canvas = content.document.createElement("canvas");
+      canvas.width = target.naturalWidth;
+      canvas.height = target.naturalHeight;
+      let ctx = canvas.getContext("2d");
+      ctx.drawImage(target, 0, 0);
+      let dataUrl = canvas.toDataURL();
+      sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result",
+                       { dataUrl });
+    }
+    catch (e) {
+      Cu.reportError(e);
+      disable = true;
+    }
+  }
+
+  if (disable)
+    sendAsyncMessage("ContextMenu:SetAsDesktopBackground:Result", { disable });
+});
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -236,17 +236,17 @@ nsContextMenu.prototype = {
     if (shell)
       haveSetDesktopBackground = shell.canSetDesktopBackground;
 #endif
     this.showItem("context-setDesktopBackground",
                   haveSetDesktopBackground && this.onLoadedImage);
 
     if (haveSetDesktopBackground && this.onLoadedImage) {
       document.getElementById("context-setDesktopBackground")
-              .disabled = this.disableSetDesktopBackground();
+              .disabled = gContextMenuContentData.disableSetDesktopBackground;
     }
 
     // Reload image depends on an image that's not fully loaded
     this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
 
     // View image depends on having an image that's not standalone
     // (or is in a frame), or a canvas.
     this.showItem("context-viewimage", (this.onImage &&
@@ -1129,70 +1129,59 @@ nsContextMenu.prototype = {
   viewBGImage: function(e) {
     urlSecurityCheck(this.bgImageURL,
                      this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true,
                                      referrerURI: gContextMenuContentData.documentURIObject });
   },
 
-  disableSetDesktopBackground: function() {
-    // Disable the Set as Desktop Background menu item if we're still trying
-    // to load the image or the load failed.
-    if (!(this.target instanceof Ci.nsIImageLoadingContent))
-      return true;
+  setDesktopBackground: function() {
+    let mm = this.browser.messageManager;
 
-    if (("complete" in this.target) && !this.target.complete)
-      return true;
-
-    if (this.target.currentURI.schemeIs("javascript"))
-      return true;
+    mm.sendAsyncMessage("ContextMenu:SetAsDesktopBackground", null,
+                        { target: this.target });
 
-    var request = this.target
-                      .QueryInterface(Ci.nsIImageLoadingContent)
-                      .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
-    if (!request)
-      return true;
+    let onMessage = (message) => {
+      mm.removeMessageListener("ContextMenu:SetAsDesktopBackground:Result",
+                               onMessage);
 
-    return false;
-  },
+      if (message.data.disable)
+        return;
 
-  setDesktopBackground: function() {
-    // Paranoia: check disableSetDesktopBackground again, in case the
-    // image changed since the context menu was initiated.
-    if (this.disableSetDesktopBackground())
-      return;
+      let image = document.createElementNS('http://www.w3.org/1999/xhtml', 'img');
+      image.src = message.data.dataUrl;
 
-    var doc = this.target.ownerDocument;
-    urlSecurityCheck(this.target.currentURI.spec, this.principal);
-
-    // Confirm since it's annoying if you hit this accidentally.
-    const kDesktopBackgroundURL =
-                  "chrome://browser/content/setDesktopBackground.xul";
+      // Confirm since it's annoying if you hit this accidentally.
+      const kDesktopBackgroundURL =
+                    "chrome://browser/content/setDesktopBackground.xul";
 #ifdef XP_MACOSX
-    // On Mac, the Set Desktop Background window is not modal.
-    // Don't open more than one Set Desktop Background window.
-    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Components.interfaces.nsIWindowMediator);
-    var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
-    if (dbWin) {
-      dbWin.gSetBackground.init(this.target);
-      dbWin.focus();
-    }
-    else {
+      // On Mac, the Set Desktop Background window is not modal.
+      // Don't open more than one Set Desktop Background window.
+      const wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+                 getService(Ci.nsIWindowMediator);
+      let dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
+      if (dbWin) {
+        dbWin.gSetBackground.init(image);
+        dbWin.focus();
+      }
+      else {
+        openDialog(kDesktopBackgroundURL, "",
+                   "centerscreen,chrome,dialog=no,dependent,resizable=no",
+                   image);
+      }
+#else
+      // On non-Mac platforms, the Set Wallpaper dialog is modal.
       openDialog(kDesktopBackgroundURL, "",
-                 "centerscreen,chrome,dialog=no,dependent,resizable=no",
-                 this.target);
-    }
-#else
-    // On non-Mac platforms, the Set Wallpaper dialog is modal.
-    openDialog(kDesktopBackgroundURL, "",
-               "centerscreen,chrome,dialog,modal,dependent",
-               this.target);
+                 "centerscreen,chrome,dialog,modal,dependent",
+                 image);
 #endif
+    };
+
+    mm.addMessageListener("ContextMenu:SetAsDesktopBackground:Result", onMessage);
   },
 
   // Save URL of clicked-on frame.
   saveFrame: function () {
     saveDocument(this.target.ownerDocument);
   },
 
   // Helper function to wait for appropriate MIME-type headers and
@@ -1293,25 +1282,27 @@ nsContextMenu.prototype = {
     function timerCallback() {}
     timerCallback.prototype = {
       notify: function sLA_timer_notify(aTimer) {
         channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
         return;
       }
     }
 
-    // set up a channel to do the saving
+    // setting up a new channel for 'right click - save link as ...'
+    // which should be treated the same way as a toplevel load, hence
+    // we use TYPE_DOCUMENT, see also bug: 1136055
     var ioService = Cc["@mozilla.org/network/io-service;1"].
                     getService(Ci.nsIIOService);
     var channel = ioService.newChannelFromURI2(makeURI(linkURL),
                                                null, // aLoadingNode
                                                this.principal, // aLoadingPrincipal
                                                null, // aTriggeringPrincipal
                                                Ci.nsILoadInfo.SEC_NORMAL,
-                                               Ci.nsIContentPolicy.TYPE_OTHER);
+                                               Ci.nsIContentPolicy.TYPE_DOCUMENT);
     if (linkDownload)
       channel.contentDispositionFilename = linkDownload;
     if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
       let docIsPrivate = PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser);
       channel.setPrivate(docIsPrivate);
     }
     channel.notificationCallbacks = new callbacks();
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3885,16 +3885,17 @@
                                           docLocation: aMessage.data.docLocation,
                                           charSet: aMessage.data.charSet,
                                           referrer: aMessage.data.referrer,
                                           referrerPolicy: aMessage.data.referrerPolicy,
                                           contentType: aMessage.data.contentType,
                                           contentDisposition: aMessage.data.contentDisposition,
                                           frameOuterWindowID: aMessage.data.frameOuterWindowID,
                                           selectionInfo: aMessage.data.selectionInfo,
+                                          disableSetDesktopBackground: aMessage.data.disableSetDesktopBg,
                                         };
               let popup = browser.ownerDocument.getElementById("contentAreaContextMenu");
               let event = gContextMenuContentData.event;
               popup.openPopupAtScreen(event.screenX, event.screenY, true);
               break;
             }
             case "DOMWebNotificationClicked": {
               let tab = this.getTabForBrowser(browser);
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -132,17 +132,17 @@ skip-if = e10s # Bug 1093153 - no about:
 [browser_autocomplete_a11y_label.js]
 skip-if = e10s # Bug 1101993 - times out for unknown reasons when run in the dir (works on its own)
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_oldschool_wrap.js]
 [browser_autocomplete_tag_star_visibility.js]
 [browser_backButtonFitts.js]
-skip-if = os != "win" # The Fitts Law back button is only supported on Windows (bug 571454)
+skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
 [browser_beforeunload_duplicate_dialogs.js]
 skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode
 [browser_blob-channelname.js]
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
 [browser_bug304198.js]
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
--- a/browser/base/content/test/general/browser_readerMode_hidden_nodes.js
+++ b/browser/base/content/test/general/browser_readerMode_hidden_nodes.js
@@ -31,15 +31,24 @@ add_task(function* test_reader_button() 
   TEST_PREFS.forEach(([name, value]) => {
     Services.prefs.setBoolPref(name, value);
   });
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   is_element_hidden(readerButton, "Reader mode button is not present on a new tab");
   // Point tab to a test page that is not reader-able due to hidden nodes.
   let url = TEST_PATH + "readerModeArticleHiddenNodes.html";
-  yield promiseTabLoadEvent(tab, url);
-  yield ContentTask.spawn(tab.linkedBrowser, "", function() {
-    return ContentTaskUtils.waitForEvent(content, "MozAfterPaint");
+  let paintPromise = ContentTask.spawn(tab.linkedBrowser, "", function() {
+    return new Promise(resolve => {
+      addEventListener("DOMContentLoaded", function onDCL() {
+        removeEventListener("DOMContentLoaded", onDCL);
+        addEventListener("MozAfterPaint", function onPaint() {
+          removeEventListener("MozAfterPaint", onPaint);
+          resolve();
+        });
+      });
+    });
   });
+  tab.linkedBrowser.loadURI(url);
+  yield paintPromise;
 
   is_element_hidden(readerButton, "Reader mode button is still not present on tab with unreadable content.");
 });
--- a/browser/base/content/test/general/healthreport_testRemoteCommands.html
+++ b/browser/base/content/test/general/healthreport_testRemoteCommands.html
@@ -1,267 +1,272 @@
-<html>
-  <head>
-    <meta charset="utf-8">
-<script type="application/javascript;version=1.7"
-            src="healthreport_pingData.js">
-</script>
-<script type="application/javascript;version=1.7">
-
-function init() {
-  window.addEventListener("message", function process(e) {
-    // The init function of abouthealth.js schedules an initial payload event,
-    // which will be sent after the payload data has been collected. This extra
-    // event can cause unexpected successes/failures in this test, so we wait
-    // for the extra event to arrive here before progressing with the actual
-    // test.
-    if (e.data.type == "payload") {
-      window.removeEventListener("message", process, false);
-
-      window.addEventListener("message", doTest, false);
-      doTest();
-    }
-  }, false);
-}
-
-function checkSubmissionValue(payload, expectedValue) {
-  return payload.enabled == expectedValue;
-}
-
-function validatePayload(payload) {
-  payload = JSON.parse(payload);
-
-  // xxxmpc - this is some pretty low-bar validation, but we have plenty of tests of that API elsewhere
-  if (!payload.thisPingDate)
-    return false;
-
-  return true;
-}
-
-function isArray(arg) {
-  return Object.prototype.toString.call(arg) === '[object Array]';
-}
-
-function writeDiagnostic(text) {
-  let node = document.createTextNode(text);
-  let br = document.createElement("br");
-  document.body.appendChild(node);
-  document.body.appendChild(br);
-}
-
-function validateCurrentTelemetryEnvironment(data) {
-  // Simple check for now: check that the received object has the expected
-  // top-level properties.
-  const expectedKeys = ["profile", "settings", "system", "build", "partner", "addons"];
-  return expectedKeys.every(key => (key in data));
-}
-
-function validateCurrentTelemetryPingData(ping) {
-  // Simple check for now: check that the received object has the expected
-  // top-level properties and that the type and reason match.
-  const expectedKeys = ["environment", "clientId", "payload", "application",
-                        "version", "type", "id"];
-  return expectedKeys.every(key => (key in ping)) &&
-         (ping.type == "main") &&
-         ("info" in ping.payload) &&
-         ("reason" in ping.payload.info) &&
-         (ping.payload.info.reason == "gather-subsession-payload");
-}
-
-function validateTelemetryPingList(list) {
-  if (!isArray(list)) {
-    console.log("Telemetry ping list is not an array.");
-    return false;
-  }
-
-  if (list.length != TEST_PINGS.length) {
-    console.log("Telemetry ping length is not correct.");
-    return false;
-  }
-
-  let valid = true;
-  for (let i=0; i<list.length; ++i) {
-    let received = list[i];
-    let expected = TEST_PINGS[i];
-    if (received.type != expected.type ||
-        received.timestampCreated != expected.date.getTime()) {
-      writeDiagnostic("Telemetry ping " + i + " does not match.");
-      writeDiagnostic("Expected: " + JSON.stringify(expected));
-      writeDiagnostic("Received: " + JSON.stringify(received));
-      valid = false;
-    } else {
-      writeDiagnostic("Telemetry ping " + i + " matches.");
-    }
-  }
-
-  return true;
-}
-
-function validateTelemetryPingData(expected, received) {
-  const receivedDate = new Date(received.creationDate);
-  if (received.id != expected.id ||
-      received.type != expected.type ||
-      receivedDate.getTime() != expected.date.getTime()) {
-    writeDiagnostic("Telemetry ping data for " + expected.id + " doesn't match.");
-    writeDiagnostic("Expected: " + JSON.stringify(expected));
-    writeDiagnostic("Received: " + JSON.stringify(received));
-    return false;
-  }
-
-  writeDiagnostic("Telemetry ping data for " + expected.id + " matched.");
-  return true;
-}
-
-var tests = [
-{
-  info: "Checking initial value is enabled",
-  event: "RequestCurrentPrefs",
-  payloadType: "prefs",
-  validateResponse: function(payload) {
-    return checkSubmissionValue(payload, true);
-  },
-},
-{
-  info: "Verifying disabling works",
-  event: "DisableDataSubmission",
-  payloadType: "prefs",
-  validateResponse: function(payload) {
-    return checkSubmissionValue(payload, false);
-  },
-},
-{
-  info: "Verifying we're still disabled",
-  event: "RequestCurrentPrefs",
-  payloadType: "prefs",
-  validateResponse: function(payload) {
-    return checkSubmissionValue(payload, false);
-  },
-},
-{
-  info: "Verifying we can get a payload while submission is disabled",
-  event: "RequestCurrentPayload",
-  payloadType: "payload",
-  validateResponse: function(payload) {
-    return validatePayload(payload);
-  },
-},
-{
-  info: "Verifying enabling works",
-  event: "EnableDataSubmission",
-  payloadType: "prefs",
-  validateResponse: function(payload) {
-    return checkSubmissionValue(payload, true);
-  },
-},
-{
-  info: "Verifying we're still re-enabled",
-  event: "RequestCurrentPrefs",
-  payloadType: "prefs",
-  validateResponse: function(payload) {
-    return checkSubmissionValue(payload, true);
-  },
-},
-{
-  info: "Verifying we can get a payload after re-enabling",
-  event: "RequestCurrentPayload",
-  payloadType: "payload",
-  validateResponse: function(payload) {
-    return validatePayload(payload);
-  },
-},
-{
-  info: "Verifying that we can get the current Telemetry environment data",
-  event: "RequestCurrentEnvironment",
-  payloadType: "telemetry-current-environment-data",
-  validateResponse: function(payload) {
-    return validateCurrentTelemetryEnvironment(payload);
-  },
-},
-{
-  info: "Verifying that we can get the current Telemetry ping data",
-  event: "RequestCurrentPingData",
-  payloadType: "telemetry-current-ping-data",
-  validateResponse: function(payload) {
-    return validateCurrentTelemetryPingData(payload);
-  },
-},
-{
-  info: "Verifying that we get the proper Telemetry ping list",
-  event: "RequestTelemetryPingList",
-  payloadType: "telemetry-ping-list",
-  validateResponse: function(payload) {
-    // Validate the ping list
-    if (!validateTelemetryPingList(payload)) {
-      return false;
-    }
-
-    // Now that we received the ping ids, set up additional test tasks
-    // that check loading the individual pings.
-    for (let i=0; i<TEST_PINGS.length; ++i) {
-      TEST_PINGS[i].id = payload[i].id;
-      tests.push({
-        info: "Verifying that we can get the proper Telemetry ping data #" + (i + 1),
-        event: "RequestTelemetryPingData",
-        eventData: { id: TEST_PINGS[i].id },
-        payloadType: "telemetry-ping-data",
-        validateResponse: function(payload) {
-          return validateTelemetryPingData(TEST_PINGS[i], payload.pingData);
-        },
-      });
-    }
-
-    return true;
-  },
-},
-];
-
-var currentTest = -1;
-function doTest(evt) {
-  if (evt) {
-    if (currentTest < 0 || !evt.data.content)
-      return; // not yet testing
-
-    var test = tests[currentTest];
-    if (evt.data.type != test.payloadType)
-      return; // skip unrequested events
-
-    var error = JSON.stringify(evt.data.content);
-    var pass = false;
-    try {
-      pass = test.validateResponse(evt.data.content)
-    } catch (e) {}
-    reportResult(test.info, pass, error);
-  }
-  // start the next test if there are any left
-  if (tests[++currentTest])
-    sendToBrowser(tests[currentTest].event, tests[currentTest].eventData);
-  else
-    reportFinished();
-}
-
-function reportResult(info, pass, error) {
-  var data = {type: "testResult", info: info, pass: pass, error: error};
-  var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
-  document.dispatchEvent(event);
-}
-
-function reportFinished(cmd) {
-  var data = {type: "testsComplete", count: tests.length};
-  var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
-  document.dispatchEvent(event);
-}
-
-function sendToBrowser(type, eventData) {
-  eventData = eventData || {};
-  let detail = {command: type};
-  for (let key of Object.keys(eventData)) {
-    detail[key] = eventData[key];
-  }
-
-  var event = new CustomEvent("RemoteHealthReportCommand", {detail: detail, bubbles: true});
-  document.dispatchEvent(event);
-}
-
-</script>
-  </head>
-  <body onload="init()">
-  </body>
-</html>
+<html>
+  <head>
+    <meta charset="utf-8">
+<script type="application/javascript;version=1.7"
+            src="healthreport_pingData.js">
+</script>
+<script type="application/javascript;version=1.7">
+
+function init() {
+  window.addEventListener("message", function process(e) {
+    // The init function of abouthealth.js schedules an initial payload event,
+    // which will be sent after the payload data has been collected. This extra
+    // event can cause unexpected successes/failures in this test, so we wait
+    // for the extra event to arrive here before progressing with the actual
+    // test.
+    if (e.data.type == "payload") {
+      window.removeEventListener("message", process, false);
+
+      window.addEventListener("message", doTest, false);
+      doTest();
+    }
+  }, false);
+}
+
+function checkSubmissionValue(payload, expectedValue) {
+  return payload.enabled == expectedValue;
+}
+
+function validatePayload(payload) {
+  payload = JSON.parse(payload);
+
+  // xxxmpc - this is some pretty low-bar validation, but we have plenty of tests of that API elsewhere
+  if (!payload.thisPingDate)
+    return false;
+
+  return true;
+}
+
+function isArray(arg) {
+  return Object.prototype.toString.call(arg) === '[object Array]';
+}
+
+function writeDiagnostic(text) {
+  let node = document.createTextNode(text);
+  let br = document.createElement("br");
+  document.body.appendChild(node);
+  document.body.appendChild(br);
+}
+
+function validateCurrentTelemetryEnvironment(data) {
+  // Simple check for now: check that the received object has the expected
+  // top-level properties.
+  const expectedKeys = ["profile", "settings", "system", "build", "partner", "addons"];
+  return expectedKeys.every(key => (key in data));
+}
+
+function validateCurrentTelemetryPingData(ping) {
+  // Simple check for now: check that the received object has the expected
+  // top-level properties and that the type and reason match.
+  const expectedKeys = ["environment", "clientId", "payload", "application",
+                        "version", "type", "id"];
+  return expectedKeys.every(key => (key in ping)) &&
+         (ping.type == "main") &&
+         ("info" in ping.payload) &&
+         ("reason" in ping.payload.info) &&
+         (ping.payload.info.reason == "gather-subsession-payload");
+}
+
+function validateTelemetryPingList(list) {
+  if (!isArray(list)) {
+    console.log("Telemetry ping list is not an array.");
+    return false;
+  }
+
+  // Telemetry may generate other pings (e.g. "deletion" pings), so filter those
+  // out.
+  const TEST_TYPES_REGEX = /^test-telemetryArchive/;
+  list = list.filter(p => TEST_TYPES_REGEX.test(p.type));
+
+  if (list.length != TEST_PINGS.length) {
+    console.log("Telemetry ping length is not correct.");
+    return false;
+  }
+
+  let valid = true;
+  for (let i=0; i<list.length; ++i) {
+    let received = list[i];
+    let expected = TEST_PINGS[i];
+    if (received.type != expected.type ||
+        received.timestampCreated != expected.date.getTime()) {
+      writeDiagnostic("Telemetry ping " + i + " does not match.");
+      writeDiagnostic("Expected: " + JSON.stringify(expected));
+      writeDiagnostic("Received: " + JSON.stringify(received));
+      valid = false;
+    } else {
+      writeDiagnostic("Telemetry ping " + i + " matches.");
+    }
+  }
+
+  return true;
+}
+
+function validateTelemetryPingData(expected, received) {
+  const receivedDate = new Date(received.creationDate);
+  if (received.id != expected.id ||
+      received.type != expected.type ||
+      receivedDate.getTime() != expected.date.getTime()) {
+    writeDiagnostic("Telemetry ping data for " + expected.id + " doesn't match.");
+    writeDiagnostic("Expected: " + JSON.stringify(expected));
+    writeDiagnostic("Received: " + JSON.stringify(received));
+    return false;
+  }
+
+  writeDiagnostic("Telemetry ping data for " + expected.id + " matched.");
+  return true;
+}
+
+var tests = [
+{
+  info: "Checking initial value is enabled",
+  event: "RequestCurrentPrefs",
+  payloadType: "prefs",
+  validateResponse: function(payload) {
+    return checkSubmissionValue(payload, true);
+  },
+},
+{
+  info: "Verifying disabling works",
+  event: "DisableDataSubmission",
+  payloadType: "prefs",
+  validateResponse: function(payload) {
+    return checkSubmissionValue(payload, false);
+  },
+},
+{
+  info: "Verifying we're still disabled",
+  event: "RequestCurrentPrefs",
+  payloadType: "prefs",
+  validateResponse: function(payload) {
+    return checkSubmissionValue(payload, false);
+  },
+},
+{
+  info: "Verifying we can get a payload while submission is disabled",
+  event: "RequestCurrentPayload",
+  payloadType: "payload",
+  validateResponse: function(payload) {
+    return validatePayload(payload);
+  },
+},
+{
+  info: "Verifying enabling works",
+  event: "EnableDataSubmission",
+  payloadType: "prefs",
+  validateResponse: function(payload) {
+    return checkSubmissionValue(payload, true);
+  },
+},
+{
+  info: "Verifying we're still re-enabled",
+  event: "RequestCurrentPrefs",
+  payloadType: "prefs",
+  validateResponse: function(payload) {
+    return checkSubmissionValue(payload, true);
+  },
+},
+{
+  info: "Verifying we can get a payload after re-enabling",
+  event: "RequestCurrentPayload",
+  payloadType: "payload",
+  validateResponse: function(payload) {
+    return validatePayload(payload);
+  },
+},
+{
+  info: "Verifying that we can get the current Telemetry environment data",
+  event: "RequestCurrentEnvironment",
+  payloadType: "telemetry-current-environment-data",
+  validateResponse: function(payload) {
+    return validateCurrentTelemetryEnvironment(payload);
+  },
+},
+{
+  info: "Verifying that we can get the current Telemetry ping data",
+  event: "RequestCurrentPingData",
+  payloadType: "telemetry-current-ping-data",
+  validateResponse: function(payload) {
+    return validateCurrentTelemetryPingData(payload);
+  },
+},
+{
+  info: "Verifying that we get the proper Telemetry ping list",
+  event: "RequestTelemetryPingList",
+  payloadType: "telemetry-ping-list",
+  validateResponse: function(payload) {
+    // Validate the ping list
+    if (!validateTelemetryPingList(payload)) {
+      return false;
+    }
+
+    // Now that we received the ping ids, set up additional test tasks
+    // that check loading the individual pings.
+    for (let i=0; i<TEST_PINGS.length; ++i) {
+      TEST_PINGS[i].id = payload[i].id;
+      tests.push({
+        info: "Verifying that we can get the proper Telemetry ping data #" + (i + 1),
+        event: "RequestTelemetryPingData",
+        eventData: { id: TEST_PINGS[i].id },
+        payloadType: "telemetry-ping-data",
+        validateResponse: function(payload) {
+          return validateTelemetryPingData(TEST_PINGS[i], payload.pingData);
+        },
+      });
+    }
+
+    return true;
+  },
+},
+];
+
+var currentTest = -1;
+function doTest(evt) {
+  if (evt) {
+    if (currentTest < 0 || !evt.data.content)
+      return; // not yet testing
+
+    var test = tests[currentTest];
+    if (evt.data.type != test.payloadType)
+      return; // skip unrequested events
+
+    var error = JSON.stringify(evt.data.content);
+    var pass = false;
+    try {
+      pass = test.validateResponse(evt.data.content)
+    } catch (e) {}
+    reportResult(test.info, pass, error);
+  }
+  // start the next test if there are any left
+  if (tests[++currentTest])
+    sendToBrowser(tests[currentTest].event, tests[currentTest].eventData);
+  else
+    reportFinished();
+}
+
+function reportResult(info, pass, error) {
+  var data = {type: "testResult", info: info, pass: pass, error: error};
+  var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
+  document.dispatchEvent(event);
+}
+
+function reportFinished(cmd) {
+  var data = {type: "testsComplete", count: tests.length};
+  var event = new CustomEvent("FirefoxHealthReportTestResponse", {detail: {data: data}, bubbles: true});
+  document.dispatchEvent(event);
+}
+
+function sendToBrowser(type, eventData) {
+  eventData = eventData || {};
+  let detail = {command: type};
+  for (let key of Object.keys(eventData)) {
+    detail[key] = eventData[key];
+  }
+
+  var event = new CustomEvent("RemoteHealthReportCommand", {detail: detail, bubbles: true});
+  document.dispatchEvent(event);
+}
+
+</script>
+  </head>
+  <body onload="init()">
+  </body>
+</html>
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -3066,16 +3066,27 @@ file, You can obtain one at http://mozil
 
           // Only handle supported notification panels.
           if (!this._notificationType) {
             return;
           }
 
           let viewsLeft = this._viewsLeft;
           if (viewsLeft) {
+            let notification = this._panel.firstElementChild.notification;
+            if (this._notificationType == "passwords" && notification && notification.options &&
+                notification.options.origin) {
+              let fxAOrigin = new URL(Services.prefs.getCharPref("identity.fxaccounts.remote.signup.uri")).origin
+              if (notification.options.origin == fxAOrigin) {
+                // Somewhat gross hack - we don't want to show the sync promo while
+                // the user may be logging into Sync.
+                return;
+              }
+            }
+
             if (Services.prefs.prefHasUserValue("services.sync.username") &&
                this._notificationType != "addons-sync-disabled") {
               // If the user has already setup Sync, don't show the notification.
               this._viewsLeft = 0;
               // Be sure to hide the panel, in case it was visible and the user
               // decided to setup Sync after noticing it.
               viewsLeft = 0;
               // The panel is still hidden, just bail out.
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -7,16 +7,17 @@
 this.EXPORTED_SYMBOLS = ["UITour", "UITourMetricsProvider"];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/TelemetryController.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ResetProfile",
   "resource://gre/modules/ResetProfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
@@ -1973,16 +1974,26 @@ this.UITour.init();
  */
 const DAILY_DISCRETE_TEXT_FIELD = Metrics.Storage.FIELD_DAILY_DISCRETE_TEXT;
 
 /**
  * Public API to be called by the UITour code
  */
 const UITourHealthReport = {
   recordTreatmentTag: function(tag, value) {
+  TelemetryController.submitExternalPing("uitour-tag",
+    {
+      version: 1,
+      tagName: tag,
+      tagValue: value,
+    },
+    {
+      addClientId: true,
+      addEnvironment: true,
+    });
 #ifdef MOZ_SERVICES_HEALTHREPORT
     Task.spawn(function*() {
       let reporter = Cc["@mozilla.org/datareporting/service;1"]
                        .getService()
                        .wrappedJSObject
                        .healthReporter;
 
       // This can happen if the FHR component of the data reporting service is
--- a/browser/components/uitour/test/browser_UITour.js
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -3,16 +3,17 @@
 
 "use strict";
 
 let gTestTab;
 let gContentAPI;
 let gContentWindow;
 
 Components.utils.import("resource:///modules/UITour.jsm");
+Components.utils.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
 
 function test() {
   UITourTest();
 }
 
 let tests = [
   function test_untrusted_host(done) {
     loadUITourTestPage(function() {
@@ -395,23 +396,34 @@ let tests = [
           Services.obs.removeObserver(observe, "browser-search-engine-modified");
           Services.search.defaultEngine = defaultEngine;
         });
 
         gContentAPI.setDefaultSearchEngine(someOtherEngineID);
       });
     });
   },
-  function test_treatment_tag(done) {
+  taskify(function* test_treatment_tag(done) {
+    let ac = new TelemetryArchiveTesting.Checker();
+    yield ac.promiseInit();
     gContentAPI.setTreatmentTag("foobar", "baz");
     gContentAPI.getTreatmentTag("foobar", (data) => {
       is(data.value, "baz", "set and retrieved treatmentTag");
-      done();
+      ac.promiseFindPing("uitour-tag", [
+        [["payload", "tagName"], "foobar"],
+        [["payload", "tagValue"], "baz"],
+      ]).then((found) => {
+        ok(found, "Telemetry ping submitted for setTreatmentTag");
+        done();
+      }, (err) => {
+        ok(false, "Exeption finding uitour telemetry ping: " + err);
+        done();
+      });
     });
-  },
+  }),
 
   // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
   taskify(function* cleanupMenus() {
     let shownPromise = promisePanelShown(window);
     gContentAPI.showMenu("appMenu");
     yield shownPromise;
   }),
 ];
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   serviceworker.js
 
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
 [browser_ignore_toolbox_network_requests.js]
 [browser_keybindings_01.js]
 [browser_keybindings_02.js]
+[browser_keybindings_03.js]
 [browser_new_activation_workflow.js]
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_keybindings_03.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the toolbox 'switch to previous host' feature works.
+// Pressing ctrl/cmd+shift+d should switch to the last used host.
+
+const URL = "data:text/html;charset=utf8,test page for toolbox switching";
+
+add_task(function*() {
+  info("Create a test tab and open the toolbox");
+  let tab = yield addTab(URL);
+  let target = TargetFactory.forTab(tab);
+  let toolbox = yield gDevTools.showToolbox(target, "webconsole");
+
+  let keyElement = toolbox.doc.getElementById("toolbox-toggle-host-key");
+
+  let {SIDE, BOTTOM, WINDOW} = devtools.Toolbox.HostType;
+  checkHostType(toolbox, BOTTOM, SIDE);
+
+  info ("Switching from bottom to side");
+  synthesizeKeyElement(keyElement);
+  yield toolbox.once("host-changed");
+  checkHostType(toolbox, SIDE, BOTTOM);
+
+  info ("Switching from side to bottom");
+  synthesizeKeyElement(keyElement);
+  yield toolbox.once("host-changed");
+  checkHostType(toolbox, BOTTOM, SIDE);
+
+  info ("Switching to window");
+  yield toolbox.switchHost(WINDOW);
+  checkHostType(toolbox, WINDOW, BOTTOM);
+
+  info ("Switching from window to bottom");
+  synthesizeKeyElement(keyElement);
+  yield toolbox.once("host-changed");
+  checkHostType(toolbox, BOTTOM, WINDOW);
+
+  yield toolbox.destroy();
+  gBrowser.removeCurrentTab();
+});
--- a/browser/devtools/framework/test/browser_toolbox_hosts.js
+++ b/browser/devtools/framework/test/browser_toolbox_hosts.js
@@ -1,130 +1,137 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let temp = {}
-Cu.import("resource:///modules/devtools/gDevTools.jsm", temp);
-let DevTools = temp.DevTools;
+"use strict";
 
-Cu.import("resource://gre/modules/devtools/Loader.jsm", temp);
-let devtools = temp.devtools;
-
-let Toolbox = devtools.Toolbox;
-
+let {SIDE, BOTTOM, WINDOW} = devtools.Toolbox.HostType;
 let toolbox, target;
 
-function test()
-{
-  gBrowser.selectedTab = gBrowser.addTab();
-  target = TargetFactory.forTab(gBrowser.selectedTab);
+const URL = "data:text/html;charset=utf8,test for opening toolbox in different hosts";
+
+add_task(function* runTest() {
+  info("Create a test tab and open the toolbox");
+  let tab = yield addTab(URL);
+  target = TargetFactory.forTab(tab);
+  toolbox = yield gDevTools.showToolbox(target, "webconsole");
 
-  gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
-    gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    gDevTools.showToolbox(target)
-             .then(testBottomHost, console.error)
-             .then(null, console.error);
-  }, true);
+  yield testBottomHost();
+  yield testSidebarHost();
+  yield testWindowHost();
+  yield testToolSelect();
+  yield testDestroy();
+  yield testRememberHost();
+  yield testPreviousHost();
 
-  content.location = "data:text/html,test for opening toolbox in different hosts";
-}
+  yield toolbox.destroy();
 
-function testBottomHost(aToolbox)
-{
-  toolbox = aToolbox;
+  toolbox = target = null;
+  gBrowser.removeCurrentTab();
+});
 
-  checkHostType(Toolbox.HostType.BOTTOM);
+function* testBottomHost() {
+  checkHostType(toolbox, BOTTOM);
 
   // test UI presence
   let nbox = gBrowser.getNotificationBox();
   let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe");
   ok(iframe, "toolbox bottom iframe exists");
 
   checkToolboxLoaded(iframe);
-
-  toolbox.switchHost(Toolbox.HostType.SIDE).then(testSidebarHost);
 }
 
-function testSidebarHost()
-{
-  checkHostType(Toolbox.HostType.SIDE);
+function* testSidebarHost() {
+  yield toolbox.switchHost(SIDE);
+  checkHostType(toolbox, SIDE);
 
   // test UI presence
   let nbox = gBrowser.getNotificationBox();
   let bottom = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe");
   ok(!bottom, "toolbox bottom iframe doesn't exist");
 
   let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe");
   ok(iframe, "toolbox side iframe exists");
 
   checkToolboxLoaded(iframe);
-
-  toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost);
 }
 
-function testWindowHost()
-{
-  checkHostType(Toolbox.HostType.WINDOW);
+function* testWindowHost() {
+  yield toolbox.switchHost(WINDOW);
+  checkHostType(toolbox, WINDOW);
 
   let nbox = gBrowser.getNotificationBox();
   let sidebar = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe");
   ok(!sidebar, "toolbox sidebar iframe doesn't exist");
 
   let win = Services.wm.getMostRecentWindow("devtools:toolbox");
   ok(win, "toolbox separate window exists");
 
   let iframe = win.document.getElementById("toolbox-iframe");
   checkToolboxLoaded(iframe);
-
-  testToolSelect();
 }
 
-function testToolSelect()
-{
+function* testToolSelect() {
   // make sure we can load a tool after switching hosts
-  toolbox.selectTool("inspector").then(testDestroy);
+  yield toolbox.selectTool("inspector");
 }
 
-function testDestroy()
-{
-  toolbox.destroy().then(function() {
-    target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.showToolbox(target).then(testRememberHost);
-  });
+function* testDestroy() {
+  yield toolbox.destroy();
+  target = TargetFactory.forTab(gBrowser.selectedTab);
+  toolbox = yield gDevTools.showToolbox(target);
 }
 
-function testRememberHost(aToolbox)
-{
-  toolbox = aToolbox;
+function* testRememberHost() {
   // last host was the window - make sure it's the same when re-opening
-  is(toolbox.hostType, Toolbox.HostType.WINDOW, "host remembered");
+  is(toolbox.hostType, WINDOW, "host remembered");
 
   let win = Services.wm.getMostRecentWindow("devtools:toolbox");
   ok(win, "toolbox separate window exists");
-
-  cleanup();
 }
 
-function checkHostType(hostType)
-{
-  is(toolbox.hostType, hostType, "host type is " + hostType);
+function* testPreviousHost() {
+  // last host was the window - make sure it's the same when re-opening
+  is(toolbox.hostType, WINDOW, "host remembered");
+
+  info("Switching to side");
+  yield toolbox.switchHost(SIDE);
+  checkHostType(toolbox, SIDE, WINDOW);
+
+  info("Switching to bottom");
+  yield toolbox.switchHost(BOTTOM);
+  checkHostType(toolbox, BOTTOM, SIDE);
+
+  info("Switching from bottom to side");
+  yield toolbox.switchToPreviousHost();
+  checkHostType(toolbox, SIDE, BOTTOM);
+
+  info("Switching from side to bottom");
+  yield toolbox.switchToPreviousHost();
+  checkHostType(toolbox, BOTTOM, SIDE);
 
-  let pref = Services.prefs.getCharPref("devtools.toolbox.host");
-  is(pref, hostType, "host pref is " + hostType);
+  info("Switching to window");
+  yield toolbox.switchHost(WINDOW);
+  checkHostType(toolbox, WINDOW, BOTTOM);
+
+  info("Switching from window to bottom");
+  yield toolbox.switchToPreviousHost();
+  checkHostType(toolbox, BOTTOM, WINDOW);
+
+  info("Forcing the previous host to match the current (bottom)")
+  Services.prefs.setCharPref("devtools.toolbox.previousHost", BOTTOM);
+
+  info("Switching from bottom to side (since previous=current=bottom");
+  yield toolbox.switchToPreviousHost();
+  checkHostType(toolbox, SIDE, BOTTOM);
+
+  info("Forcing the previous host to match the current (side)")
+  Services.prefs.setCharPref("devtools.toolbox.previousHost", SIDE);
+  info("Switching from side to bottom (since previous=current=side");
+  yield toolbox.switchToPreviousHost();
+  checkHostType(toolbox, BOTTOM, SIDE);
 }
 
-function checkToolboxLoaded(iframe)
-{
+function checkToolboxLoaded(iframe) {
   let tabs = iframe.contentDocument.getElementById("toolbox-tabs");
   ok(tabs, "toolbox UI has been loaded into iframe");
 }
-
-function cleanup()
-{
-  Services.prefs.setCharPref("devtools.toolbox.host", Toolbox.HostType.BOTTOM);
-
-  toolbox.destroy().then(function() {
-    DevTools = Toolbox = toolbox = target = null;
-    gBrowser.removeCurrentTab();
-    finish();
-  });
- }
--- a/browser/devtools/framework/test/browser_toolbox_window_reload_target.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_reload_target.js
@@ -63,35 +63,27 @@ function testAllTheTools(docked, callbac
             testAllTheTools(docked, callback, toolNum+1);
           });
         });
       });
     });
   });
 }
 
-function synthesizeKeyForToolbox(keyId) {
-  let el = toolbox.doc.getElementById(keyId);
-  let key = el.getAttribute("key") || el.getAttribute("keycode");
-  let mod = {};
-  el.getAttribute("modifiers").split(" ").forEach((m) => mod[m+"Key"] = true);
-  info("Synthesizing: key="+key+", mod="+JSON.stringify(mod));
-  EventUtils.synthesizeKey(key, mod, toolbox.doc.defaultView);
-}
-
 function testReload(key, docked, toolID, callback) {
   let complete = () => {
     gBrowser.selectedBrowser.messageManager.removeMessageListener("devtools:test:load", complete);
     return callback();
   };
   gBrowser.selectedBrowser.messageManager.addMessageListener("devtools:test:load", complete);
 
   description = docked+" devtools with tool "+toolID+", key #" + key;
   info("Testing reload in "+description);
-  synthesizeKeyForToolbox(key);
+  let el = toolbox.doc.getElementById(key);
+  synthesizeKeyElement(el);
   reloadsSent++;
 }
 
 function finishUp() {
   toolbox.destroy().then(() => {
     gBrowser.removeCurrentTab();
 
     target = toolbox = description = reloadsSent = toolIDs = null;
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -104,8 +104,41 @@ function executeInContent(name, data={},
 
   mm.sendAsyncMessage(name, data, objects);
   if (expectResponse) {
     return waitForContentMessage(name);
   } else {
     return promise.resolve();
   }
 }
+
+/**
+ * Synthesize a keypress from a <key> element, taking into account
+ * any modifiers.
+ * @param {Element} el the <key> element to synthesize
+ */
+function synthesizeKeyElement(el) {
+  let key = el.getAttribute("key") || el.getAttribute("keycode");
+  let mod = {};
+  el.getAttribute("modifiers").split(" ").forEach((m) => mod[m+"Key"] = true);
+  info(`Synthesizing: key=${key}, mod=${JSON.stringify(mod)}`);
+  EventUtils.synthesizeKey(key, mod, el.ownerDocument.defaultView);
+}
+
+/* Check the toolbox host type and prefs to make sure they match the
+ * expected values
+ * @param {Toolbox}
+ * @param {HostType} hostType
+ *        One of {SIDE, BOTTOM, WINDOW} from devtools.Toolbox.HostType
+ * @param {HostType} Optional previousHostType
+ *        The host that will be switched to when calling switchToPreviousHost
+ */
+function checkHostType(toolbox, hostType, previousHostType) {
+  is(toolbox.hostType, hostType, "host type is " + hostType);
+
+  let pref = Services.prefs.getCharPref("devtools.toolbox.host");
+  is(pref, hostType, "host pref is " + hostType);
+
+  if (previousHostType) {
+    is (Services.prefs.getCharPref("devtools.toolbox.previousHost"),
+      previousHostType, "The previous host is correct");
+  }
+}
--- a/browser/devtools/framework/test/shared-head.js
+++ b/browser/devtools/framework/test/shared-head.js
@@ -25,19 +25,21 @@ function getFrameScript() {
   mm.loadFrameScript(frameURL, false);
   SimpleTest.registerCleanupFunction(() => {
     mm = null;
   });
   return mm;
 }
 
 gDevTools.testing = true;
-SimpleTest.registerCleanupFunction(() => {
+registerCleanupFunction(() => {
   gDevTools.testing = false;
   Services.prefs.clearUserPref("devtools.dump.emit");
+  Services.prefs.clearUserPref("devtools.toolbox.host");
+  Services.prefs.clearUserPref("devtools.toolbox.previousHost");
 });
 
 registerCleanupFunction(function cleanup() {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -183,17 +183,18 @@ Toolbox.HostType = {
 };
 
 Toolbox.prototype = {
   _URL: "chrome://browser/content/devtools/framework/toolbox.xul",
 
   _prefs: {
     LAST_HOST: "devtools.toolbox.host",
     LAST_TOOL: "devtools.toolbox.selectedTool",
-    SIDE_ENABLED: "devtools.toolbox.sideEnabled"
+    SIDE_ENABLED: "devtools.toolbox.sideEnabled",
+    PREVIOUS_HOST: "devtools.toolbox.previousHost"
   },
 
   currentToolId: null,
 
   /**
    * Returns a *copy* of the _toolPanels collection.
    *
    * @return {Map} panels
@@ -492,22 +493,26 @@ Toolbox.prototype = {
         this.reloadTarget(force);
       }, true);
     });
   },
 
   _addHostListeners: function() {
     let nextKey = this.doc.getElementById("toolbox-next-tool-key");
     nextKey.addEventListener("command", this.selectNextTool.bind(this), true);
+
     let prevKey = this.doc.getElementById("toolbox-previous-tool-key");
     prevKey.addEventListener("command", this.selectPreviousTool.bind(this), true);
 
     let minimizeKey = this.doc.getElementById("toolbox-minimize-key");
     minimizeKey.addEventListener("command", this._toggleMinimizeMode, true);
 
+    let toggleKey = this.doc.getElementById("toolbox-toggle-host-key");
+    toggleKey.addEventListener("command", this.switchToPreviousHost.bind(this), true);
+
     // Split console uses keypress instead of command so the event can be
     // cancelled with stopPropagation on the keypress, and not preventDefault.
     this.doc.addEventListener("keypress", this._splitConsoleOnKeypress, false);
 
     this.doc.addEventListener("focus", this._onFocus, true);
   },
 
   _saveSplitConsoleHeight: function() {
@@ -1575,16 +1580,36 @@ Toolbox.prototype = {
 
     // clean up the toolbox if its window is closed
     let newHost = new Hosts[hostType](this.target.tab, options);
     newHost.on("window-closed", this.destroy);
     return newHost;
   },
 
   /**
+   * Switch to the last used host for the toolbox UI.
+   * This is determined by the devtools.toolbox.previousHost pref.
+   */
+  switchToPreviousHost: function() {
+    let hostType = Services.prefs.getCharPref(this._prefs.PREVIOUS_HOST);
+
+    // Handle the case where the previous host happens to match the current
+    // host. If so, switch to bottom if it's not already used, and side if not.
+    if (hostType === this._host.type) {
+      if (hostType === Toolbox.HostType.BOTTOM) {
+        hostType = Toolbox.HostType.SIDE;
+      } else {
+        hostType = Toolbox.HostType.BOTTOM;
+      }
+    }
+
+    return this.switchHost(hostType);
+  },
+
+  /**
    * Switch to a new host for the toolbox UI. E.g. bottom, sidebar, window,
    * and focus the window when done.
    *
    * @param {string} hostType
    *        The host type of the new host object
    */
   switchHost: function(hostType) {
     if (hostType == this._host.type || !this._target.isLocalTab) {
@@ -1602,20 +1627,22 @@ Toolbox.prototype = {
       // See bug 1022726, most probably because of swapFrameLoaders we need to
       // first focus the window here, and then once again further below to make
       // sure focus actually happens.
       this.frame.contentWindow.focus();
 
       this._host.off("window-closed", this.destroy);
       this.destroyHost();
 
+      let prevHostType = this._host.type;
       this._host = newHost;
 
       if (this.hostType != Toolbox.HostType.CUSTOM) {
         Services.prefs.setCharPref(this._prefs.LAST_HOST, this._host.type);
+        Services.prefs.setCharPref(this._prefs.PREVIOUS_HOST, prevHostType);
       }
 
       this._buildDockButtons();
       this._addKeysToWindow();
 
       // Focus the contentWindow to make sure keyboard shortcuts work straight
       // away.
       this.frame.contentWindow.focus();
--- a/browser/devtools/framework/toolbox.xul
+++ b/browser/devtools/framework/toolbox.xul
@@ -69,16 +69,20 @@
     <key id="toolbox-force-reload-key2"
          keycode="VK_F5"
          oncommand="void(0);"
          modifiers="accel"/>
     <key id="toolbox-minimize-key"
          key="&toolboxToggleMinimize.key;"
          oncommand="void(0);"
          modifiers="shift, accel"/>
+    <key id="toolbox-toggle-host-key"
+         key="&toolboxToggle.key;"
+         oncommand="void(0);"
+         modifiers="accel shift"/>
   </keyset>
 
   <popupset>
     <menupopup id="toolbox-textbox-context-popup">
       <menuitem id="cMenu_undo"/>
       <menuseparator/>
       <menuitem id="cMenu_cut"/>
       <menuitem id="cMenu_copy"/>
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -259,28 +259,43 @@ var Scratchpad = {
 
   /**
    * Check or uncheck view menu items according to stored preferences.
    */
   _updateViewMenuItems: function SP_updateViewMenuItems() {
     this._updateViewMenuItem(SHOW_LINE_NUMBERS, "sp-menu-line-numbers");
     this._updateViewMenuItem(WRAP_TEXT, "sp-menu-word-wrap");
     this._updateViewMenuItem(SHOW_TRAILING_SPACE, "sp-menu-highlight-trailing-space");
+    this._updateViewFontMenuItem(MINIMUM_FONT_SIZE, "sp-cmd-smaller-font");
+    this._updateViewFontMenuItem(MAXIMUM_FONT_SIZE, "sp-cmd-larger-font");
   },
 
+  /**
+   * Check or uncheck view menu item according to stored preferences.
+   */
   _updateViewMenuItem: function SP_updateViewMenuItem(preferenceName, menuId) {
     let checked = Services.prefs.getBoolPref(preferenceName);
     if (checked) {
         document.getElementById(menuId).setAttribute('checked', true);
     } else {
         document.getElementById(menuId).removeAttribute('checked');
     }
   },
 
   /**
+   * Disable view menu item if the stored font size is equals to the given one.
+   */
+  _updateViewFontMenuItem: function SP_updateViewFontMenuItem(fontSize, commandId) {
+    let prefFontSize = Services.prefs.getIntPref(EDITOR_FONT_SIZE);
+    if (prefFontSize === fontSize) {
+      document.getElementById(commandId).setAttribute('disabled', true);
+    }
+  },
+
+  /**
    * The script execution context. This tells Scratchpad in which context the
    * script shall execute.
    *
    * Possible values:
    *   - SCRATCHPAD_CONTEXT_CONTENT to execute code in the context of the current
    *   tab content window object.
    *   - SCRATCHPAD_CONTEXT_BROWSER to execute code in the context of the
    *   currently active chrome window object.
@@ -1934,40 +1949,55 @@ var Scratchpad = {
   increaseFontSize: function SP_increaseFontSize()
   {
     let size = this.editor.getFontSize();
 
     if (size < MAXIMUM_FONT_SIZE) {
       let newFontSize = size + 1;
       this.editor.setFontSize(newFontSize);
       Services.prefs.setIntPref(EDITOR_FONT_SIZE, newFontSize);
+
+      if (newFontSize === MAXIMUM_FONT_SIZE) {
+        document.getElementById("sp-cmd-larger-font").setAttribute('disabled', true);
+      }
+
+      document.getElementById("sp-cmd-smaller-font").removeAttribute('disabled');
     }
   },
 
   /**
    * Decrease the editor's font size by 1 px.
    */
   decreaseFontSize: function SP_decreaseFontSize()
   {
     let size = this.editor.getFontSize();
 
     if (size > MINIMUM_FONT_SIZE) {
       let newFontSize = size - 1;
       this.editor.setFontSize(newFontSize);
       Services.prefs.setIntPref(EDITOR_FONT_SIZE, newFontSize);
+
+      if (newFontSize === MINIMUM_FONT_SIZE) {
+        document.getElementById("sp-cmd-smaller-font").setAttribute('disabled', true);
+      }
     }
+
+    document.getElementById("sp-cmd-larger-font").removeAttribute('disabled');
   },
 
   /**
    * Restore the editor's original font size.
    */
   normalFontSize: function SP_normalFontSize()
   {
     this.editor.setFontSize(NORMAL_FONT_SIZE);
     Services.prefs.setIntPref(EDITOR_FONT_SIZE, NORMAL_FONT_SIZE);
+
+    document.getElementById("sp-cmd-larger-font").removeAttribute('disabled');
+    document.getElementById("sp-cmd-smaller-font").removeAttribute('disabled');
   },
 
   _observers: [],
 
   /**
    * Add an observer for Scratchpad events.
    *
    * The observer implements IScratchpadObserver := {
--- a/browser/devtools/scratchpad/test/browser.ini
+++ b/browser/devtools/scratchpad/test/browser.ini
@@ -39,8 +39,9 @@ support-files = NS_ERROR_ILLEGAL_INPUT.t
 [browser_scratchpad_pprint-02.js]
 [browser_scratchpad_pprint.js]
 [browser_scratchpad_pprint_error_goto_line.js]
 [browser_scratchpad_restore.js]
 [browser_scratchpad_tab_switch.js]
 [browser_scratchpad_ui.js]
 [browser_scratchpad_close_toolbox.js]
 [browser_scratchpad_remember_view_options.js]
+[browser_scratchpad_disable_view_menu_items.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_disable_view_menu_items.js
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test if the view menu items "Larger Font" and "Smaller Font" are disabled
+// when the font size reaches the maximum/minimum values.
+
+let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+
+function test() {
+  const options = {
+    tabContent: 'test if view menu items "Larger Font" and "Smaller Font" are enabled/disabled.'
+  };
+  openTabAndScratchpad(options)
+    .then(Task.async(runTests))
+    .then(finish, console.error);
+}
+
+function* runTests([win, sp]) {
+  yield testMaximumFontSize(win, sp);
+
+  yield testMinimumFontSize(win, sp);
+}
+
+const MAXIMUM_FONT_SIZE = 96;
+const MINIMUM_FONT_SIZE = 6;
+const NORMAL_FONT_SIZE = 12;
+
+let testMaximumFontSize = Task.async(function* (win, sp) {
+  let doc = win.document;
+
+  Services.prefs.clearUserPref('devtools.scratchpad.editorFontSize');
+
+  let menu = doc.getElementById('sp-menu-larger-font');
+
+  for (let i = NORMAL_FONT_SIZE; i <= MAXIMUM_FONT_SIZE; i++) {
+    menu.doCommand();
+  }
+
+  let cmd = doc.getElementById('sp-cmd-larger-font');
+  ok(cmd.getAttribute('disabled') === 'true', 'Command "sp-cmd-larger-font" is disabled.');
+
+  menu = doc.getElementById('sp-menu-smaller-font');
+  menu.doCommand();
+
+  ok(cmd.hasAttribute('disabled') === false, 'Command "sp-cmd-larger-font" is enabled.');
+});
+
+let testMinimumFontSize = Task.async(function* (win, sp) {
+  let doc = win.document;
+
+  let menu = doc.getElementById('sp-menu-smaller-font');
+
+  for (let i = MAXIMUM_FONT_SIZE; i >= MINIMUM_FONT_SIZE; i--) {
+    menu.doCommand();
+  }
+
+  let cmd = doc.getElementById('sp-cmd-smaller-font');
+  ok(cmd.getAttribute('disabled') === 'true', 'Command "sp-cmd-smaller-font" is disabled.');
+
+  menu = doc.getElementById('sp-menu-larger-font');
+  menu.doCommand();
+
+  ok(cmd.hasAttribute('disabled') === false, 'Command "sp-cmd-smaller-font" is enabled.');
+
+  Services.prefs.clearUserPref('devtools.scratchpad.editorFontSize');
+});
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -19,16 +19,17 @@
 <!ENTITY toolboxZoomIn.key2            "="> <!-- + is above this key on many keyboards -->
 <!ENTITY toolboxZoomOut.key            "-">
 <!ENTITY toolboxZoomReset.key          "0">
 
 <!ENTITY toolboxReload.key             "r">
 <!-- This key is used with the accel+shift modifiers to minimize the toolbox -->
 <!ENTITY toolboxToggleMinimize.key     "U">
 
+<!ENTITY toolboxToggle.key             "d">
 <!-- LOCALIZATION NOTE (toolboxFramesButton): This is the label for
   -  the iframes menu list that appears only when the document has some.
   -  It allows you to switch the context of the whole toolbox. -->
 <!ENTITY toolboxFramesTooltip          "Select an iframe as the currently targeted document">
 
 <!-- LOCALIZATION NOTE (browserToolboxErrorMessage): This is the label
   -  shown next to error details when the Browser Toolbox is unable to open. -->
 <!ENTITY browserToolboxErrorMessage          "Error opening Browser Toolbox:">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -675,27 +675,27 @@ toolbarbutton[constrain-size="true"][cui
 
 :-moz-any(#TabsToolbar, #nav-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
   -moz-margin-start: -4px;
   margin-top: 3px;
   margin-bottom: 3px;
 }
 
 #back-button {
-  margin-top: 3px;
-  margin-bottom: 3px;
-  -moz-margin-start: 5px;
-  padding: 0;
+  padding-top: 3px;
+  padding-bottom: 3px;
+  -moz-padding-start: 5px;
+  -moz-padding-end: 0;
   position: relative;
   z-index: 1;
-  border-radius: 10000px;
+  border-radius: 0 10000px 10000px 0;
 }
 
-#back-button:not(:-moz-lwtheme) {
-  background-color: -moz-dialog;
+#back-button:-moz-locale-dir(rtl) {
+  border-radius: 10000px 0 0 10000px;
 }
 
 #back-button > menupopup {
   margin-top: -1px;
 }
 
 #back-button > .toolbarbutton-icon {
   border-radius: 10000px;
@@ -889,27 +889,23 @@ toolbarbutton[constrain-size="true"][cui
 }
 
 @conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
 @conditionalForwardWithUrlbar@ {
-  clip-path: url("chrome://browser/content/browser.xul#urlbar-clip-path");
+  clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
   -moz-margin-start: -5px;
 }
 
-@conditionalForwardWithUrlbar@:-moz-lwtheme {
-  clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-}
-
 @conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
 @conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
-  /* Let clip-path clip the urlbar-wrapper's right side for RTL. */
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
   transform: scaleX(-1);
 }
 
 @conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
   -moz-box-direction: reverse;
 }
 
 #urlbar-icons {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -112,25 +112,34 @@ browser.jar:
   skin/classic/browser/tab-crashed.svg                      (../shared/incontent-icons/tab-crashed.svg)
   skin/classic/browser/welcome-back.svg                     (../shared/incontent-icons/welcome-back.svg)
   skin/classic/browser/reader-tour.png                      (../shared/reader/reader-tour.png)
   skin/classic/browser/reader-tour@2x.png                   (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                       (../shared/reader/readerMode.svg)
   skin/classic/browser/readinglist/icons.svg          (../shared/readinglist/icons.svg)
   skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
 * skin/classic/browser/readinglist/sidebar.css        (readinglist/sidebar.css)
-  skin/classic/browser/webRTC-shareDevice-16.png
-  skin/classic/browser/webRTC-shareDevice-64.png
+  skin/classic/browser/webRTC-shareDevice-16.png      (../shared/webrtc/webRTC-shareDevice-16.png)
+  skin/classic/browser/webRTC-shareDevice-16@2x.png   (../shared/webrtc/webRTC-shareDevice-16@2x.png)
+  skin/classic/browser/webRTC-shareDevice-64.png      (../shared/webrtc/webRTC-shareDevice-64.png)
+  skin/classic/browser/webRTC-shareDevice-64@2x.png   (../shared/webrtc/webRTC-shareDevice-64@2x.png)
   skin/classic/browser/webRTC-sharingDevice-16.png    (../shared/webrtc/webRTC-sharingDevice-16.png)
-  skin/classic/browser/webRTC-shareMicrophone-16.png
-  skin/classic/browser/webRTC-shareMicrophone-64.png
+  skin/classic/browser/webRTC-sharingDevice-16@2x.png (../shared/webrtc/webRTC-sharingDevice-16@2x.png)
+  skin/classic/browser/webRTC-shareMicrophone-16.png  (../shared/webrtc/webRTC-shareMicrophone-16.png)
+  skin/classic/browser/webRTC-shareMicrophone-16@2x.png (../shared/webrtc/webRTC-shareMicrophone-16@2x.png)
+  skin/classic/browser/webRTC-shareMicrophone-64.png  (../shared/webrtc/webRTC-shareMicrophone-64.png)
+  skin/classic/browser/webRTC-shareMicrophone-64@2x.png (../shared/webrtc/webRTC-shareMicrophone-64@2x.png)
   skin/classic/browser/webRTC-sharingMicrophone-16.png (../shared/webrtc/webRTC-sharingMicrophone-16.png)
+  skin/classic/browser/webRTC-sharingMicrophone-16@2x.png (../shared/webrtc/webRTC-sharingMicrophone-16@2x.png)
   skin/classic/browser/webRTC-shareScreen-16.png      (../shared/webrtc/webRTC-shareScreen-16.png)
+  skin/classic/browser/webRTC-shareScreen-16@2x.png   (../shared/webrtc/webRTC-shareScreen-16@2x.png)
   skin/classic/browser/webRTC-shareScreen-64.png      (../shared/webrtc/webRTC-shareScreen-64.png)
+  skin/classic/browser/webRTC-shareScreen-64@2x.png   (../shared/webrtc/webRTC-shareScreen-64@2x.png)
   skin/classic/browser/webRTC-sharingScreen-16.png    (../shared/webrtc/webRTC-sharingScreen-16.png)
+  skin/classic/browser/webRTC-sharingScreen-16@2x.png (../shared/webrtc/webRTC-sharingScreen-16@2x.png)
   skin/classic/browser/webRTC-indicator.css           (../shared/webrtc/indicator.css)
   skin/classic/browser/webRTC-camera-white-16.png     (../shared/webrtc/camera-white-16.png)
   skin/classic/browser/webRTC-microphone-white-16.png (../shared/webrtc/microphone-white-16.png)
   skin/classic/browser/webRTC-screen-white-16.png     (../shared/webrtc/screen-white-16.png)
   skin/classic/browser/loop/menuPanel.png             (loop/menuPanel.png)
   skin/classic/browser/loop/menuPanel@2x.png          (loop/menuPanel@2x.png)
   skin/classic/browser/loop/toolbar.png               (loop/toolbar.png)
   skin/classic/browser/loop/toolbar@2x.png            (loop/toolbar@2x.png)
deleted file mode 100644
index 8bc5b3acaed5f864dd06c36ae9350dd15524df9a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d125789fbce71eff4c4d5af890dd5ed6a862c50e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index ac67cdbed3a9b7e43456b1ba6b2dc7d8501366f2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6bb5a8880c93a61e6b95807209c509bd8648c04a..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -150,26 +150,26 @@ browser.jar:
   skin/classic/browser/tab-crashed.svg                (../shared/incontent-icons/tab-crashed.svg)
   skin/classic/browser/welcome-back.svg               (../shared/incontent-icons/welcome-back.svg)
   skin/classic/browser/reader-tour.png                (../shared/reader/reader-tour.png)
   skin/classic/browser/reader-tour@2x.png             (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                 (../shared/reader/readerMode.svg)
   skin/classic/browser/readinglist/icons.svg          (../shared/readinglist/icons.svg)
   skin/classic/browser/readinglist/readinglist-icon.svg (../shared/readinglist/readinglist-icon.svg)
 * skin/classic/browser/readinglist/sidebar.css        (readinglist/sidebar.css)
-  skin/classic/browser/webRTC-shareDevice-16.png
-  skin/classic/browser/webRTC-shareDevice-16@2x.png
-  skin/classic/browser/webRTC-shareDevice-64.png
-  skin/classic/browser/webRTC-shareDevice-64@2x.png
+  skin/classic/browser/webRTC-shareDevice-16.png      (../shared/webrtc/webRTC-shareDevice-16.png)
+  skin/classic/browser/webRTC-shareDevice-16@2x.png   (../shared/webrtc/webRTC-shareDevice-16@2x.png)
+  skin/classic/browser/webRTC-shareDevice-64.png      (../shared/webrtc/webRTC-shareDevice-64.png)
+  skin/classic/browser/webRTC-shareDevice-64@2x.png   (../shared/webrtc/webRTC-shareDevice-64@2x.png)
   skin/classic/browser/webRTC-sharingDevice-16.png    (../shared/webrtc/webRTC-sharingDevice-16.png)
   skin/classic/browser/webRTC-sharingDevice-16@2x.png (../shared/webrtc/webRTC-sharingDevice-16@2x.png)
-  skin/classic/browser/webRTC-shareMicrophone-16.png
-  skin/classic/browser/webRTC-shareMicrophone-16@2x.png
-  skin/classic/browser/webRTC-shareMicrophone-64.png
-  skin/classic/browser/webRTC-shareMicrophone-64@2x.png
+  skin/classic/browser/webRTC-shareMicrophone-16.png  (../shared/webrtc/webRTC-shareMicrophone-16.png)
+  skin/classic/browser/webRTC-shareMicrophone-16@2x.png (../shared/webrtc/webRTC-shareMicrophone-16@2x.png)
+  skin/classic/browser/webRTC-shareMicrophone-64.png  (../shared/webrtc/webRTC-shareMicrophone-64.png)
+  skin/classic/browser/webRTC-shareMicrophone-64@2x.png (../shared/webrtc/webRTC-shareMicrophone-64@2x.png)
   skin/classic/browser/webRTC-sharingMicrophone-16.png (../shared/webrtc/webRTC-sharingMicrophone-16.png)
   skin/classic/browser/webRTC-sharingMicrophone-16@2x.png (../shared/webrtc/webRTC-sharingMicrophone-16@2x.png)
   skin/classic/browser/webRTC-shareScreen-16.png      (../shared/webrtc/webRTC-shareScreen-16.png)
   skin/classic/browser/webRTC-shareScreen-16@2x.png   (../shared/webrtc/webRTC-shareScreen-16@2x.png)
   skin/classic/browser/webRTC-shareScreen-64.png      (../shared/webrtc/webRTC-shareScreen-64.png)
   skin/classic/browser/webRTC-shareScreen-64@2x.png   (../shared/webrtc/webRTC-shareScreen-64@2x.png)
   skin/classic/browser/webRTC-sharingScreen-16.png    (../shared/webrtc/webRTC-sharingScreen-16.png)
   skin/classic/browser/webRTC-sharingScreen-16@2x.png (../shared/webrtc/webRTC-sharingScreen-16@2x.png)
--- a/browser/themes/shared/notification-icons.inc.css
+++ b/browser/themes/shared/notification-icons.inc.css
@@ -313,16 +313,61 @@
 }
 
 /* HiDPI notification icons */
 @media (min-resolution: 1.1dppx) {
   #notification-popup-box {
     border-image: url("chrome://browser/skin/urlbar-arrow@2x.png") 0 16 0 0 fill;
   }
 
+  .webRTC-shareDevices-notification-icon,
+  #webRTC-shareDevices-notification-icon {
+    list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16@2x.png);
+  }
+
+  .webRTC-sharingDevices-notification-icon,
+  #webRTC-sharingDevices-notification-icon {
+    list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16@2x.png);
+  }
+
+  .webRTC-shareMicrophone-notification-icon,
+  #webRTC-shareMicrophone-notification-icon {
+    list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16@2x.png);
+  }
+
+  .webRTC-sharingMicrophone-notification-icon,
+  #webRTC-sharingMicrophone-notification-icon {
+    list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16@2x.png);
+  }
+
+  .webRTC-shareScreen-notification-icon,
+  #webRTC-shareScreen-notification-icon {
+    list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16@2x.png);
+  }
+
+  .webRTC-sharingScreen-notification-icon,
+  #webRTC-sharingScreen-notification-icon {
+    list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-16@2x.png);
+  }
+
+  .popup-notification-icon[popupid="webRTC-sharingDevices"],
+  .popup-notification-icon[popupid="webRTC-shareDevices"] {
+    list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64@2x.png);
+  }
+
+  .popup-notification-icon[popupid="webRTC-sharingMicrophone"],
+  .popup-notification-icon[popupid="webRTC-shareMicrophone"] {
+    list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64@2x.png);
+  }
+
+  .popup-notification-icon[popupid="webRTC-sharingScreen"],
+  .popup-notification-icon[popupid="webRTC-shareScreen"] {
+    list-style-image: url(chrome://browser/skin/webRTC-shareScreen-64@2x.png);
+  }
+
 %ifdef XP_MACOSX
 /* OSX only until we have icons for Windows and Linux */
   .default-notification-icon,
   #default-notification-icon {
     list-style-image: url(chrome://global/skin/icons/information-32.png);
   }
 
   .geo-notification-icon,
@@ -376,46 +421,16 @@
   #bad-content-blocked-notification-icon {
     list-style-image: url(chrome://browser/skin/bad-content-blocked-16@2x.png);
   }
 
   #bad-content-unblocked-notification-icon {
     list-style-image: url(chrome://browser/skin/bad-content-unblocked-16@2x.png);
   }
 
-  .webRTC-shareDevices-notification-icon,
-  #webRTC-shareDevices-notification-icon {
-    list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16@2x.png);
-  }
-
-  .webRTC-sharingDevices-notification-icon,
-  #webRTC-sharingDevices-notification-icon {
-    list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16@2x.png);
-  }
-
-  .webRTC-shareMicrophone-notification-icon,
-  #webRTC-shareMicrophone-notification-icon {
-    list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-16@2x.png);
-  }
-
-  .webRTC-sharingMicrophone-notification-icon,
-  #webRTC-sharingMicrophone-notification-icon {
-    list-style-image: url(chrome://browser/skin/webRTC-sharingMicrophone-16@2x.png);
-  }
-
-  .webRTC-shareScreen-notification-icon,
-  #webRTC-shareScreen-notification-icon {
-    list-style-image: url(chrome://browser/skin/webRTC-shareScreen-16@2x.png);
-  }
-
-  .webRTC-sharingScreen-notification-icon,
-  #webRTC-sharingScreen-notification-icon {
-    list-style-image: url(chrome://browser/skin/webRTC-sharingScreen-16@2x.png);
-  }
-
   .web-notifications-notification-icon,
   #web-notifications-notification-icon {
     list-style-image: url(chrome://browser/skin/notification-16@2x.png);
   }
 
   .pointerLock-notification-icon,
   #pointerLock-notification-icon {
     list-style-image: url(chrome://browser/skin/pointerLock-16@2x.png);
@@ -453,31 +468,16 @@
   .popup-notification-icon[popupid="bad-content"][trackingblockdisabled] {
     list-style-image: url(chrome://browser/skin/bad-content-unblocked-64@2x.png);
   }
 
   .popup-notification-icon[popupid="pointerLock"] {
     list-style-image: url(chrome://browser/skin/pointerLock-64@2x.png);
   }
 
-  .popup-notification-icon[popupid="webRTC-sharingDevices"],
-  .popup-notification-icon[popupid="webRTC-shareDevices"] {
-    list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64@2x.png);
-  }
-
-  .popup-notification-icon[popupid="webRTC-sharingMicrophone"],
-  .popup-notification-icon[popupid="webRTC-shareMicrophone"] {
-    list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64@2x.png);
-  }
-
-  .popup-notification-icon[popupid="webRTC-sharingScreen"],
-  .popup-notification-icon[popupid="webRTC-shareScreen"] {
-    list-style-image: url(chrome://browser/skin/webRTC-shareScreen-64@2x.png);
-  }
-
   .popup-notification-icon[popupid="servicesInstall"] {
     list-style-image: url(chrome://browser/skin/social/services-64@2x.png);
   }
 
   #servicesInstall-notification-icon {
     list-style-image: url(chrome://browser/skin/social/services-16@2x.png);
   }
 %endif
rename from browser/themes/osx/webRTC-shareDevice-16.png
rename to browser/themes/shared/webrtc/webRTC-shareDevice-16.png
rename from browser/themes/osx/webRTC-shareDevice-16@2x.png
rename to browser/themes/shared/webrtc/webRTC-shareDevice-16@2x.png
rename from browser/themes/osx/webRTC-shareDevice-64.png
rename to browser/themes/shared/webrtc/webRTC-shareDevice-64.png
rename from browser/themes/osx/webRTC-shareDevice-64@2x.png
rename to browser/themes/shared/webrtc/webRTC-shareDevice-64@2x.png
rename from browser/themes/osx/webRTC-shareMicrophone-16.png
rename to browser/themes/shared/webrtc/webRTC-shareMicrophone-16.png
rename from browser/themes/osx/webRTC-shareMicrophone-16@2x.png
rename to browser/themes/shared/webrtc/webRTC-shareMicrophone-16@2x.png
rename from browser/themes/osx/webRTC-shareMicrophone-64.png
rename to browser/themes/shared/webrtc/webRTC-shareMicrophone-64.png
rename from browser/themes/osx/webRTC-shareMicrophone-64@2x.png
rename to browser/themes/shared/webrtc/webRTC-shareMicrophone-64@2x.png
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -150,25 +150,34 @@ browser.jar:
         skin/classic/browser/reader-tour@2x.png                      (../shared/reader/reader-tour@2x.png)
         skin/classic/browser/readerMode.svg                          (../shared/reader/readerMode.svg)
         skin/classic/browser/readinglist/icons.svg                   (../shared/readinglist/icons.svg)
         skin/classic/browser/readinglist/readinglist-icon.svg        (../shared/readinglist/readinglist-icon.svg)
 *       skin/classic/browser/readinglist/sidebar.css                 (readinglist/sidebar.css)
         skin/classic/browser/notification-pluginNormal.png           (../shared/plugins/notification-pluginNormal.png)
         skin/classic/browser/notification-pluginAlert.png            (../shared/plugins/notification-pluginAlert.png)
         skin/classic/browser/notification-pluginBlocked.png          (../shared/plugins/notification-pluginBlocked.png)
-        skin/classic/browser/webRTC-shareDevice-16.png
-        skin/classic/browser/webRTC-shareDevice-64.png
+        skin/classic/browser/webRTC-shareDevice-16.png               (../shared/webrtc/webRTC-shareDevice-16.png)
+        skin/classic/browser/webRTC-shareDevice-16@2x.png            (../shared/webrtc/webRTC-shareDevice-16@2x.png)
+        skin/classic/browser/webRTC-shareDevice-64.png               (../shared/webrtc/webRTC-shareDevice-64.png)
+        skin/classic/browser/webRTC-shareDevice-64@2x.png            (../shared/webrtc/webRTC-shareDevice-64@2x.png)
         skin/classic/browser/webRTC-sharingDevice-16.png             (../shared/webrtc/webRTC-sharingDevice-16.png)
-        skin/classic/browser/webRTC-shareMicrophone-16.png
-        skin/classic/browser/webRTC-shareMicrophone-64.png
+        skin/classic/browser/webRTC-sharingDevice-16@2x.png          (../shared/webrtc/webRTC-sharingDevice-16@2x.png)
+        skin/classic/browser/webRTC-shareMicrophone-16.png           (../shared/webrtc/webRTC-shareMicrophone-16.png)
+        skin/classic/browser/webRTC-shareMicrophone-16@2x.png        (../shared/webrtc/webRTC-shareMicrophone-16@2x.png)
+        skin/classic/browser/webRTC-shareMicrophone-64.png           (../shared/webrtc/webRTC-shareMicrophone-64.png)
+        skin/classic/browser/webRTC-shareMicrophone-64@2x.png        (../shared/webrtc/webRTC-shareMicrophone-64@2x.png)
         skin/classic/browser/webRTC-sharingMicrophone-16.png         (../shared/webrtc/webRTC-sharingMicrophone-16.png)
+        skin/classic/browser/webRTC-sharingMicrophone-16@2x.png      (../shared/webrtc/webRTC-sharingMicrophone-16@2x.png)
         skin/classic/browser/webRTC-shareScreen-16.png               (../shared/webrtc/webRTC-shareScreen-16.png)
+        skin/classic/browser/webRTC-shareScreen-16@2x.png            (../shared/webrtc/webRTC-shareScreen-16@2x.png)
         skin/classic/browser/webRTC-shareScreen-64.png               (../shared/webrtc/webRTC-shareScreen-64.png)
+        skin/classic/browser/webRTC-shareScreen-64@2x.png            (../shared/webrtc/webRTC-shareScreen-64@2x.png)
         skin/classic/browser/webRTC-sharingScreen-16.png             (../shared/webrtc/webRTC-sharingScreen-16.png)
+        skin/classic/browser/webRTC-sharingScreen-16@2x.png          (../shared/webrtc/webRTC-sharingScreen-16@2x.png)
         skin/classic/browser/webRTC-indicator.css                    (../shared/webrtc/indicator.css)
         skin/classic/browser/webRTC-camera-white-16.png              (../shared/webrtc/camera-white-16.png)
         skin/classic/browser/webRTC-microphone-white-16.png          (../shared/webrtc/microphone-white-16.png)
         skin/classic/browser/webRTC-screen-white-16.png              (../shared/webrtc/screen-white-16.png)
         skin/classic/browser/loop/menuPanel.png                      (loop/menuPanel.png)
         skin/classic/browser/loop/menuPanel@2x.png                   (loop/menuPanel@2x.png)
         skin/classic/browser/loop/menuPanel-aero.png                 (loop/menuPanel-aero.png)
         skin/classic/browser/loop/menuPanel-aero@2x.png              (loop/menuPanel-aero@2x.png)
deleted file mode 100644
index df01b33515889863c9ef99d0daf650b13a104f2d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 0982941669352ea9335b10a43b240e176daec8f9..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f50726fd673b579c8e21b6174d06e4b0f81f949c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 73e299b93c6ebb396584f40cd5817d9dda7756dc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -431,23 +431,23 @@ LOCAL_INCLUDES += [
     '/layout/style',
     '/layout/svg',
     '/layout/xul',
     '/netwerk/base',
     '/widget',
     '/xpcom/ds',
 ]
 
-if CONFIG['MOZ_B2G_BT_API_V2']:
+if CONFIG['MOZ_B2G_BT_API_V1']:
     LOCAL_INCLUDES += [
-        '../bluetooth/bluetooth2',
+        '../bluetooth/bluetooth1',
     ]
 else:
     LOCAL_INCLUDES += [
-        '../bluetooth/bluetooth1',
+        '../bluetooth/bluetooth2',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     LOCAL_INCLUDES += [
         '../fmradio',
         '../system/gonk',
     ]
 
--- a/dom/bindings/moz.build
+++ b/dom/bindings/moz.build
@@ -86,23 +86,23 @@ SOURCES += [
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['MOZ_AUDIO_CHANNEL_MANAGER']:
     LOCAL_INCLUDES += [
         '/dom/system/gonk',
     ]
 
-if CONFIG['MOZ_B2G_BT_API_V2']:
+if CONFIG['MOZ_B2G_BT_API_V1']:
     LOCAL_INCLUDES += [
-        '/dom/bluetooth/bluetooth2',
+        '/dom/bluetooth/bluetooth1',
     ]
 else:
     LOCAL_INCLUDES += [
-        '/dom/bluetooth/bluetooth1',
+        '/dom/bluetooth/bluetooth2',
     ]
 
 FINAL_LIBRARY = 'xul'
 
 SPHINX_TREES['webidl'] = 'docs'
 SPHINX_PYTHON_PACKAGE_DIRS += ['mozwebidlcodegen']
 
 if CONFIG['MOZ_BUILD_APP'] in ['browser', 'mobile/android', 'xulrunner']:
--- a/dom/bluetooth/BluetoothUtils.cpp
+++ b/dom/bluetooth/BluetoothUtils.cpp
@@ -273,17 +273,17 @@ DispatchReplySuccess(BluetoothReplyRunna
 void
 DispatchReplyError(BluetoothReplyRunnable* aRunnable,
                    const nsAString& aErrorStr)
 {
   MOZ_ASSERT(aRunnable);
   MOZ_ASSERT(!aErrorStr.IsEmpty());
 
   // Reply will be deleted by the runnable after running on main thread
-#if MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   BluetoothReply* reply =
     new BluetoothReply(BluetoothReplyError(STATUS_FAIL, nsString(aErrorStr)));
 #else
   BluetoothReply* reply =
     new BluetoothReply(BluetoothReplyError(nsString(aErrorStr)));
 #endif
 
   aRunnable->SetReply(reply); // runnable will delete reply after Run()
@@ -293,17 +293,17 @@ DispatchReplyError(BluetoothReplyRunnabl
 void
 DispatchReplyError(BluetoothReplyRunnable* aRunnable,
                    const enum BluetoothStatus aStatus)
 {
   MOZ_ASSERT(aRunnable);
   MOZ_ASSERT(aStatus != STATUS_SUCCESS);
 
   // Reply will be deleted by the runnable after running on main thread
-#if MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   BluetoothReply* reply =
     new BluetoothReply(BluetoothReplyError(aStatus, EmptyString()));
 #else
   BluetoothReply* reply =
     new BluetoothReply(
       BluetoothReplyError(NS_LITERAL_STRING("Internal error")));
 #endif
 
@@ -320,17 +320,17 @@ DispatchStatusChangedEvent(const nsAStri
 
   InfallibleTArray<BluetoothNamedValue> data;
   BT_APPEND_NAMED_VALUE(data, "address", nsString(aAddress));
   BT_APPEND_NAMED_VALUE(data, "status", aStatus);
 
   BluetoothService* bs = BluetoothService::Get();
   NS_ENSURE_TRUE_VOID(bs);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   bs->DistributeSignal(aType, NS_LITERAL_STRING(KEY_ADAPTER), data);
 #else
   BluetoothSignal signal(nsString(aType), NS_LITERAL_STRING(KEY_ADAPTER), data);
   bs->DistributeSignal(signal);
 #endif
 }
 
 bool
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
@@ -1060,17 +1060,17 @@ BluetoothA2dpManager::GetPlayStatusNotif
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothService* bs = BluetoothService::Get();
   if (!bs) {
     return;
   }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   bs->DistributeSignal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID),
                        NS_LITERAL_STRING(KEY_ADAPTER));
 #else
   bs->DistributeSignal(
     BluetoothSignal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID),
                     NS_LITERAL_STRING(KEY_ADAPTER),
                     InfallibleTArray<BluetoothNamedValue>()));
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -14,39 +14,39 @@
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 
 #include "BluetoothServiceBluedroid.h"
 
 #include "BluetoothA2dpManager.h"
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 #include "BluetoothGattManager.h"
 #else
 // TODO: Support GATT
 #endif
 #include "BluetoothHfpManager.h"
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 #include "BluetoothHidManager.h"
 #else
 // TODO: Support HID
 #endif
 #include "BluetoothOppManager.h"
 #include "BluetoothPbapManager.h"
 #include "BluetoothProfileController.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/ipc/SocketBase.h"
 #include "mozilla/StaticMutex.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/unused.h"
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 #include "nsDataHashtable.h"
 #endif
 
 #define ENSURE_BLUETOOTH_IS_READY(runnable, result)                    \
   do {                                                                 \
     if (!sBtInterface || !IsEnabled()) {                               \
       DispatchReplyError(runnable,                                     \
         NS_LITERAL_STRING("Bluetooth is not ready"));                  \
@@ -58,17 +58,17 @@
   do {                                                                 \
     if (!sBtInterface || !IsEnabled()) {                               \
       DispatchReplyError(runnable,                                     \
         NS_LITERAL_STRING("Bluetooth is not ready"));                  \
       return;                                                          \
     }                                                                  \
   } while(0)
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 
 #define ENSURE_GATT_MGR_IS_READY_VOID(gatt, runnable)                  \
   do {                                                                 \
     if (!gatt) {                                                       \
       DispatchReplyError(runnable,                                     \
         NS_LITERAL_STRING("GattManager is not ready"));                \
       return;                                                          \
     }                                                                  \
@@ -942,17 +942,17 @@ BluetoothServiceBluedroid::StopInternal(
     BluetoothService::AcknowledgeToggleBt(true);
     BT_LOGR("Error");
   }
 
   return ret;
 }
 #endif
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 nsresult
 BluetoothServiceBluedroid::GetAdaptersInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /**
    * Wrap BluetoothValue =
@@ -1034,17 +1034,17 @@ public:
   GetRemoteDevicePropertiesResultHandler(const nsAString& aDeviceAddress)
   : mDeviceAddress(aDeviceAddress)
   { }
 
   void OnError(BluetoothStatus aStatus) override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     BT_WARNING("GetRemoteDeviceProperties(%s) failed: %d",
                NS_ConvertUTF16toUTF8(mDeviceAddress).get(), aStatus);
 
     /* dispatch result after final pending operation */
     if (--sRequestedDeviceCountArray[0] == 0) {
       if (!sGetDeviceRunnableArray.IsEmpty()) {
         DispatchReplyError(sGetDeviceRunnableArray[0],
           NS_LITERAL_STRING("GetRemoteDeviceProperties failed"));
@@ -1078,17 +1078,17 @@ private:
 nsresult
 BluetoothServiceBluedroid::GetConnectedDevicePropertiesInternal(
   uint16_t aServiceUuid, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   BluetoothProfileManagerBase* profile =
     BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid);
   if (!profile) {
     DispatchReplyError(aRunnable, NS_LITERAL_STRING(ERR_UNKNOWN_PROFILE));
     return NS_OK;
   }
 
   nsTArray<nsString> deviceAddresses;
@@ -1143,17 +1143,17 @@ BluetoothServiceBluedroid::GetConnectedD
 nsresult
 BluetoothServiceBluedroid::GetPairedDevicePropertiesInternal(
   const nsTArray<nsString>& aDeviceAddress, BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   ENSURE_BLUETOOTH_IS_READY(aRunnable, NS_OK);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   int requestedDeviceCount = aDeviceAddress.Length();
   if (requestedDeviceCount == 0) {
     DispatchReplySuccess(aRunnable);
     return NS_OK;
   }
 #else
   int requestedDeviceCount = aDeviceAddress.Length();
   if (requestedDeviceCount == 0) {
@@ -1177,17 +1177,17 @@ BluetoothServiceBluedroid::GetPairedDevi
 class BluetoothServiceBluedroid::StartDiscoveryResultHandler final
   : public BluetoothResultHandler
 {
 public:
   StartDiscoveryResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   void OnError(BluetoothStatus aStatus) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     sChangeDiscoveryRunnableArray.RemoveElement(mRunnable);
     DispatchReplyError(mRunnable, aStatus);
   }
 #else
   void StartDiscovery() override
@@ -1209,34 +1209,34 @@ private:
 
 void
 BluetoothServiceBluedroid::StartDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
 #else
   // Missing in bluetooth1
 #endif
 
   sBtInterface->StartDiscovery(new StartDiscoveryResultHandler(aRunnable));
 }
 
 class BluetoothServiceBluedroid::CancelDiscoveryResultHandler final
   : public BluetoothResultHandler
 {
 public:
   CancelDiscoveryResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   void OnError(BluetoothStatus aStatus) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     sChangeDiscoveryRunnableArray.RemoveElement(mRunnable);
     DispatchReplyError(mRunnable, aStatus);
   }
 #else
   void CancelDiscovery() override
@@ -1251,17 +1251,17 @@ public:
     ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("StopDiscovery"));
   }
 #endif
 
 private:
   BluetoothReplyRunnable* mRunnable;
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 class BluetoothServiceBluedroid::GetRemoteServicesResultHandler final
   : public BluetoothResultHandler
 {
 public:
   GetRemoteServicesResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
@@ -1305,17 +1305,17 @@ BluetoothServiceBluedroid::FetchUuidsInt
 
 void
 BluetoothServiceBluedroid::StopDiscoveryInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ENSURE_BLUETOOTH_IS_READY_VOID(aRunnable);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   sChangeDiscoveryRunnableArray.AppendElement(aRunnable);
 #else
   // Missing in bluetooth1
 #endif
 
   sBtInterface->CancelDiscovery(new CancelDiscoveryResultHandler(aRunnable));
 }
 
@@ -1326,17 +1326,17 @@ public:
   SetAdapterPropertyResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
   void OnError(BluetoothStatus aStatus) override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     sSetPropertyRunnableArray.RemoveElement(mRunnable);
     DispatchReplyError(mRunnable, aStatus);
 #else
     ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("SetProperty"));
 #endif
   }
 private:
   BluetoothReplyRunnable* mRunnable;
@@ -1383,17 +1383,17 @@ public:
   CreateBondResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   {
     MOZ_ASSERT(mRunnable);
   }
 
   void OnError(BluetoothStatus aStatus) override
   {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     sBondingRunnableArray.RemoveElement(mRunnable);
     DispatchReplyError(mRunnable, aStatus);
 #else
     sBondingRunnableArray.RemoveElement(mRunnable);
     ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("CreatedPairedDevice"));
 #endif
   }
 
@@ -1424,17 +1424,17 @@ public:
   RemoveBondResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   {
     MOZ_ASSERT(mRunnable);
   }
 
   void OnError(BluetoothStatus aStatus) override
   {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     sUnbondingRunnableArray.RemoveElement(mRunnable);
     DispatchReplyError(mRunnable, aStatus);
 #else
     sUnbondingRunnableArray.RemoveElement(mRunnable);
     ReplyStatusError(mRunnable, aStatus, NS_LITERAL_STRING("RemoveDevice"));
 #endif
   }
 
@@ -1453,17 +1453,17 @@ BluetoothServiceBluedroid::RemoveDeviceI
   sUnbondingRunnableArray.AppendElement(aRunnable);
 
   sBtInterface->RemoveBond(aDeviceAddress,
                            new RemoveBondResultHandler(aRunnable));
 
   return NS_OK;
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 class BluetoothServiceBluedroid::PinReplyResultHandler final
   : public BluetoothResultHandler
 {
 public:
   PinReplyResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
@@ -1535,17 +1535,17 @@ BluetoothServiceBluedroid::SetPinCodeInt
 
   sBtInterface->PinReply(aDeviceAddress, true, aPinCode,
     new PinReplyResultHandler(aRunnable));
 
   return true;
 }
 #endif
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothServiceBluedroid::SetPasskeyInternal(
   const nsAString& aDeviceAddress, uint32_t aPasskey,
   BluetoothReplyRunnable* aRunnable)
 {
   // Lecagy method used by BlueZ only.
 }
 #else
@@ -1553,17 +1553,17 @@ bool
 BluetoothServiceBluedroid::SetPasskeyInternal(
   const nsAString& aDeviceAddress, uint32_t aPasskey,
   BluetoothReplyRunnable* aRunnable)
 {
   return true;
 }
 #endif
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 class BluetoothServiceBluedroid::SspReplyResultHandler final
   : public BluetoothResultHandler
 {
 public:
   SspReplyResultHandler(BluetoothReplyRunnable* aRunnable)
   : mRunnable(aRunnable)
   { }
 
@@ -1636,17 +1636,17 @@ BluetoothServiceBluedroid::SetPairingCon
 
   sBtInterface->SspReply(aDeviceAddress,
                          SSP_VARIANT_PASSKEY_CONFIRMATION,
                          aConfirm, 0, new SspReplyResultHandler(aRunnable));
   return true;
 }
 #endif
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in bluetooth2
 #else
 bool
 BluetoothServiceBluedroid::SetAuthorizationInternal(
   const nsAString& aDeviceAddress, bool aAllow,
   BluetoothReplyRunnable* aRunnable)
 {
   return true;
@@ -1654,17 +1654,17 @@ BluetoothServiceBluedroid::SetAuthorizat
 
 nsresult
 BluetoothServiceBluedroid::PrepareAdapterInternal()
 {
   return NS_OK;
 }
 #endif
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothServiceBluedroid::NextBluetoothProfileController()
 #else
 static void
 NextBluetoothProfileController()
 #endif
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -1674,17 +1674,17 @@ NextBluetoothProfileController()
   sControllerArray.RemoveElementAt(0);
 
   // Start the next task if task array is not empty
   if (!sControllerArray.IsEmpty()) {
     sControllerArray[0]->StartSession();
   }
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothServiceBluedroid::ConnectDisconnect(
   bool aConnect, const nsAString& aDeviceAddress,
   BluetoothReplyRunnable* aRunnable,
   uint16_t aServiceUuid, uint32_t aCod)
 #else
 static void
 ConnectDisconnect(bool aConnect, const nsAString& aDeviceAddress,
@@ -1723,17 +1723,17 @@ BluetoothServiceBluedroid::Connect(const
 void
 BluetoothServiceBluedroid::Disconnect(
   const nsAString& aDeviceAddress, uint16_t aServiceUuid,
   BluetoothReplyRunnable* aRunnable)
 {
   ConnectDisconnect(false, aDeviceAddress, aRunnable, aServiceUuid);
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 bool
 BluetoothServiceBluedroid::IsConnected(uint16_t aProfileId)
 {
   return true;
 }
 #else
 void
 BluetoothServiceBluedroid::IsConnected(const uint16_t aServiceUuid,
@@ -1948,17 +1948,17 @@ BluetoothServiceBluedroid::IgnoreWaiting
 {
 }
 
 void
 BluetoothServiceBluedroid::ToggleCalls(BluetoothReplyRunnable* aRunnable)
 {
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in bluetooth2
 #else
 uint16_t
 BluetoothServiceBluedroid::UuidToServiceClassInt(const BluetoothUuid& mUuid)
 {
   // extract short UUID 0000xxxx-0000-1000-8000-00805f9b34fb
   uint16_t shortUuid;
   memcpy(&shortUuid, mUuid.mUuid + 2, sizeof(uint16_t));
@@ -2057,17 +2057,17 @@ public:
   {
     BT_LOGR("Fail to set: BT_SCAN_MODE_CONNECTABLE");
   }
 };
 
 void
 BluetoothServiceBluedroid::AdapterStateChangedNotification(bool aState)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   BT_LOGR("BT_STATE: %d", aState);
 
   sAdapterEnabled = aState;
 
   if (!sAdapterEnabled) {
     static void (* const sDeinitManager[])(BluetoothProfileResultHandler*) = {
@@ -2230,17 +2230,17 @@ BluetoothServiceBluedroid::AdapterStateC
  * BluetoothManager and BluetoothAdapter, do not register observer
  * yet.
  */
 void
 BluetoothServiceBluedroid::AdapterPropertiesNotification(
   BluetoothStatus aStatus, int aNumProperties,
   const BluetoothProperty* aProperties)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
 
   for (int i = 0; i < aNumProperties; i++) {
 
     const BluetoothProperty& p = aProperties[i];
 
@@ -2385,17 +2385,17 @@ BluetoothServiceBluedroid::AdapterProper
  *   (4) as result of GetRemoteDeviceProperties, or
  *   (5) as result of GetRemoteServices.
  */
 void
 BluetoothServiceBluedroid::RemoteDevicePropertiesNotification(
   BluetoothStatus aStatus, const nsAString& aBdAddr,
   int aNumProperties, const BluetoothProperty* aProperties)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
 
   BT_APPEND_NAMED_VALUE(propertiesArray, "Address", nsString(aBdAddr));
 
   for (int i = 0; i < aNumProperties; ++i) {
 
@@ -2591,17 +2591,17 @@ BluetoothServiceBluedroid::RemoteDeviceP
                                    BluetoothValue(props)));
 #endif
 }
 
 void
 BluetoothServiceBluedroid::DeviceFoundNotification(
   int aNumProperties, const BluetoothProperty* aProperties)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothValue propertyValue;
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
 
   for (int i = 0; i < aNumProperties; i++) {
 
     const BluetoothProperty& p = aProperties[i];
@@ -2679,17 +2679,17 @@ BluetoothServiceBluedroid::DeviceFoundNo
                                    NS_LITERAL_STRING(KEY_ADAPTER),
                                    BluetoothValue(propertiesArray)));
 #endif
 }
 
 void
 BluetoothServiceBluedroid::DiscoveryStateChangedNotification(bool aState)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   sAdapterDiscovering = aState;
 
   // Fire PropertyChanged of Discovering
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
   BT_APPEND_NAMED_VALUE(propertiesArray, "Discovering", sAdapterDiscovering);
 
@@ -2722,17 +2722,17 @@ BluetoothServiceBluedroid::DiscoveryStat
 #endif
 }
 
 void
 BluetoothServiceBluedroid::PinRequestNotification(const nsAString& aRemoteBdAddr,
                                                   const nsAString& aBdName,
                                                   uint32_t aCod)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
 
   BT_APPEND_NAMED_VALUE(propertiesArray, "address", nsString(aRemoteBdAddr));
   BT_APPEND_NAMED_VALUE(propertiesArray, "name", nsString(aBdName));
   BT_APPEND_NAMED_VALUE(propertiesArray, "passkey", EmptyString());
   BT_APPEND_NAMED_VALUE(propertiesArray, "type",
@@ -2761,17 +2761,17 @@ BluetoothServiceBluedroid::PinRequestNot
 
 void
 BluetoothServiceBluedroid::SspRequestNotification(
   const nsAString& aRemoteBdAddr, const nsAString& aBdName, uint32_t aCod,
   BluetoothSspVariant aPairingVariant, uint32_t aPassKey)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   InfallibleTArray<BluetoothNamedValue> propertiesArray;
   nsAutoString passkey;
   nsAutoString pairingType;
 
   /**
    * Assign pairing request type and passkey based on the pairing variant.
    *
    * passkey value based on pairing request type:
@@ -2821,17 +2821,17 @@ BluetoothServiceBluedroid::SspRequestNot
 #endif
 }
 
 void
 BluetoothServiceBluedroid::BondStateChangedNotification(
   BluetoothStatus aStatus, const nsAString& aRemoteBdAddr,
   BluetoothBondState aState)
 {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   MOZ_ASSERT(NS_IsMainThread());
 
   if (aState == BOND_STATE_BONDING) {
     // No need to handle bonding state
     return;
   }
 
   BT_LOGR("Bond state: %d status: %d", aState, aStatus);
@@ -3018,17 +3018,17 @@ void
 BluetoothServiceBluedroid::LeTestModeNotification(BluetoothStatus aStatus,
                                                   uint16_t aNumPackets)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // FIXME: This will be implemented in the later patchset
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // TODO: Support EnergyInfoNotification
 #else
 void
 BluetoothServiceBluedroid::EnergyInfoNotification(
   const BluetoothActivityEnergyInfo& aInfo)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -3054,17 +3054,17 @@ BluetoothServiceBluedroid::BackendErrorN
   NS_ENSURE_TRUE_VOID(hfp);
   hfp->HandleBackendError();
   BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
   NS_ENSURE_TRUE_VOID(a2dp);
   a2dp->HandleBackendError();
 
   sIsRestart = true;
   BT_LOGR("Recovery step2: stop bluetooth");
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   StopBluetooth(false, nullptr);
 #else
   StopBluetooth(false);
 #endif
 }
 
 void
 BluetoothServiceBluedroid::CompleteToggleBt(bool aEnabled)
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.h
@@ -2,17 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothservicebluedroid_h__
 #define mozilla_dom_bluetooth_bluetoothservicebluedroid_h__
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 
 #include "BluetoothCommon.h"
 #include "BluetoothInterface.h"
 #include "BluetoothService.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothServiceBluedroid : public BluetoothService
--- a/dom/bluetooth/bluez/BluetoothDBusService.cpp
+++ b/dom/bluetooth/bluez/BluetoothDBusService.cpp
@@ -86,17 +86,17 @@ USING_BLUETOOTH_NAMESPACE
 /**
  * To not lock Bluetooth switch button on Settings UI because of any accident,
  * we will force disabling Bluetooth 5 seconds after the user requesting to
  * turn off Bluetooth.
  */
 #define TIMEOUT_FORCE_TO_DISABLE_BT 5
 #define BT_LAZY_THREAD_TIMEOUT_MS 3000
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // missing on blutooth2
 #else
 // Set Class of Device value bit
 #define SET_AUDIO_BIT(cod) (cod |= 0x200000)
 #define SET_RENDERING_BIT(cod) (cod |= 0x40000)
 #endif
 
 #ifdef MOZ_WIDGET_GONK
@@ -144,17 +144,17 @@ public:
   bool Disable()
   {
     MOZ_ASSERT(!NS_IsMainThread()); // BT thread
 
     if (!IsEnabled()) {
       return true;
     }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     // TODO: This is the wrong place for handling manager classes
     BluetoothProfileManagerBase* profile;
     profile = BluetoothHfpManager::Get();
     NS_ENSURE_TRUE(profile, false);
     if (profile->IsConnected()) {
       profile->Disconnect(nullptr);
     } else {
       profile->Reset();
@@ -622,17 +622,17 @@ class TryFiringAdapterAddedTask : public
 public:
   void Run() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     BluetoothService* bs = BluetoothService::Get();
     NS_ENSURE_TRUE_VOID(bs);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in bluetooth2
 #else
     bs->AdapterAddedReceived();
     bs->TryFiringAdapterAdded();
 #endif
   }
 };
 
@@ -658,17 +658,17 @@ public:
 
     return NS_OK;
   }
 
 private:
   bool mDelay;
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in bluetooth2
 #else
 class InternalStopDiscoveryTask : public nsRunnable
 {
   nsresult Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
@@ -913,17 +913,17 @@ SinkDisconnectCallback(DBusMessage* aMsg
 }
 
 static bool
 HasAudioService(uint32_t aCodValue)
 {
   return ((aCodValue & 0x200000) == 0x200000);
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 static bool
 ContainsIcon(const InfallibleTArray<BluetoothNamedValue>& aProperties)
 {
   for (uint8_t i = 0; i < aProperties.Length(); i++) {
     if (aProperties[i].name().EqualsLiteral("Icon")) {
       return true;
     }
   }
@@ -1777,17 +1777,17 @@ public:
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
   }
 
   nsresult Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     BluetoothService* bs = BluetoothService::Get();
     NS_ENSURE_TRUE(bs, NS_ERROR_FAILURE);
 
     bs->DistributeSignal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID),
                          NS_LITERAL_STRING(KEY_ADAPTER));
 #else
     BluetoothSignal signal(NS_LITERAL_STRING(REQUEST_MEDIA_PLAYSTATUS_ID),
                            NS_LITERAL_STRING(KEY_ADAPTER),
@@ -1881,17 +1881,17 @@ EventFilter(DBusConnection* aConn, DBusM
       // have all of the information to correctly build the device.
       nsAutoString address = NS_ConvertUTF8toUTF16(addr);
       properties.AppendElement(
         BluetoothNamedValue(NS_LITERAL_STRING("Address"), address));
       properties.AppendElement(
         BluetoothNamedValue(NS_LITERAL_STRING("Path"),
                             GetObjectPathFromAddress(signalPath, address)));
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
       if (!ContainsIcon(properties)) {
 #else
       if (FindProperty(properties, "Icon") < 0) {
 #endif
         for (uint32_t i = 0; i < properties.Length(); i++) {
           // It is possible that property Icon missed due to CoD of major
           // class is TOY but service class is "Audio", we need to assign
           // Icon as audio-card. This is for PTS test TC_AG_COD_BV_02_I.
@@ -1903,17 +1903,17 @@ EventFilter(DBusConnection* aConn, DBusM
                 BluetoothNamedValue(NS_LITERAL_STRING("Icon"),
                                     NS_LITERAL_STRING("audio-card")));
             }
             break;
           }
         }
       }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in bluetooth2
 #else
       if (FindProperty(properties, "Class") < 0) {
         // Check whether the properties array contains CoD. If it doesn't,
         // fallback to restore CoD value. This usually happens due to NFC
         // directly triggers pairing that makes bluez not update CoD value.
         uint32_t cod = 0;
         int uuidIndex = FindProperty(properties, "UUIDs");
@@ -1982,17 +1982,17 @@ EventFilter(DBusConnection* aConn, DBusM
   } else if (dbus_message_is_signal(aMsg, DBUS_ADAPTER_IFACE,
                                     "PropertyChanged")) {
     ParsePropertyChange(aMsg,
                         v,
                         errorStr,
                         sAdapterProperties,
                         ArrayLength(sAdapterProperties));
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in bluetooth2
 #else
     BluetoothNamedValue& property = v.get_ArrayOfBluetoothNamedValue()[0];
     if (property.name().EqualsLiteral("Discovering")) {
       // Special handling when discovery process is stopped by the stack. It
       // does happen when the stack uses Periodic Inquiry instead of Inquiry.
       bool isDiscovering = property.value();
       if (!isDiscovering &&
@@ -2015,17 +2015,17 @@ EventFilter(DBusConnection* aConn, DBusM
     }
 
     BluetoothNamedValue& property = v.get_ArrayOfBluetoothNamedValue()[0];
     if (property.name().EqualsLiteral("Paired")) {
       // Original approach: Broadcast system message of
       // "bluetooth-pairedstatuschanged" from BluetoothService.
       BluetoothValue newValue(v);
       ToLowerCase(newValue.get_ArrayOfBluetoothNamedValue()[0].name());
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
       BluetoothSignal signal(NS_LITERAL_STRING("pairedstatuschanged"),
                              NS_LITERAL_STRING(KEY_LOCAL_AGENT),
                              newValue);
 #else
       BluetoothSignal signal(NS_LITERAL_STRING(PAIRED_STATUS_CHANGED_ID),
                              NS_LITERAL_STRING(KEY_LOCAL_AGENT),
                              newValue);
 #endif
@@ -2289,17 +2289,17 @@ public:
 
     Task* task = new StartDBusConnectionTask(connection);
     DispatchToDBusThread(task);
 
     return NS_OK;
   }
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 nsresult
 BluetoothDBusService::StartInternal(BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(!aRunnable);
 #else
 nsresult
 BluetoothDBusService::StartInternal()
 {
@@ -2424,17 +2424,17 @@ public:
     }
 
     DispatchToDBusThread(new DeleteDBusConnectionTask());
 
     return NS_OK;
   }
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 nsresult
 BluetoothDBusService::StopInternal(BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(!aRunnable);
 #else
 nsresult
 BluetoothDBusService::StopInternal()
 {
@@ -2582,17 +2582,17 @@ public:
 
     unused << handler.forget(); // picked up by callback handler
   }
 
 private:
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 nsresult
 BluetoothDBusService::GetAdaptersInternal(BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /**
    * TODO: implement method GetAdaptersInternal for bluez
    */
@@ -2634,17 +2634,17 @@ OnSendDiscoveryMessageReply(DBusMessage 
   MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
 
   nsAutoString errorStr;
 
   if (!aReply) {
     errorStr.AssignLiteral("SendDiscovery failed");
   }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 // Missing in blueooth2
 #else
   // aData may be a nullptr because we may call StopDiscovery internally when
   // receiving PropertyChanged event of property Discovering from BlueZ.
   //
   // Please see bug 942104 for more details.
   if (!aData) {
     BluetoothSignal signal(NS_LITERAL_STRING(DISCOVERY_STATE_CHANGED_ID),
@@ -2667,17 +2667,17 @@ class SendDiscoveryMessageTask : public 
 {
 public:
   SendDiscoveryMessageTask(const char* aMessageName,
                            BluetoothReplyRunnable* aRunnable)
     : mMessageName(aMessageName)
     , mRunnable(aRunnable)
   {
     MOZ_ASSERT(!mMessageName.IsEmpty());
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     MOZ_ASSERT(mRunnable);
 #else
     // Missing in bluetooth1
 #endif
   }
 
   void Run() override
   {
@@ -2705,17 +2705,17 @@ private:
 nsresult
 BluetoothDBusService::SendDiscoveryMessage(const char* aMessageName,
                                            BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!sAdapterPath.IsEmpty());
 
   if (!IsReady()) {
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
     DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
 #else
     if (aRunnable) {
       NS_NAMED_LITERAL_STRING(errorStr, "Bluetooth service is not ready yet!");
       DispatchBluetoothReply(aRunnable, BluetoothValue(), errorStr);
     }
 #endif
@@ -2905,17 +2905,17 @@ public:
     devicePropertiesArray.AppendElement(
       BluetoothNamedValue(NS_LITERAL_STRING("Path"), mObjectPath));
 
     // It is possible that property Icon missed due to CoD of major
     // class is TOY but service class is "Audio", we need to assign
     // Icon as audio-card. This is for PTS test TC_AG_COD_BV_02_I.
     // As HFP specification defined that
     // service class is "Audio" can be considered as HFP AG.
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     if (!ContainsIcon(devicePropertiesArray)) {
 #else
     if (FindProperty(devicePropertiesArray, "Icon") < 0) {
 #endif
       for (uint32_t j = 0; j < devicePropertiesArray.Length(); ++j) {
         BluetoothNamedValue& deviceProperty = devicePropertiesArray[j];
         if (deviceProperty.name().EqualsLiteral("Class")) {
           if (HasAudioService(deviceProperty.value().get_uint32_t())) {
@@ -2923,17 +2923,17 @@ public:
               BluetoothNamedValue(NS_LITERAL_STRING("Icon"),
                                   NS_LITERAL_STRING("audio-card")));
           }
           break;
         }
       }
     }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     // Missing in bluetooth2
 #else
     // Check whether the properties array contains CoD. If it doesn't, fallback to restore
     // CoD value. This usually happens due to NFC directly triggers pairing that
     // makes bluez not update CoD value.
     if (FindProperty(devicePropertiesArray, "Class") < 0) {
       uint32_t cod = 0;
       int uuidIndex = FindProperty(devicePropertiesArray, "UUIDs");
@@ -3106,17 +3106,17 @@ BluetoothDBusService::GetPairedDevicePro
                                                      GetPairedDevicesFilter,
                                                      aRunnable);
   Task* task = new ProcessRemainingDeviceAddressesTask(handler, aRunnable);
   DispatchToDBusThread(task);
 
   return NS_OK;
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 nsresult
 BluetoothDBusService::FetchUuidsInternal(const nsAString& aDeviceAddress,
                                          BluetoothReplyRunnable* aRunnable)
 {
   return NS_OK;
 }
 #else
 // missing in bluetooth1
@@ -3491,17 +3491,17 @@ public:
   }
 
 private:
   const nsString mDeviceAddress;
   const nsCString mPinCode;
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothDBusService::PinReplyInternal(
   const nsAString& aDeviceAddress, bool aAccept,
   const nsAString& aPinCode, BluetoothReplyRunnable* aRunnable)
 {
   // Legacy interface used by Bluedroid only.
 }
 
@@ -3596,17 +3596,17 @@ public:
   }
 
 private:
   nsString mDeviceAddress;
   uint32_t mPasskey;
   nsRefPtr<BluetoothReplyRunnable> mRunnable;
 };
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothDBusService::SetPasskeyInternal(const nsAString& aDeviceAddress,
                                          uint32_t aPasskey,
                                          BluetoothReplyRunnable* aRunnable)
 {
   Task* task = new SetPasskeyTask(aDeviceAddress,
                                   aPasskey,
                                   aRunnable);
@@ -3622,17 +3622,17 @@ BluetoothDBusService::SetPasskeyInternal
                                   aPasskey,
                                   aRunnable);
   DispatchToDBusThread(task);
 
   return true;
 }
 #endif
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothDBusService::SetPairingConfirmationInternal(
                                               const nsAString& aDeviceAddress,
                                               bool aConfirm,
                                               BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
@@ -3709,17 +3709,17 @@ BluetoothDBusService::Connect(const nsAS
 void
 BluetoothDBusService::Disconnect(const nsAString& aDeviceAddress,
                                  uint16_t aServiceUuid,
                                  BluetoothReplyRunnable* aRunnable)
 {
   ConnectDisconnect(false, aDeviceAddress, aRunnable, aServiceUuid);
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 bool
 BluetoothDBusService::IsConnected(const uint16_t aServiceUuid)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   BluetoothProfileManagerBase* profile =
     BluetoothUuidHelper::GetBluetoothProfileManager(aServiceUuid);
   if (!profile) {
@@ -3782,17 +3782,17 @@ BluetoothDBusService::ToggleCalls(Blueto
 
   DispatchBluetoothReply(aRunnable, BluetoothValue(true), EmptyString());
 }
 #endif // MOZ_B2G_RIL
 
 class OnUpdateSdpRecordsRunnable : public nsRunnable
 {
 public:
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   OnUpdateSdpRecordsRunnable(const nsAString& aObjectPath,
                              BluetoothProfileManagerBase* aManager)
     : mManager(aManager)
   {
     MOZ_ASSERT(!aObjectPath.IsEmpty());
     MOZ_ASSERT(aManager);
 
     mDeviceAddress = GetAddressFromObjectPath(aObjectPath);
@@ -3813,17 +3813,17 @@ public:
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     mManager->OnUpdateSdpRecords(mDeviceAddress);
 
     return NS_OK;
   }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   // Missing in bluetooth2
 #else
   void
   GetDeviceAddress(nsAString& aRetDeviceAddress)
   {
     aRetDeviceAddress = mDeviceAddress;
   }
 #endif
@@ -4008,17 +4008,17 @@ public:
   }
 
   void Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
     MOZ_ASSERT(sDBusConnection);
     MOZ_ASSERT(!sAdapterPath.IsEmpty());
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     const nsString objectPath =
       GetObjectPathFromAddress(sAdapterPath, mDeviceAddress);
 
     // I choose to use raw pointer here because this is going to be passed as an
     // argument into SendWithReply() at once.
     OnUpdateSdpRecordsRunnable* callbackRunnable =
       new OnUpdateSdpRecordsRunnable(objectPath, mBluetoothProfileManager);
 
@@ -4050,17 +4050,17 @@ public:
       DBUS_ADAPTER_IFACE,
       "CreateDevice",
       DBUS_TYPE_STRING, &cAddress,
       DBUS_TYPE_INVALID);
 #endif
   }
 
 protected:
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   // Missing in bluetooth2
 #else
   static void CreateDeviceCallback(DBusMessage* aMsg, void* aData)
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
 
     nsAutoString errorString;
     OnUpdateSdpRecordsRunnable* r =
@@ -4091,17 +4091,17 @@ protected:
     NS_DispatchToMainThread(r);
   }
 #endif
 
   static void DiscoverServicesCallback(DBusMessage* aMsg, void* aData)
   {
     MOZ_ASSERT(!NS_IsMainThread()); // I/O thread
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
     nsRefPtr<OnUpdateSdpRecordsRunnable> r(
       static_cast<OnUpdateSdpRecordsRunnable*>(aData));
     NS_DispatchToMainThread(r);
 #else
     nsAutoString errorStr;
 
     if (IsDBusMessageError(aMsg, nullptr, errorStr)) {
       BT_LOGR("%s", NS_ConvertUTF16toUTF8(errorStr).get());
@@ -4119,17 +4119,17 @@ private:
 };
 
 bool
 BluetoothDBusService::UpdateSdpRecords(const nsAString& aDeviceAddress,
                                        BluetoothProfileManagerBase* aManager)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   Task* task = new UpdateSdpRecordsTask(aDeviceAddress, aManager);
   DispatchToDBusThread(task);
 #else
   DispatchToDBusThread(new UpdateSdpRecordsTask(aDeviceAddress, aManager));
 #endif
 
   return true;
 }
@@ -4374,17 +4374,17 @@ BluetoothDBusService::SendMetaData(const
                            NS_LITERAL_STRING(ERR_AVRCP_IS_DISCONNECTED));
     return;
   }
 
   nsAutoString prevTitle, prevAlbum;
   a2dp->GetTitle(prevTitle);
   a2dp->GetAlbum(prevAlbum);
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   uint64_t mediaNumber = static_cast<uint64_t>(aMediaNumber);
   if (mediaNumber != a2dp->GetMediaNumber() ||
       !aTitle.Equals(prevTitle) ||
       !aAlbum.Equals(prevAlbum)) {
     UpdateNotification(ControlEventId::EVENT_TRACK_CHANGED, aMediaNumber);
   }
 #else
   if (aMediaNumber < 0 || (uint64_t)aMediaNumber != a2dp->GetMediaNumber() ||
@@ -4685,17 +4685,17 @@ BluetoothDBusService::UpdateNotification
 
   nsAutoString deviceAddress;
   a2dp->GetAddress(deviceAddress);
 
   Task* task = new UpdateNotificationTask(deviceAddress, aEventId, aData);
   DispatchToDBusThread(task);
 }
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
 void
 BluetoothDBusService::StartLeScanInternal(
   const nsTArray<nsString>& aServiceUuids,
   BluetoothReplyRunnable* aRunnable)
 {
 }
 
 void
--- a/dom/bluetooth/bluez/BluetoothDBusService.h
+++ b/dom/bluetooth/bluez/BluetoothDBusService.h
@@ -42,17 +42,17 @@ public:
     EVENT_UNKNOWN
   };
 
   BluetoothDBusService();
   ~BluetoothDBusService();
 
   bool IsReady();
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   virtual nsresult StartInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult StopInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   GetAdaptersInternal(BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
@@ -104,17 +104,17 @@ public:
   CreatePairedDeviceInternal(const nsAString& aDeviceAddress,
                              int aTimeout,
                              BluetoothReplyRunnable* aRunnable) override;
 
   virtual nsresult
   RemoveDeviceInternal(const nsAString& aDeviceObjectPath,
                        BluetoothReplyRunnable* aRunnable) override;
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   virtual void
   PinReplyInternal(const nsAString& aDeviceAddress,
                    bool aAccept,
                    const nsAString& aPinCode,
                    BluetoothReplyRunnable* aRunnable);
 
   virtual void
   SspReplyInternal(const nsAString& aDeviceAddress,
@@ -148,17 +148,17 @@ public:
 #endif
 
   virtual void
   Connect(const nsAString& aDeviceAddress,
           uint32_t aCod,
           uint16_t aServiceUuid,
           BluetoothReplyRunnable* aRunnable) override;
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   virtual bool
   IsConnected(uint16_t aServiceUuid) override;
 #else
   virtual void
   IsConnected(const uint16_t aServiceUuid,
               BluetoothReplyRunnable* aRunnable) override;
 #endif
 
@@ -228,17 +228,17 @@ public:
   virtual nsresult
   SendSinkMessage(const nsAString& aDeviceAddresses,
                   const nsAString& aMessage) override;
 
   virtual nsresult
   SendInputMessage(const nsAString& aDeviceAddresses,
                    const nsAString& aMessage) override;
 
-#ifdef MOZ_B2G_BT_API_V2
+#ifndef MOZ_B2G_BT_API_V1
   virtual void
   StartLeScanInternal(const nsTArray<nsString>& aServiceUuids,
                       BluetoothReplyRunnable* aRunnable) override;
 
   virtual void
   StopLeScanInternal(const nsAString& aAppUuid,
                      BluetoothReplyRunnable* aRunnable) override;
 
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -19,17 +19,35 @@ if CONFIG['MOZ_B2G_BT']:
         'ObexBase.cpp'
     ]
 
     if CONFIG['MOZ_B2G_RIL']:
         SOURCES += [
             'BluetoothRilListener.cpp'
         ]
 
-    if CONFIG['MOZ_B2G_BT_API_V2']:
+    if CONFIG['MOZ_B2G_BT_API_V1']:
+        SOURCES += [
+            'bluetooth1/BluetoothAdapter.cpp',
+            'bluetooth1/BluetoothDevice.cpp',
+            'bluetooth1/BluetoothManager.cpp',
+            'bluetooth1/BluetoothProfileController.cpp',
+            'bluetooth1/BluetoothPropertyContainer.cpp',
+            'bluetooth1/BluetoothReplyRunnable.cpp',
+            'bluetooth1/BluetoothService.cpp',
+            'bluetooth1/ipc/BluetoothChild.cpp',
+            'bluetooth1/ipc/BluetoothParent.cpp',
+            'bluetooth1/ipc/BluetoothServiceChildProcess.cpp',
+        ]
+        LOCAL_INCLUDES += [
+            'bluetooth1',
+            'bluetooth1/ipc',
+        ]
+        DEFINES['MOZ_B2G_BT_API_V1'] = True
+    else:
         SOURCES += [
             'bluetooth2/BluetoothAdapter.cpp',
             'bluetooth2/BluetoothClassOfDevice.cpp',
             'bluetooth2/BluetoothDevice.cpp',
             'bluetooth2/BluetoothDiscoveryHandle.cpp',
             'bluetooth2/BluetoothGatt.cpp',
             'bluetooth2/BluetoothGattCharacteristic.cpp',
             'bluetooth2/BluetoothGattDescriptor.cpp',
@@ -44,34 +62,16 @@ if CONFIG['MOZ_B2G_BT']:
             'bluetooth2/ipc/BluetoothChild.cpp',
             'bluetooth2/ipc/BluetoothParent.cpp',
             'bluetooth2/ipc/BluetoothServiceChildProcess.cpp',
         ]
         LOCAL_INCLUDES += [
             'bluetooth2',
             'bluetooth2/ipc',
         ]
-        DEFINES['MOZ_B2G_BT_API_V2'] = True
-    else:
-        SOURCES += [
-            'bluetooth1/BluetoothAdapter.cpp',
-            'bluetooth1/BluetoothDevice.cpp',
-            'bluetooth1/BluetoothManager.cpp',
-            'bluetooth1/BluetoothProfileController.cpp',
-            'bluetooth1/BluetoothPropertyContainer.cpp',
-            'bluetooth1/BluetoothReplyRunnable.cpp',
-            'bluetooth1/BluetoothService.cpp',
-            'bluetooth1/ipc/BluetoothChild.cpp',
-            'bluetooth1/ipc/BluetoothParent.cpp',
-            'bluetooth1/ipc/BluetoothServiceChildProcess.cpp',
-        ]
-        LOCAL_INCLUDES += [
-            'bluetooth1',
-            'bluetooth1/ipc',
-        ]
 
     #
     # Bluetooth backends
     #
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
         if CONFIG['MOZ_B2G_BT_BLUEZ']:
             CXXFLAGS += CONFIG['MOZ_DBUS_CFLAGS']
@@ -125,17 +125,17 @@ if CONFIG['MOZ_B2G_BT']:
                 ]
             else:
                 SOURCES += [
                     'bluedroid/hfp-fallback/BluetoothHfpManager.cpp',
                 ]
                 LOCAL_INCLUDES += [
                     'bluedroid/hfp-fallback',
                 ]
-            if CONFIG['MOZ_B2G_BT_API_V2']:
+            if not CONFIG['MOZ_B2G_BT_API_V1']:
                 SOURCES += [
                     'bluedroid/BluetoothGattManager.cpp',
                 ]
 
             DEFINES['MOZ_B2G_BT_BLUEDROID'] = True
             if CONFIG['MOZ_B2G_BT_DAEMON']:
                 DEFINES['MOZ_B2G_BT_DAEMON'] = True
     elif CONFIG['MOZ_ENABLE_DBUS']:
@@ -158,17 +158,31 @@ if CONFIG['MOZ_B2G_BT']:
 #
 # Exported interfaces
 #
 
 EXPORTS.mozilla.dom.bluetooth += [
   'BluetoothCommon.h'
 ]
 
-if CONFIG['MOZ_B2G_BT_API_V2']:
+if CONFIG['MOZ_B2G_BT_API_V1']:
+    EXPORTS.mozilla.dom.bluetooth.ipc += [
+        'bluetooth1/ipc/BluetoothMessageUtils.h',
+    ]
+    EXPORTS.mozilla.dom.bluetooth += [
+        'bluetooth1/BluetoothAdapter.h',
+        'bluetooth1/BluetoothDevice.h',
+        'bluetooth1/BluetoothManager.h',
+    ]
+    IPDL_SOURCES += [
+        'bluetooth1/ipc/BluetoothTypes.ipdlh',
+        'bluetooth1/ipc/PBluetooth.ipdl',
+        'bluetooth1/ipc/PBluetoothRequest.ipdl',
+    ]
+else:
     EXPORTS.mozilla.dom.bluetooth.ipc += [
         'bluetooth2/ipc/BluetoothMessageUtils.h',
     ]
     EXPORTS.mozilla.dom.bluetooth += [
         'bluetooth2/BluetoothAdapter.h',
         'bluetooth2/BluetoothClassOfDevice.h',
         'bluetooth2/BluetoothDevice.h',
         'bluetooth2/BluetoothDiscoveryHandle.h',
@@ -181,30 +195,16 @@ if CONFIG['MOZ_B2G_BT_API_V2']:
         'bluetooth2/BluetoothPairingHandle.h',
         'bluetooth2/BluetoothPairingListener.h',
     ]
     IPDL_SOURCES += [
         'bluetooth2/ipc/BluetoothTypes.ipdlh',
         'bluetooth2/ipc/PBluetooth.ipdl',
         'bluetooth2/ipc/PBluetoothRequest.ipdl',
     ]
-else:
-    EXPORTS.mozilla.dom.bluetooth.ipc += [
-        'bluetooth1/ipc/BluetoothMessageUtils.h',
-    ]
-    EXPORTS.mozilla.dom.bluetooth += [
-        'bluetooth1/BluetoothAdapter.h',
-        'bluetooth1/BluetoothDevice.h',
-        'bluetooth1/BluetoothManager.h',
-    ]
-    IPDL_SOURCES += [
-        'bluetooth1/ipc/BluetoothTypes.ipdlh',
-        'bluetooth1/ipc/PBluetooth.ipdl',
-        'bluetooth1/ipc/PBluetoothRequest.ipdl',
-    ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '../base',
     '../network',
     '../system/gonk',
 ]
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -157,26 +157,26 @@ if CONFIG['MOZ_SANDBOX'] and CONFIG['OS_
 DEFINES['BIN_SUFFIX'] = '"%s"' % CONFIG['BIN_SUFFIX']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('android', 'gtk2', 'gonk', 'qt'):
     DEFINES['MOZ_ENABLE_FREETYPE'] = True
 
 if CONFIG['MOZ_TOOLKIT_SEARCH']:
     DEFINES['MOZ_TOOLKIT_SEARCH'] = True
 
-if CONFIG['MOZ_B2G_BT_API_V2']:
+if CONFIG['MOZ_B2G_BT_API_V1']:
+    LOCAL_INCLUDES += [
+        '/dom/bluetooth/bluetooth1',
+        '/dom/bluetooth/bluetooth1/ipc',
+    ]
+else:
     LOCAL_INCLUDES += [
         '/dom/bluetooth/bluetooth2',
         '/dom/bluetooth/bluetooth2/ipc',
     ]
-else:
-    LOCAL_INCLUDES += [
-        '/dom/bluetooth/bluetooth1',
-        '/dom/bluetooth/bluetooth1/ipc',
-    ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 CXXFLAGS += CONFIG['TK_CFLAGS']
--- a/dom/system/gonk/moz.build
+++ b/dom/system/gonk/moz.build
@@ -120,18 +120,18 @@ include('/ipc/chromium/chromium-config.m
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/bluetooth',
     '/dom/geolocation',
     '/dom/wifi',
 ]
 
-if CONFIG['MOZ_B2G_BT_API_V2']:
+if CONFIG['MOZ_B2G_BT_API_V1']:
+    LOCAL_INCLUDES += [
+        '/dom/bluetooth/bluetooth1',
+    ]
+else:
     LOCAL_INCLUDES += [
         '/dom/bluetooth/bluetooth2',
     ]
-else:
-    LOCAL_INCLUDES += [
-        '/dom/bluetooth/bluetooth1',
-    ]
 
 FINAL_LIBRARY = 'xul'
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -179,26 +179,50 @@ var interfaceNamesInGlobalScope =
     "BiquadFilterNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Blob",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BlobEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothAdapter", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothAdapterEvent", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothAttributeEvent", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothClassOfDevice", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothDevice", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothDeviceEvent", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothDiscoveryStateChangedEvent", b2g: true,
+    {name: "BluetoothDiscoveryHandle", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothGatt", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothGattCharacteristic", b2g: true,
      permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothGattCharacteristicEvent", b2g: true,
+     permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothGattDescriptor", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothGattService", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothLeDeviceEvent", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BluetoothManager", b2g: true, permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "BluetoothStatusChangedEvent", b2g: true, permission: ["bluetooth"]},
+    {name: "BluetoothPairingEvent", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothPairingHandle", b2g: true, permission: ["bluetooth"]},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "BluetoothStatusChangedEvent", b2g: true,
+     permission: ["bluetooth"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "BoxObject", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "BroadcastChannel",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "Cache", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "CacheStorage", release: false},
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -640,37 +640,37 @@ WEBIDL_FILES += [
 ]
 
 # We only expose our prefable test interfaces in debug builds, just to be on
 # the safe side.
 if CONFIG['MOZ_DEBUG']:
     WEBIDL_FILES += ['TestInterfaceJS.webidl', 'TestInterfaceJSDictionaries.webidl']
 
 if CONFIG['MOZ_B2G_BT']:
-    if CONFIG['MOZ_B2G_BT_API_V2']:
+    if CONFIG['MOZ_B2G_BT_API_V1']:
+        WEBIDL_FILES += [
+            'BluetoothAdapter.webidl',
+            'BluetoothDevice.webidl',
+            'BluetoothManager.webidl',
+        ]
+    else:
         WEBIDL_FILES += [
             'BluetoothAdapter2.webidl',
             'BluetoothClassOfDevice.webidl',
             'BluetoothDevice2.webidl',
             'BluetoothDiscoveryHandle.webidl',
             'BluetoothGatt.webidl',
             'BluetoothGattCharacteristic.webidl',
             'BluetoothGattDescriptor.webidl',
             'BluetoothGattService.webidl',
             'BluetoothLeDeviceEvent.webidl',
             'BluetoothManager2.webidl',
             'BluetoothPairingHandle.webidl',
             'BluetoothPairingListener.webidl',
         ]
-    else:
-        WEBIDL_FILES += [
-            'BluetoothAdapter.webidl',
-            'BluetoothDevice.webidl',
-            'BluetoothManager.webidl',
-        ]
 
 if CONFIG['MOZ_SIMPLEPUSH']:
     WEBIDL_FILES += [
         'SimplePushManager.webidl'
     ]
 else:
     WEBIDL_FILES += [
         'PushEvent.webidl',
@@ -791,27 +791,27 @@ if CONFIG['MOZ_WEBSPEECH']:
 if CONFIG['MOZ_GAMEPAD']:
     GENERATED_EVENTS_WEBIDL_FILES += [
         'GamepadAxisMoveEvent.webidl',
         'GamepadButtonEvent.webidl',
         'GamepadEvent.webidl',
     ]
 
 if CONFIG['MOZ_B2G_BT']:
-    if CONFIG['MOZ_B2G_BT_API_V2']:
+    if CONFIG['MOZ_B2G_BT_API_V1']:
+        GENERATED_EVENTS_WEBIDL_FILES += [
+            'BluetoothDiscoveryStateChangedEvent.webidl',
+        ]
+    else:
         GENERATED_EVENTS_WEBIDL_FILES += [
             'BluetoothAdapterEvent.webidl',
             'BluetoothAttributeEvent.webidl',
             'BluetoothGattCharacteristicEvent.webidl',
             'BluetoothPairingEvent.webidl',
         ]
-    else:
-        GENERATED_EVENTS_WEBIDL_FILES += [
-            'BluetoothDiscoveryStateChangedEvent.webidl',
-        ]
 
     GENERATED_EVENTS_WEBIDL_FILES += [
         'BluetoothDeviceEvent.webidl',
         'BluetoothStatusChangedEvent.webidl',
     ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     GENERATED_EVENTS_WEBIDL_FILES += [
--- a/js/xpconnect/src/moz.build
+++ b/js/xpconnect/src/moz.build
@@ -68,16 +68,16 @@ LOCAL_INCLUDES += [
     '/layout/style',
     '/xpcom/reflect/xptinfo',
 ]
 
 if CONFIG['MOZ_B2G_BT']:
     LOCAL_INCLUDES += [
         '/dom/bluetooth',
     ]
-    if CONFIG['MOZ_B2G_BT_API_V2']:
+    if CONFIG['MOZ_B2G_BT_API_V1']:
+        LOCAL_INCLUDES += [
+            '/dom/bluetooth/bluetooth1',
+        ]
+    else:
         LOCAL_INCLUDES += [
             '/dom/bluetooth/bluetooth2',
         ]
-    else:
-        LOCAL_INCLUDES += [
-            '/dom/bluetooth/bluetooth1',
-        ]
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -382,21 +382,24 @@ GetClosest(nsIFrame* aRoot, const nsPoin
       bestTarget = f;
     }
   }
   return bestTarget;
 }
 
 /*
  * Return always true when touch cluster detection is OFF.
- * When cluster detection is ON, return true if the text inside
- * the frame is readable (by human eyes):
- *   in this case, the frame is really clickable.
- * Frames with a too small size will return false:
- *   in this case, the frame is considered not clickable.
+ * When cluster detection is ON, return true:
+ *   if the text inside the frame is readable (by human eyes)
+ *   or
+ *   if the structure is too complex to determine the size.
+ * In both cases, the frame is considered as clickable.
+ *
+ * Frames with a too small size will return false.
+ * In this case, the frame is considered not clickable.
  */
 static bool
 IsElementClickableAndReadable(nsIFrame* aFrame, WidgetGUIEvent* aEvent, const EventRadiusPrefs* aPrefs)
 {
   if (aPrefs->mTouchClusterDetectionDisabled) {
     return true;
   }
 
@@ -408,21 +411,47 @@ IsElementClickableAndReadable(nsIFrame* 
   nsSize frameSize = aFrame->GetSize();
   nsPresContext* pc = aFrame->PresContext();
   nsIPresShell* presShell = pc->PresShell();
   float cumulativeResolution = presShell->GetCumulativeResolution();
   if ((pc->AppUnitsToGfxUnits(frameSize.height) * cumulativeResolution) < limitReadableSize ||
       (pc->AppUnitsToGfxUnits(frameSize.width) * cumulativeResolution) < limitReadableSize) {
     return false;
   }
-  nsRefPtr<nsFontMetrics> fm;
-  nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
-    nsLayoutUtils::FontSizeInflationFor(aFrame));
-  if (fm) {
-    if ((fm->EmHeight() > 0) && // See bug 1171731
+  // We want to detect small clickable text elements using the font size.
+  // Two common cases are supported for now:
+  //    1. text node
+  //    2. any element with only one child of type text node
+  // All the other cases are currently ignored.
+  nsIContent *content = aFrame->GetContent();
+  bool testFontSize = false;
+  if (content) {
+    nsINodeList* childNodes = content->ChildNodes();
+    uint32_t childNodeCount = childNodes->Length();
+    if ((content->IsNodeOfType(nsINode::eTEXT)) ||
+      // click occurs on the text inside <a></a> or other clickable tags with text inside
+
+      (childNodeCount == 1 && childNodes->Item(0) &&
+        childNodes->Item(0)->IsNodeOfType(nsINode::eTEXT))) {
+      // The click occurs on an element with only one text node child. In this case, the font size
+      // can be tested.
+      // The number of child nodes is tested to avoid the following cases (See bug 1172488):
+      //   Some jscript libraries transform text elements into Canvas elements but keep the text nodes
+      //   with a very small size (1px) to handle the selection of text.
+      //   With such libraries, the font size of the text elements is not relevant to detect small elements.
+
+      testFontSize = true;
+    }
+  }
+
+  if (testFontSize) {
+    nsRefPtr<nsFontMetrics> fm;
+    nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
+      nsLayoutUtils::FontSizeInflationFor(aFrame));
+    if (fm && fm->EmHeight() > 0 && // See bug 1171731
         (pc->AppUnitsToGfxUnits(fm->EmHeight()) * cumulativeResolution) < limitReadableSize) {
       return false;
     }
   }
 
   return true;
 }
 
--- a/layout/build/moz.build
+++ b/layout/build/moz.build
@@ -103,23 +103,23 @@ if CONFIG['MOZ_B2G_FM']:
     LOCAL_INCLUDES += [
         '/dom/fmradio',
     ]
 
 if CONFIG['MOZ_B2G_BT']:
     LOCAL_INCLUDES += [
         '/dom/bluetooth',
     ]
-    if CONFIG['MOZ_B2G_BT_API_V2']:
+    if CONFIG['MOZ_B2G_BT_API_V1']:
         LOCAL_INCLUDES += [
-            '/dom/bluetooth/bluetooth2',
+            '/dom/bluetooth/bluetooth1',
         ]
     else:
         LOCAL_INCLUDES += [
-            '/dom/bluetooth/bluetooth1',
+            '/dom/bluetooth/bluetooth2',
         ]
 
 if CONFIG['MOZ_WEBSPEECH']:
     LOCAL_INCLUDES += [
         '/dom/media/webspeech/recognition',
         '/dom/media/webspeech/synth',
     ]
 
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -95,16 +95,17 @@ import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.MessageQueue;
 import android.os.SystemClock;
 import android.os.Vibrator;
+import android.provider.Browser;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Base64;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.HapticFeedbackConstants;
@@ -955,33 +956,47 @@ public class GeckoAppShell
         Uri uri = aURL.indexOf(':') >= 0 ? Uri.parse(aURL) : new Uri.Builder().scheme(aURL).build();
 
         Intent intent = getOpenURIIntent(getContext(), uri.toString(), "",
             TextUtils.isEmpty(aAction) ? Intent.ACTION_VIEW : aAction, "");
 
         return getHandlersForIntent(intent);
     }
 
+    static List<ResolveInfo> queryIntentActivities(Intent intent) {
+        final PackageManager pm = getContext().getPackageManager();
+
+        // Exclude any non-exported activities: we can't open them even if we want to!
+        // Bug 1031569 has some details.
+        final ArrayList<ResolveInfo> list = new ArrayList<>();
+        for (ResolveInfo ri: pm.queryIntentActivities(intent, 0)) {
+            if (ri.activityInfo.exported) {
+                list.add(ri);
+            }
+        }
+
+        return list;
+    }
+
     static boolean hasHandlersForIntent(Intent intent) {
         try {
-            PackageManager pm = getContext().getPackageManager();
-            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
-            return !list.isEmpty();
+            return !queryIntentActivities(intent).isEmpty();
         } catch (Exception ex) {
             Log.e(LOGTAG, "Exception in GeckoAppShell.hasHandlersForIntent");
             return false;
         }
     }
 
     static String[] getHandlersForIntent(Intent intent) {
+        final PackageManager pm = getContext().getPackageManager();
         try {
-            PackageManager pm = getContext().getPackageManager();
-            List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+            final List<ResolveInfo> list = queryIntentActivities(intent);
+
             int numAttr = 4;
-            String[] ret = new String[list.size() * numAttr];
+            final String[] ret = new String[list.size() * numAttr];
             for (int i = 0; i < list.size(); i++) {
                 ResolveInfo resolveInfo = list.get(i);
                 ret[i * numAttr] = resolveInfo.loadLabel(pm).toString();
                 if (resolveInfo.isDefault)
                     ret[i * numAttr + 1] = "default";
                 else
                     ret[i * numAttr + 1] = "";
                 ret[i * numAttr + 2] = resolveInfo.activityInfo.applicationInfo.packageName;
@@ -1092,16 +1107,20 @@ public class GeckoAppShell
             }
         }
 
         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
         try {
             context.startActivity(intent);
             return true;
         } catch (ActivityNotFoundException e) {
+            Log.w(LOGTAG, "Activity not found.", e);
+            return false;
+        } catch (SecurityException e) {
+            Log.w(LOGTAG, "Forbidden to launch activity.", e);
             return false;
         }
     }
 
     /**
      * Return a <code>Uri</code> instance which is equivalent to <code>u</code>,
      * but with a guaranteed-lowercase scheme as if the API level 16 method
      * <code>u.normalizeScheme</code> had been called.
@@ -1168,16 +1187,36 @@ public class GeckoAppShell
      *         produced.
      */
     static Intent getOpenURIIntent(final Context context,
                                    final String targetURI,
                                    final String mimeType,
                                    final String action,
                                    final String title) {
 
+        // The resultant chooser can return non-exported activities in 4.1 and earlier.
+        // https://code.google.com/p/android/issues/detail?id=29535
+        final Intent intent = getOpenURIIntentInner(context, targetURI, mimeType, action, title);
+
+        if (intent != null) {
+            // Only handle applications which can accept arbitrary data from a browser.
+            intent.addCategory(Intent.CATEGORY_BROWSABLE);
+
+            // Some applications use this field to return to the same browser after processing the
+            // Intent. While there is some danger (e.g. denial of service), other major browsers already
+            // use it and so it's the norm.
+            intent.putExtra(Browser.EXTRA_APPLICATION_ID, GeckoApp.class.getPackage().getName());
+        }
+
+        return intent;
+    }
+
+    private static Intent getOpenURIIntentInner(final Context context,  final String targetURI,
+            final String mimeType, final String action, final String title) {
+
         if (action.equalsIgnoreCase(Intent.ACTION_SEND)) {
             Intent shareIntent = getShareIntent(context, targetURI, mimeType, title);
             return Intent.createChooser(shareIntent,
                                         context.getResources().getString(R.string.share_title)); 
         }
 
         final Uri uri = normalizeUriScheme(targetURI.indexOf(':') >= 0 ? Uri.parse(targetURI) : new Uri.Builder().scheme(targetURI).build());
         if (!TextUtils.isEmpty(mimeType)) {
@@ -1195,19 +1234,16 @@ public class GeckoAppShell
             final Intent intent;
             try {
                 intent = Intent.parseUri(targetURI, Intent.URI_INTENT_SCHEME);
             } catch (final URISyntaxException e) {
                 Log.e(LOGTAG, "Unable to parse URI - " + e);
                 return null;
             }
 
-            // Only handle applications which can accept arbitrary data from a browser.
-            intent.addCategory(Intent.CATEGORY_BROWSABLE);
-
             // Prevent site from explicitly opening our internal activities, which can leak data.
             intent.setComponent(null);
             nullIntentSelector(intent);
 
             return intent;
         }
 
         // Compute our most likely intent, then check to see if there are any
--- a/mobile/android/base/IntentHelper.java
+++ b/mobile/android/base/IntentHelper.java
@@ -97,17 +97,23 @@ public final class IntentHelper implemen
     private void openForResult(final JSONObject message) throws JSONException {
         Intent intent = GeckoAppShell.getOpenURIIntent(activity,
                                                        message.optString("url"),
                                                        message.optString("mime"),
                                                        message.optString("action"),
                                                        message.optString("title"));
         intent.setClassName(message.optString("packageName"), message.optString("className"));
         intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
+
+        final ResultHandler handler = new ResultHandler(message);
+        try {
+            ActivityHandlerHelper.startIntentForActivity(activity, intent, handler);
+        } catch (SecurityException e) {
+            Log.w(LOGTAG, "Forbidden to launch activity.", e);
+        }
     }
 
     private void openWebActivity(JSONObject message) throws JSONException {
         final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
     }
 
     private static class ResultHandler implements ActivityResultHandler {
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -39,17 +39,16 @@ GARBAGE += \
   AndroidManifest.xml  \
   WebappManifestFragment.xml.frag \
   classes.dex  \
   gecko.ap_  \
   res/values/strings.xml \
   res/raw/browsersearch.json \
   res/raw/suggestedsites.json \
   .aapt.deps \
-  fennec_ids.txt \
   javah.out \
   jni-stubs.inc \
   GeneratedJNIWrappers.cpp \
   GeneratedJNIWrappers.h \
   $(NULL)
 
 GARBAGE_DIRS += classes db jars res sync services generated
 
@@ -231,16 +230,17 @@ ANNOTATION_PROCESSOR_JAR_FILES := $(DEPT
 
 GeneratedJNIWrappers.cpp: $(ANNOTATION_PROCESSOR_JAR_FILES)
 GeneratedJNIWrappers.cpp: $(ALL_JARS)
 	$(JAVA) -classpath gecko-mozglue.jar:$(JAVA_BOOTCLASSPATH):$(JAVA_CLASSPATH):$(ANNOTATION_PROCESSOR_JAR_FILES) org.mozilla.gecko.annotationProcessors.AnnotationProcessor $(ALL_JARS)
 
 manifest := \
   AndroidManifest.xml.in \
   WebappManifestFragment.xml.frag.in \
+  fennec_ids.txt.in \
   $(NULL)
 
 PP_TARGETS += manifest
 
 # Certain source files need to be preprocessed.  This special rule
 # generates these files into generated/org/mozilla/gecko for
 # consumption by the build system and IDEs.
 
@@ -444,19 +444,16 @@ endef
 # toolkit/mozapps/installer/packager.mk.
 
 # .aapt.deps: $(all_resources)
 $(eval $(call aapt_command,.aapt.deps,$(all_resources),gecko.ap_,generated/,./))
 
 # .aapt.nodeps: $(CURDIR)/AndroidManifest.xml FORCE
 $(eval $(call aapt_command,.aapt.nodeps,$(CURDIR)/AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
 
-fennec_ids.txt: generated/org/mozilla/gecko/R.java fennec-ids-generator.py
-	$(PYTHON) $(topsrcdir)/mobile/android/base/fennec-ids-generator.py -i $< -o $@
-
 # Override the Java settings with some specific android settings
 include $(topsrcdir)/config/android-common.mk
 
 update-generated-wrappers:
 	@mv $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp.old
 	@mv $(topsrcdir)/widget/android/GeneratedJNIWrappers.h $(topsrcdir)/widget/android/GeneratedJNIWrappers.h.old
 	@echo old GeneratedJNIWrappers.cpp/h moved to GeneratedJNIWrappers.cpp/h.old
 	@cp $(CURDIR)/jni-stubs.inc $(topsrcdir)/mozglue/android
@@ -483,17 +480,17 @@ update-generated-wrappers:
 
 # Targets built very early during a Gradle build.
 gradle-targets: .aapt.deps
 
 gradle-omnijar: $(abspath $(DIST)/fennec/$(OMNIJAR_NAME))
 
 .PHONY: gradle-targets gradle-omnijar
 
-libs:: geckoview_resources.zip classes.dex jni-stubs.inc GeneratedJNIWrappers.cpp fennec_ids.txt
+libs:: geckoview_resources.zip classes.dex jni-stubs.inc GeneratedJNIWrappers.cpp $(CURDIR)/fennec_ids.txt
 	$(INSTALL) geckoview_resources.zip $(FINAL_TARGET)
 	$(INSTALL) classes.dex $(FINAL_TARGET)
 	@(diff jni-stubs.inc $(topsrcdir)/mozglue/android/jni-stubs.inc >/dev/null && diff GeneratedJNIWrappers.cpp $(topsrcdir)/widget/android/GeneratedJNIWrappers.cpp >/dev/null) || \
 	 (echo '*****************************************************' && \
 	  echo '***   Error: The generated JNI code has changed   ***' && \
 	  echo '* To update generated code in the tree, please run  *' && \
 	  echo && \
 	  echo '  make -C $(CURDIR) update-generated-wrappers' && \
--- a/mobile/android/base/ZoomedView.java
+++ b/mobile/android/base/ZoomedView.java
@@ -30,16 +30,20 @@ import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.graphics.Shader;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewTreeObserver;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.view.animation.OvershootInterpolator;
+import android.view.animation.ScaleAnimation;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
 import java.nio.ByteBuffer;
 import java.text.DecimalFormat;
 
@@ -47,45 +51,55 @@ public class ZoomedView extends FrameLay
         LayerView.ZoomedViewListener, GeckoEventListener {
     private static final String LOGTAG = "Gecko" + ZoomedView.class.getSimpleName();
 
     private static final float[] ZOOM_FACTORS_LIST = {2.0f, 3.0f, 1.5f};
     private static final int W_CAPTURED_VIEW_IN_PERCENT = 50;
     private static final int H_CAPTURED_VIEW_IN_PERCENT = 50;
     private static final int MINIMUM_DELAY_BETWEEN_TWO_RENDER_CALLS_NS = 1000000;
     private static final int DELAY_BEFORE_NEXT_RENDER_REQUEST_MS = 2000;
+    private static final int OPENING_ANIMATION_DURATION_MS = 250;
+    private static final int CLOSING_ANIMATION_DURATION_MS = 150;
+    private static final float OVERSHOOT_INTERPOLATOR_TENSION = 1.5f;
 
     private float zoomFactor;
     private int currentZoomFactorIndex;
     private ImageView zoomedImageView;
     private LayerView layerView;
     private int viewWidth;
     private int viewHeight; // Only the zoomed view height, no toolbar, no shadow ...
     private int viewContainerWidth;
     private int viewContainerHeight; // Zoomed view height with toolbar and other elements like shadow, ...
     private int containterSize; // shadow, margin, ...
     private Point lastPosition;
     private boolean shouldSetVisibleOnUpdate;
     private PointF returnValue;
+    private final PointF animationStart;
     private ImageView closeButton;
     private TextView changeZoomFactorButton;
     private boolean toolbarOnTop;
     private float offsetDueToToolBarPosition;
     private int toolbarHeight;
     private int cornerRadius;
 
     private boolean stopUpdateView;
 
     private int lastOrientation;
 
     private ByteBuffer buffer;
     private Runnable requestRenderRunnable;
     private long startTimeReRender;
     private long lastStartTimeReRender;
 
+    private ZoomedViewTouchListener touchListener;
+
+    private enum StartPointUpdate {
+        GECKO_POSITION, CENTER, NO_CHANGE
+    }
+
     private class RoundedBitmapDrawable extends BitmapDrawable {
         private Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
         final float cornerRadius;
         final boolean squareOnTopOfDrawable;
 
         RoundedBitmapDrawable(Resources res, Bitmap bitmap, boolean squareOnTop, int radius) {
             super(res, bitmap);
             squareOnTopOfDrawable = squareOnTop;
@@ -177,17 +191,17 @@ public class ZoomedView extends FrameLay
                 // When the user just touches the screen ACTION_MOVE can be detected for a very small delta on position.
                 // In this case, the move is ignored if the delta is lower than 1 unit.
                 return false;
             }
 
             float newLeftMargin = params.leftMargin + event.getRawX() - originRawX;
             float newTopMargin = params.topMargin + event.getRawY() - originRawY;
             ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
-            ZoomedView.this.moveZoomedView(metrics, newLeftMargin, newTopMargin);
+            ZoomedView.this.moveZoomedView(metrics, newLeftMargin, newTopMargin, StartPointUpdate.CENTER);
             originRawX = event.getRawX();
             originRawY = event.getRawY();
             return true;
         }
     }
 
     public ZoomedView(Context context) {
         this(context, null, 0);
@@ -195,24 +209,26 @@ public class ZoomedView extends FrameLay
 
     public ZoomedView(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
 
     public ZoomedView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
         returnValue = new PointF();
+        animationStart = new PointF();
         currentZoomFactorIndex = 0;
         zoomFactor = ZOOM_FACTORS_LIST[currentZoomFactorIndex];
         requestRenderRunnable = new Runnable() {
             @Override
             public void run() {
                 requestZoomedViewRender();
             }
         };
+        touchListener = new ZoomedViewTouchListener();
         EventDispatcher.getInstance().registerGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
     }
 
     void destroy() {
         ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
         EventDispatcher.getInstance().unregisterGeckoThreadListener(this,
                 "Gesture:clusteredLinksClicked", "Window:Resize", "Content:LocationChange");
@@ -220,40 +236,51 @@ public class ZoomedView extends FrameLay
 
     // This method (onFinishInflate) is called only when the zoomed view class is used inside
     // an xml structure <org.mozilla.gecko.ZoomedView ...
     // It won't be called if the class is used from java code like "new  ZoomedView(context);"
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
         closeButton = (ImageView) findViewById(R.id.dialog_close);
-        closeButton.setOnClickListener(new View.OnClickListener() {
-            public void onClick(View view) {
-                stopZoomDisplay();
-            }
-        });
+        changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
+        zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
 
-        changeZoomFactorButton = (TextView) findViewById(R.id.change_zoom_factor);
-        changeZoomFactorButton.setOnClickListener(new View.OnClickListener() {
-            public void onClick(View view) {
-                changeZoomFactor();
-            }
-        });
         setTextInZoomFactorButton(ZOOM_FACTORS_LIST[0]);
 
-        zoomedImageView = (ImageView) findViewById(R.id.zoomed_image_view);
-        this.setOnTouchListener(new ZoomedViewTouchListener());
-
         toolbarHeight = getResources().getDimensionPixelSize(R.dimen.zoomed_view_toolbar_height);
         containterSize = getResources().getDimensionPixelSize(R.dimen.drawable_dropshadow_size);
         cornerRadius = getResources().getDimensionPixelSize(R.dimen.button_corner_radius);
 
         moveToolbar(true);
     }
 
+    private void setListeners() {
+        closeButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View view) {
+                stopZoomDisplay(true);
+            }
+        });
+
+        changeZoomFactorButton.setOnClickListener(new View.OnClickListener() {
+            public void onClick(View view) {
+                changeZoomFactor();
+            }
+        });
+
+        setOnTouchListener(touchListener);
+    }
+
+    private void removeListeners() {
+        closeButton.setOnClickListener(null);
+
+        changeZoomFactorButton.setOnClickListener(null);
+
+        setOnTouchListener(null);
+    }
     /*
      * Convert a click from ZoomedView. Return the position of the click in the
      * LayerView
      */
     private PointF getUnzoomedPositionFromPointInZoomedView(float x, float y) {
         if (toolbarOnTop && y > toolbarHeight) {
            y = y - toolbarHeight;
         }
@@ -318,17 +345,18 @@ public class ZoomedView extends FrameLay
         returnValue.y = (int) ((((y + offsetDueToToolBarPosition - (viewHeight / (2 * zoomFactor)))) /
                         ((parentHeight - offset.y + offsetDueToToolBarPosition - (viewHeight / zoomFactor)) /
                         (parentHeight - offset.y - viewContainerHeight)))
                 + offset.y);
 
         return returnValue;
     }
 
-    private void moveZoomedView(ImmutableViewportMetrics metrics, float newLeftMargin, float newTopMargin) {
+    private void moveZoomedView(ImmutableViewportMetrics metrics, float newLeftMargin, float newTopMargin,
+            StartPointUpdate animateStartPoint) {
         final float parentWidth = metrics.getWidth();
         final float parentHeight = metrics.getHeight();
         RelativeLayout.LayoutParams newLayoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
         newLayoutParams.leftMargin = (int) newLeftMargin;
         newLayoutParams.topMargin = (int) newTopMargin;
         int topMarginMin;
         int leftMarginMin;
         PointF offset = metrics.getMarginOffset();
@@ -348,16 +376,32 @@ public class ZoomedView extends FrameLay
         }
 
         if (newLayoutParams.topMargin < topMarginMin + 1) {
             moveToolbar(false);
         } else if (newLayoutParams.topMargin + viewContainerHeight > parentHeight - 1) {
             moveToolbar(true);
         }
 
+        if (animateStartPoint == StartPointUpdate.GECKO_POSITION) {
+            // Before this point, the animationStart point is relative to the layerView.
+            // The value is initialized in startZoomDisplay using the click point position coming from Gecko.
+            // The position of the zoomed view is now calculated, so the position of the animation
+            // can now be correctly set relative to the zoomed view
+            animationStart.x = animationStart.x - newLayoutParams.leftMargin;
+            animationStart.y = animationStart.y - newLayoutParams.topMargin;
+        } else if (animateStartPoint == StartPointUpdate.CENTER) {
+            // At this point, the animationStart point is no more valid probably because
+            // the zoomed view has been moved by the user.
+            // In this case, the animationStart point is set to the center point of the zoomed view.
+            PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(viewContainerWidth / 2, viewContainerHeight / 2);
+            animationStart.x = convertedPosition.x - newLayoutParams.leftMargin;
+            animationStart.y = convertedPosition.y - newLayoutParams.topMargin;
+        }
+
         setLayoutParams(newLayoutParams);
         PointF convertedPosition = getUnzoomedPositionFromPointInZoomedView(0, 0);
         lastPosition = PointUtils.round(convertedPosition);
         requestZoomedViewRender();
     }
 
     private void moveToolbar(boolean moveTop) {
         if (toolbarOnTop == moveTop) {
@@ -404,17 +448,17 @@ public class ZoomedView extends FrameLay
 
     private void refreshZoomedViewSize(ImmutableViewportMetrics viewport) {
         if (layerView == null) {
             return;
         }
 
         RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) getLayoutParams();
         setCapturedSize(viewport);
-        moveZoomedView(viewport, params.leftMargin, params.topMargin);
+        moveZoomedView(viewport, params.leftMargin, params.topMargin, StartPointUpdate.NO_CHANGE);
     }
 
     private void setCapturedSize(ImmutableViewportMetrics metrics) {
         float parentMinSize = Math.min(metrics.getWidth(), metrics.getHeight());
         viewWidth = (int) ((parentMinSize * W_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
         viewHeight = (int) ((parentMinSize * H_CAPTURED_VIEW_IN_PERCENT / (zoomFactor * 100.0)) * zoomFactor);
         viewContainerHeight = viewHeight + toolbarHeight +
                 2 * containterSize; // Top and bottom shadows
@@ -438,22 +482,31 @@ public class ZoomedView extends FrameLay
             layerView = aLayerView;
             layerView.addZoomedViewListener(this);
             layerView.setOnMetricsChangedZoomedViewportListener(this);
             ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
             setCapturedSize(metrics);
         }
         startTimeReRender = 0;
         shouldSetVisibleOnUpdate = true;
+
+        ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
+        PointF offset = metrics.getMarginOffset();
+        // At this point, the start point is relative to the layerView.
+        // Later, it will be converted relative to the zoomed view as soon as
+        // the position of the zoomed view will be calculated.
+        animationStart.x = (float) leftFromGecko * metrics.zoomFactor + offset.x;
+        animationStart.y = (float) topFromGecko * metrics.zoomFactor + offset.y;
+
         moveUsingGeckoPosition(leftFromGecko, topFromGecko);
     }
 
-    private void stopZoomDisplay() {
+    private void stopZoomDisplay(boolean withAnimation) {
         shouldSetVisibleOnUpdate = false;
-        this.setVisibility(View.GONE);
+        hideZoomedView(withAnimation);
         ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
         if (layerView != null) {
             layerView.setOnMetricsChangedZoomedViewportListener(null);
             layerView.removeZoomedViewListener(this);
             layerView = null;
         }
     }
 
@@ -489,34 +542,34 @@ public class ZoomedView extends FrameLay
                         LayerView geckoAppLayerView = GeckoAppShell.getLayerView();
                         if (geckoAppLayerView != null) {
                             startZoomDisplay(geckoAppLayerView, left, top);
                         }
                     } else if (event.equals("Window:Resize")) {
                         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
                         refreshZoomedViewSize(metrics);
                     } else if (event.equals("Content:LocationChange")) {
-                        stopZoomDisplay();
+                        stopZoomDisplay(false);
                     }
                 } catch (JSONException e) {
                     Log.e(LOGTAG, "JSON exception", e);
                 }
             }
         });
     }
 
     private void moveUsingGeckoPosition(int leftFromGecko, int topFromGecko) {
         ImmutableViewportMetrics metrics = layerView.getViewportMetrics();
         final float parentHeight = metrics.getHeight();
         // moveToolbar is called before getZoomedViewTopLeftPositionFromTouchPosition in order to
         // correctly center vertically the zoomed area
         moveToolbar((topFromGecko * metrics.zoomFactor > parentHeight / 2));
         PointF convertedPosition = getZoomedViewTopLeftPositionFromTouchPosition((leftFromGecko * metrics.zoomFactor),
                 (topFromGecko * metrics.zoomFactor));
-        moveZoomedView(metrics, convertedPosition.x, convertedPosition.y);
+        moveZoomedView(metrics, convertedPosition.x, convertedPosition.y, StartPointUpdate.GECKO_POSITION);
     }
 
     @Override
     public void onMetricsChanged(final ImmutableViewportMetrics viewport) {
         // It can be called from a Gecko thread (forceViewportMetrics in GeckoLayerClient).
         // Post to UI Thread to avoid Exception:
         //    "Only the original thread that created a view hierarchy can touch its views."
         ThreadUtils.postToUiThread(new Runnable() {
@@ -543,23 +596,76 @@ public class ZoomedView extends FrameLay
                 Log.w(LOGTAG, iae.toString());
             }
             if (zoomedImageView != null) {
                 RoundedBitmapDrawable ob3 = new RoundedBitmapDrawable(getResources(), sb3, toolbarOnTop, cornerRadius);
                 zoomedImageView.setImageDrawable(ob3);
             }
         }
         if (shouldSetVisibleOnUpdate) {
-            this.setVisibility(View.VISIBLE);
-            shouldSetVisibleOnUpdate = false;
+            this.showZoomedView();
         }
         lastStartTimeReRender = startTimeReRender;
         startTimeReRender = 0;
     }
 
+    private void showZoomedView() {
+        // no animation if the zoomed view is already visible
+        if (getVisibility() != View.VISIBLE) {
+            final Animation anim = new ScaleAnimation(
+                    0f, 1f, // Start and end values for the X axis scaling
+                    0f, 1f, // Start and end values for the Y axis scaling
+                    Animation.ABSOLUTE, animationStart.x, // Pivot point of X scaling
+                    Animation.ABSOLUTE, animationStart.y); // Pivot point of Y scaling
+            anim.setFillAfter(true); // Needed to keep the result of the animation
+            anim.setDuration(OPENING_ANIMATION_DURATION_MS);
+            anim.setInterpolator(new OvershootInterpolator(OVERSHOOT_INTERPOLATOR_TENSION));
+            anim.setAnimationListener(new AnimationListener() {
+                public void onAnimationEnd(Animation animation) {
+                    setListeners();
+                }
+                public void onAnimationRepeat(Animation animation) {
+                }
+                public void onAnimationStart(Animation animation) {
+                    removeListeners();
+                }
+            });
+            setAnimation(anim);
+        }
+        setVisibility(View.VISIBLE);
+        shouldSetVisibleOnUpdate = false;
+    }
+
+    private void hideZoomedView(boolean withAnimation) {
+        if (withAnimation) {
+            final Animation anim = new ScaleAnimation(
+                1f, 0f, // Start and end values for the X axis scaling
+                1f, 0f, // Start and end values for the Y axis scaling
+                Animation.ABSOLUTE, animationStart.x, // Pivot point of X scaling
+                Animation.ABSOLUTE, animationStart.y); // Pivot point of Y scaling
+            anim.setFillAfter(true); // Needed to keep the result of the animation
+            anim.setDuration(CLOSING_ANIMATION_DURATION_MS);
+            anim.setAnimationListener(new AnimationListener() {
+                public void onAnimationEnd(Animation animation) {
+                }
+                public void onAnimationRepeat(Animation animation) {
+                }
+                public void onAnimationStart(Animation animation) {
+                    removeListeners();
+                }
+            });
+            setAnimation(anim);
+        } else {
+            removeListeners();
+            setAnimation(null);
+        }
+        setVisibility(View.GONE);
+        shouldSetVisibleOnUpdate = false;
+    }
+
     private void updateBufferSize() {
         int pixelSize = (GeckoAppShell.getScreenDepth() == 24) ? 4 : 2;
         int capacity = viewWidth * viewHeight * pixelSize;
         if (buffer == null || buffer.capacity() != capacity) {
             buffer = DirectBufferAllocator.free(buffer);
             buffer = DirectBufferAllocator.allocate(capacity);
         }
     }
deleted file mode 100644
--- a/mobile/android/base/fennec-ids-generator.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-import re
-import os
-import sys
-import optparse
-
-def getFile(filename):
-  fHandle = open(filename, 'r')
-  data = fHandle.read()
-  fHandle.close()
-  return data
-
-def findIDs(data):
-  start_function = False
-  reID = re.compile('.*public static final class id {.*')
-  reEnd = re.compile('.*}.*')
-  idlist = []
-
-  for line in data.split('\n'):
-    if reEnd.match(line):
-      start_function = False
-
-    if start_function:
-      id_value = line.split(' ')[-1]
-      idlist.append(id_value.split(';')[0].split('='))
-
-    if reID.match(line):
-      start_function = True
-
-  return idlist
-
-
-def printIDs(outputFile, idlist):
-  fOutput = open(outputFile, 'w')
-  for item in idlist:
-    fOutput.write("%s=%s\n" % (item[0], item[1]))
-  fOutput.close()
-
-def main(args=sys.argv[1:]):
-  parser = optparse.OptionParser()
-  parser.add_option('-o', '--output', dest='outputFile', default='',
-                    help="output file with the id=value pairs")
-  parser.add_option('-i', '--input', dest='inputFile', default='',
-                    help="filename of the input R.java file")
-  options, args = parser.parse_args(args)
-
-  if options.inputFile == '':
-    print "Error: please provide input file: -i <filename>"
-    sys.exit(1)
-
-  if options.outputFile == '':
-    print "Error: please provide output file: -o <filename>"
-    sys.exit(1)
-
-  data = getFile(os.path.abspath(options.inputFile));
-  idlist = findIDs(data)
-  printIDs(os.path.abspath(options.outputFile), idlist)
-
-if __name__ == "__main__":
-    main()
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fennec_ids.txt.in
@@ -0,0 +1,2 @@
+#filter slashslash
+// fennec_ids.txt needs to exist (for automation) but contains no content.
--- a/mobile/android/chrome/content/aboutLogins.js
+++ b/mobile/android/chrome/content/aboutLogins.js
@@ -58,20 +58,16 @@ let Logins = {
 
   init: function () {
     window.addEventListener("popstate", this , false);
 
     Services.obs.addObserver(this, "passwordmgr-storage-changed", false);
 
     this._loadList(this._getLogins());
 
-    document.getElementById("copyusername-btn").addEventListener("click", this._copyUsername.bind(this), false);
-    document.getElementById("copypassword-btn").addEventListener("click", this._copyPassword.bind(this), false);
-    document.getElementById("details-header").addEventListener("click", this._openLink.bind(this), false);
-
     let filterInput = document.getElementById("filter-input");
     let filterContainer = document.getElementById("filter-input-container");
 
     filterInput.addEventListener("input", (event) => {
       // Stop any in-progress filter timer
       if (this._filterTimer) {
         clearTimeout(this._filterTimer);
         this._filterTimer = null;
@@ -120,52 +116,35 @@ let Logins = {
       let item = this._createItemForLogin(login);
       newList.appendChild(item);
     });
 
     list.parentNode.replaceChild(newList, list);
   },
 
   _showList: function () {
-    // Hide the detail page and show the list
-    let details = document.getElementById("login-details");
-    details.setAttribute("hidden", "true");
     let list = document.getElementById("logins-list");
     list.removeAttribute("hidden");
   },
 
-  _onPopState: function (event) {
-    // Called when back/forward is used to change the state of the page
-    if (event.state) {
-      // Show the detail page for an addon
-      this._showDetails(this._getElementForLogin(event.state.id));
-    } else {
-      // Clear any previous detail addon
-      let detailItem = document.querySelector("#login-details > .login-item");
-      detailItem.login = null;
-      this._showList();
-    }
-  },
-
   _onLoginClick: function (event) {
     let loginItem = event.currentTarget;
     let login = loginItem.login;
     if (!login) {
       debug("No login!");
       return;
     }
 
     let prompt = new Prompt({
       window: window,
     });
     let menuItems = [
       { label: gStringBundle.GetStringFromName("loginsMenu.showPassword") },
       { label: gStringBundle.GetStringFromName("loginsMenu.copyPassword") },
       { label: gStringBundle.GetStringFromName("loginsMenu.copyUsername") },
-      { label: gStringBundle.GetStringFromName("loginsMenu.details") },
       { label: gStringBundle.GetStringFromName("loginsMenu.delete") }
     ];
 
     prompt.setSingleChoiceItems(menuItems);
     prompt.show((data) => {
       // Switch on indices of buttons, as they were added when creating login item.
       switch (data.button) {
         case 0:
@@ -185,20 +164,16 @@ let Logins = {
           break;
         case 1:
           copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
           break;
         case 2:
           copyStringAndToast(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied"));
           break;
         case 3:
-          this._showDetails(loginItem);
-          history.pushState({ id: login.guid }, document.title);
-          break;
-        case 4:
           let confirmPrompt = new Prompt({
             window: window,
             message: gStringBundle.GetStringFromName("loginsDialog.confirmDelete"),
             buttons: [
               gStringBundle.GetStringFromName("loginsDialog.confirm"),
               gStringBundle.GetStringFromName("loginsDialog.cancel") ]
           });
           confirmPrompt.show((data) => {
@@ -260,22 +235,16 @@ let Logins = {
     descPart.className = "username";
     inner.appendChild(descPart);
 
     loginItem.appendChild(inner);
     loginItem.login = login;
     return loginItem;
   },
 
-  _getElementForLogin: function (login) {
-    let list = document.getElementById("logins-list");
-    let element = list.querySelector("div[loginID=" + login.quote() + "]");
-    return element;
-  },
-
   handleEvent: function (event) {
     switch (event.type) {
       case "popstate": {
         this._onPopState(event);
         break;
       }
       case "click": {
         this._onLoginClick(event);
@@ -289,71 +258,16 @@ let Logins = {
       case "passwordmgr-storage-changed": {
         // Reload logins content.
         this._loadList(this._getLogins());
         break;
       }
     }
   },
 
-  _showDetails: function (listItem) {
-    let detailItem = document.querySelector("#login-details > .login-item");
-    let login = detailItem.login = listItem.login;
-    let favicon = detailItem.querySelector(".icon");
-    favicon.style["background-image"] = listItem.querySelector(".icon").style["background-image"];
-    favicon.style.visibility = "visible";
-    document.getElementById("details-header").setAttribute("link", login.hostname);
-
-    document.getElementById("detail-hostname").textContent = login.hostname;
-    document.getElementById("detail-realm").textContent = login.httpRealm;
-    document.getElementById("detail-username").textContent = login.username;
-
-    // Borrowed from desktop Firefox: http://mxr.mozilla.org/mozilla-central/source/browser/base/content/urlbarBindings.xml#204
-    let matchedURL = login.hostname.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
-
-    let userInputs = [];
-    if (matchedURL) {
-      let [, , domain] = matchedURL;
-      userInputs = domain.split(".").filter(part => part.length > 3);
-    }
-
-    let lastChanged = new Date(login.QueryInterface(Ci.nsILoginMetaInfo).timePasswordChanged);
-    let days = Math.round((Date.now() - lastChanged) / 1000 / 60 / 60/ 24);
-    document.getElementById("detail-age").textContent = gStringBundle.formatStringFromName("loginsDetails.age", [days], 1);
-
-    let list = document.getElementById("logins-list");
-    list.setAttribute("hidden", "true");
-
-    let loginDetails = document.getElementById("login-details");
-    loginDetails.removeAttribute("hidden");
-
-    // Password details page is loaded.
-    let loadEvent = document.createEvent("Events");
-    loadEvent.initEvent("PasswordsDetailsLoad", true, false);
-    window.dispatchEvent(loadEvent);
-  },
-
-  _copyUsername: function() {
-    let detailItem = document.querySelector("#login-details > .login-item");
-    let login = detailItem.login;
-    copyStringAndToast(login.username, gStringBundle.GetStringFromName("loginsDetails.usernameCopied"));
-  },
-
-  _copyPassword: function() {
-    let detailItem = document.querySelector("#login-details > .login-item");
-    let login = detailItem.login;
-    copyStringAndToast(login.password, gStringBundle.GetStringFromName("loginsDetails.passwordCopied"));
-  },
-
-  _openLink: function (clickEvent) {
-    let url = clickEvent.currentTarget.getAttribute("link");
-    let BrowserApp = gChromeWin.BrowserApp;
-    BrowserApp.addTab(url, { selected: true, parentId: BrowserApp.selectedTab.id });
-  },
-
   _filter: function(event) {
     let value = event.target.value.toLowerCase();
     let logins = this._logins.filter((login) => {
       if (login.hostname.toLowerCase().indexOf(value) != -1) {
         return true;
       }
       if (login.username &&
           login.username.toLowerCase().indexOf(value) != -1) {
--- a/mobile/android/chrome/content/aboutLogins.xhtml
+++ b/mobile/android/chrome/content/aboutLogins.xhtml
@@ -24,31 +24,14 @@
     <div id="logins-header" class="header">
       <div>&aboutLogins.title;</div>
       <ul class="toolbar-buttons">
         <li id="filter-button"></li>
       </ul>
     </div>
     <div id="logins-list" class="list" hidden="true">
     </div>
-    <div id="login-details" class="list" hidden="true">
-      <div class="login-item list-item">
-        <div class="icon"/>
-        <div id="details-header" class="inner">
-          <div class="details">
-            <div id="detail-hostname" class="hostname"></div>
-            <div id="detail-realm" class="realm"></div>
-          </div>
-          <div id="detail-username" class="username"></div>
-          <div id="detail-age"></div>
-        </div>
-        <div class="buttons">
-          <button id="copyusername-btn">&aboutLogins.copyUsername;</button>
-          <button id="copypassword-btn">&aboutLogins.copyPassword;</button>
-        </div>
-      </div>
-    </div>
     <div id="filter-input-container" hidden="true">
       <input id="filter-input" type="search"/>
       <div id="filter-clear"></div>
     </div>
   </body>
 </html>
--- a/mobile/android/locales/en-US/chrome/aboutLogins.dtd
+++ b/mobile/android/locales/en-US/chrome/aboutLogins.dtd
@@ -1,8 +1,5 @@
 <!-- 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/. -->
 
 <!ENTITY aboutLogins.title                       "Logins">
-
-<!ENTITY aboutLogins.copyUsername                "Copy Username">
-<!ENTITY aboutLogins.copyPassword                "Copy Password">
--- a/mobile/android/locales/en-US/chrome/aboutLogins.properties
+++ b/mobile/android/locales/en-US/chrome/aboutLogins.properties
@@ -1,16 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loginsMenu.showPassword=Show password
 loginsMenu.copyPassword=Copy password
 loginsMenu.copyUsername=Copy username
-loginsMenu.details=Details
 loginsMenu.delete=Delete
 
 loginsDialog.confirmDelete=Delete this login?
 loginsDialog.copy=Copy
 loginsDialog.confirm=OK
 loginsDialog.cancel=Cancel
 
 loginsDetails.age=Age: %S days
--- a/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
+++ b/mobile/android/search/java/org/mozilla/search/PostSearchFragment.java
@@ -9,16 +9,17 @@ import java.net.URISyntaxException;
 import java.net.URL;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.search.providers.SearchEngine;
 
+import android.annotation.SuppressLint;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.provider.Settings;
 import android.support.v4.app.Fragment;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -39,16 +40,17 @@ public class PostSearchFragment extends 
     private SearchEngine engine;
 
     private ProgressBar progressBar;
     private WebView webview;
     private View errorView;
 
     private String resultsPageHost;
 
+    @SuppressLint("SetJavaScriptEnabled")
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
         View mainView = inflater.inflate(R.layout.search_fragment_post_search, container, false);
 
         progressBar = (ProgressBar) mainView.findViewById(R.id.progress_bar);
 
         webview = (WebView) mainView.findViewById(R.id.webview);
--- a/mobile/android/themes/core/aboutLogins.css
+++ b/mobile/android/themes/core/aboutLogins.css
@@ -4,24 +4,16 @@
 
 %filter substitution
 %include defines.inc
 
 .hidden {
   display: none;
 }
 
-.details {
-  width: 100%;
-}
-
-.details > div {
-  display: inline;
-}
-
 .username {
   width: 100%;
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
 }
 
 .hostname {
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -648,24 +648,16 @@ var LoginManagerContent = {
       return;
 
     var hostname = LoginUtils._getPasswordOrigin(doc.documentURI);
     if (!hostname) {
       log("(form submission ignored -- invalid hostname)");
       return;
     }
 
-    // Somewhat gross hack - we don't want to show the "remember password"
-    // notification on about:accounts for Firefox.
-    let topWin = win.top;
-    if (/^about:accounts($|\?)/i.test(topWin.document.documentURI)) {
-      log("(form submission ignored -- about:accounts)");
-      return;
-    }
-
     let formSubmitURL = LoginUtils._getActionOrigin(form);
     let messageManager = messageManagerFromWindow(win);
 
     let recipesArray = messageManager.sendSyncMessage("RemoteLogins:findRecipes", {
       formOrigin: hostname,
     })[0];
     let recipes = new Set(recipesArray);
 
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -933,16 +933,17 @@ LoginManagerPrompter.prototype = {
       browser,
       "password",
       this._getLocalizedString(initialMsgNames.prompt, [displayHost]),
       "password-notification-icon",
       mainAction,
       secondaryActions,
       {
         timeout: Date.now() + 10000,
+        origin: login.hostname,
         persistWhileVisible: true,
         passwordNotificationType: type,
         eventCallback: function (topic) {
           switch (topic) {
             case "showing":
               currentNotification = this;
               chromeDoc.getElementById("password-notification-username")
                        .addEventListener("input", onInput);
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -49,16 +49,17 @@ const PING_FORMAT_VERSION = 4;
 
 // Delay before intializing telemetry (ms)
 const TELEMETRY_DELAY = 60000;
 // Delay before initializing telemetry if we're testing (ms)
 const TELEMETRY_TEST_DELAY = 100;
 
 // Ping types.
 const PING_TYPE_MAIN = "main";
+const PING_TYPE_DELETION = "deletion";
 
 // Session ping reasons.
 const REASON_GATHER_PAYLOAD = "gather-payload";
 const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ClientID",
                                   "resource://gre/modules/ClientID.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
@@ -138,16 +139,17 @@ this.TelemetryController = Object.freeze
   initLogging: function() {
     configureLogging();
   },
   /**
    * Used only for testing purposes.
    */
   reset: function() {
     Impl._clientID = null;
+    Impl._detachObservers();
     TelemetryStorage.reset();
     TelemetrySend.reset();
 
     return this.setup();
   },
   /**
    * Used only for testing purposes.
    */
@@ -648,16 +650,18 @@ let Impl = {
       this._sessionRecorder.onStartup();
     }
 
     if (!this.enableTelemetryRecording()) {
       this._log.config("setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup.");
       return Promise.resolve();
     }
 
+    this._attachObservers();
+
     // For very short session durations, we may never load the client
     // id from disk.
     // We try to cache it in prefs to avoid this, even though this may
     // lead to some stale client ids.
     this._clientID = Preferences.get(PREF_CACHED_CLIENTID, null);
 
     // Delay full telemetry initialization to give the browser time to
     // run various late initializers. Otherwise our gathered memory
@@ -712,25 +716,26 @@ let Impl = {
 
   // Do proper shutdown waiting and cleanup.
   _cleanupOnShutdown: Task.async(function*() {
     if (!this._initialized) {
       return;
     }
 
     Preferences.ignore(PREF_BRANCH_LOG, configureLogging);
+    this._detachObservers();
 
     // Now do an orderly shutdown.
     try {
+      // Stop any ping sending.
+      yield TelemetrySend.shutdown();
+
       // First wait for clients processing shutdown.
       yield this._shutdownBarrier.wait();
 
-      // Stop any ping sending.
-      yield TelemetrySend.shutdown();
-
       // ... and wait for any outstanding async ping activity.
       yield this._connectionsBarrier.wait();
 
       // Perform final shutdown operations.
       yield TelemetryStorage.shutdown();
     } finally {
       // Reset state.
       this._initialized = false;
@@ -800,16 +805,47 @@ let Impl = {
       initStarted: this._initStarted,
       haveDelayedInitTask: !!this._delayedInitTask,
       shutdownBarrier: this._shutdownBarrier.state,
       connectionsBarrier: this._connectionsBarrier.state,
     };
   },
 
   /**
+   * Called whenever the FHR Upload preference changes (e.g. when user disables FHR from
+   * the preferences panel), this triggers sending the deletion ping.
+   */
+  _onUploadPrefChange: function() {
+    const uploadEnabled = Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
+    if (uploadEnabled) {
+      // There's nothing we should do if we are enabling upload.
+      return;
+    }
+    // Send the deletion ping.
+    this._log.trace("_onUploadPrefChange - Sending deletion ping.");
+    this.submitExternalPing(PING_TYPE_DELETION, {}, { addClientId: true });
+  },
+
+  _attachObservers: function() {
+    if (IS_UNIFIED_TELEMETRY) {
+      // Watch the FHR upload setting to trigger deletion pings.
+      Preferences.observe(PREF_FHR_UPLOAD_ENABLED, this._onUploadPrefChange, this);
+    }
+  },
+
+  /**
+   * Remove the preference observer to avoid leaks.
+   */
+  _detachObservers: function() {
+    if (IS_UNIFIED_TELEMETRY) {
+      Preferences.ignore(PREF_FHR_UPLOAD_ENABLED, this._onUploadPrefChange, this);
+    }
+  },
+
+  /**
    * Allows waiting for TelemetryControllers delayed initialization to complete.
    * This will complete before TelemetryController is shutting down.
    * @return {Promise} Resolved when delayed TelemetryController initialization completed.
    */
   promiseInitialized: function() {
     return this._delayedInitTaskDeferred.promise;
   },
 
--- a/toolkit/components/telemetry/TelemetrySend.jsm
+++ b/toolkit/components/telemetry/TelemetrySend.jsm
@@ -49,16 +49,18 @@ const TOPIC_IDLE_DAILY = "idle-daily";
 const TOPIC_QUIT_APPLICATION = "quit-application";
 
 // Whether the FHR/Telemetry unification features are enabled.
 // Changing this pref requires a restart.
 const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false);
 
 const PING_FORMAT_VERSION = 4;
 
+const PING_TYPE_DELETION = "deletion";
+
 // We try to spread "midnight" pings out over this interval.
 const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000;
 // We delay sending "midnight" pings on this client by this interval.
 const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;
 
 // Timeout after which we consider a ping submission failed.
 const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000;
 
@@ -88,16 +90,25 @@ let Policy = {
 /**
  * Determine if the ping has the new v4 ping format or the legacy v2 one or earlier.
  */
 function isV4PingFormat(aPing) {
   return ("id" in aPing) && ("application" in aPing) &&
          ("version" in aPing) && (aPing.version >= 2);
 }
 
+/**
+ * Check if the provided ping is a deletion ping.
+ * @param {Object} aPing The ping to check.
+ * @return {Boolean} True if the ping is a deletion ping, false otherwise.
+ */
+function isDeletionPing(aPing) {
+  return isV4PingFormat(aPing) && (aPing.type == PING_TYPE_DELETION);
+}
+
 function tomorrow(date) {
   let d = new Date(date);
   d.setDate(d.getDate() + 1);
   return d;
 }
 
 /**
  * @return {String} This returns a string with the gzip compressed data.
@@ -203,27 +214,34 @@ this.TelemetrySend = {
   },
 
   /**
    * Only used in tests.
    */
   setServer: function(server) {
     return TelemetrySendImpl.setServer(server);
   },
+
+  /**
+   * Only used in tests to wait on outgoing pending pings.
+   */
+  testWaitOnOutgoingPings: function() {
+    return TelemetrySendImpl.promisePendingPingActivity();
+  },
 };
 
 let TelemetrySendImpl = {
   _sendingEnabled: false,
   _logger: null,
   // Timer for scheduled ping sends.
   _pingSendTimer: null,
   // This tracks all pending ping requests to the server.
   _pendingPingRequests: new Map(),
-  // This is a private barrier blocked by pending async ping activity (sending & saving).
-  _connectionsBarrier: new AsyncShutdown.Barrier("TelemetrySend: Waiting for pending ping activity"),
+  // This tracks all the pending async ping activity.
+  _pendingPingActivity: new Set(),
   // This is true when running in the test infrastructure.
   _testMode: false,
 
   // Count of pending pings we discarded for age on startup.
   _discardedPingsCount: 0,
   // Count of pending pings we evicted for being over the limit on startup.
   _evictedPingsCount: 0,
   // Count of pending pings that were overdue.
@@ -258,23 +276,31 @@ let TelemetrySendImpl = {
     this._discardedPingsCount = 0;
     this._evictedPingsCount = 0;
 
     Services.obs.addObserver(this, TOPIC_IDLE_DAILY, false);
 
     this._server = Preferences.get(PREF_SERVER, undefined);
 
     // If any pings were submitted before the delayed init finished
-    // we will send them now.
-    yield this._sendPersistedPings();
+    // we will send them now. We don't wait on sending as this could take some time.
+    this._sendPersistedPings();
 
     // Check the pending pings on disk now.
-    yield this._checkPendingPings();
+    let haveOverduePings = yield this._checkPendingPings();
+    if (haveOverduePings) {
+      // We don't wait on sending as this could take some time.
+      this._sendPersistedPings();
+    }
   }),
 
+  /**
+   * Discard old pings from the pending pings and detect overdue ones.
+   * @return {Boolean} True if we have overdue pings, false otherwise.
+   */
   _checkPendingPings: Task.async(function*() {
     // Scan the pending pings - that gives us a list sorted by last modified, descending.
     let infos = yield TelemetryStorage.loadPendingPingList();
     this._log.info("_checkPendingPings - pending ping count: " + infos.length);
     if (infos.length == 0) {
       this._log.trace("_checkPendingPings - no pending pings");
       return;
     }
@@ -314,39 +340,40 @@ let TelemetrySendImpl = {
     Services.telemetry.getHistogramById('TELEMETRY_FILES_EVICTED')
                       .add(evictedCount);
 
     // Check for overdue pings.
     const overduePings = infos.filter((info) =>
       (now.getTime() - info.lastModificationDate) > OVERDUE_PING_FILE_AGE);
     this._overduePingCount = overduePings.length;
 
-
     if (overduePings.length > 0) {
       this._log.trace("_checkForOverduePings - Have " + overduePings.length +
-                       " overdue pending pings, sending " + infos.length +
+                       " overdue pending pings, ready to send " + infos.length +
                        " pings now.");
-      yield this._sendPersistedPings();
+      return true;
     }
+
+    return false;
    }),
 
   shutdown: Task.async(function*() {
     for (let topic of this.OBSERVER_TOPICS) {
       Services.obs.removeObserver(this, topic);
     }
 
     // We can't send anymore now.
     this._sendingEnabled = false;
 
     // Clear scheduled ping sends.
     this._clearPingSendTimer();
     // Cancel any outgoing requests.
     yield this._cancelOutgoingRequests();
     // ... and wait for any outstanding async ping activity.
-    yield this._connectionsBarrier.wait();
+    yield this.promisePendingPingActivity();
   }),
 
   reset: function() {
     this._log.trace("reset");
 
     this._overduePingCount = 0;
     this._discardedPingsCount = 0;
     this._evictedPingsCount = 0;
@@ -365,17 +392,17 @@ let TelemetrySendImpl = {
     switch(topic) {
     case TOPIC_IDLE_DAILY:
       this._sendPersistedPings();
       break;
     }
   },
 
   submitPing: function(ping) {
-    if (!this._canSend()) {
+    if (!this._canSend(ping)) {
       this._log.trace("submitPing - Telemetry is not allowed to send pings.");
       return Promise.resolve();
     }
 
     // Check if we can send pings now.
     const now = Policy.now();
     const nextPingSendTime = this._getNextPingSendTime(now);
     const throttled = (nextPingSendTime > now.getTime());
@@ -545,17 +572,17 @@ let TelemetrySendImpl = {
       }
     }
 
     let slug = pathComponents.join("/");
     return "/submit/telemetry/" + slug;
   },
 
   _doPing: function(ping, id, isPersisted) {
-    if (!this._canSend()) {
+    if (!this._canSend(ping)) {
       // We can't send the pings to the server, so don't try to.
       this._log.trace("_doPing - Sending is disabled.");
       return Promise.resolve();
     }
 
     this._log.trace("_doPing - server: " + this._server + ", persisted: " + isPersisted +
                     ", id: " + id);
     const isNewPing = isV4PingFormat(ping);
@@ -649,30 +676,36 @@ let TelemetrySendImpl = {
     startTime = new Date();
     request.send(payloadStream);
 
     return deferred.promise;
   },
 
   /**
    * Check if pings can be sent to the server. If FHR is not allowed to upload,
-   * pings are not sent to the server (Telemetry is a sub-feature of FHR).
+   * pings are not sent to the server (Telemetry is a sub-feature of FHR). If trying
+   * to send a deletion ping, don't block it.
    * If unified telemetry is off, don't send pings if Telemetry is disabled.
    *
+   * @param {Object} [ping=null] A ping to be checked.
    * @return {Boolean} True if pings can be send to the servers, false otherwise.
    */
-  _canSend: function() {
+  _canSend: function(ping = null) {
     // We only send pings from official builds, but allow overriding this for tests.
     if (!Telemetry.isOfficialTelemetry && !this._testMode) {
       return false;
     }
 
     // With unified Telemetry, the FHR upload setting controls whether we can send pings.
     // The Telemetry pref enables sending extended data sets instead.
     if (IS_UNIFIED_TELEMETRY) {
+      // Deletion pings are sent even if the upload is disabled.
+      if (ping && isDeletionPing(ping)) {
+        return true;
+      }
       return Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
     }
 
     // Without unified Telemetry, the Telemetry enabled pref controls ping sending.
     return Preferences.get(PREF_TELEMETRY_ENABLED, false);
   },
 
   _reschedulePingSendTimer: function(timestamp) {
@@ -688,11 +721,25 @@ let TelemetrySendImpl = {
     }
   },
 
   /**
    * Track any pending ping send and save tasks through the promise passed here.
    * This is needed to block shutdown on any outstanding ping activity.
    */
   _trackPendingPingTask: function (promise) {
-    this._connectionsBarrier.client.addBlocker("Waiting for ping task", promise);
+    let clear = () => this._pendingPingActivity.delete(promise);
+    promise.then(clear, clear);
+    this._pendingPingActivity.add(promise);
+  },
+
+  /**
+   * Return a promise that allows to wait on pending pings.
+   * @return {Object<Promise>} A promise resolved when all the pending pings promises
+   *         are resolved.
+   */
+  promisePendingPingActivity: function () {
+    this._log.trace("promisePendingPingActivity - Waiting for ping task");
+    return Promise.all([for (p of this._pendingPingActivity) p.catch(ex => {
+      this._log.error("promisePendingPingActivity - ping activity had an error", ex);
+    })]);
   },
 };
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/deletion-ping.rst
@@ -0,0 +1,17 @@
+
+"deletion" ping
+===============
+
+This ping is generated when a user turns off FHR upload from the Preferences panel, changing the related ``datareporting.healthreport.uploadEnabled`` preference. This requests that all associated data from that user be deleted.
+
+This ping contains the client id and no environment data.
+
+Structure::
+
+    {
+      version: 4,
+      type: "deletion",
+      ... common ping data
+      clientId: <UUID>,
+      payload: { }
+    }
\ No newline at end of file
--- a/toolkit/components/telemetry/docs/index.rst
+++ b/toolkit/components/telemetry/docs/index.rst
@@ -14,9 +14,10 @@ Client-side, this consists of:
 
 .. toctree::
    :maxdepth: 2
 
    pings
    common-ping
    environment
    main-ping
+   deletion-ping
    preferences
--- a/toolkit/components/telemetry/docs/pings.rst
+++ b/toolkit/components/telemetry/docs/pings.rst
@@ -26,19 +26,20 @@ The telemetry server team is working tow
 * `5XX` - there was a server-side error, the client should try to resubmit later
 
 Ping types
 ==========
 
 * :doc:`main <main-ping>` - contains the information collected by Telemetry (Histograms, hang stacks, ...)
 * :doc:`saved-session <main-ping>` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side. This is temporary and will be removed soon.
 * :doc:`crash <crash-ping>` - a ping that is captured and sent after Firefox crashes.
+* :doc:`uitour-ping` - a ping submitted via the UITour API
 * ``activation`` - *planned* - sent right after installation or profile creation
 * ``upgrade`` - *planned* - sent right after an upgrade
-* ``deletion`` - *planned* - on opt-out we may have to tell the server to delete user data
+* :doc:`deletion <deletion-ping>` - sent when FHR upload is disabled, requesting deletion of the data associated with this user
 
 Archiving
 =========
 
 When archiving is enabled through the relative preference, pings submitted to ``TelemetryController`` are also stored locally in the user profile directory, in `<profile-dir>/datareporting/archived`.
 
 To allow for cheaper lookup of archived pings, storage follows a specific naming scheme for both the directory and the ping file name: `<YYYY-MM>/<timestamp>.<UUID>.<type>.json`.
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/uitour-ping.rst
@@ -0,0 +1,24 @@
+
+"uitour-tag" ping
+=================
+
+This ping is submitted via the UITour setTreatmentTag API. It may be used by
+the tour to record what settings were made by a user or to track the result of
+A/B experiments.
+
+The client ID is submitted with this ping.
+
+Structure::
+
+    {
+      version: 1,
+      type: "uitour-tag",
+      clientId: <string>,
+      payload: {
+        tagName: <string>,
+        tagValue: <string>
+      }
+    }
+
+See also: :doc:`common ping fields <common-ping>`
+
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js
@@ -16,16 +16,17 @@ Cu.import("resource://gre/modules/Teleme
 Cu.import("resource://gre/modules/TelemetryStorage.jsm", this);
 Cu.import("resource://gre/modules/TelemetrySend.jsm", this);
 Cu.import("resource://gre/modules/TelemetryArchive.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Preferences.jsm");
 
 const PING_FORMAT_VERSION = 4;
+const DELETION_PING_TYPE = "deletion";
 const TEST_PING_TYPE = "test-ping-type";
 
 const PLATFORM_VERSION = "1.9.2";
 const APP_VERSION = "1";
 const APP_NAME = "XPCShell";
 
 const PREF_BRANCH = "toolkit.telemetry.";
 const PREF_ENABLED = PREF_BRANCH + "enabled";
@@ -169,16 +170,35 @@ add_task(function* test_simplePing() {
   // Make sure the version in the query string matches the new ping format version.
   let params = request.queryString.split("&");
   Assert.ok(params.find(p => p == ("v=" + PING_FORMAT_VERSION)));
 
   let ping = decodeRequestPayload(request);
   checkPingFormat(ping, TEST_PING_TYPE, false, false);
 });
 
+add_task(function* test_deletionPing() {
+  const isUnified = Preferences.get(PREF_UNIFIED, false);
+  if (!isUnified) {
+    // Skipping the test if unified telemetry is off, as no deletion ping will
+    // be generated.
+    return;
+  }
+
+  // Disable FHR upload: this should trigger a deletion ping.
+  Preferences.set(PREF_FHR_UPLOAD_ENABLED, false);
+
+  let request = yield gRequestIterator.next();
+  let ping = decodeRequestPayload(request);
+  checkPingFormat(ping, DELETION_PING_TYPE, true, false);
+
+  // Restore FHR Upload.
+  Preferences.set(PREF_FHR_UPLOAD_ENABLED, true);
+});
+
 add_task(function* test_pingHasClientId() {
   // Send a ping with a clientId.
   yield sendPing(true, false);
 
   let request = yield gRequestIterator.next();
   let ping = decodeRequestPayload(request);
   checkPingFormat(ping, TEST_PING_TYPE, true, false);
 
@@ -226,16 +246,24 @@ add_task(function* test_archivePings() {
 
   // Disable ping upload so that pings don't get sent.
   // With unified telemetry the FHR upload pref controls this,
   // with non-unified telemetry the Telemetry enabled pref.
   const isUnified = Preferences.get(PREF_UNIFIED, false);
   const uploadPref = isUnified ? PREF_FHR_UPLOAD_ENABLED : PREF_ENABLED;
   Preferences.set(uploadPref, false);
 
+  // If we're using unified telemetry, disabling ping upload will generate a "deletion"
+  // ping. Catch it.
+  if (isUnified) {
+    let request = yield gRequestIterator.next();
+    let ping = decodeRequestPayload(request);
+    checkPingFormat(ping, DELETION_PING_TYPE, true, false);
+  }
+
   // Register a new Ping Handler that asserts if a ping is received, then send a ping.
   registerPingHandler(() => Assert.ok(false, "Telemetry must not send pings if not allowed to."));
   let pingId = yield sendPing(true, true);
 
   // Check that the ping was archived, even with upload disabled.
   let ping = yield TelemetryArchive.promiseArchivedPingById(pingId);
   Assert.equal(ping.id, pingId, "TelemetryController should still archive pings.");
 
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
@@ -314,16 +314,17 @@ add_task(function* test_overdue_old_form
   yield TelemetryStorage.savePing(PING_NO_PAYLOAD, true);
   yield TelemetryStorage.savePingToFile(PING_NO_SLUG, PING_FILES_PATHS[3], true);
 
   for (let f in PING_FILES_PATHS) {
     yield File.setDates(PING_FILES_PATHS[f], null, Date.now() - OVERDUE_PING_FILE_AGE);
   }
 
   yield TelemetryController.reset();
+  yield TelemetrySend.testWaitOnOutgoingPings();
   assertReceivedPings(OLD_FORMAT_PINGS);
 
   // |TelemetryStorage.cleanup| doesn't know how to remove a ping with no slug or id,
   // so remove it manually so that the next test doesn't fail.
   yield OS.File.remove(PING_FILES_PATHS[3]);
 
   yield clearPendingPings();
 });
@@ -340,16 +341,17 @@ add_task(function* test_overdue_pings_tr
     { num: OVERDUE_PINGS, age: OVERDUE_PING_FILE_AGE },
   ];
   let pings = yield createSavedPings(pingTypes);
   let recentPings = pings.slice(0, RECENT_PINGS);
   let expiredPings = pings.slice(RECENT_PINGS, RECENT_PINGS + EXPIRED_PINGS);
   let overduePings = pings.slice(-OVERDUE_PINGS);
 
   yield TelemetryController.reset();
+  yield TelemetrySend.testWaitOnOutgoingPings();
   assertReceivedPings(TOTAL_EXPECTED_PINGS);
 
   yield assertNotSaved(recentPings);
   yield assertNotSaved(expiredPings);
   yield assertNotSaved(overduePings);
 
   yield clearPendingPings();
 });
@@ -393,16 +395,17 @@ add_task(function* test_overdue_old_form
     // Make sure the version in the query string matches the old ping format version.
     let params = request.queryString.split("&");
     Assert.ok(params.find(p => p == "v=1"));
 
     receivedPings++;
   });
 
   yield TelemetryController.reset();
+  yield TelemetrySend.testWaitOnOutgoingPings();
   Assert.equal(receivedPings, 1, "We must receive a ping in the old format.");
 
   yield clearPendingPings();
 });
 
 add_task(function* teardown() {
   yield stopHttpServer();
 });
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -207,16 +207,19 @@ PopupNotifications.prototype = {
    * @param secondaryActions
    *        An optional JavaScript array describing the notification's alternate
    *        actions. The array should contain objects with the same properties
    *        as mainAction. These are used to populate the notification button's
    *        dropdown menu.
    * @param options
    *        An options JavaScript object holding additional properties for the
    *        notification. The following properties are currently supported:
+   *        origin:      A string representing the origin of the site presenting
+   *                     a notification so it can be shown to the user (possibly
+   *                     with a favicon). e.g. https://example.com:8080
    *        persistence: An integer. The notification will not automatically
    *                     dismiss for this many page loads.
    *        timeout:     A time in milliseconds. The notification will not
    *                     automatically dismiss before this time.
    *        persistWhileVisible:
    *                     A boolean. If true, a visible notification will always
    *                     persist across location changes.
    *        dismissed:   Whether the notification should be added as a dismissed