Merge m-c to inbound a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Thu, 26 Feb 2015 18:52:43 -0800
changeset 231113 6bfb780221debb09d3cf094a4793988a6bd3c6cb
parent 231112 577937f4dce7cfcf2ec5d3485f8bdb0ba42f3952 (current diff)
parent 231079 c7968255c1eadfa38a97c69324377cf671e933a5 (diff)
child 231114 81589d78bc6fb391417a9b96f76e445d3335a2f2
push id28344
push userryanvm@gmail.com
push dateFri, 27 Feb 2015 18:20:08 +0000
treeherdermozilla-central@9dd9d1e5b43c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound a=merge CLOSED TREE
browser/app/profile/firefox.js
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
--- 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="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- 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="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <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"/>
@@ -130,12 +130,12 @@
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="125ccf9bd5986c7728ea44508b3e1d1185ac028b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c5f8d282efe4a4e8b1e31a37300944e338e60e4f"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d19dad5844e8803d5e88d1577a2742b4f5cbc467"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="84395037a7a04546e8ef7cb81572eb516b85562b"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
--- 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="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="97c3d9b8b87774ca7a08c89145e95b55652459ef"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
@@ -141,13 +141,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="a74adcf8d88320d936daa8d20ce88ca0107fb916"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
   <project name="platform/system/core" path="system/core" revision="42839aedcf70bf6bc92a3b7ea4a5cc9bf9aef3f9"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d19dad5844e8803d5e88d1577a2742b4f5cbc467"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="84395037a7a04546e8ef7cb81572eb516b85562b"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="d8952a42771045fca73ec600e2b42a4c7129d723"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4c187c1f3a0dffd8e51a961735474ea703535b99"/>
 </manifest>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
@@ -140,13 +140,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="5e110615212302c5d798a3c223dcee458817651c"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="fa9ffd47948eb24466de227e48fe9c4a7c5e7711"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="cd76b19aafd4229ccf83853d02faef8c51ca8b34"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
   <project name="platform/system/core" path="system/core" revision="adc485d8755af6a61641d197de7cfef667722580"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d19dad5844e8803d5e88d1577a2742b4f5cbc467"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="84395037a7a04546e8ef7cb81572eb516b85562b"/>
   <project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4ae5df252123591d5b941191790e7abed1bce5a4"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="ce18b47b4a4f93a581d672bbd5cb6d12fe796ca9"/>
 </manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "22230c9a340a0a2c3af73320cf47e1cb544517ad", 
+        "git_revision": "7512026a377271a0cade12d70846557f0bc7781c", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "298fece36f2564e042e347a48fcc104aca78e3b5", 
+    "revision": "53713e514b92cccfd55404cebd2b627cb9804bc2", 
     "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="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
   <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"/>
@@ -125,17 +125,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d19dad5844e8803d5e88d1577a2742b4f5cbc467"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="84395037a7a04546e8ef7cb81572eb516b85562b"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="22230c9a340a0a2c3af73320cf47e1cb544517ad"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="7512026a377271a0cade12d70846557f0bc7781c"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3905180d9e2dbe03d7ce3b3a6debe4d4adc938d1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c42985975f2bbc42859b9136ed348186d989b93d"/>
   <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="a1ddea3133e0807350326cee5dcf0d06fad00c08"/>
@@ -136,26 +136,26 @@
   <project name="platform/system/vold" path="system/vold" revision="eb59d2afd5f6e1cbab2ef985a8dd1c7105b499e8"/>
   <project name="platform_frameworks_av" path="frameworks/av" remote="b2g" revision="ea531874885eed7f68802048218ed86dde927f58"/>
   <project name="platform_frameworks_base" path="frameworks/base" remote="b2g" revision="df7e0cfbbc7e954ed26c73ac17832a5ff035f046"/>
   <project name="platform_frameworks_wilhelm" path="frameworks/wilhelm" remote="b2g" revision="73f7e7f12c8c5459f7a39e2fa343f083c942864d"/>
   <project name="platform_system_core" path="system/core" remote="b2g" revision="4df51d9abf6cc9a6ec49b965e621699e0e6dc4fb"/>
   <default remote="caf" revision="refs/tags/android-5.0.0_r6" sync-j="4"/>
   <!-- Nexus 5 specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="ba62cc8b78c30d36181b8060a2016cc8da166236"/>
-  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="b765862a60e0063fc2451f5e3e137247dd9838c3"/>
+  <project name="device-hammerhead" path="device/lge/hammerhead" remote="b2g" revision="dc525eeb67301a44f514a7ac40a59ec592b34e31"/>
   <project name="device/qcom/common" path="device/qcom/common" remote="caf" revision="3697e5acf25629b82658334e3f8ee3b6df5becab"/>
   <project name="device_lge_hammerhead-kernel" path="device/lge/hammerhead-kernel" remote="b2g" revision="1268f640184df5ef759ada669f101a613451673a"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="0cb8574d338bf9f15b45ace7c08ad6deae9673ee"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="16abda2258c9aa1ed78f00bb0a9b2b43b4cb919e"/>
   <project name="platform/hardware/broadcom/libbt" path="hardware/broadcom/libbt" revision="3e856528121ae0af0ca26c97cb563160c7e16d85"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="d9e716e14e685f4fc124ae591a1cd0899bc4d7b5"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="505aa92674337b76ce7d038800f2a6d7dc340ac9"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="0a21f566d8c9b01b9754d639e13358bdcb6c7650"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="028649652cd8f8f18cfb47d34bd78c435eb030ca"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="758a80fbb178b5663d4edbb46944b2dc553cb1ca"/>
   <project name="platform/hardware/qcom/msm8x74" path="hardware/qcom/msm8x74" revision="aa0124820e22302149b1f2db603a9a72f1972527"/>
   <project name="platform/hardware/qcom/power" path="hardware/qcom/power" revision="37499eb89f31233135ca73b830b067ab24dc1be2"/>
   <project name="platform/hardware/qcom/sensors" path="hardware/qcom/sensors" revision="3724fd91ef5183684d97e2bf1d7ff948faabe090"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2e54754cc0529d26ccac37ed291600048adbf6c0"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="71dfa8228ad0d6cdf6bac0426ac59404ab74b7f3"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d19dad5844e8803d5e88d1577a2742b4f5cbc467"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="84395037a7a04546e8ef7cb81572eb516b85562b"/>
 </manifest>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1572,16 +1572,19 @@ pref("devtools.browserconsole.filter.war
 pref("devtools.browserconsole.filter.info", true);
 pref("devtools.browserconsole.filter.log", true);
 pref("devtools.browserconsole.filter.secerror", true);
 pref("devtools.browserconsole.filter.secwarn", true);
 
 // Text size in the Web Console. Use 0 for the system default size.
 pref("devtools.webconsole.fontSize", 0);
 
+// Max number of inputs to store in web console history.
+pref("devtools.webconsole.inputHistoryCount", 50);
+
 // Persistent logging: |true| if you want the Web Console to keep all of the
 // logged messages after reloading the page, |false| if you want the output to
 // be cleared each time page navigation happens.
 pref("devtools.webconsole.persistlog", false);
 
 // Web Console timestamp: |true| if you want the logs and instructions
 // in the Web Console to display a timestamp, or |false| to not display
 // any timestamps.
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -6,33 +6,45 @@ Cu.import("resource://gre/modules/XPCOMU
 
 #ifdef MOZ_SERVICES_CLOUDSYNC
 XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
                                   "resource://gre/modules/CloudSync.jsm");
 #else
 let CloudSync = null;
 #endif
 
+XPCOMUtils.defineLazyModuleGetter(this, "ReadingListScheduler",
+                                  "resource:///modules/readinglist/Scheduler.jsm");
+
 // gSyncUI handles updating the tools menu and displaying notifications.
 let gSyncUI = {
   _obs: ["weave:service:sync:start",
+         "weave:service:sync:finish",
+         "weave:service:sync:error",
          "weave:service:quota:remaining",
          "weave:service:setup-complete",
          "weave:service:login:start",
          "weave:service:login:finish",
+         "weave:service:login:error",
          "weave:service:logout:finish",
          "weave:service:start-over",
          "weave:service:start-over:finish",
          "weave:ui:login:error",
          "weave:ui:sync:error",
          "weave:ui:sync:finish",
          "weave:ui:clear-error",
+
+         "readinglist:sync:start",
+         "readinglist:sync:finish",
+         "readinglist:sync:error",
   ],
 
   _unloaded: false,
+  // The number of "active" syncs - while this is non-zero, our button will spin
+  _numActiveSyncTasks: 0,
 
   init: function () {
     Cu.import("resource://services-common/stringbundle.js");
 
     // Proceed to set up the UI if Sync has already started up.
     // Otherwise we'll do it when Sync is firing up.
     let xps = Components.classes["@mozilla.org/weave/service;1"]
                                 .getService(Components.interfaces.nsISupports)
@@ -90,148 +102,182 @@ let gSyncUI = {
     // notificationbox will listen to observers from now on.
     Services.obs.removeObserver(this, "weave:notification:added");
     let idx = this._obs.indexOf("weave:notification:added");
     if (idx >= 0) {
       this._obs.splice(idx, 1);
     }
   },
 
-  _needsSetup: function SUI__needsSetup() {
+  _needsSetup() {
     // We want to treat "account needs verification" as "needs setup". So
     // "reach in" to Weave.Status._authManager to check whether we the signed-in
     // user is verified.
     // Referencing Weave.Status spins a nested event loop to initialize the
     // authManager, so this should always return a value directly.
     // This only applies to fxAccounts-based Sync.
-    if (Weave.Status._authManager._signedInUser) {
-      // If we have a signed in user already, and that user is not verified,
-      // revert to the "needs setup" state.
-      if (!Weave.Status._authManager._signedInUser.verified) {
-        return true;
-      }
+    if (Weave.Status._authManager._signedInUser !== undefined) {
+      // So we are using Firefox accounts - in this world, checking Sync isn't
+      // enough as reading list may be configured but not Sync.
+      // We consider ourselves setup if we have a verified user.
+      // XXX - later we should consider checking preferences to ensure at least
+      // one engine is enabled?
+      return !Weave.Status._authManager._signedInUser ||
+             !Weave.Status._authManager._signedInUser.verified;
     }
 
+    // So we are using legacy sync, and reading-list isn't supported for such
+    // users, so check sync itself.
     let firstSync = "";
     try {
       firstSync = Services.prefs.getCharPref("services.sync.firstSync");
     } catch (e) { }
 
     return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
            firstSync == "notReady";
   },
 
   _loginFailed: function () {
-    return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+    return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED ||
+           ReadingListScheduler.state == ReadingListScheduler.STATE_ERROR_AUTHENTICATION;
   },
 
   updateUI: function SUI_updateUI() {
     let needsSetup = this._needsSetup();
     let loginFailed = this._loginFailed();
 
     // Start off with a clean slate
     document.getElementById("sync-reauth-state").hidden = true;
     document.getElementById("sync-setup-state").hidden = true;
     document.getElementById("sync-syncnow-state").hidden = true;
 
     if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
       document.getElementById("sync-syncnow-state").hidden = false;
     } else if (loginFailed) {
       document.getElementById("sync-reauth-state").hidden = false;
+      this.showLoginError();
     } else if (needsSetup) {
       document.getElementById("sync-setup-state").hidden = false;
     } else {
       document.getElementById("sync-syncnow-state").hidden = false;
     }
 
     if (!gBrowser)
       return;
 
     let syncButton = document.getElementById("sync-button");
-    if (syncButton) {
-      syncButton.removeAttribute("status");
-    }
-    let panelHorizontalButton = document.getElementById("PanelUI-fxa-status");
-    if (panelHorizontalButton) {
-      panelHorizontalButton.removeAttribute("syncstatus");
-    }
-
     if (needsSetup && syncButton)
       syncButton.removeAttribute("tooltiptext");
 
     this._updateLastSyncTime();
   },
 
 
   // Functions called by observers
-  onActivityStart: function SUI_onActivityStart() {
+  onActivityStart() {
     if (!gBrowser)
       return;
 
-    let button = document.getElementById("sync-button");
-    if (button) {
-      button.setAttribute("status", "active");
+    this.log.debug("onActivityStart with numActive", this._numActiveSyncTasks);
+    if (++this._numActiveSyncTasks == 1) {
+      let button = document.getElementById("sync-button");
+      if (button) {
+        button.setAttribute("status", "active");
+      }
+      button = document.getElementById("PanelUI-fxa-status");
+      if (button) {
+        button.setAttribute("syncstatus", "active");
+      }
     }
-    button = document.getElementById("PanelUI-fxa-status");
-    if (button) {
-      button.setAttribute("syncstatus", "active");
+  },
+
+  onActivityStop() {
+    if (!gBrowser)
+      return;
+    this.log.debug("onActivityStop with numActive", this._numActiveSyncTasks);
+    if (--this._numActiveSyncTasks) {
+      if (this._numActiveSyncTasks < 0) {
+        // This isn't particularly useful (it seems more likely we'll set a
+        // "start" without a "stop" meaning it forever remains > 0) but it
+        // might offer some value...
+        this.log.error("mismatched onActivityStart/Stop calls",
+                       new Error("active=" + this._numActiveSyncTasks));
+      }
+      return; // active tasks are still ongoing...
+    }
+
+    let syncButton = document.getElementById("sync-button");
+    if (syncButton) {
+      syncButton.removeAttribute("status");
+    }
+    let panelHorizontalButton = document.getElementById("PanelUI-fxa-status");
+    if (panelHorizontalButton) {
+      panelHorizontalButton.removeAttribute("syncstatus");
     }
   },
 
   onLoginFinish: function SUI_onLoginFinish() {
     // Clear out any login failure notifications
     let title = this._stringBundle.GetStringFromName("error.login.title");
     this.clearError(title);
   },
 
   onSetupComplete: function SUI_onSetupComplete() {
     this.onLoginFinish();
   },
 
   onLoginError: function SUI_onLoginError() {
+    // Note: This is used for *both* Sync and ReadingList login errors.
     // if login fails, any other notifications are essentially moot
     Weave.Notifications.removeAll();
 
     // if we haven't set up the client, don't show errors
     if (this._needsSetup()) {
       this.updateUI();
       return;
     }
     // if we are still waiting for the identity manager to initialize, don't show errors
     if (Weave.Status.login == Weave.LOGIN_FAILED_NOT_READY) {
       this.updateUI();
       return;
     }
+    this.showLoginError();
+    this.updateUI();
+  },
 
+  showLoginError() {
+    // Note: This is used for *both* Sync and ReadingList login errors.
     let title = this._stringBundle.GetStringFromName("error.login.title");
 
     let description;
-    if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+    if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE ||
+        this.isProlongedReadingListError()) {
+      this.log.debug("showLoginError has a prolonged login error");
       // Convert to days
       let lastSync =
         Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
       description =
         this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
     } else {
       let reason = Weave.Utils.getErrorString(Weave.Status.login);
       description =
         this._stringBundle.formatStringFromName("error.sync.description", [reason], 1);
+      this.log.debug("showLoginError has a non-prolonged error", reason);
     }
 
     let buttons = [];
     buttons.push(new Weave.NotificationButton(
       this._stringBundle.GetStringFromName("error.login.prefs.label"),
       this._stringBundle.GetStringFromName("error.login.prefs.accesskey"),
       function() { gSyncUI.openPrefs(); return true; }
     ));
 
     let notification = new Weave.Notification(title, description, null,
                                               Weave.Notifications.PRIORITY_WARNING, buttons);
     Weave.Notifications.replaceTitle(notification);
-    this.updateUI();
   },
 
   onLogout: function SUI_onLogout() {
     this.updateUI();
   },
 
   onStartOver: function SUI_onStartOver() {
     this.clearError();
@@ -266,16 +312,17 @@ let gSyncUI = {
   doSync: function SUI_doSync() {
     let needsSetup = this._needsSetup();
 
     if (!needsSetup) {
       setTimeout(function () Weave.Service.errorHandler.syncAndReportErrors(), 0);
     }
 
     Services.obs.notifyObservers(null, "cloudsync:user-sync", null);
+    Services.obs.notifyObservers(null, "readinglist:user-sync", null);
   },
 
   handleToolbarButton: function SUI_handleStatusbarButton() {
     if (this._needsSetup())
       this.openSetup();
     else
       this.doSync();
   },
@@ -362,45 +409,115 @@ let gSyncUI = {
       return;
 
     let syncButton = document.getElementById("sync-button");
     if (!syncButton)
       return;
 
     let lastSync;
     try {
-      lastSync = Services.prefs.getCharPref("services.sync.lastSync");
+      lastSync = new Date(Services.prefs.getCharPref("services.sync.lastSync"));
+    }
+    catch (e) { };
+    // and reading-list time - we want whatever one is the most recent.
+    try {
+      let lastRLSync = new Date(Services.prefs.getCharPref("readinglist.scheduler.lastSync"));
+      if (!lastSync || lastRLSync > lastSync) {
+        lastSync = lastRLSync;
+      }
     }
     catch (e) { };
     if (!lastSync || this._needsSetup()) {
       syncButton.removeAttribute("tooltiptext");
       return;
     }
 
     // Show the day-of-week and time (HH:MM) of last sync
-    let lastSyncDate = new Date(lastSync).toLocaleFormat("%a %H:%M");
+    let lastSyncDateString = lastSync.toLocaleFormat("%a %H:%M");
     let lastSyncLabel =
-      this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDate], 1);
+      this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
 
     syncButton.setAttribute("tooltiptext", lastSyncLabel);
   },
 
   clearError: function SUI_clearError(errorString) {
     Weave.Notifications.removeAll(errorString);
     this.updateUI();
   },
 
   onSyncFinish: function SUI_onSyncFinish() {
     let title = this._stringBundle.GetStringFromName("error.sync.title");
 
     // Clear out sync failures on a successful sync
     this.clearError(title);
   },
 
+  // Return true if the reading-list is in a "prolonged" error state. That
+  // engine doesn't impose what that means, so calculate it here. For
+  // consistency, we just use the sync prefs.
+  isProlongedReadingListError() {
+    let lastSync, threshold, prolonged;
+    try {
+      lastSync = new Date(Services.prefs.getCharPref("readinglist.scheduler.lastSync"));
+      threshold = new Date(Date.now() - Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout"));
+      prolonged = lastSync <= threshold;
+    } catch (ex) {
+      // no pref, assume not prolonged.
+      prolonged = false;
+    }
+    this.log.debug("isProlongedReadingListError has last successful sync at ${lastSync}, threshold is ${threshold}, prolonged=${prolonged}",
+                   {lastSync, threshold, prolonged});
+    return prolonged;
+  },
+
+  onRLSyncError() {
+    // Like onSyncError, but from the reading-list engine.
+    // However, the current UX around Sync is that error notifications should
+    // generally *not* be seen as they typically aren't actionable - so only
+    // authentication errors (which require user action) and "prolonged" errors
+    // (which technically aren't actionable, but user really should know anyway)
+    // are shown.
+    this.log.debug("onRLSyncError with readingList state", ReadingListScheduler.state);
+    if (ReadingListScheduler.state == ReadingListScheduler.STATE_ERROR_AUTHENTICATION) {
+      this.onLoginError();
+      return;
+    }
+    // If it's not prolonged there's nothing to do.
+    if (!this.isProlongedReadingListError()) {
+      this.log.debug("onRLSyncError has a non-authentication, non-prolonged error, so not showing any error UI");
+      return;
+    }
+    // So it's a prolonged error.
+    // Unfortunate duplication from below...
+    this.log.debug("onRLSyncError has a prolonged error");
+    let title = this._stringBundle.GetStringFromName("error.sync.title");
+    // XXX - this is somewhat wrong - we are reporting the threshold we consider
+    // to be prolonged, not how long it actually has been. (ie, lastSync below
+    // is effectively constant) - bit it too is copied from below.
+    let lastSync =
+      Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+    let description =
+      this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+    let priority = Weave.Notifications.PRIORITY_INFO;
+    let buttons = [
+      new Weave.NotificationButton(
+        this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
+        this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
+        function() { gSyncUI.doSync(); return true; }
+      ),
+    ];
+    let notification =
+      new Weave.Notification(title, description, null, priority, buttons);
+    Weave.Notifications.replaceTitle(notification);
+
+    this.updateUI();
+  },
+
   onSyncError: function SUI_onSyncError() {
+    this.log.debug("onSyncError");
     let title = this._stringBundle.GetStringFromName("error.sync.title");
 
     if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
       this.onLoginError();
       return;
     }
 
     let description;
@@ -413,17 +530,19 @@ let gSyncUI = {
     } else {
       let error = Weave.Utils.getErrorString(Weave.Status.sync);
       description =
         this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
     }
     let priority = Weave.Notifications.PRIORITY_WARNING;
     let buttons = [];
 
-    // Check if the client is outdated in some way
+    // Check if the client is outdated in some way (but note: we've never in the
+    // past, and probably never will, bump the relevent version numbers, so
+    // this is effectively dead code!)
     let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE;
     for (let [engine, reason] in Iterator(Weave.Status.engines))
       outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE;
 
     if (outdated) {
       description = this._stringBundle.GetStringFromName(
         "error.sync.needUpdate.description");
       buttons.push(new Weave.NotificationButton(
@@ -463,47 +582,61 @@ let gSyncUI = {
     let notification =
       new Weave.Notification(title, description, null, priority, buttons);
     Weave.Notifications.replaceTitle(notification);
 
     this.updateUI();
   },
 
   observe: function SUI_observe(subject, topic, data) {
+    this.log.debug("observed", topic);
     if (this._unloaded) {
       Cu.reportError("SyncUI observer called after unload: " + topic);
       return;
     }
 
     // Unwrap, just like Svc.Obs, but without pulling in that dependency.
     if (subject && typeof subject == "object" &&
         ("wrappedJSObject" in subject) &&
         ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
       subject = subject.wrappedJSObject.object;
     }
 
+    // First handle "activity" only.
     switch (topic) {
       case "weave:service:sync:start":
+      case "weave:service:login:start":
+      case "readinglist:sync:start":
         this.onActivityStart();
         break;
+      case "weave:service:sync:finish":
+      case "weave:service:sync:error":
+      case "weave:service:login:finish":
+      case "weave:service:login:error":
+      case "readinglist:sync:finish":
+      case "readinglist:sync:error":
+        this.onActivityStop();
+        break;
+    }
+    // Now non-activity state (eg, enabled, errors, etc)
+    // Note that sync uses the ":ui:" notifications for errors because sync.
+    // ReadingList has no such concept (yet?; hopefully the :error is enough!)
+    switch (topic) {
       case "weave:ui:sync:finish":
         this.onSyncFinish();
         break;
       case "weave:ui:sync:error":
         this.onSyncError();
         break;
       case "weave:service:quota:remaining":
         this.onQuotaNotice();
         break;
       case "weave:service:setup-complete":
         this.onSetupComplete();
         break;
-      case "weave:service:login:start":
-        this.onActivityStart();
-        break;
       case "weave:service:login:finish":
         this.onLoginFinish();
         break;
       case "weave:ui:login:error":
         this.onLoginError();
         break;
       case "weave:service:logout:finish":
         this.onLogout();
@@ -518,16 +651,23 @@ let gSyncUI = {
         this.initUI();
         break;
       case "weave:notification:added":
         this.initNotifications();
         break;
       case "weave:ui:clear-error":
         this.clearError();
         break;
+
+      case "readinglist:sync:error":
+        this.onRLSyncError();
+        break;
+      case "readinglist:sync:finish":
+        this.clearError();
+        break;
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference
   ])
 };
@@ -535,8 +675,11 @@ let gSyncUI = {
 XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
   //XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
   //        but for now just make it work
   return Cc["@mozilla.org/intl/stringbundle;1"].
          getService(Ci.nsIStringBundleService).
          createBundle("chrome://weave/locale/services/sync.properties");
 });
 
+XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
+  return Log.repository.getLogger("browserwindow.syncui");
+});
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -32,16 +32,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "GMPInstallManager",
                                   "resource://gre/modules/GMPInstallManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
                                   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch",
                                   "resource:///modules/ContentSearch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHome",
                                   "resource:///modules/AboutHome.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Log",
+                                  "resource://gre/modules/Log.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Favicons",
                                    "@mozilla.org/browser/favicon-service;1",
                                    "mozIAsyncFavicons");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 
 const nsIWebNavigation = Ci.nsIWebNavigation;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -398,16 +398,18 @@ skip-if = buildapp == 'mulet' || e10s # 
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
   searchSuggestionUI.js
 [browser_selectTabAtIndex.js]
 [browser_ssl_error_reports.js]
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
+[browser_syncui.js]
+skip-if = e10s # Bug 1137087 - browser_tabopen_reflows.js fails if this was previously run with e10s
 [browser_tabDrop.js]
 skip-if = buildapp == 'mulet' || e10s
 [browser_tabMatchesInAwesomebar.js]
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
 skip-if = e10s || os == 'linux' # Bug 1093373, bug 1104755
 [browser_tab_drag_drop_perwindow.js]
 skip-if = buildapp == 'mulet'
 [browser_tab_dragdrop.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_syncui.js
@@ -0,0 +1,244 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
+let {Weave} = Cu.import("resource://services-sync/main.js", {});
+let {Notifications} = Cu.import("resource://services-sync/notifications.js", {});
+// The BackStagePass allows us to get this test-only non-exported function.
+let {getInternalScheduler} = Cu.import("resource:///modules/readinglist/Scheduler.jsm", {});
+
+let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
+                   .getService(Ci.nsIStringBundleService)
+                   .createBundle("chrome://weave/locale/services/sync.properties");
+
+// ensure test output sees log messages.
+Log.repository.getLogger("browserwindow.syncui").addAppender(new Log.DumpAppender());
+
+function promiseObserver(topic) {
+  return new Promise(resolve => {
+    let obs = (subject, topic, data) => {
+      Services.obs.removeObserver(obs, topic);
+      resolve(subject);
+    }
+    Services.obs.addObserver(obs, topic, false);
+  });
+}
+
+add_task(function* prepare() {
+  let xps = Components.classes["@mozilla.org/weave/service;1"]
+                              .getService(Components.interfaces.nsISupports)
+                              .wrappedJSObject;
+  yield xps.whenLoaded();
+  // mock out the "_needsSetup()" function so we don't short-circuit.
+  let oldNeedsSetup = window.gSyncUI._needsSetup;
+  window.gSyncUI._needsSetup = () => false;
+  registerCleanupFunction(() => {
+    window.gSyncUI._needsSetup = oldNeedsSetup;
+  });
+});
+
+add_task(function* testProlongedSyncError() {
+  let promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
+
+  // Pretend we are in the "prolonged error" state.
+  Weave.Status.sync = Weave.PROLONGED_SYNC_FAILURE;
+  Weave.Status.login = Weave.LOGIN_SUCCEEDED;
+  Services.obs.notifyObservers(null, "weave:ui:sync:error", null);
+
+  let subject = yield promiseNotificationAdded;
+  let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.sync.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // Now pretend we just had a successful sync - the error notification should go away.
+  let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
+  Weave.Status.sync = Weave.STATUS_OK;
+  Services.obs.notifyObservers(null, "weave:ui:sync:finish", null);
+  yield promiseNotificationRemoved;
+  Assert.equal(Notifications.notifications.length, 0, "no notifications left");
+});
+
+add_task(function* testProlongedRLError() {
+  let promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
+
+  // Pretend the reading-list is in the "prolonged error" state.
+  let longAgo = new Date(Date.now() - 100 * 24 * 60 * 60 * 1000); // 100 days ago.
+  Services.prefs.setCharPref("readinglist.scheduler.lastSync", longAgo.toString());
+  getInternalScheduler().state = ReadingListScheduler.STATE_ERROR_OTHER;
+  Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+  Services.obs.notifyObservers(null, "readinglist:sync:error", null);
+
+  let subject = yield promiseNotificationAdded;
+  let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.sync.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // Now pretend we just had a successful sync - the error notification should go away.
+  let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
+  Services.prefs.setCharPref("readinglist.scheduler.lastSync", Date.now().toString());
+  Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+  Services.obs.notifyObservers(null, "readinglist:sync:finish", null);
+  yield promiseNotificationRemoved;
+  Assert.equal(Notifications.notifications.length, 0, "no notifications left");
+});
+
+add_task(function* testSyncLoginError() {
+  let promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
+
+  // Pretend we are in the "prolonged error" state.
+  Weave.Status.sync = Weave.LOGIN_FAILED;
+  Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
+  Services.obs.notifyObservers(null, "weave:ui:sync:error", null);
+
+  let subject = yield promiseNotificationAdded;
+  let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.login.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // Now pretend we just had a successful login - the error notification should go away.
+  Weave.Status.sync = Weave.STATUS_OK;
+  Weave.Status.login = Weave.LOGIN_SUCCEEDED;
+  let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
+  Services.obs.notifyObservers(null, "weave:service:login:start", null);
+  Services.obs.notifyObservers(null, "weave:service:login:finish", null);
+  yield promiseNotificationRemoved;
+  Assert.equal(Notifications.notifications.length, 0, "no notifications left");
+});
+
+add_task(function* testRLLoginError() {
+  let promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
+
+  // Pretend RL is in an auth error state
+  getInternalScheduler().state = ReadingListScheduler.STATE_ERROR_AUTHENTICATION;
+  Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+  Services.obs.notifyObservers(null, "readinglist:sync:error", null);
+
+  let subject = yield promiseNotificationAdded;
+  let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.login.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // Now pretend we just had a successful sync - the error notification should go away.
+  getInternalScheduler().state = ReadingListScheduler.STATE_OK;
+  let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
+  Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+  Services.obs.notifyObservers(null, "readinglist:sync:finish", null);
+  yield promiseNotificationRemoved;
+  Assert.equal(Notifications.notifications.length, 0, "no notifications left");
+});
+
+// Here we put readinglist into an "authentication error" state (should see
+// the error bar reflecting this), then report a prolonged error from Sync (an
+// infobar to reflect the sync error should replace it), then resolve the sync
+// error - the authentication error from readinglist should remain.
+add_task(function* testRLLoginErrorRemains() {
+  let promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
+
+  // Pretend RL is in an auth error state
+  getInternalScheduler().state = ReadingListScheduler.STATE_ERROR_AUTHENTICATION;
+  Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+  Services.obs.notifyObservers(null, "readinglist:sync:error", null);
+
+  let subject = yield promiseNotificationAdded;
+  let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.login.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // Now Sync into a prolonged auth error state.
+  promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Weave.Status.sync = Weave.PROLONGED_SYNC_FAILURE;
+  Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
+  Services.obs.notifyObservers(null, "weave:ui:sync:error", null);
+  subject = yield promiseNotificationAdded;
+  // still exactly 1 notification with the "login" title.
+  notification = subject.wrappedJSObject.object;
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.login.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // Resolve the sync problem.
+  promiseNotificationAdded = promiseObserver("weave:notification:added");
+  Weave.Status.sync = Weave.STATUS_OK;
+  Weave.Status.login = Weave.LOGIN_SUCCEEDED;
+  Services.obs.notifyObservers(null, "weave:ui:sync:finish", null);
+
+  // Expect one notification - the RL login problem.
+  subject = yield promiseNotificationAdded;
+  // still exactly 1 notification with the "login" title.
+  notification = subject.wrappedJSObject.object;
+  Assert.equal(notification.title, stringBundle.GetStringFromName("error.login.title"));
+  Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
+
+  // and cleanup - resolve the readinglist error.
+  getInternalScheduler().state = ReadingListScheduler.STATE_OK;
+  let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
+  Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+  Services.obs.notifyObservers(null, "readinglist:sync:finish", null);
+  yield promiseNotificationRemoved;
+  Assert.equal(Notifications.notifications.length, 0, "no notifications left");
+});
+
+function checkButtonsStatus(shouldBeActive) {
+  let button = document.getElementById("sync-button");
+  let panelbutton = document.getElementById("PanelUI-fxa-status");
+  if (shouldBeActive) {
+    Assert.equal(button.getAttribute("status"), "active");
+    Assert.equal(panelbutton.getAttribute("syncstatus"), "active");
+  } else {
+    Assert.ok(!button.hasAttribute("status"));
+    Assert.ok(!panelbutton.hasAttribute("syncstatus"));
+  }
+}
+
+function testButtonActions(startNotification, endNotification) {
+  checkButtonsStatus(false);
+  // pretend a sync is starting.
+  Services.obs.notifyObservers(null, startNotification, null);
+  checkButtonsStatus(true);
+  // and has stopped
+  Services.obs.notifyObservers(null, endNotification, null);
+  checkButtonsStatus(false);
+}
+
+add_task(function* testButtonActivities() {
+  // add the Sync button to the panel so we can get it!
+  CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+  // check the button's functionality
+  yield PanelUI.show();
+  try {
+    testButtonActions("weave:service:login:start", "weave:service:login:finish");
+    testButtonActions("weave:service:login:start", "weave:service:login:error");
+
+    testButtonActions("weave:service:sync:start", "weave:service:sync:finish");
+    testButtonActions("weave:service:sync:start", "weave:service:sync:error");
+
+    testButtonActions("readinglist:sync:start", "readinglist:sync:finish");
+    testButtonActions("readinglist:sync:start", "readinglist:sync:error");
+
+    // and ensure the counters correctly handle multiple in-flight syncs
+    Services.obs.notifyObservers(null, "weave:service:sync:start", null);
+    checkButtonsStatus(true);
+    Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+    checkButtonsStatus(true);
+    Services.obs.notifyObservers(null, "readinglist:sync:finish", null);
+    // sync is still going...
+    checkButtonsStatus(true);
+    // another reading list starts
+    Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+    checkButtonsStatus(true);
+    // The initial sync stops.
+    Services.obs.notifyObservers(null, "weave:service:sync:finish", null);
+    // RL is still going...
+    checkButtonsStatus(true);
+    // RL finishes with an error, so no longer active.
+    Services.obs.notifyObservers(null, "readinglist:sync:error", null);
+    checkButtonsStatus(false);
+  } finally {
+    PanelUI.hide();
+    CustomizableUI.removeWidgetFromArea("sync-button");
+  }
+});
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -1,14 +1,16 @@
 /* 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/. */
 
 let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
                                   "resource://gre/modules/DownloadUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
                                   "resource:///modules/DownloadsViewUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -61,16 +61,18 @@
  * us to draw a ring around the currently focused element. If the panel is
  * closed or the mouse moves over the panel, we remove the attribute.
  */
 
 "use strict";
 
 let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
                                   "resource:///modules/DownloadsCommon.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
                                   "resource:///modules/DownloadsViewUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
new file mode 100644
--- /dev/null
+++ b/browser/components/readinglist/Scheduler.jsm
@@ -0,0 +1,338 @@
+/* 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/. */
+
+"use strict;"
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+
+XPCOMUtils.defineLazyModuleGetter(this, 'LogManager',
+  'resource://services-common/logmanager.js');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'Log',
+  'resource://gre/modules/Log.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'Preferences',
+  'resource://gre/modules/Preferences.jsm');
+
+XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout',
+  'resource://gre/modules/Timer.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, 'clearTimeout',
+  'resource://gre/modules/Timer.jsm');
+
+Cu.import('resource://gre/modules/Task.jsm');
+
+this.EXPORTED_SYMBOLS = ["ReadingListScheduler"];
+
+// A list of "external" observer topics that may cause us to change when we
+// sync.
+const OBSERVERS = [
+  // We don't sync when offline and restart when online.
+  "network:offline-status-changed",
+  // FxA notifications also cause us to check if we should sync.
+  "fxaccounts:onverified",
+  // When something notices a local change to an item.
+  "readinglist:item-changed",
+  // some notifications the engine might send if we have been requested to backoff.
+  "readinglist:backoff-requested",
+  // request to sync now
+  "readinglist:user-sync",
+
+];
+
+///////// A temp object until we get our "engine"
+let engine = {
+  ERROR_AUTHENTICATION: "authentication error",
+  sync: Task.async(function* () {
+  }),
+}
+
+let prefs = new Preferences("readinglist.scheduler.");
+
+// A helper to manage our interval values.
+let intervals = {
+  // Getters for our intervals.
+  _fixupIntervalPref(prefName, def) {
+    // All pref values are seconds, but we return ms.
+    return prefs.get(prefName, def) * 1000;
+  },
+
+  // How long after startup do we do an initial sync?
+  get initial() this._fixupIntervalPref("initial", 20), // 20 seconds.
+  // Every interval after the first.
+  get schedule() this._fixupIntervalPref("schedule", 2 * 60 * 60), // 2 hours
+  // After we've been told an item has changed
+  get dirty() this._fixupIntervalPref("dirty", 2 * 60), // 2 mins
+  // After an error
+  get retry() this._fixupIntervalPref("retry", 2 * 60), // 2 mins
+};
+
+// This is the implementation, but it's not exposed directly.
+function InternalScheduler() {
+  // oh, I don't know what logs yet - let's guess!
+  let logs = ["readinglist", "FirefoxAccounts", "browserwindow.syncui"];
+  this._logManager = new LogManager("readinglist.", logs, "readinglist");
+  this.log = Log.repository.getLogger("readinglist.scheduler");
+  this.log.info("readinglist scheduler created.")
+  this.state = this.STATE_OK;
+
+  // don't this.init() here, but instead at the module level - tests want to
+  // add hooks before it is called.
+}
+
+InternalScheduler.prototype = {
+  // When the next scheduled sync should happen.  If we can sync, there will
+  // be a timer set to fire then. If we can't sync there will not be a timer,
+  // but it will be set to fire then as soon as we can.
+  _nextScheduledSync: null,
+  // The time when the most-recent "backoff request" expires - we will never
+  // schedule a new timer before this.
+  _backoffUntil: 0,
+  // Our current timer.
+  _timer: null,
+  // Our timer fires a promise - _timerRunning is true until it resolves or
+  // rejects.
+  _timerRunning: false,
+  // Our sync engine - XXX - maybe just a callback?
+  _engine: engine,
+
+  // Our state variable and constants.
+  state: null,
+  STATE_OK: "ok",
+  STATE_ERROR_AUTHENTICATION: "authentication error",
+  STATE_ERROR_OTHER: "other error",
+
+  init() {
+    this.log.info("scheduler initialzing");
+    this._observe = this.observe.bind(this);
+    for (let notification of OBSERVERS) {
+      Services.obs.addObserver(this._observe, notification, false);
+    }
+    this._nextScheduledSync = Date.now() + intervals.initial;
+    this._setupTimer();
+  },
+
+  // Note: only called by tests.
+  finalize() {
+    this.log.info("scheduler finalizing");
+    this._clearTimer();
+    for (let notification of OBSERVERS) {
+      Services.obs.removeObserver(this._observe, notification);
+    }
+    this._observe = null;
+  },
+
+  observe(subject, topic, data) {
+    this.log.debug("observed ${}", topic);
+    switch (topic) {
+      case "readinglist:backoff-requested": {
+        // The subject comes in as a string, a number of seconds.
+        let interval = parseInt(data, 10);
+        if (isNaN(interval)) {
+          this.log.warn("Backoff request had non-numeric value", data);
+          return;
+        }
+        this.log.info("Received a request to backoff for ${} seconds", interval);
+        this._backoffUntil = Date.now() + interval * 1000;
+        this._maybeReschedule(0);
+        break;
+      }
+      case "readinglist:local:dirty":
+        this._maybeReschedule(intervals.dirty);
+        break;
+      case "readinglist:user-sync":
+        this._syncNow();
+        break;
+      case "fxaccounts:onverified":
+        // If we were in an authentication error state, reset that now.
+        if (this.state == this.STATE_ERROR_AUTHENTICATION) {
+          this.state = this.STATE_OK;
+        }
+        break;
+
+      // The rest just indicate that now is probably a good time to check if
+      // we can sync as normal using whatever schedule was previously set.
+      default:
+        break;
+    }
+    // When observers fire we ignore the current sync error state as the
+    // notification may indicate it's been resolved.
+    this._setupTimer(true);
+  },
+
+  // Is the current error state such that we shouldn't schedule a new sync.
+  _isBlockedOnError() {
+    // this needs more thought...
+    return this.state == this.STATE_ERROR_AUTHENTICATION;
+  },
+
+  // canSync indicates if we can currently sync.
+  _canSync(ignoreBlockingErrors = false) {
+    if (Services.io.offline) {
+      this.log.info("canSync=false - we are offline");
+      return false;
+    }
+    if (!ignoreBlockingErrors && this._isBlockedOnError()) {
+      this.log.info("canSync=false - we are in a blocked error state", this.state);
+      return false;
+    }
+    this.log.info("canSync=true");
+    return true;
+  },
+
+  // _setupTimer checks the current state and the environment to see when
+  // we should next sync and creates the timer with the appropriate delay.
+  _setupTimer(ignoreBlockingErrors = false) {
+    if (!this._canSync(ignoreBlockingErrors)) {
+      this._clearTimer();
+      return;
+    }
+    if (this._timer) {
+      let when = new Date(this._nextScheduledSync);
+      let delay = this._nextScheduledSync - Date.now();
+      this.log.info("checkStatus - already have a timer - will fire in ${delay}ms at ${when}",
+                    {delay, when});
+      return;
+    }
+    if (this._timerRunning) {
+      this.log.info("checkStatus - currently syncing");
+      return;
+    }
+    // no timer and we can sync, so start a new one.
+    let now = Date.now();
+    let delay = Math.max(0, this._nextScheduledSync - now);
+    let when = new Date(now + delay);
+    this.log.info("next scheduled sync is in ${delay}ms (at ${when})", {delay, when})
+    this._timer = this._setTimeout(delay);
+  },
+
+  // Something (possibly naively) thinks the next sync should happen in
+  // delay-ms. If there's a backoff in progress, ignore the requested delay
+  // and use the back-off. If there's already a timer scheduled for earlier
+  // than delay, let the earlier timer remain. Otherwise, use the requested
+  // delay.
+  _maybeReschedule(delay) {
+    // If there's no delay specified and there's nothing currently scheduled,
+    // it means a backoff request while the sync is actually running - there's
+    // no need to do anything here - the next reschedule after the sync
+    // completes will take the backoff into account.
+    if (!delay && !this._nextScheduledSync) {
+      this.log.debug("_maybeReschedule ignoring a backoff request while running");
+      return;
+    }
+    let now = Date.now();
+    if (!this._nextScheduledSync) {
+      this._nextScheduledSync = now + delay;
+    }
+    // If there is something currently scheduled before the requested delay,
+    // keep the existing value (eg, if we have a timer firing in 1 second, and
+    // get a "dirty" notification that says we should sync in 2 seconds, we
+    // keep the 1 second value)
+    this._nextScheduledSync = Math.min(this._nextScheduledSync, now + delay);
+    // But we still need to honor a backoff.
+    this._nextScheduledSync = Math.max(this._nextScheduledSync, this._backoffUntil);
+    // And always create a new timer next time _setupTimer is called.
+    this._clearTimer();
+  },
+
+  // callback for when the timer fires.
+  _doSync() {
+    this.log.debug("starting sync");
+    this._timer = null;
+    this._timerRunning = true;
+    // flag that there's no new schedule yet, so a request coming in while
+    // we are running does the right thing.
+    this._nextScheduledSync = 0;
+    Services.obs.notifyObservers(null, "readinglist:sync:start", null);
+    this._engine.sync().then(() => {
+      this.log.info("Sync completed successfully");
+      // Write a pref in the same format used to services/sync to indicate
+      // the last success.
+      prefs.set("lastSync", new Date().toString());
+      this.state = this.STATE_OK;
+      this._logManager.resetFileLog(this._logManager.REASON_SUCCESS);
+      Services.obs.notifyObservers(null, "readinglist:sync:finish", null);
+      return intervals.schedule;
+    }).catch(err => {
+      this.log.error("Sync failed", err);
+      // XXX - how to detect an auth error?
+      this.state = err == this._engine.ERROR_AUTHENTICATION ?
+                   this.STATE_ERROR_AUTHENTICATION : this.STATE_ERROR_OTHER;
+      this._logManager.resetFileLog(this._logManager.REASON_ERROR);
+      Services.obs.notifyObservers(null, "readinglist:sync:error", null);
+      return intervals.retry;
+    }).then(nextDelay => {
+      this._timerRunning = false;
+      // ensure a new timer is setup for the appropriate next time.
+      this._maybeReschedule(nextDelay);
+      this._setupTimer();
+      this._onAutoReschedule(); // just for tests...
+    }).catch(err => {
+      // We should never get here, but better safe than sorry...
+      this.log.error("Failed to reschedule after sync completed", err);
+    });
+  },
+
+  _clearTimer() {
+    if (this._timer) {
+      clearTimeout(this._timer);
+      this._timer = null;
+    }
+  },
+
+  // A function to "sync now", but not allowing it to start if one is
+  // already running, and rescheduling the timer.
+  // To call this, just send a "readinglist:user-sync" notification.
+  _syncNow() {
+    if (this._timerRunning) {
+      this.log.info("syncNow() but a sync is already in progress - ignoring");
+      return;
+    }
+    this._clearTimer();
+    this._doSync();
+  },
+
+  // A couple of hook-points for testing.
+  // xpcshell tests hook this so (a) it can check the expected delay is set
+  // and (b) to ignore the delay and set a timeout of 0 so the test is fast.
+  _setTimeout(delay) {
+    return setTimeout(() => this._doSync(), delay);
+  },
+  // xpcshell tests hook this to make sure that the correct state etc exist
+  // after a sync has been completed and a new timer created (or not).
+  _onAutoReschedule() {},
+};
+
+let internalScheduler = new InternalScheduler();
+internalScheduler.init();
+
+// The public interface into this module is tiny, so a simple object that
+// delegates to the implementation.
+let ReadingListScheduler = {
+  get STATE_OK() internalScheduler.STATE_OK,
+  get STATE_ERROR_AUTHENTICATION() internalScheduler.STATE_ERROR_AUTHENTICATION,
+  get STATE_ERROR_OTHER() internalScheduler.STATE_ERROR_OTHER,
+
+  get state() internalScheduler.state,
+};
+
+// These functions are exposed purely for tests, which manage to grab them
+// via a BackstagePass.
+function createTestableScheduler() {
+  // kill the "real" scheduler as we don't want it listening to notifications etc.
+  if (internalScheduler) {
+    internalScheduler.finalize();
+    internalScheduler = null;
+  }
+  // No .init() call - that's up to the tests after hooking.
+  return new InternalScheduler();
+}
+
+// mochitests want the internal state of the real scheduler for various things.
+function getInternalScheduler() {
+  return internalScheduler;
+}
--- a/browser/components/readinglist/moz.build
+++ b/browser/components/readinglist/moz.build
@@ -8,8 +8,14 @@ EXTRA_JS_MODULES.readinglist += [
     'ReadingList.jsm',
 ]
 
 TESTING_JS_MODULES += [
     'test/ReadingListTestUtils.jsm',
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
+
+EXTRA_JS_MODULES.readinglist += [
+    'Scheduler.jsm',
+]
new file mode 100644
--- /dev/null
+++ b/browser/components/readinglist/test/xpcshell/head.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
new file mode 100644
--- /dev/null
+++ b/browser/components/readinglist/test/xpcshell/test_scheduler.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, 'setTimeout',
+  'resource://gre/modules/Timer.jsm');
+
+// Setup logging prefs before importing the scheduler module.
+Services.prefs.setCharPref("readinglist.log.appender.dump", "Trace");
+
+let {createTestableScheduler} = Cu.import("resource:///modules/readinglist/Scheduler.jsm", {});
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Timer.jsm");
+
+// Log rotation needs a profile dir.
+do_get_profile();
+
+let prefs = new Preferences("readinglist.scheduler.");
+
+function promiseObserver(topic) {
+  return new Promise(resolve => {
+    let obs = (subject, topic, data) => {
+      Services.obs.removeObserver(obs, topic);
+      resolve(data);
+    }
+    Services.obs.addObserver(obs, topic, false);
+  });
+}
+
+function createScheduler(options) {
+  // avoid typos in the test and other footguns in the options.
+  let allowedOptions = ["expectedDelay", "expectNewTimer", "syncFunction"];
+  for (let key of Object.keys(options)) {
+    if (!allowedOptions.includes(key)) {
+      throw new Error("Invalid option " + key);
+    }
+  }
+  let scheduler = createTestableScheduler();
+  // make our hooks
+  let syncFunction = options.syncFunction || Promise.resolve;
+  scheduler._engine.sync = syncFunction;
+  // we expect _setTimeout to be called *twice* - first is the initial sync,
+  // and there's no need to test the delay used for that. options.expectedDelay
+  // is to check the *subsequent* timer.
+  let numCalls = 0;
+  scheduler._setTimeout = function(delay) {
+    ++numCalls;
+    print("Test scheduler _setTimeout call number " + numCalls + " with delay=" + delay);
+    switch (numCalls) {
+      case 1:
+        // this is the first and boring schedule as it initializes - do nothing
+        // other than return a timer that fires immediately.
+        return setTimeout(() => scheduler._doSync(), 0);
+        break;
+      case 2:
+        // This is the one we are interested in, so check things.
+        if (options.expectedDelay) {
+          // a little slop is OK as it takes a few ms to actually set the timer
+          ok(Math.abs(options.expectedDelay * 1000 - delay) < 500, [options.expectedDelay * 1000, delay]);
+        }
+        // and return a timeout that "never" fires
+        return setTimeout(() => scheduler._doSync(), 10000000);
+        break;
+      default:
+        // This is unexpected!
+        ok(false, numCalls);
+    }
+  };
+  // And a callback made once we've determined the next delay. This is always
+  // called even if _setTimeout isn't (due to no timer being created)
+  scheduler._onAutoReschedule = () => {
+    // Most tests expect a new timer, so this is "opt out"
+    let expectNewTimer = options.expectNewTimer === undefined ? true : options.expectNewTimer;
+    ok(expectNewTimer ? scheduler._timer : !scheduler._timer);
+  }
+  // calling .init fires things off...
+  scheduler.init();
+  return scheduler;
+}
+
+add_task(function* testSuccess() {
+  // promises which resolve once we've got all the expected notifications.
+  let allNotifications = [
+    promiseObserver("readinglist:sync:start"),
+    promiseObserver("readinglist:sync:finish"),
+  ];
+  // New delay should be "as regularly scheduled".
+  prefs.set("schedule", 100);
+  let scheduler = createScheduler({expectedDelay: 100});
+  yield Promise.all(allNotifications);
+  scheduler.finalize();
+});
+
+add_task(function* testOffline() {
+  let scheduler = createScheduler({expectNewTimer: false});
+  Services.io.offline = true;
+  ok(!scheduler._canSync(), "_canSync is false when offline.")
+  ok(!scheduler._timer, "there is no current timer while offline.")
+  Services.io.offline = false;
+  ok(scheduler._canSync(), "_canSync is true when online.")
+  ok(scheduler._timer, "there is a new timer when back online.")
+  scheduler.finalize();
+});
+
+add_task(function* testRetryableError() {
+  let allNotifications = [
+    promiseObserver("readinglist:sync:start"),
+    promiseObserver("readinglist:sync:error"),
+  ];
+  prefs.set("retry", 10);
+  let scheduler = createScheduler({
+    expectedDelay: 10,
+    syncFunction: () => Promise.reject("transient"),
+  });
+  yield Promise.all(allNotifications);
+  scheduler.finalize();
+});
+
+add_task(function* testAuthError() {
+  prefs.set("retry", 10);
+  // We expect an auth error to result in no new timer (as it's waiting for
+  // some indication it can proceed), but with the next delay being a normal
+  // "retry" interval (so when we can proceed it is probably already stale, so
+  // is effectively "immediate")
+  let scheduler = createScheduler({
+    expectedDelay: 10,
+    syncFunction:  () => {
+      return Promise.reject(ReadingListScheduler._engine.ERROR_AUTHENTICATION);
+    },
+    expectNewTimer: false
+  });
+  // XXX - TODO - send an observer that "unblocks" us and ensure we actually
+  // do unblock.
+  scheduler.finalize();
+});
+
+add_task(function* testBackoff() {
+  let scheduler = createScheduler({expectedDelay: 1000});
+  Services.obs.notifyObservers(null, "readinglist:backoff-requested", 1000);
+  // XXX - this needs a little love as nothing checks createScheduler actually
+  // made the checks we think it does.
+  scheduler.finalize();
+});
+
+function run_test() {
+  run_next_test();
+}
new file mode 100644
--- /dev/null
+++ b/browser/components/readinglist/test/xpcshell/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head = head.js
+firefox-appdir = browser
+
+[test_scheduler.js]
--- a/browser/devtools/netmonitor/test/browser_net_content-type.js
+++ b/browser/devtools/netmonitor/test/browser_net_content-type.js
@@ -43,17 +43,17 @@ function test() {
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
         "GET", CONTENT_TYPE_SJS + "?fmt=json", {
           status: 200,
           statusText: "OK",
           type: "json",
           fullMimeType: "application/json; charset=utf-8",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
         "GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
           status: 404,
           statusText: "Not Found",
           type: "html",
           fullMimeType: "text/html; charset=utf-8",
@@ -62,17 +62,17 @@ function test() {
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
         "GET", TEST_IMAGE, {
           fuzzyUrl: true,
           status: 200,
           statusText: "OK",
           type: "png",
           fullMimeType: "image/png",
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.75),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.76),
           time: true
         });
 
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.getElementById("details-pane-toggle"));
       EventUtils.sendMouseEvent({ type: "mousedown" },
         document.querySelectorAll("#details-pane tab")[3]);
 
--- a/browser/devtools/netmonitor/test/browser_net_sort-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-02.js
@@ -223,51 +223,51 @@ function test() {
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
         "GET2", SORTING_SJS + "?index=2", {
           fuzzyUrl: true,
           status: 200,
           statusText: "Meh",
           type: "2",
           fullMimeType: "text/2",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
         "GET3", SORTING_SJS + "?index=3", {
           fuzzyUrl: true,
           status: 300,
           statusText: "Meh",
           type: "3",
           fullMimeType: "text/3",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
         "GET4", SORTING_SJS + "?index=4", {
           fuzzyUrl: true,
           status: 400,
           statusText: "Meh",
           type: "4",
           fullMimeType: "text/4",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
           time: true
         });
       verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
         "GET5", SORTING_SJS + "?index=5", {
           fuzzyUrl: true,
           status: 500,
           statusText: "Meh",
           type: "5",
           fullMimeType: "text/5",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.05),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.05),
           time: true
         });
 
       return promise.resolve(null);
     }
 
     aDebuggee.performRequests();
   });
--- a/browser/devtools/netmonitor/test/browser_net_sort-03.js
+++ b/browser/devtools/netmonitor/test/browser_net_sort-03.js
@@ -138,57 +138,57 @@ function test() {
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len]),
           "GET2", SORTING_SJS + "?index=2", {
             fuzzyUrl: true,
             status: 200,
             statusText: "Meh",
             type: "2",
             fullMimeType: "text/2",
-            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
-            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 2]),
           "GET3", SORTING_SJS + "?index=3", {
             fuzzyUrl: true,
             status: 300,
             statusText: "Meh",
             type: "3",
             fullMimeType: "text/3",
-            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
-            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.02),
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
+            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 3]),
           "GET4", SORTING_SJS + "?index=4", {
             fuzzyUrl: true,
             status: 400,
             statusText: "Meh",
             type: "4",
             fullMimeType: "text/4",
-            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
-            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.03),
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
+            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
             time: true
           });
       }
       for (let i = 0, len = aOrder.length / 5; i < len; i++) {
         verifyRequestItemTarget(RequestsMenu.getItemAtIndex(aOrder[i + len * 4]),
           "GET5", SORTING_SJS + "?index=5", {
             fuzzyUrl: true,
             status: 500,
             statusText: "Meh",
             type: "5",
             fullMimeType: "text/5",
-            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
-            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
+            transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.05),
+            size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.05),
             time: true
           });
       }
 
       return promise.resolve(null);
     }
 
     aDebuggee.performRequests();
--- a/browser/devtools/projecteditor/lib/stores/resource.js
+++ b/browser/devtools/projecteditor/lib/stores/resource.js
@@ -43,21 +43,16 @@ var Resource = Class({
   setURI: function(uri) {
     if (typeof(uri) === "string") {
       uri = URL.URL(uri);
     }
     this.uri = uri;
   },
 
   /**
-   * Return the trailing name component of this.uri.
-   */
-  get basename() { return this.uri.path.replace(/\/+$/, '').replace(/\\/g,'/').replace( /.*\//, '' ); },
-
-  /**
    * Is there more than 1 child Resource?
    */
   get hasChildren() { return this.children && this.children.size > 0; },
 
   /**
    * Is this Resource the root (top level for the store)?
    */
   get isRoot() {
@@ -234,16 +229,23 @@ var FileResource = Class({
         this._refreshDeferred.resolve(this);
         this._refreshDeferred = null;
       }
     });
     return this._refreshDeferred.promise;
   },
 
   /**
+   * Return the trailing name component of this Resource
+   */
+  get basename() {
+    return this.path.replace(/\/+$/, '').replace(/\\/g,'/').replace( /.*\//, '' );
+  },
+
+  /**
    * A string to be used when displaying this Resource in views
    */
   get displayName() {
     return this.basename + (this.isDir ? "/" : "")
   },
 
   /**
    * Is this FileResource a directory?  Rather than checking children
--- a/browser/devtools/projecteditor/test/browser_projecteditor_rename_file.js
+++ b/browser/devtools/projecteditor/test/browser_projecteditor_rename_file.js
@@ -8,52 +8,75 @@
 
 add_task(function*() {
   let projecteditor = yield addProjectEditorTabForTempDirectory();
   ok(true, "ProjectEditor has loaded");
 
   let root = [...projecteditor.project.allStores()][0].root;
   is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
   for (let child of root.children) {
-    yield renameWithContextMenu(projecteditor, projecteditor.projectTree.getViewContainer(child));
+    yield renameWithContextMenu(projecteditor,
+                                projecteditor.projectTree.getViewContainer(child),
+                                ".renamed");
+  }
+});
+
+add_task(function*() {
+  let projecteditor = yield addProjectEditorTabForTempDirectory();
+  ok(true, "ProjectEditor has loaded");
+
+  let root = [...projecteditor.project.allStores()][0].root;
+  is(root.path, TEMP_PATH, "The root store is set to the correct temp path.");
+
+  let childrenList = new Array();
+  for (let child of root.children) {
+    yield renameWithContextMenu(projecteditor,
+                                projecteditor.projectTree.getViewContainer(child),
+                                ".ren\u0061\u0308med");
+    childrenList.push(child.basename + ".ren\u0061\u0308med");
+  }
+  for (let child of root.children) {
+    is (childrenList.indexOf(child.basename) == -1, false,
+        "Failed to update tree with non-ascii character");
   }
 });
 
 function openContextMenuOn(node) {
   EventUtils.synthesizeMouseAtCenter(
     node,
     {button: 2, type: "contextmenu"},
     node.ownerDocument.defaultView
   );
 }
 
-function renameWithContextMenu(projecteditor, container) {
+function renameWithContextMenu(projecteditor, container, newName) {
   let defer = promise.defer();
   let popup = projecteditor.contextMenuPopup;
   let resource = container.resource;
   info ("Going to attempt renaming for: " + resource.path);
 
   onPopupShow(popup).then(function () {
     let renameCommand = popup.querySelector("[command=cmd-rename]");
     ok (renameCommand, "Rename command exists in popup");
     is (renameCommand.getAttribute("hidden"), "", "Rename command is visible");
     is (renameCommand.getAttribute("disabled"), "", "Rename command is enabled");
 
     projecteditor.project.on("refresh-complete", function refreshComplete() {
       projecteditor.project.off("refresh-complete", refreshComplete);
-      OS.File.stat(resource.path + ".renamed").then(() => {
+      OS.File.stat(resource.path + newName).then(() => {
         ok (true, "File is renamed");
         defer.resolve();
       }, (ex) => {
         ok (false, "Failed to rename file");
         defer.resolve();
       });
     });
 
     renameCommand.click();
     popup.hidePopup();
-    EventUtils.sendString(resource.basename + ".renamed", projecteditor.window);
+    let input = container.elt.previousElementSibling;
+    input.value = resource.basename + newName;
     EventUtils.synthesizeKey("VK_RETURN", {}, projecteditor.window);
   });
 
   openContextMenuOn(container.label);
   return defer.promise;
 }
--- a/browser/devtools/shared/test/browser_graphs-09a.js
+++ b/browser/devtools/shared/test/browser_graphs-09a.js
@@ -49,17 +49,17 @@ function* testGraph(graph) {
     "The maximum tooltip displays the correct info.");
   is(graph._avgTooltip.querySelector("[text=info]").textContent, "avg",
     "The average tooltip displays the correct info.");
   is(graph._minTooltip.querySelector("[text=info]").textContent, "min",
     "The minimum tooltip displays the correct info.");
 
   is(graph._maxTooltip.querySelector("[text=value]").textContent, "60",
     "The maximum tooltip displays the correct value.");
-  is(graph._avgTooltip.querySelector("[text=value]").textContent, "41.71",
+  is(graph._avgTooltip.querySelector("[text=value]").textContent, "41.72",
     "The average tooltip displays the correct value.");
   is(graph._minTooltip.querySelector("[text=value]").textContent, "10",
     "The minimum tooltip displays the correct value.");
 
   is(graph._maxTooltip.querySelector("[text=metric]").textContent, "fps",
     "The maximum tooltip displays the correct metric.");
   is(graph._avgTooltip.querySelector("[text=metric]").textContent, "fps",
     "The average tooltip displays the correct metric.");
--- a/browser/devtools/shared/test/browser_num-l10n.js
+++ b/browser/devtools/shared/test/browser_num-l10n.js
@@ -3,17 +3,17 @@
 
 // Tests that ViewHelpers.Prefs work properly.
 
 let {ViewHelpers} = Cu.import("resource:///modules/devtools/ViewHelpers.jsm", {});
 
 function test() {
   let l10n = new ViewHelpers.L10N();
 
-  is(l10n.numberWithDecimals(1234.56789, 2), "1,234.56",
+  is(l10n.numberWithDecimals(1234.56789, 2), "1,234.57",
     "The first number was properly localized.");
   is(l10n.numberWithDecimals(0.0001, 2), "0",
     "The second number was properly localized.");
   is(l10n.numberWithDecimals(1.0001, 2), "1",
     "The third number was properly localized.");
   is(l10n.numberWithDecimals(NaN, 2), "0",
     "NaN was properly localized.");
   is(l10n.numberWithDecimals(null, 2), "0",
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -364,31 +364,28 @@ ViewHelpers.L10N.prototype = {
   numberWithDecimals: function(aNumber, aDecimals = 0) {
     // If this is an integer, don't do anything special.
     if (aNumber == (aNumber | 0)) {
       return aNumber;
     }
     if (isNaN(aNumber) || aNumber == null) {
       return "0";
     }
-    // Remove {n} trailing decimals. Can't use toFixed(n) because
-    // toLocaleString converts the number to a string. Also can't use
-    // toLocaleString(, { maximumFractionDigits: n }) because it's not
-    // implemented on OS X (bug 368838). Gross.
     let localized = aNumber.toLocaleString(); // localize
 
     // If no grouping or decimal separators are available, bail out, because
     // padding with zeros at the end of the string won't make sense anymore.
     if (!localized.match(/[^\d]/)) {
       return localized;
     }
 
-    let padded = localized + new Array(aDecimals).join("0"); // pad with zeros
-    let match = padded.match("([^]*?\\d{" + aDecimals + "})\\d*$");
-    return match.pop();
+    return aNumber.toLocaleString(undefined, {
+      maximumFractionDigits: aDecimals,
+      minimumFractionDigits: aDecimals
+    });
   }
 };
 
 /**
  * Shortcuts for lazily accessing and setting various preferences.
  * Usage:
  *   let prefs = new ViewHelpers.Prefs("root.path.to.branch", {
  *     myIntPref: ["Int", "leaf.path.to.my-int-pref"],
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -442,23 +442,25 @@ StyleEditorUI.prototype = {
   /**
    * Called when a StyleSheetEditor's source has been fetched. Create a
    * summary UI for the editor.
    *
    * @param  {StyleSheetEditor} editor
    *         Editor to create UI for.
    */
   _sourceLoaded: function(editor) {
+    let ordinal = editor.styleSheet.styleSheetIndex;
+    ordinal = ordinal == -1 ? Number.MAX_SAFE_INTEGER : ordinal;
     // add new sidebar item and editor to the UI
     this._view.appendTemplatedItem(STYLE_EDITOR_TEMPLATE, {
       data: {
         editor: editor
       },
       disableAnimations: this._alwaysDisableAnimations,
-      ordinal: editor.styleSheet.styleSheetIndex,
+      ordinal: ordinal,
       onCreate: function(summary, details, data) {
         let editor = data.editor;
         editor.summary = summary;
         editor.details = details;
 
         wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) {
           event.stopPropagation();
           event.target.blur();
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -10,16 +10,17 @@ this.EXPORTED_SYMBOLS = ["StyleSheetEdit
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
 const Editor  = require("devtools/sourceeditor/editor");
 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 const {CssLogic} = require("devtools/styleinspector/css-logic");
+const {console} = require("resource://gre/modules/devtools/Console.jsm");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/event-emitter.js");
 Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
@@ -246,21 +247,37 @@ StyleSheetEditor.prototype = {
         }
         this._state.text = source;
         this.sourceLoaded = true;
 
         if (callback) {
           callback(source);
         }
         return source;
+      }, e => {
+        if (this._isDestroyed) {
+          console.warn("Could not fetch the source for " +
+                       this.styleSheet.href +
+                       ", the editor was destroyed");
+          Cu.reportError(e);
+        } else {
+          throw e;
+        }
       });
     }, e => {
-      this.emit("error", { key: LOAD_ERROR, append: this.styleSheet.href });
-      throw e;
-    })
+      if (this._isDestroyed) {
+        console.warn("Could not fetch the source for " +
+                     this.styleSheet.href +
+                     ", the editor was destroyed");
+        Cu.reportError(e);
+      } else {
+        this.emit("error", { key: LOAD_ERROR, append: this.styleSheet.href });
+        throw e;
+      }
+    });
   },
 
   /**
    * Add markup to a region. UNUSED_CLASS is added to specified lines
    * @param region An object shaped like
    *   {
    *     start: { line: L1, column: C1 },
    *     end: { line: L2, column: C2 }    // optional
@@ -707,16 +724,17 @@ StyleSheetEditor.prototype = {
         this._sourceEditor.container.removeEventListener("mousemove",
           this._onMouseMove);
       }
       this._sourceEditor.destroy();
     }
     this.cssSheet.off("property-change", this._onPropertyChange);
     this.cssSheet.off("media-rules-changed", this._onMediaRulesChanged);
     this.styleSheet.off("error", this._onError);
+    this._isDestroyed = true;
   }
 }
 
 /**
  * Find a path on disk for a file given it's hosted uri, the uri of the
  * original resource that generated it (e.g. Sass file), and the location of the
  * local file for that source.
  *
--- a/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
@@ -4,17 +4,17 @@
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed. 
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
-const TESTCASE_URI = TEST_BASE + "autocomplete.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "autocomplete.html";
 const MAX_SUGGESTIONS = 15;
 
 // Pref which decides if CSS autocompletion is enabled in Style Editor or not.
 const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
 
 const {CSSProperties, CSSValues} = getCSSKeywords();
 
 // Test cases to test that autocompletion works correctly when enabled.
--- a/browser/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
@@ -3,17 +3,17 @@
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed. 
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
-const TESTCASE_URI = TEST_BASE + "four.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "four.html";
 
 let gUI;
 
 function test() {
   waitForExplicitFinish();
 
   addTabAndOpenStyleEditors(4, runTests);
 
--- a/browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
@@ -1,14 +1,14 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const TESTCASE_URI_HTML = TEST_BASE + "simple.html";
-const TESTCASE_URI_CSS = TEST_BASE + "simple.css";
+const TESTCASE_URI_HTML = TEST_BASE_HTTP + "simple.html";
+const TESTCASE_URI_CSS = TEST_BASE_HTTP + "simple.css";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 let tempScope = {};
 Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
 Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
 let FileUtils = tempScope.FileUtils;
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -4,17 +4,17 @@
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed.
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: summary is undefined");
 
-const TESTCASE_URI = TEST_BASE + "simple.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
 
 let gUI;
 
 function test()
 {
   waitForExplicitFinish();
 
   addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, testEditorAdded);
--- a/browser/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js
@@ -6,18 +6,18 @@
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed. 
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
 let gUI;
 
-const FIRST_TEST_PAGE = TEST_BASE + "inline-1.html"
-const SECOND_TEST_PAGE = TEST_BASE + "inline-2.html"
+const FIRST_TEST_PAGE = TEST_BASE_HTTP + "inline-1.html"
+const SECOND_TEST_PAGE = TEST_BASE_HTTP + "inline-2.html"
 const SAVE_PATH = "test.css";
 
 function test()
 {
   waitForExplicitFinish();
 
   addTabAndOpenStyleEditors(2, function(panel) {
     gUI = panel.UI;
--- a/browser/devtools/styleeditor/test/browser_styleeditor_new.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_new.js
@@ -4,17 +4,17 @@
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed. 
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
-const TESTCASE_URI = TEST_BASE + "simple.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
 
 let TESTCASE_CSS_SOURCE = "body{background-color:red;";
 
 let gOriginalHref;
 let gUI;
 
 waitForExplicitFinish();
 
--- a/browser/devtools/styleeditor/test/browser_styleeditor_nostyle.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_nostyle.js
@@ -1,13 +1,13 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const TESTCASE_URI = TEST_BASE + "nostyle.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "nostyle.html";
 
 
 function test()
 {
   waitForExplicitFinish();
 
   // launch Style Editor right when the tab is created (before load)
   // this checks that the Style Editor still launches correctly when it is opened
--- a/browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
@@ -4,17 +4,17 @@
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed. 
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
-const TESTCASE_URI = TEST_BASE + "minified.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "minified.html";
 
 let gUI;
 
 function test()
 {
   waitForExplicitFinish();
 
   addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, editor => {
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js
@@ -1,21 +1,21 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://gre/modules/Task.jsm");
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
-const TESTCASE_URI_HTML = TEST_BASE + "sourcemaps-watching.html";
-const TESTCASE_URI_CSS = TEST_BASE + "sourcemap-css/sourcemaps.css";
-const TESTCASE_URI_REG_CSS = TEST_BASE + "simple.css";
-const TESTCASE_URI_SCSS = TEST_BASE + "sourcemap-sass/sourcemaps.scss";
-const TESTCASE_URI_MAP = TEST_BASE + "sourcemap-css/sourcemaps.css.map";
+const TESTCASE_URI_HTML = TEST_BASE_HTTP + "sourcemaps-watching.html";
+const TESTCASE_URI_CSS = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css";
+const TESTCASE_URI_REG_CSS = TEST_BASE_HTTP + "simple.css";
+const TESTCASE_URI_SCSS = TEST_BASE_HTTP + "sourcemap-sass/sourcemaps.scss";
+const TESTCASE_URI_MAP = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css.map";
 const TESTCASE_SCSS_NAME = "sourcemaps.scss";
 
 const TRANSITIONS_PREF = "devtools.styleeditor.transitions";
 
 const CSS_TEXT = "* { color: blue }";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
@@ -4,17 +4,17 @@
 
 ///////////////////
 //
 // Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed. 
 //
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
-const TESTCASE_URI = TEST_BASE + "four.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "four.html";
 
 let gUI;
 
 function test()
 {
   waitForExplicitFinish();
 
   addTabAndOpenStyleEditors(4, runTests);
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
@@ -1,13 +1,13 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const TESTCASE_URI = TEST_BASE + "simple.html";
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
 
 let gOriginalWidth; // these are set by runTests()
 let gOriginalHeight;
 
 function test()
 {
   waitForExplicitFinish();
 
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -349,16 +349,17 @@ skip-if = e10s # Bug 1042253 - webconsol
 [browser_webconsole_log_file_filter.js]
 [browser_webconsole_expandable_timestamps.js]
 [browser_webconsole_autocomplete_in_debugger_stackframe.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_autocomplete_popup_close_on_tab_switch.js]
 skip-if = e10s # Bug 1042253 - webconsole tests disabled with e10s
 [browser_webconsole_autocomplete-properties-with-non-alphanumeric-names.js]
 [browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js]
+[browser_console_history_persist.js]
 [browser_webconsole_output_01.js]
 skip-if = e10s # Bug 1042253 - webconsole e10s tests
 [browser_webconsole_output_02.js]
 [browser_webconsole_output_03.js]
 [browser_webconsole_output_04.js]
 [browser_webconsole_output_05.js]
 [browser_webconsole_output_06.js]
 [browser_webconsole_output_dom_elements_01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_console_history_persist.js
@@ -0,0 +1,96 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that console command input is persisted across toolbox loads.
+// See Bug 943306.
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,Web Console test for persisting history - bug 943306";
+const INPUT_HISTORY_COUNT = 10;
+
+let test = asyncTest(function* () {
+  info ("Setting custom input history pref to " + INPUT_HISTORY_COUNT);
+  Services.prefs.setIntPref("devtools.webconsole.inputHistoryCount", INPUT_HISTORY_COUNT);
+
+  // First tab: run a bunch of commands and then make sure that you can
+  // navigate through their history.
+  yield loadTab(TEST_URI);
+  let hud1 = yield openConsole();
+  is (JSON.stringify(hud1.jsterm.history), "[]", "No history on first tab initially");
+  yield populateInputHistory(hud1);
+  is (JSON.stringify(hud1.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
+    "First tab has populated history");
+
+  // Second tab: Just make sure that you can navigate through the history
+  // generated by the first tab.
+  yield loadTab(TEST_URI);
+  let hud2 = yield openConsole();
+  is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
+    "Second tab has populated history");
+  yield testNaviatingHistoryInUI(hud2);
+  is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9",""]',
+    "An empty entry has been added in the second tab due to history perusal");
+
+  // Third tab: Should have the same history as first tab, but if we run a
+  // command, then the history of the first and second shouldn't be affected
+  yield loadTab(TEST_URI);
+  let hud3 = yield openConsole();
+  is (JSON.stringify(hud3.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
+    "Third tab has populated history");
+
+  // Set input value separately from execute so UP arrow accurately navigates history.
+  hud3.jsterm.setInputValue('"hello from third tab"');
+  hud3.jsterm.execute();
+
+  is (JSON.stringify(hud1.jsterm.history), '["0","1","2","3","4","5","6","7","8","9"]',
+    "First tab history hasn't changed due to command in third tab");
+  is (JSON.stringify(hud2.jsterm.history), '["0","1","2","3","4","5","6","7","8","9",""]',
+    "Second tab history hasn't changed due to command in third tab");
+  is (JSON.stringify(hud3.jsterm.history), '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
+    "Third tab has updated history (and purged the first result) after running a command");
+
+  // Fourth tab: Should have the latest command from the third tab, followed
+  // by the rest of the history from the first tab.
+  yield loadTab(TEST_URI);
+  let hud4 = yield openConsole();
+  is (JSON.stringify(hud4.jsterm.history), '["1","2","3","4","5","6","7","8","9","\\"hello from third tab\\""]',
+    "Fourth tab has most recent history");
+
+  info ("Clearing custom input history pref");
+  Services.prefs.clearUserPref("devtools.webconsole.inputHistoryCount");
+});
+
+/**
+ * Populate the history by running the following commands:
+ *  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
+ */
+function* populateInputHistory(hud) {
+  let jsterm = hud.jsterm;
+  let {inputNode} = jsterm;
+
+  for (let i = 0; i < INPUT_HISTORY_COUNT; i++) {
+    // Set input value separately from execute so UP arrow accurately navigates history.
+    jsterm.setInputValue(i);
+    jsterm.execute();
+  }
+}
+
+/**
+ * Check pressing up results in history traversal like:
+ *  [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
+ */
+function* testNaviatingHistoryInUI(hud) {
+  let jsterm = hud.jsterm;
+  let {inputNode} = jsterm;
+  inputNode.focus();
+
+  // Count backwards from original input and make sure that pressing up
+  // restores this.
+  for (let i = INPUT_HISTORY_COUNT - 1; i >= 0; i--) {
+    EventUtils.synthesizeKey("VK_UP", {});
+    is(inputNode.value, i, "Pressing up restores last input");
+  }
+}
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -8,16 +8,17 @@
 let {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 let {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let {require, TargetFactory} = devtools;
 let {Utils: WebConsoleUtils} = require("devtools/toolkit/webconsole/utils");
 let {Messages} = require("devtools/webconsole/console-output");
+const asyncStorage = require("devtools/toolkit/shared/async-storage");
 
 // promise._reportErrors = true; // please never leave me.
 //Services.prefs.setBoolPref("devtools.debugger.log", true);
 
 let gPendingOutputTest = 0;
 
 // The various categories of messages.
 const CATEGORY_NETWORK = 0;
@@ -317,16 +318,19 @@ let finishTest = Task.async(function* ()
   yield gDevTools.closeToolbox(target);
 
   finish();
 });
 
 registerCleanupFunction(function*() {
   gDevTools.testing = false;
 
+  // Remove stored console commands in between tests
+  yield asyncStorage.removeItem("webConsoleHistory");
+
   dumpConsoles();
 
   if (HUDService.getBrowserConsole()) {
     HUDService.toggleBrowserConsole();
   }
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   yield gDevTools.closeToolbox(target);
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -21,16 +21,18 @@ loader.lazyGetter(this, "AutocompletePop
 loader.lazyGetter(this, "ToolSidebar",
                   () => require("devtools/framework/sidebar").ToolSidebar);
 loader.lazyGetter(this, "NetworkPanel",
                   () => require("devtools/webconsole/network-panel").NetworkPanel);
 loader.lazyGetter(this, "ConsoleOutput",
                   () => require("devtools/webconsole/console-output").ConsoleOutput);
 loader.lazyGetter(this, "Messages",
                   () => require("devtools/webconsole/console-output").Messages);
+loader.lazyGetter(this, "asyncStorage",
+                  () => require("devtools/toolkit/shared/async-storage"));
 loader.lazyImporter(this, "EnvironmentClient", "resource://gre/modules/devtools/dbg-client.jsm");
 loader.lazyImporter(this, "ObjectClient", "resource://gre/modules/devtools/dbg-client.jsm");
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
 loader.lazyImporter(this, "VariablesViewController", "resource:///modules/devtools/VariablesViewController.jsm");
 loader.lazyImporter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
@@ -171,16 +173,17 @@ const THROTTLE_UPDATES = 1000; // millis
 const FILTER_PREFS_PREFIX = "devtools.webconsole.filter.";
 
 // The minimum font size.
 const MIN_FONT_SIZE = 10;
 
 const PREF_CONNECTION_TIMEOUT = "devtools.debugger.remote-timeout";
 const PREF_PERSISTLOG = "devtools.webconsole.persistlog";
 const PREF_MESSAGE_TIMESTAMP = "devtools.webconsole.timestampMessages";
+const PREF_INPUT_HISTORY_COUNT = "devtools.webconsole.inputHistoryCount";
 
 /**
  * A WebConsoleFrame instance is an interactive console initialized *per target*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the target's document content.
  *
  * The WebConsoleFrame is responsible for the actual Web Console UI
  * implementation.
@@ -438,22 +441,39 @@ WebConsoleFrame.prototype = {
    */
   get persistLog() {
     return Services.prefs.getBoolPref(PREF_PERSISTLOG);
   },
 
   /**
    * Initialize the WebConsoleFrame instance.
    * @return object
-   *         A promise object for the initialization.
+   *         A promise object that resolves once the frame is ready to use.
    */
-  init: function WCF_init()
+  init: function()
   {
     this._initUI();
-    return this._initConnection();
+    let connectionInited = this._initConnection();
+
+    // Don't reject if the history fails to load for some reason.
+    // This would be fine, the panel will just start with empty history.
+    let allReady = this.jsterm.historyLoaded.catch(() => {}).then(() => {
+      return connectionInited;
+    });
+
+    // This notification is only used in tests. Don't chain it onto
+    // the returned promise because the console panel needs to be attached
+    // to the toolbox before the web-console-created event is receieved.
+    let notifyObservers = () => {
+      let id = WebConsoleUtils.supportsString(this.hudId);
+      Services.obs.notifyObservers(id, "web-console-created", null);
+    };
+    allReady.then(notifyObservers, notifyObservers);
+
+    return allReady;
   },
 
   /**
    * Connect to the server using the remote debugging protocol.
    *
    * @private
    * @return object
    *         A promise object that is resolved/reject based on the connection
@@ -470,19 +490,16 @@ WebConsoleFrame.prototype = {
 
     this.proxy.connect().then(() => { // on success
       this._initDefer.resolve(this);
     }, (aReason) => { // on failure
       let node = this.createMessageNode(CATEGORY_JS, SEVERITY_ERROR,
                                         aReason.error + ": " + aReason.message);
       this.outputMessage(CATEGORY_JS, node, [aReason]);
       this._initDefer.reject(aReason);
-    }).then(() => {
-      let id = WebConsoleUtils.supportsString(this.hudId);
-      Services.obs.notifyObservers(id, "web-console-created", null);
     });
 
     return this._initDefer.promise;
   },
 
   /**
    * Find the Web Console UI elements and setup event listeners as needed.
    * @private
@@ -3049,42 +3066,68 @@ function getterOrSetterEvalMacro(aItem, 
  * @constructor
  * @param object aWebConsoleFrame
  *        The WebConsoleFrame object that owns this JSTerm instance.
  */
 function JSTerm(aWebConsoleFrame)
 {
   this.hud = aWebConsoleFrame;
   this.hudId = this.hud.hudId;
+  this.inputHistoryCount = Services.prefs.getIntPref(PREF_INPUT_HISTORY_COUNT);
 
   this.lastCompletion = { value: null };
-  this.history = [];
-
-  // Holds the number of entries in history. This value is incremented in
-  // this.execute().
-  this.historyIndex = 0; // incremented on this.execute()
-
-  // Holds the index of the history entry that the user is currently viewing.
-  // This is reset to this.history.length when this.execute() is invoked.
-  this.historyPlaceHolder = 0;
+  this._loadHistory();
+
   this._objectActorsInVariablesViews = new Map();
 
   this._keyPress = this._keyPress.bind(this);
   this._inputEventHandler = this._inputEventHandler.bind(this);
   this._focusEventHandler = this._focusEventHandler.bind(this);
   this._onKeypressInVariablesView = this._onKeypressInVariablesView.bind(this);
   this._blurEventHandler = this._blurEventHandler.bind(this);
 
   EventEmitter.decorate(this);
 }
 
 JSTerm.prototype = {
   SELECTED_FRAME: -1,
 
   /**
+   * Load the console history from previous sessions.
+   * @private
+   */
+  _loadHistory: function() {
+    this.history = [];
+    this.historyIndex = this.historyPlaceHolder = 0;
+
+    this.historyLoaded = asyncStorage.getItem("webConsoleHistory").then(value => {
+      if (Array.isArray(value)) {
+        // Since it was gotten asynchronously, there could be items already in
+        // the history.  It's not likely but stick them onto the end anyway.
+        this.history = value.concat(this.history);
+
+        // Holds the number of entries in history. This value is incremented in
+        // this.execute().
+        this.historyIndex = this.history.length;
+
+        // Holds the index of the history entry that the user is currently viewing.
+        // This is reset to this.history.length when this.execute() is invoked.
+        this.historyPlaceHolder = this.history.length;
+      }
+    }, console.error);
+  },
+
+  /**
+   * Stores the console history for future sessions.
+   */
+  storeHistory: function() {
+    asyncStorage.setItem("webConsoleHistory", this.history);
+  },
+
+  /**
    * Stores the data for the last completion.
    * @type object
    */
   lastCompletion: null,
 
   /**
    * Array that caches the user input suggestions received from the server.
    * @private
@@ -3383,16 +3426,22 @@ JSTerm.prototype = {
 
     this.requestEvaluation(aExecuteString, options).then(onResult, onResult);
 
     // Append a new value in the history of executed code, or overwrite the most
     // recent entry. The most recent entry may contain the last edited input
     // value that was not evaluated yet.
     this.history[this.historyIndex++] = aExecuteString;
     this.historyPlaceHolder = this.history.length;
+
+    if (this.history.length > this.inputHistoryCount) {
+      this.history.splice(0, this.history.length - this.inputHistoryCount);
+      this.historyIndex = this.historyPlaceHolder = this.history.length;
+    }
+    this.storeHistory();
     WebConsoleUtils.usageCount++;
     this.setInputValue("");
     this.clearCompletion();
     return deferred.promise;
   },
 
   /**
    * Request a JavaScript string evaluation from the server.
--- a/browser/themes/shared/devtools/netmonitor.inc.css
+++ b/browser/themes/shared/devtools/netmonitor.inc.css
@@ -490,19 +490,24 @@ label.requests-menu-status-code {
 
 .tabpanel-summary-label {
   -moz-padding-start: 4px;
   -moz-padding-end: 3px;
   font-weight: 600;
 }
 
 .tabpanel-summary-value {
+  color: inherit;
   -moz-padding-start: 3px;
 }
 
+.theme-dark .tabpanel-summary-value {
+  color: var(--theme-selection-color);
+}
+
 /* Headers tabpanel */
 
 #headers-summary-status,
 #headers-summary-version {
   padding-bottom: 2px;
 }
 
 #headers-summary-size {
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/BluetoothInterfaceHelpers.cpp
@@ -0,0 +1,28 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BluetoothInterfaceHelpers.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+//
+// Conversion
+//
+
+nsresult
+Convert(nsresult aIn, BluetoothStatus& aOut)
+{
+  if (NS_SUCCEEDED(aIn)) {
+    aOut = STATUS_SUCCESS;
+  } else if (aIn == NS_ERROR_OUT_OF_MEMORY) {
+    aOut = STATUS_NOMEM;
+  } else {
+    aOut = STATUS_FAIL;
+  }
+  return NS_OK;
+}
+
+END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/BluetoothInterfaceHelpers.h
+++ b/dom/bluetooth/BluetoothInterfaceHelpers.h
@@ -8,16 +8,23 @@
 #define mozilla_dom_bluetooth_bluetoothinterfacehelpers_h
 
 #include "BluetoothCommon.h"
 #include "nsThreadUtils.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 //
+// Conversion
+//
+
+nsresult
+Convert(nsresult aIn, BluetoothStatus& aOut);
+
+//
 // Result handling
 //
 // The classes of type |BluetoothResultRunnable[0..3]| transfer
 // a result handler from the I/O thread to the main thread for
 // execution. Call the methods |Create| and |Dispatch| to create or
 // create-and-dispatch a result runnable.
 //
 // You need to specify the called method. The |Create| and |Dispatch|
@@ -44,16 +51,19 @@ public:
     }
     return runnable.forget();
   }
 
   template <typename InitOp>
   static void
   Dispatch(Obj* aObj, Res (Obj::*aMethod)(), const InitOp& aInitOp)
   {
+    if (!aObj) {
+      return; // silently return if no result runnable has been given
+    }
     nsRefPtr<SelfType> runnable = Create(aObj, aMethod, aInitOp);
     if (!runnable) {
       BT_LOGR("BluetoothResultRunnable0::Create failed");
       return;
     }
     nsresult rv = NS_DispatchToMainThread(runnable);
     if (NS_FAILED(rv)) {
       BT_LOGR("NS_DispatchToMainThread failed: %X", rv);
@@ -103,16 +113,19 @@ public:
     }
     return runnable.forget();
   }
 
   template <typename InitOp>
   static void
   Dispatch(Obj* aObj, Res (Obj::*aMethod)(Arg1), const InitOp& aInitOp)
   {
+    if (!aObj) {
+      return; // silently return if no result runnable has been given
+    }
     nsRefPtr<SelfType> runnable = Create(aObj, aMethod, aInitOp);
     if (!runnable) {
       BT_LOGR("BluetoothResultRunnable1::Create failed");
       return;
     }
     nsresult rv = NS_DispatchToMainThread(runnable);
     if (NS_FAILED(rv)) {
       BT_LOGR("NS_DispatchToMainThread failed: %X", rv);
@@ -169,16 +182,19 @@ public:
     return runnable.forget();
   }
 
   template<typename InitOp>
   static void
   Dispatch(Obj* aObj, Res (Obj::*aMethod)(Arg1, Arg2, Arg3),
            const InitOp& aInitOp)
   {
+    if (!aObj) {
+      return; // silently return if no result runnable has been given
+    }
     nsRefPtr<SelfType> runnable = Create(aObj, aMethod, aInitOp);
     if (!runnable) {
       BT_LOGR("BluetoothResultRunnable3::Create failed");
       return;
     }
     nsresult rv = NS_DispatchToMainThread(runnable);
     if (NS_FAILED(rv)) {
       BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
@@ -330,16 +330,19 @@ public:
   CleanupAvrcpResultHandler(BluetoothProfileResultHandler* aRes)
   : mRes(aRes)
   { }
 
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     BT_WARNING("BluetoothAvrcpInterface::Cleanup failed: %d",
                (int)aStatus);
+
+    sBtAvrcpInterface = nullptr;
+
     if (mRes) {
       if (aStatus == STATUS_UNSUPPORTED) {
         /* Not all versions of Bluedroid support AVRCP. So if the
          * cleanup fails with STATUS_UNSUPPORTED, we still signal
          * success.
          */
         mRes->Deinit();
       } else {
@@ -366,16 +369,19 @@ public:
   CleanupA2dpResultHandler(BluetoothProfileResultHandler* aRes)
   : mRes(aRes)
   { }
 
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     BT_WARNING("BluetoothA2dpInterface::Cleanup failed: %d",
                (int)aStatus);
+
+    sBtA2dpInterface = nullptr;
+
     if (mRes) {
       mRes->OnError(NS_ERROR_FAILURE);
     }
   }
 
   void Cleanup() MOZ_OVERRIDE
   {
     sBtA2dpInterface = nullptr;
--- a/dom/bluetooth/bluedroid/BluetoothDaemonA2dpInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonA2dpInterface.cpp
@@ -390,17 +390,17 @@ BluetoothDaemonA2dpInterface::Init(
   } else {
     // We don't need a result handler if the caller is not interested.
     res = nullptr;
   }
 
   nsresult rv = mModule->RegisterModule(BluetoothDaemonA2dpModule::SERVICE_ID,
     0x00, BluetoothDaemonA2dpModule::MAX_NUM_CLIENTS, res);
   if (NS_FAILED(rv) && aRes) {
-    DispatchError(aRes, STATUS_FAIL);
+    DispatchError(aRes, rv);
   }
 }
 
 class BluetoothDaemonA2dpInterface::CleanupResultHandler MOZ_FINAL
   : public BluetoothSetupResultHandler
 {
 public:
   CleanupResultHandler(BluetoothDaemonA2dpModule* aModule,
@@ -438,43 +438,65 @@ private:
   BluetoothDaemonA2dpModule* mModule;
   nsRefPtr<BluetoothA2dpResultHandler> mRes;
 };
 
 void
 BluetoothDaemonA2dpInterface::Cleanup(
   BluetoothA2dpResultHandler* aRes)
 {
-  mModule->UnregisterModule(BluetoothDaemonA2dpModule::SERVICE_ID,
-                            new CleanupResultHandler(mModule, aRes));
+  nsresult rv = mModule->UnregisterModule(
+    BluetoothDaemonA2dpModule::SERVICE_ID,
+    new CleanupResultHandler(mModule, aRes));
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Connect / Disconnect */
 
 void
 BluetoothDaemonA2dpInterface::Connect(
   const nsAString& aBdAddr, BluetoothA2dpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ConnectCmd(aBdAddr, aRes);
+  nsresult rv = mModule->ConnectCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonA2dpInterface::Disconnect(
   const nsAString& aBdAddr, BluetoothA2dpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->DisconnectCmd(aBdAddr, aRes);
+  nsresult rv = mModule->DisconnectCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonA2dpInterface::DispatchError(
   BluetoothA2dpResultHandler* aRes, BluetoothStatus aStatus)
 {
   BluetoothResultRunnable1<BluetoothA2dpResultHandler, void,
                            BluetoothStatus, BluetoothStatus>::Dispatch(
     aRes, &BluetoothA2dpResultHandler::OnError,
     ConstantInitOp1<BluetoothStatus>(aStatus));
 }
 
+void
+BluetoothDaemonA2dpInterface::DispatchError(
+  BluetoothA2dpResultHandler* aRes, nsresult aRv)
+{
+  BluetoothStatus status;
+
+  if (NS_WARN_IF(NS_FAILED(Convert(aRv, status)))) {
+    status = STATUS_FAIL;
+  }
+  DispatchError(aRes, status);
+}
+
 END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/bluedroid/BluetoothDaemonA2dpInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonA2dpInterface.h
@@ -150,15 +150,16 @@ public:
   void Connect(const nsAString& aBdAddr,
                BluetoothA2dpResultHandler* aRes);
   void Disconnect(const nsAString& aBdAddr,
                   BluetoothA2dpResultHandler* aRes);
 
 private:
   void DispatchError(BluetoothA2dpResultHandler* aRes,
                      BluetoothStatus aStatus);
+  void DispatchError(BluetoothA2dpResultHandler* aRes, nsresult aRv);
 
   BluetoothDaemonA2dpModule* mModule;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothDaemonAvrcpInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonAvrcpInterface.cpp
@@ -888,17 +888,17 @@ BluetoothDaemonAvrcpInterface::Init(
     res = nullptr;
   }
 
   nsresult rv = mModule->RegisterModule(
     BluetoothDaemonAvrcpModule::SERVICE_ID,
     BluetoothDaemonAvrcpModule::MAX_NUM_CLIENTS, 0x00, res);
 
   if (NS_FAILED(rv) && aRes) {
-    DispatchError(aRes, STATUS_FAIL);
+    DispatchError(aRes, rv);
   }
 }
 
 class BluetoothDaemonAvrcpInterface::CleanupResultHandler MOZ_FINAL
   : public BluetoothSetupResultHandler
 {
 public:
   CleanupResultHandler(BluetoothDaemonAvrcpModule* aModule,
@@ -938,122 +938,173 @@ private:
 };
 
 void
 BluetoothDaemonAvrcpInterface::Cleanup(
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->UnregisterModule(BluetoothDaemonAvrcpModule::SERVICE_ID,
-                            new CleanupResultHandler(mModule, aRes));
+  nsresult rv = mModule->UnregisterModule(
+    BluetoothDaemonAvrcpModule::SERVICE_ID,
+    new CleanupResultHandler(mModule, aRes));
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::GetPlayStatusRsp(
   ControlPlayStatus aPlayStatus, uint32_t aSongLen, uint32_t aSongPos,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->GetPlayStatusRspCmd(aPlayStatus, aSongLen, aSongPos, aRes);
+  nsresult rv = mModule->GetPlayStatusRspCmd(aPlayStatus, aSongLen,
+                                             aSongPos, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::ListPlayerAppAttrRsp(
   int aNumAttr, const BluetoothAvrcpPlayerAttribute* aPAttrs,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ListPlayerAppAttrRspCmd(aNumAttr, aPAttrs, aRes);
+  nsresult rv = mModule->ListPlayerAppAttrRspCmd(aNumAttr, aPAttrs, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::ListPlayerAppValueRsp(
   int aNumVal, uint8_t* aPVals, BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ListPlayerAppValueRspCmd(aNumVal, aPVals, aRes);
+  nsresult rv = mModule->ListPlayerAppValueRspCmd(aNumVal, aPVals, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::GetPlayerAppValueRsp(
   uint8_t aNumAttrs, const uint8_t* aIds, const uint8_t* aValues,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->GetPlayerAppValueRspCmd(aNumAttrs, aIds, aValues, aRes);
+  nsresult rv = mModule->GetPlayerAppValueRspCmd(aNumAttrs, aIds,
+                                                 aValues, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::GetPlayerAppAttrTextRsp(
   int aNumAttr, const uint8_t* aIds, const char** aTexts,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->GetPlayerAppAttrTextRspCmd(aNumAttr, aIds, aTexts, aRes);
+  nsresult rv = mModule->GetPlayerAppAttrTextRspCmd(aNumAttr, aIds,
+                                                    aTexts, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::GetPlayerAppValueTextRsp(
   int aNumVal, const uint8_t* aIds, const char** aTexts,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->GetPlayerAppValueTextRspCmd(aNumVal, aIds, aTexts, aRes);
+  nsresult rv = mModule->GetPlayerAppValueTextRspCmd(aNumVal, aIds,
+                                                     aTexts, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::GetElementAttrRsp(
   uint8_t aNumAttr, const BluetoothAvrcpElementAttribute* aAttr,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->GetElementAttrRspCmd(aNumAttr, aAttr, aRes);
+  nsresult rv = mModule->GetElementAttrRspCmd(aNumAttr, aAttr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::SetPlayerAppValueRsp(
   BluetoothAvrcpStatus aRspStatus, BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->SetPlayerAppValueRspCmd(aRspStatus, aRes);
+  nsresult rv = mModule->SetPlayerAppValueRspCmd(aRspStatus, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::RegisterNotificationRsp(
   BluetoothAvrcpEvent aEvent,
   BluetoothAvrcpNotification aType,
   const BluetoothAvrcpNotificationParam& aParam,
   BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->RegisterNotificationRspCmd(aEvent, aType, aParam, aRes);
+  nsresult rv = mModule->RegisterNotificationRspCmd(aEvent, aType,
+                                                    aParam, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::SetVolume(
   uint8_t aVolume, BluetoothAvrcpResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->SetVolumeCmd(aVolume, aRes);
+  nsresult rv = mModule->SetVolumeCmd(aVolume, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonAvrcpInterface::DispatchError(
   BluetoothAvrcpResultHandler* aRes, BluetoothStatus aStatus)
 {
   BluetoothResultRunnable1<BluetoothAvrcpResultHandler, void,
                            BluetoothStatus, BluetoothStatus>::Dispatch(
     aRes, &BluetoothAvrcpResultHandler::OnError,
     ConstantInitOp1<BluetoothStatus>(aStatus));
 }
 
+void
+BluetoothDaemonAvrcpInterface::DispatchError(
+  BluetoothAvrcpResultHandler* aRes, nsresult aRv)
+{
+  BluetoothStatus status;
+
+  if (NS_WARN_IF(NS_FAILED(Convert(aRv, status)))) {
+    status = STATUS_FAIL;
+  }
+  DispatchError(aRes, status);
+}
+
 END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/bluedroid/BluetoothDaemonAvrcpInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonAvrcpInterface.h
@@ -341,15 +341,16 @@ public:
                                BluetoothAvrcpResultHandler* aRes) MOZ_OVERRIDE;
 
   void SetVolume(uint8_t aVolume,
                  BluetoothAvrcpResultHandler* aRes) MOZ_OVERRIDE;
 
 private:
   void DispatchError(BluetoothAvrcpResultHandler* aRes,
                      BluetoothStatus aStatus);
+  void DispatchError(BluetoothAvrcpResultHandler* aRes, nsresult aRv);
 
   BluetoothDaemonAvrcpModule* mModule;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHandsfreeInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHandsfreeInterface.cpp
@@ -1507,17 +1507,17 @@ BluetoothDaemonHandsfreeInterface::Init(
     res = nullptr;
   }
 
   nsresult rv = mModule->RegisterModule(
     BluetoothDaemonHandsfreeModule::SERVICE_ID, MODE_NARROWBAND_SPEECH,
     aMaxNumClients, res);
 
   if (NS_FAILED(rv) && aRes) {
-    DispatchError(aRes, STATUS_FAIL);
+    DispatchError(aRes, rv);
   }
 }
 
 class BluetoothDaemonHandsfreeInterface::CleanupResultHandler MOZ_FINAL
   : public BluetoothSetupResultHandler
 {
 public:
   CleanupResultHandler(BluetoothDaemonHandsfreeModule* aModule,
@@ -1527,25 +1527,27 @@ public:
   {
     MOZ_ASSERT(mModule);
   }
 
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    BT_LOGR("%s:%d", __func__, __LINE__);
     if (mRes) {
       mRes->OnError(aStatus);
     }
   }
 
   void UnregisterModule() MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
+    BT_LOGR("%s:%d", __func__, __LINE__);
     // Clear notification handler _after_ module has been
     // unregistered. While unregistering the module, we might
     // still receive notifications.
     mModule->SetNotificationHandler(nullptr);
 
     if (mRes) {
       mRes->Cleanup();
     }
@@ -1555,197 +1557,264 @@ private:
   BluetoothDaemonHandsfreeModule* mModule;
   nsRefPtr<BluetoothHandsfreeResultHandler> mRes;
 };
 
 void
 BluetoothDaemonHandsfreeInterface::Cleanup(
   BluetoothHandsfreeResultHandler* aRes)
 {
-  mModule->UnregisterModule(BluetoothDaemonHandsfreeModule::SERVICE_ID,
-                            new CleanupResultHandler(mModule, aRes));
+  BT_LOGR("%s:%d", __func__, __LINE__);
+  nsresult rv = mModule->UnregisterModule(
+    BluetoothDaemonHandsfreeModule::SERVICE_ID,
+    new CleanupResultHandler(mModule, aRes));
+  BT_LOGR("%s:%d", __func__, __LINE__);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Connect / Disconnect */
 
 void
 BluetoothDaemonHandsfreeInterface::Connect(
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ConnectCmd(aBdAddr, aRes);
+  nsresult rv = mModule->ConnectCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::Disconnect(
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->DisconnectCmd(aBdAddr, aRes);
+  nsresult rv = mModule->DisconnectCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::ConnectAudio(
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ConnectAudioCmd(aBdAddr, aRes);
+  nsresult rv = mModule->ConnectAudioCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::DisconnectAudio(
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->DisconnectAudioCmd(aBdAddr, aRes);
+  nsresult rv = mModule->DisconnectAudioCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Voice Recognition */
 
 void
 BluetoothDaemonHandsfreeInterface::StartVoiceRecognition(
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->StartVoiceRecognitionCmd(aBdAddr, aRes);
+  nsresult rv = mModule->StartVoiceRecognitionCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::StopVoiceRecognition(
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->StopVoiceRecognitionCmd(aBdAddr, aRes);
+  nsresult rv = mModule->StopVoiceRecognitionCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Volume */
 
 void
 BluetoothDaemonHandsfreeInterface::VolumeControl(
   BluetoothHandsfreeVolumeType aType, int aVolume, const nsAString& aBdAddr,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->VolumeControlCmd(aType, aVolume, aBdAddr, aRes);
+  nsresult rv = mModule->VolumeControlCmd(aType, aVolume, aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Device status */
 
 void
 BluetoothDaemonHandsfreeInterface::DeviceStatusNotification(
   BluetoothHandsfreeNetworkState aNtkState,
   BluetoothHandsfreeServiceType aSvcType, int aSignal, int aBattChg,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->DeviceStatusNotificationCmd(aNtkState, aSvcType, aSignal,
-                                       aBattChg, aRes);
+  nsresult rv = mModule->DeviceStatusNotificationCmd(aNtkState, aSvcType,
+                                                     aSignal, aBattChg,
+                                                     aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Responses */
 
 void
 BluetoothDaemonHandsfreeInterface::CopsResponse(
   const char* aCops, const nsAString& aBdAddr,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->CopsResponseCmd(aCops, aBdAddr, aRes);
+  nsresult rv = mModule->CopsResponseCmd(aCops, aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::CindResponse(
   int aSvc, int aNumActive, int aNumHeld,
   BluetoothHandsfreeCallState aCallSetupState,
   int aSignal, int aRoam, int aBattChg,
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->CindResponseCmd(aSvc, aNumActive, aNumHeld, aCallSetupState,
-                           aSignal, aRoam, aBattChg, aBdAddr, aRes);
+  nsresult rv = mModule->CindResponseCmd(aSvc, aNumActive, aNumHeld,
+                                         aCallSetupState, aSignal,
+                                         aRoam, aBattChg, aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::FormattedAtResponse(
   const char* aRsp, const nsAString& aBdAddr,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->FormattedAtResponseCmd(aRsp, aBdAddr, aRes);
+  nsresult rv = mModule->FormattedAtResponseCmd(aRsp, aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::AtResponse(
   BluetoothHandsfreeAtResponse aResponseCode, int aErrorCode,
   const nsAString& aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->AtResponseCmd(aResponseCode, aErrorCode, aBdAddr, aRes);
+  nsresult rv = mModule->AtResponseCmd(aResponseCode, aErrorCode,
+                                       aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::ClccResponse(
   int aIndex, BluetoothHandsfreeCallDirection aDir,
   BluetoothHandsfreeCallState aState,
   BluetoothHandsfreeCallMode aMode,
   BluetoothHandsfreeCallMptyType aMpty,
   const nsAString& aNumber,
   BluetoothHandsfreeCallAddressType aType,
   const nsAString& aBdAddr,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ClccResponseCmd(aIndex, aDir, aState, aMode, aMpty, aNumber,
-                           aType, aBdAddr, aRes);
+  nsresult rv = mModule->ClccResponseCmd(aIndex, aDir, aState, aMode, aMpty,
+                                         aNumber, aType, aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Phone State */
 
 void
 BluetoothDaemonHandsfreeInterface::PhoneStateChange(
   int aNumActive, int aNumHeld,
   BluetoothHandsfreeCallState aCallSetupState,
   const nsAString& aNumber,
   BluetoothHandsfreeCallAddressType aType,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->PhoneStateChangeCmd(aNumActive, aNumHeld, aCallSetupState, aNumber,
-                               aType, aRes);
+  nsresult rv = mModule->PhoneStateChangeCmd(aNumActive, aNumHeld,
+                                             aCallSetupState, aNumber,
+                                             aType, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Wide Band Speech */
 
 void
 BluetoothDaemonHandsfreeInterface::ConfigureWbs(
   const nsAString& aBdAddr, BluetoothHandsfreeWbsConfig aConfig,
   BluetoothHandsfreeResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ConfigureWbsCmd(aBdAddr, aConfig, aRes);
+  nsresult rv = mModule->ConfigureWbsCmd(aBdAddr, aConfig, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonHandsfreeInterface::DispatchError(
   BluetoothHandsfreeResultHandler* aRes, BluetoothStatus aStatus)
 {
   BluetoothResultRunnable1<BluetoothHandsfreeResultHandler, void,
                            BluetoothStatus, BluetoothStatus>::Dispatch(
     aRes, &BluetoothHandsfreeResultHandler::OnError,
     ConstantInitOp1<BluetoothStatus>(aStatus));
 }
 
+void
+BluetoothDaemonHandsfreeInterface::DispatchError(
+  BluetoothHandsfreeResultHandler* aRes, nsresult aRv)
+{
+  BluetoothStatus status;
+
+  if (NS_WARN_IF(NS_FAILED(Convert(aRv, status)))) {
+    status = STATUS_FAIL;
+  }
+  DispatchError(aRes, status);
+}
+
 END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/bluedroid/BluetoothDaemonHandsfreeInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonHandsfreeInterface.h
@@ -467,15 +467,16 @@ public:
   /* Wide Band Speech */
   void ConfigureWbs(const nsAString& aBdAddr,
                     BluetoothHandsfreeWbsConfig aConfig,
                     BluetoothHandsfreeResultHandler* aRes);
 
 private:
   void DispatchError(BluetoothHandsfreeResultHandler* aRes,
                      BluetoothStatus aStatus);
+  void DispatchError(BluetoothHandsfreeResultHandler* aRes, nsresult aRv);
 
   BluetoothDaemonHandsfreeModule* mModule;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothDaemonInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonInterface.cpp
@@ -1888,17 +1888,25 @@ BluetoothDaemonInterface::OnConnectError
       break;
   }
 }
 
 void
 BluetoothDaemonInterface::OnDisconnect(enum Channel aChannel)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!mResultHandlerQ.IsEmpty());
+
+  if (mResultHandlerQ.IsEmpty()) {
+    if (sNotificationHandler) {
+      // Bluetooth daemon crashed; clear state
+      sNotificationHandler->AdapterStateChangedNotification(false);
+      sNotificationHandler = nullptr;
+    }
+    return;
+  }
 
   switch (aChannel) {
     case CMD_CHANNEL:
       // We don't have to do anything here. Step 4 is triggered
       // by the daemon.
       break;
     case NTF_CHANNEL:
       // Cleanup, step 4: Close listen socket
@@ -2189,143 +2197,195 @@ private:
 void
 BluetoothDaemonInterface::Cleanup(BluetoothResultHandler* aRes)
 {
   sNotificationHandler = nullptr;
 
   mResultHandlerQ.AppendElement(aRes);
 
   // Cleanup, step 1: Unregister Socket module
-  mProtocol->UnregisterModuleCmd(0x02, new CleanupResultHandler(this));
+  nsresult rv = mProtocol->UnregisterModuleCmd(
+    0x02, new CleanupResultHandler(this));
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::Enable(BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>(mProtocol)->EnableCmd(aRes);
+  nsresult rv =
+    static_cast<BluetoothDaemonCoreModule*>(mProtocol)->EnableCmd(aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::Disable(BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>(mProtocol)->DisableCmd(aRes);
+  nsresult rv =
+    static_cast<BluetoothDaemonCoreModule*>(mProtocol)->DisableCmd(aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Adapter Properties */
 
 void
 BluetoothDaemonInterface::GetAdapterProperties(BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->GetAdapterPropertiesCmd(aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::GetAdapterProperty(const nsAString& aName,
                                              BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->GetAdapterPropertyCmd(aName, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::SetAdapterProperty(
   const BluetoothNamedValue& aProperty, BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->SetAdapterPropertyCmd(aProperty, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Remote Device Properties */
 
 void
 BluetoothDaemonInterface::GetRemoteDeviceProperties(
   const nsAString& aRemoteAddr, BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->GetRemoteDevicePropertiesCmd(aRemoteAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::GetRemoteDeviceProperty(
   const nsAString& aRemoteAddr, const nsAString& aName,
   BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->GetRemoteDevicePropertyCmd(aRemoteAddr, aName, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::SetRemoteDeviceProperty(
   const nsAString& aRemoteAddr, const BluetoothNamedValue& aProperty,
   BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->SetRemoteDevicePropertyCmd(aRemoteAddr, aProperty, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Remote Services */
 
 void
 BluetoothDaemonInterface::GetRemoteServiceRecord(const nsAString& aRemoteAddr,
                                                  const uint8_t aUuid[16],
                                                  BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>(
-    mProtocol)->GetRemoteServiceRecordCmd(aRemoteAddr, aUuid, aRes);
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
+    (mProtocol)->GetRemoteServiceRecordCmd(aRemoteAddr, aUuid, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::GetRemoteServices(const nsAString& aRemoteAddr,
                                             BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>(
-    mProtocol)->GetRemoteServicesCmd(aRemoteAddr, aRes);
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
+    (mProtocol)->GetRemoteServicesCmd(aRemoteAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Discovery */
 
 void
 BluetoothDaemonInterface::StartDiscovery(BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>(mProtocol)->StartDiscoveryCmd(aRes);
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
+    (mProtocol)->StartDiscoveryCmd(aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::CancelDiscovery(BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->CancelDiscoveryCmd(aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Bonds */
 
 void
 BluetoothDaemonInterface::CreateBond(const nsAString& aBdAddr,
                                      BluetoothTransport aTransport,
                                      BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->CreateBondCmd(aBdAddr, aTransport, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::RemoveBond(const nsAString& aBdAddr,
                                      BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->RemoveBondCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::CancelBond(const nsAString& aBdAddr,
                                      BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->CancelBondCmd(aBdAddr, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Connection */
 
 void
 BluetoothDaemonInterface::GetConnectionState(const nsAString& aBdAddr,
                                              BluetoothResultHandler* aRes)
 {
@@ -2334,58 +2394,73 @@ BluetoothDaemonInterface::GetConnectionS
 
 /* Authentication */
 
 void
 BluetoothDaemonInterface::PinReply(const nsAString& aBdAddr, bool aAccept,
                                    const nsAString& aPinCode,
                                    BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->PinReplyCmd(aBdAddr, aAccept, aPinCode, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::SspReply(const nsAString& aBdAddr,
                                    const nsAString& aVariant,
                                    bool aAccept, uint32_t aPasskey,
                                    BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->SspReplyCmd(aBdAddr, aVariant, aAccept, aPasskey, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* DUT Mode */
 
 void
 BluetoothDaemonInterface::DutModeConfigure(bool aEnable,
                                            BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->DutModeConfigureCmd(aEnable, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonInterface::DutModeSend(uint16_t aOpcode, uint8_t* aBuf,
                                       uint8_t aLen,
                                       BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->DutModeSendCmd(aOpcode, aBuf, aLen, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* LE Mode */
 
 void
 BluetoothDaemonInterface::LeTestMode(uint16_t aOpcode, uint8_t* aBuf,
                                      uint8_t aLen,
                                      BluetoothResultHandler* aRes)
 {
-  static_cast<BluetoothDaemonCoreModule*>
+  nsresult rv = static_cast<BluetoothDaemonCoreModule*>
     (mProtocol)->LeTestModeCmd(aOpcode, aBuf, aLen, aRes);
+  if (NS_FAILED(rv)) {
+    DispatchError(aRes, rv);
+  }
 }
 
 /* Energy Information */
 
 void
 BluetoothDaemonInterface::ReadEnergyInfo(BluetoothResultHandler* aRes)
 {
   // NO-OP: no corresponding interface of current BlueZ
@@ -2396,16 +2471,28 @@ BluetoothDaemonInterface::DispatchError(
                                         BluetoothStatus aStatus)
 {
   BluetoothResultRunnable1<
     BluetoothResultHandler, void, BluetoothStatus, BluetoothStatus>::Dispatch(
     aRes, &BluetoothResultHandler::OnError,
     ConstantInitOp1<BluetoothStatus>(aStatus));
 }
 
+void
+BluetoothDaemonInterface::DispatchError(BluetoothResultHandler* aRes,
+                                        nsresult aRv)
+{
+  BluetoothStatus status;
+
+  if (NS_WARN_IF(NS_FAILED(Convert(aRv, status)))) {
+    status = STATUS_FAIL;
+  }
+  DispatchError(aRes, status);
+}
+
 // Profile Interfaces
 //
 
 BluetoothSocketInterface*
 BluetoothDaemonInterface::GetBluetoothSocketInterface()
 {
   if (mSocketInterface) {
     return mSocketInterface;
--- a/dom/bluetooth/bluedroid/BluetoothDaemonInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonInterface.h
@@ -130,16 +130,17 @@ protected:
   void OnDisconnect(enum Channel aChannel);
 
   nsresult CreateRandomAddressString(const nsACString& aPrefix,
                                      unsigned long aPostfixLength,
                                      nsACString& aAddress);
 
 private:
   void DispatchError(BluetoothResultHandler* aRes, BluetoothStatus aStatus);
+  void DispatchError(BluetoothResultHandler* aRes, nsresult aRv);
 
   nsCString mListenSocketName;
   nsRefPtr<BluetoothDaemonListenSocket> mListenSocket;
   nsRefPtr<BluetoothDaemonChannel> mCmdChannel;
   nsRefPtr<BluetoothDaemonChannel> mNtfChannel;
   nsAutoPtr<BluetoothDaemonProtocol> mProtocol;
 
   nsTArray<nsRefPtr<BluetoothResultHandler> > mResultHandlerQ;
--- a/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.cpp
@@ -310,43 +310,78 @@ BluetoothDaemonSocketInterface::Listen(B
                                        const nsAString& aServiceName,
                                        const uint8_t aServiceUuid[16],
                                        int aChannel, bool aEncrypt,
                                        bool aAuth,
                                        BluetoothSocketResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ListenCmd(aType, aServiceName, aServiceUuid, aChannel,
-                     aEncrypt, aAuth, aRes);
+  nsresult rv = mModule->ListenCmd(aType, aServiceName, aServiceUuid,
+                                   aChannel, aEncrypt, aAuth, aRes);
+  if (NS_FAILED(rv))  {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonSocketInterface::Connect(const nsAString& aBdAddr,
                                         BluetoothSocketType aType,
                                         const uint8_t aUuid[16],
                                         int aChannel, bool aEncrypt,
                                         bool aAuth,
                                         BluetoothSocketResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->ConnectCmd(aBdAddr, aType, aUuid, aChannel, aEncrypt, aAuth, aRes);
+  nsresult rv = mModule->ConnectCmd(aBdAddr, aType, aUuid, aChannel,
+                                    aEncrypt, aAuth, aRes);
+  if (NS_FAILED(rv))  {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonSocketInterface::Accept(int aFd,
                                     BluetoothSocketResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->AcceptCmd(aFd, aRes);
+  nsresult rv = mModule->AcceptCmd(aFd, aRes);
+  if (NS_FAILED(rv))  {
+    DispatchError(aRes, rv);
+  }
 }
 
 void
 BluetoothDaemonSocketInterface::Close(BluetoothSocketResultHandler* aRes)
 {
   MOZ_ASSERT(mModule);
 
-  mModule->CloseCmd(aRes);
+  nsresult rv = mModule->CloseCmd(aRes);
+  if (NS_FAILED(rv))  {
+    DispatchError(aRes, rv);
+  }
+}
+
+void
+BluetoothDaemonSocketInterface::DispatchError(
+  BluetoothSocketResultHandler* aRes, BluetoothStatus aStatus)
+{
+  BluetoothResultRunnable1<BluetoothSocketResultHandler, void,
+                           BluetoothStatus, BluetoothStatus>::Dispatch(
+    aRes, &BluetoothSocketResultHandler::OnError,
+    ConstantInitOp1<BluetoothStatus>(aStatus));
+}
+
+void
+BluetoothDaemonSocketInterface::DispatchError(
+  BluetoothSocketResultHandler* aRes, nsresult aRv)
+{
+  BluetoothStatus status;
+
+  if (NS_WARN_IF(NS_FAILED(Convert(aRv, status)))) {
+    status = STATUS_FAIL;
+  }
+  DispatchError(aRes, status);
 }
 
 END_BLUETOOTH_NAMESPACE
--- a/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.h
@@ -108,14 +108,18 @@ public:
                int aChannel, bool aEncrypt, bool aAuth,
                BluetoothSocketResultHandler* aRes);
 
   void Accept(int aFd, BluetoothSocketResultHandler* aRes);
 
   void Close(BluetoothSocketResultHandler* aRes);
 
 private:
+  void DispatchError(BluetoothSocketResultHandler* aRes,
+                     BluetoothStatus aStatus);
+  void DispatchError(BluetoothSocketResultHandler* aRes, nsresult aRv);
+
   BluetoothDaemonSocketModule* mModule;
 };
 
 END_BLUETOOTH_NAMESPACE
 
 #endif
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -305,17 +305,18 @@ class BluetoothServiceBluedroid::Disable
 {
 public:
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     BT_LOGR("BluetoothInterface::Disable failed: %d", aStatus);
 
-    BluetoothService::AcknowledgeToggleBt(true);
+    // Always make progress; even on failures
+    BluetoothService::AcknowledgeToggleBt(false);
   }
 };
 
 nsresult
 BluetoothServiceBluedroid::StopGonkBluetooth()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -427,16 +427,19 @@ class CleanupResultHandler MOZ_FINAL : p
 public:
   CleanupResultHandler(BluetoothProfileResultHandler* aRes)
   : mRes(aRes)
   { }
 
   void OnError(BluetoothStatus aStatus) MOZ_OVERRIDE
   {
     BT_WARNING("BluetoothHandsfreeInterface::Cleanup failed: %d", (int)aStatus);
+
+    sBluetoothHfpInterface = nullptr;
+
     if (mRes) {
       mRes->OnError(NS_ERROR_FAILURE);
     }
   }
 
   void Cleanup() MOZ_OVERRIDE
   {
     sBluetoothHfpInterface = nullptr;
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 if CONFIG['MOZ_B2G_BT']:
     SOURCES += [
         'BluetoothAdapter.cpp',
         'BluetoothDevice.cpp',
         'BluetoothHidManager.cpp',
         'BluetoothInterface.cpp',
+        'BluetoothInterfaceHelpers.cpp',
         'BluetoothManager.cpp',
         'BluetoothProfileController.cpp',
         'BluetoothPropertyContainer.cpp',
         'BluetoothReplyRunnable.cpp',
         'BluetoothService.cpp',
         'BluetoothUtils.cpp',
         'BluetoothUuid.cpp',
         'ipc/BluetoothChild.cpp',
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -37,16 +37,18 @@ static const char *sEGLExtensionNames[] 
     "EGL_KHR_fence_sync",
     "EGL_ANDROID_native_fence_sync"
 };
 
 #if defined(ANDROID)
 
 static PRLibrary* LoadApitraceLibrary()
 {
+    // Initialization of gfx prefs here is only needed during the unit tests...
+    gfxPrefs::GetSingleton();
     if (!gfxPrefs::UseApitrace()) {
         return nullptr;
     }
 
     static PRLibrary* sApitraceLibrary = nullptr;
 
     if (sApitraceLibrary)
         return sApitraceLibrary;
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -386,17 +386,17 @@ pref("dom.max_chrome_script_run_time", 0
 pref("dom.max_script_run_time", 20);
 
 // JS error console
 pref("devtools.errorconsole.enabled", false);
 // Absolute path to the devtools unix domain socket file used
 // to communicate with a usb cable via adb forward.
 pref("devtools.debugger.unix-domain-socket", "/data/data/@ANDROID_PACKAGE_NAME@/firefox-debugger-socket");
 
-pref("font.size.inflation.minTwips", 120);
+pref("font.size.inflation.minTwips", 0);
 
 // When true, zooming will be enabled on all sites, even ones that declare user-scalable=no.
 pref("browser.ui.zoom.force-user-scalable", false);
 
 pref("ui.zoomedview.enabled", false);
 pref("ui.zoomedview.limitReadableSize", 8);  // value in layer pixels
 
 pref("ui.touch.radius.enabled", false);
--- a/mobile/android/base/distribution/ReferrerReceiver.java
+++ b/mobile/android/base/distribution/ReferrerReceiver.java
@@ -40,27 +40,29 @@ public class ReferrerReceiver extends Br
     @Override
     public void onReceive(Context context, Intent intent) {
         Log.v(LOGTAG, "Received intent " + intent);
         if (!ACTION_INSTALL_REFERRER.equals(intent.getAction())) {
             // This should never happen.
             return;
         }
 
+        // Track the referrer object for distribution handling.
         ReferrerDescriptor referrer = new ReferrerDescriptor(intent.getStringExtra("referrer"));
 
-        // Track the referrer object for distribution handling.
+        if (!TextUtils.equals(referrer.source, MOZILLA_UTM_SOURCE)) {
+            return;
+        }
+
         if (TextUtils.equals(referrer.campaign, DISTRIBUTION_UTM_CAMPAIGN)) {
             Distribution.onReceivedReferrer(context, referrer);
         } else {
             Log.d(LOGTAG, "Not downloading distribution: non-matching campaign.");
-        }
-
-        // If this is a Mozilla campaign, pass the campaign along to Gecko.
-        if (TextUtils.equals(referrer.source, MOZILLA_UTM_SOURCE)) {
+            // If this is a Mozilla campaign, pass the campaign along to Gecko.
+            // It'll pretend to be a "playstore" distribution for BLP purposes.
             propagateMozillaCampaign(referrer);
         }
 
         // Broadcast a secondary, local intent to allow test code to respond.
         final Intent receivedIntent = new Intent(ACTION_REFERRER_RECEIVED);
         LocalBroadcastManager.getInstance(context).sendBroadcast(receivedIntent);
     }
 
--- a/mobile/android/base/util/ActivityUtils.java
+++ b/mobile/android/base/util/ActivityUtils.java
@@ -15,17 +15,17 @@ import org.mozilla.gecko.AppConstants.Ve
 public class ActivityUtils {
     private ActivityUtils() {
     }
 
     public static void setFullScreen(Activity activity, boolean fullscreen) {
         // Hide/show the system notification bar
         Window window = activity.getWindow();
 
-        if (Versions.feature11Plus) {
+        if (Versions.feature16Plus) {
             final int newVis;
             if (fullscreen) {
                 newVis = View.SYSTEM_UI_FLAG_FULLSCREEN |
                          View.SYSTEM_UI_FLAG_LOW_PROFILE;
             } else {
                 newVis = View.SYSTEM_UI_FLAG_VISIBLE;
             }
 
@@ -35,17 +35,17 @@ public class ActivityUtils {
                             WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
                             WindowManager.LayoutParams.FLAG_FULLSCREEN);
         }
     }
 
     public static boolean isFullScreen(final Activity activity) {
         final Window window = activity.getWindow();
 
-        if (Versions.feature11Plus) {
+        if (Versions.feature16Plus) {
             final int vis = window.getDecorView().getSystemUiVisibility();
             return (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
-        } else {
-            final int flags = window.getAttributes().flags;
-            return ((flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0);
         }
+
+        final int flags = window.getAttributes().flags;
+        return ((flags & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0);
     }
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -621,16 +621,27 @@ var BrowserApp = {
           button: {
             icon: "drawable://switch_button_icon",
             label: buttonLabel,
             callback: () => { BrowserApp.selectTab(tab); },
           }
         });
       });
 
+    NativeWindow.contextmenus.add(stringGetter("contextmenu.addToReadingList"),
+      NativeWindow.contextmenus.linkOpenableContext,
+      function(aTarget) {
+        let url = NativeWindow.contextmenus._getLinkURL(aTarget);
+        Messaging.sendRequestForResult({
+            type: "Reader:AddToList",
+            title: truncate(url, MAX_TITLE_LENGTH),
+            url: truncate(url, MAX_URI_LENGTH),
+        }).catch(Cu.reportError);
+      });
+
     NativeWindow.contextmenus.add(stringGetter("contextmenu.copyLink"),
       NativeWindow.contextmenus.linkCopyableContext,
       function(aTarget) {
         UITelemetry.addEvent("action.1", "contextmenu", null, "web_copy_link");
 
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
         NativeWindow.contextmenus._copyStringToDefaultClipboard(url);
       });
--- a/services/common/logmanager.js
+++ b/services/common/logmanager.js
@@ -1,39 +1,39 @@
 /* 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/. */
+"use strict;"
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, 'Services',
-  'resource://gre/modules/Services.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Preferences',
-  'resource://gre/modules/Preferences.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'FileUtils',
-  'resource://gre/modules/FileUtils.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Log',
-  'resource://gre/modules/Log.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'Task',
-  'resource://gre/modules/Task.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'OS',
-  'resource://gre/modules/osfile.jsm');
-XPCOMUtils.defineLazyModuleGetter(this, 'CommonUtils',
-  'resource://services-common/utils.js');
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Log",
+  "resource://gre/modules/Log.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
+  "resource://services-common/utils.js");
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
 
 this.EXPORTED_SYMBOLS = [
   "LogManager",
 ];
 
 const DEFAULT_MAX_ERROR_AGE = 20 * 24 * 60 * 60; // 20 days
 
 // "shared" logs (ie, where the same log name is used by multiple LogManager
 // instances) are a fact of life here - eg, FirefoxAccounts logs are used by
-// both Sync and Reading-list.
+// both Sync and Reading List.
 // However, different instances have different pref branches, so we need to
 // handle when one pref branch says "Debug" and the other says "Error"
 // So we (a) keep singleton console and dump appenders and (b) keep track
 // of the minimum (ie, most verbose) level and use that.
 // This avoids (a) the most recent setter winning (as that is indeterminate)
 // and (b) multiple dump/console appenders being added to the same log multiple
 // times, which would cause messages to appear twice.
 
@@ -98,17 +98,17 @@ LogManager.prototype = {
       return observer;
     }
 
     this._observeConsolePref = setupAppender(consoleAppender, "log.appender.console", Log.Level.Error, true);
     this._observeDumpPref = setupAppender(dumpAppender, "log.appender.dump", Log.Level.Error, true);
 
     // The file appender doesn't get the special singleton behaviour.
     let fapp = this._fileAppender = new Log.StorageStreamAppender(formatter);
-    // the stream gets a default of Debug as the user must go out of there way
+    // the stream gets a default of Debug as the user must go out of their way
     // to see the stuff spewed to it.
     this._observeStreamPref = setupAppender(fapp, "log.appender.file.level", Log.Level.Debug);
 
     // now attach the appenders to all our logs.
     for (let logName of logNames) {
       let log = Log.repository.getLogger(logName);
       for (let appender of [fapp, dumpAppender, consoleAppender]) {
         log.addAppender(appender);
@@ -146,121 +146,119 @@ LogManager.prototype = {
    * completion or rejected if there is an error.
    */
   _copyStreamToFile: Task.async(function* (inputStream, outputFile) {
     // The log data could be large, so we don't want to pass it all in a single
     // message, so use BUFFER_SIZE chunks.
     const BUFFER_SIZE = 8192;
 
     // get a binary stream
-    let binaryStream = Cc['@mozilla.org/binaryinputstream;1'].createInstance(Ci.nsIBinaryInputStream);
+    let binaryStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
     binaryStream.setInputStream(inputStream);
     yield OS.File.makeDir(outputFile.parent.path, { ignoreExisting: true });
     let output = yield OS.File.open(outputFile.path, { write: true} );
     try {
       while (true) {
         let available = binaryStream.available();
         if (!available) {
           break;
         }
         let chunk = binaryStream.readByteArray(Math.min(available, BUFFER_SIZE));
         yield output.write(new Uint8Array(chunk));
       }
     } finally {
-      inputStream.close();
-      binaryStream.close();
-      yield output.close();
+      try {
+        binaryStream.close(); // inputStream is closed by the binaryStream
+        yield output.close();
+      } catch (ex) {
+        this._log.error("Failed to close the input stream", ex);
+      }
     }
     this._log.trace("finished copy to", outputFile.path);
-    return (yield OS.File.stat(outputFile.path)).lastModificationDate;
   }),
 
   /**
    * Possibly generate a log file for all accumulated log messages and refresh
    * the input & output streams.
    * Returns a promise that resolves on completion or rejects if the file could
    * not be written.
    */
-  resetFileLog(reason) {
-    return new Promise((resolve, reject) => {
-      try {
-        let flushToFile;
-        let reasonPrefix;
-        switch (reason) {
-          case this.REASON_SUCCESS:
-            flushToFile = this._prefs.get("log.appender.file.logOnSuccess");
-            reasonPrefix = "success";
-            break;
-          case this.REASON_ERROR:
-            flushToFile = this._prefs.get("log.appender.file.logOnError");
-            reasonPrefix = "error";
-            break;
-          default:
-            return reject(new Error("Invalid reason"));
-        }
+  resetFileLog: Task.async(function* (reason) {
+    try {
+      let flushToFile;
+      let reasonPrefix;
+      switch (reason) {
+        case this.REASON_SUCCESS:
+          flushToFile = this._prefs.get("log.appender.file.logOnSuccess", false);
+          reasonPrefix = "success";
+          break;
+        case this.REASON_ERROR:
+          flushToFile = this._prefs.get("log.appender.file.logOnError", true);
+          reasonPrefix = "error";
+          break;
+        default:
+          throw new Error("Invalid reason");
+      }
 
-        let inStream = this._fileAppender.getInputStream();
+      // might as well avoid creating an input stream if we aren't going to use it.
+      if (!flushToFile) {
         this._fileAppender.reset();
-        if (flushToFile && inStream) {
-          this._log.debug("Flushing file log");
-          let filename = this.logFilePrefix + "-" + reasonPrefix + "-" + Date.now() + ".txt";
-          let file = this._logFileDirectory;
-          file.append(filename);
-          this._log.trace("Beginning stream copy to " + file.leafName + ": " +
-                          Date.now());
-          this._copyStreamToFile(inStream, file).then(
-            modDate => {
-              this._log.trace("onCopyComplete: " + Date.now());
-              this._log.trace("Output file timestamp: " + modDate + " (" + modDate.getTime() + ")");
-            },
-            err => {
-              this._log.error("Failed to copy log stream to file", err)
-              reject(err)
-            }
-          ).then(
-            () => {
-              // It's not completely clear to markh why we only do log cleanups
-              // for errors, but for now the Sync semantics have been copied...
-              // (one theory is that only cleaning up on error makes it less
-              // likely old error logs would be removed, but that's not true if
-              // there are occasional errors - let's address this later!)
-              if (reason == this.REASON_ERROR &&
-                  !this._cleaningUpFileLogs) {
-                this._log.trace("Scheduling cleanup.");
-                // Note we don't return or wait on this promise - it continues
-                // in the background
-                this.cleanupLogs().then(null, err => {
-                  this._log.error("Failed to cleanup logs", err);
-                });
-              }
-              resolve();
-            }
-          );
-        } else {
-          resolve();
+        return;
+      }
+
+      let inStream = this._fileAppender.getInputStream();
+      this._fileAppender.reset();
+      if (inStream) {
+        this._log.debug("Flushing file log");
+        // We have reasonPrefix at the start of the filename so all "error"
+        // logs are grouped in about:sync-log.
+        let filename = reasonPrefix + "-" + this.logFilePrefix + "-" + Date.now() + ".txt";
+        let file = this._logFileDirectory;
+        file.append(filename);
+        this._log.trace("Beginning stream copy to " + file.leafName + ": " +
+                        Date.now());
+        try {
+          yield this._copyStreamToFile(inStream, file);
+          this._log.trace("onCopyComplete", Date.now());
+        } catch (ex) {
+          this._log.error("Failed to copy log stream to file", ex);
+          return;
         }
-      } catch (ex) {
-        this._log.error("Failed to resetFileLog", ex)
-        reject(ex);
+        // It's not completely clear to markh why we only do log cleanups
+        // for errors, but for now the Sync semantics have been copied...
+        // (one theory is that only cleaning up on error makes it less
+        // likely old error logs would be removed, but that's not true if
+        // there are occasional errors - let's address this later!)
+        if (reason == this.REASON_ERROR && !this._cleaningUpFileLogs) {
+          this._log.trace("Scheduling cleanup.");
+          // Note we don't return/yield or otherwise wait on this promise - it
+          // continues in the background
+          this.cleanupLogs().catch(err => {
+            this._log.error("Failed to cleanup logs", err);
+          });
+        }
       }
-    })
-  },
+    } catch (ex) {
+      this._log.error("Failed to resetFileLog", ex)
+    }
+  }),
 
   /**
    * Finds all logs older than maxErrorAge and deletes them using async I/O.
    */
   cleanupLogs: Task.async(function* () {
     this._cleaningUpFileLogs = true;
     let iterator = new OS.File.DirectoryIterator(this._logFileDirectory.path);
     let maxAge = this._prefs.get("log.appender.file.maxErrorAge", DEFAULT_MAX_ERROR_AGE);
     let threshold = Date.now() - 1000 * maxAge;
 
     this._log.debug("Log cleanup threshold time: " + threshold);
     yield iterator.forEach(Task.async(function* (entry) {
-      if (!entry.name.startsWith(this.logFilePrefix + "-")) {
+      if (!entry.name.startsWith("error-" + this.logFilePrefix + "-") &&
+          !entry.name.startsWith("success-" + this.logFilePrefix + "-")) {
         return;
       }
       try {
         // need to call .stat() as the enumerator doesn't give that to us on *nix.
         let info = yield OS.File.stat(entry.path);
         if (info.lastModificationDate.getTime() >= threshold) {
           return;
         }
--- a/services/common/tests/unit/test_logmanager.js
+++ b/services/common/tests/unit/test_logmanager.js
@@ -18,34 +18,33 @@ function getAppenders(log) {
   equal(capps.length, 1, "should only have one console appender");
   let dapps = log.appenders.filter(app => app instanceof Log.DumpAppender);
   equal(dapps.length, 1, "should only have one dump appender");
   let fapps = log.appenders.filter(app => app instanceof Log.StorageStreamAppender);
   return [capps[0], dapps[0], fapps];
 }
 
 // Test that the correct thing happens when no prefs exist for the log manager.
-add_test(function test_noPrefs() {
+add_task(function* test_noPrefs() {
   // tell the log manager to init with a pref branch that doesn't exist.
   let lm = new LogManager("no-such-branch.", ["TestLog"], "test");
 
   let log = Log.repository.getLogger("TestLog");
   let [capp, dapp, fapps] = getAppenders(log);
   // the "dump" and "console" appenders should get Error level
   equal(capp.level, Log.Level.Error);
   equal(dapp.level, Log.Level.Error);
   // and the file (stream) appender gets Dump by default
   equal(fapps.length, 1, "only 1 file appender");
   equal(fapps[0].level, Log.Level.Debug);
   lm.finalize();
-  run_next_test();
 });
 
 // Test that changes to the prefs used by the log manager are updated dynamically.
-add_test(function test_PrefChanges() {
+add_task(function* test_PrefChanges() {
   Services.prefs.setCharPref("log-manager.test.log.appender.console", "Trace");
   Services.prefs.setCharPref("log-manager.test.log.appender.dump", "Trace");
   Services.prefs.setCharPref("log-manager.test.log.appender.file.level", "Trace");
   let lm = new LogManager("log-manager.test.", ["TestLog2"], "test");
 
   let log = Log.repository.getLogger("TestLog2");
   let [capp, dapp, [fapp]] = getAppenders(log);
   equal(capp.level, Log.Level.Trace);
@@ -61,21 +60,20 @@ add_test(function test_PrefChanges() {
   // and invalid values should cause them to fallback to their defaults.
   Services.prefs.setCharPref("log-manager.test.log.appender.console", "xxx");
   Services.prefs.setCharPref("log-manager.test.log.appender.dump", "xxx");
   Services.prefs.setCharPref("log-manager.test.log.appender.file.level", "xxx");
   equal(capp.level, Log.Level.Error);
   equal(dapp.level, Log.Level.Error);
   equal(fapp.level, Log.Level.Debug);
   lm.finalize();
-  run_next_test();
 });
 
 // Test that the same log used by multiple log managers does the right thing.
-add_test(function test_SharedLogs() {
+add_task(function* test_SharedLogs() {
   // create the prefs for the first instance.
   Services.prefs.setCharPref("log-manager-1.test.log.appender.console", "Trace");
   Services.prefs.setCharPref("log-manager-1.test.log.appender.dump", "Trace");
   Services.prefs.setCharPref("log-manager-1.test.log.appender.file.level", "Trace");
   let lm1 = new LogManager("log-manager-1.test.", ["TestLog3"], "test");
 
   // and the second.
   Services.prefs.setCharPref("log-manager-2.test.log.appender.console", "Debug");
@@ -97,11 +95,9 @@ add_test(function test_SharedLogs() {
   Services.prefs.setCharPref("log-manager-1.test.log.appender.dump", "Error");
   Services.prefs.setCharPref("log-manager-1.test.log.appender.file.level", "Error");
 
   equal(capp.level, Log.Level.Debug);
   equal(dapp.level, Log.Level.Debug);
 
   lm1.finalize();
   lm2.finalize();
-
-  run_next_test();
 });
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -612,17 +612,17 @@ this.BrowserIDManager.prototype = {
           this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR;
         }
         // this._authFailureReason being set to be non-null in the above if clause
         // ensures we are in the correct currentAuthState, and
         // this._shouldHaveSyncKeyBundle being true ensures everything that cares knows
         // that there is no authentication dance still under way.
         this._shouldHaveSyncKeyBundle = true;
         Weave.Status.login = this._authFailureReason;
-        Services.obs.notifyObservers(null, "weave:service:login:error", null);
+        Services.obs.notifyObservers(null, "weave:ui:login:error", null);
         throw err;
       });
   },
 
   // Returns a promise that is resolved when we have a valid token for the
   // current user stored in this._token.  When resolved, this._token is valid.
   _ensureValidToken: function() {
     if (this.hasValidToken()) {
--- a/services/sync/tests/unit/test_errorhandler.js
+++ b/services/sync/tests/unit/test_errorhandler.js
@@ -1765,17 +1765,17 @@ add_task(function test_sync_engine_gener
       _("Status.engines: " + JSON.stringify(Status.engines));
       do_check_eq(Status.engines["catapult"], ENGINE_UNKNOWN_FAIL);
       do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
 
       // Test Error log was written on SYNC_FAILED_PARTIAL.
       let entries = logsdir.directoryEntries;
       do_check_true(entries.hasMoreElements());
       let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
-      do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
+      do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
 
       clean();
       server.stop(deferred.resolve);
     });
   });
 
   do_check_true(yield setUp(server));
   Service.sync();
@@ -1796,17 +1796,17 @@ add_test(function test_logs_on_sync_erro
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Test that error log was written.
     let entries = logsdir.directoryEntries;
     do_check_true(entries.hasMoreElements());
     let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
-    do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
+    do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
 
     clean();
     run_next_test();
   });
   Svc.Obs.notify("weave:service:sync:error", {});
 });
 
 add_test(function test_logs_on_login_error_despite_shouldReportError() {
@@ -1823,17 +1823,17 @@ add_test(function test_logs_on_login_err
 
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Test that error log was written.
     let entries = logsdir.directoryEntries;
     do_check_true(entries.hasMoreElements());
     let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
-    do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
+    do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
 
     clean();
     run_next_test();
   });
   Svc.Obs.notify("weave:service:login:error", {});
 });
 
 // This test should be the last one since it monkeypatches the engine object
@@ -1857,17 +1857,17 @@ add_task(function test_engine_applyFaile
 
     do_check_eq(Status.engines["catapult"], ENGINE_APPLY_FAIL);
     do_check_eq(Status.service, SYNC_FAILED_PARTIAL);
 
     // Test Error log was written on SYNC_FAILED_PARTIAL.
     let entries = logsdir.directoryEntries;
     do_check_true(entries.hasMoreElements());
     let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
-    do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
+    do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
 
     clean();
     server.stop(deferred.resolve);
   });
 
   do_check_eq(Status.engines["catapult"], undefined);
   do_check_true(yield setUp(server));
   Service.sync();
--- a/services/sync/tests/unit/test_errorhandler_filelog.js
+++ b/services/sync/tests/unit/test_errorhandler_filelog.js
@@ -103,17 +103,17 @@ add_test(function test_logOnSuccess_true
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Exactly one log file was written.
     let entries = logsdir.directoryEntries;
     do_check_true(entries.hasMoreElements());
     let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
     do_check_eq(logfile.leafName.slice(-4), ".txt");
-    do_check_true(logfile.leafName.startsWith("sync-success-"), logfile.leafName);
+    do_check_true(logfile.leafName.startsWith("success-sync-"), logfile.leafName);
     do_check_false(entries.hasMoreElements());
 
     // Ensure the log message was actually written to file.
     readFile(logfile, function (error, data) {
       do_check_true(Components.isSuccessCode(error));
       do_check_neq(data.indexOf(MESSAGE), -1);
 
       // Clean up.
@@ -170,17 +170,17 @@ add_test(function test_sync_error_logOnE
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Exactly one log file was written.
     let entries = logsdir.directoryEntries;
     do_check_true(entries.hasMoreElements());
     let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
     do_check_eq(logfile.leafName.slice(-4), ".txt");
-    do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
+    do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
     do_check_false(entries.hasMoreElements());
 
     // Ensure the log message was actually written to file.
     readFile(logfile, function (error, data) {
       do_check_true(Components.isSuccessCode(error));
       do_check_neq(data.indexOf(MESSAGE), -1);
 
       // Clean up.
@@ -237,17 +237,17 @@ add_test(function test_login_error_logOn
   Svc.Obs.add("weave:service:reset-file-log", function onResetFileLog() {
     Svc.Obs.remove("weave:service:reset-file-log", onResetFileLog);
 
     // Exactly one log file was written.
     let entries = logsdir.directoryEntries;
     do_check_true(entries.hasMoreElements());
     let logfile = entries.getNext().QueryInterface(Ci.nsILocalFile);
     do_check_eq(logfile.leafName.slice(-4), ".txt");
-    do_check_true(logfile.leafName.startsWith("sync-error-"), logfile.leafName);
+    do_check_true(logfile.leafName.startsWith("error-sync-"), logfile.leafName);
     do_check_false(entries.hasMoreElements());
 
     // Ensure the log message was actually written to file.
     readFile(logfile, function (error, data) {
       do_check_true(Components.isSuccessCode(error));
       do_check_neq(data.indexOf(MESSAGE), -1);
 
       // Clean up.
@@ -276,17 +276,17 @@ add_test(function test_logErrorCleanup_a
   let errString = "some error log\n";
 
   Svc.Prefs.set("log.appender.file.logOnError", true);
   Svc.Prefs.set("log.appender.file.maxErrorAge", maxAge);
 
   _("Making some files.");
   for (let i = 0; i < numLogs; i++) {
     let now = Date.now();
-    let filename = "sync-error-" + now + "" + i + ".txt";
+    let filename = "error-sync-" + now + "" + i + ".txt";
     let newLog = FileUtils.getFile("ProfD", ["weave", "logs", filename]);
     let foStream = FileUtils.openFileOutputStream(newLog);
     foStream.write(errString, errString.length);
     foStream.close();
     _("  > Created " + filename);
     oldLogs.push(newLog.leafName);
   }
 
new file mode 100644
--- /dev/null
+++ b/testing/taskcluster/tasks/branches/b2g-inbound/job_flags.yml
@@ -0,0 +1,35 @@
+---
+# For complete sample of all build and test jobs,
+# see <gecko>/testing/taskcluster/tasks/job_flags.yml
+
+$inherits:
+  from: tasks/branches/base_job_flags.yml
+
+builds:
+  flame-kk:
+    platforms:
+      - b2g
+    types:
+      opt:
+        task: tasks/builds/b2g_flame_kk_opt.yml
+  flame-kk-eng:
+    platforms:
+      - b2g
+    types:
+      opt:
+        task: tasks/builds/b2g_flame_kk_eng.yml
+
+tests:
+  gaia-build:
+    allowed_build_tasks:
+      tasks/builds/b2g_flame_kk_opt.yml:
+        task: tasks/tests/b2g_build_test.yml
+      tasks/builds/b2g_flame_kk_eng.yml:
+        task: tasks/tests/b2g_build_test.yml
+  gaia-linter:
+    allowed_build_tasks:
+      tasks/builds/b2g_flame_kk_opt.yml:
+        task: tasks/tests/b2g_linter.yml
+      tasks/builds/b2g_flame_kk_eng.yml:
+        task: tasks/tests/b2g_linter.yml
+
new file mode 100644
--- /dev/null
+++ b/testing/taskcluster/tasks/branches/cypress/job_flags.yml
@@ -0,0 +1,35 @@
+---
+# For complete sample of all build and test jobs,
+# see <gecko>/testing/taskcluster/tasks/job_flags.yml
+
+$inherits:
+  from: tasks/branches/base_job_flags.yml
+
+builds:
+  flame-kk:
+    platforms:
+      - b2g
+    types:
+      opt:
+        task: tasks/builds/b2g_flame_kk_opt.yml
+  flame-kk-eng:
+    platforms:
+      - b2g
+    types:
+      opt:
+        task: tasks/builds/b2g_flame_kk_eng.yml
+
+tests:
+  gaia-build:
+    allowed_build_tasks:
+      tasks/builds/b2g_flame_kk_opt.yml:
+        task: tasks/tests/b2g_build_test.yml
+      tasks/builds/b2g_flame_kk_eng.yml:
+        task: tasks/tests/b2g_build_test.yml
+  gaia-linter:
+    allowed_build_tasks:
+      tasks/builds/b2g_flame_kk_opt.yml:
+        task: tasks/tests/b2g_linter.yml
+      tasks/builds/b2g_flame_kk_eng.yml:
+        task: tasks/tests/b2g_linter.yml
+
--- a/toolkit/devtools/server/actors/stylesheets.js
+++ b/toolkit/devtools/server/actors/stylesheets.js
@@ -99,84 +99,115 @@ let StyleSheetsActor = exports.StyleShee
    *         Promise that resolves with an array of StyleSheetActors
    */
   _addAllStyleSheets: function() {
     return Task.spawn(function*() {
       let documents = [this.document];
       let actors = [];
 
       for (let doc of documents) {
-        let sheets = yield this._addStyleSheets(doc.styleSheets);
+        let sheets = yield this._addStyleSheets(doc);
         actors = actors.concat(sheets);
 
         // Recursively handle style sheets of the documents in iframes.
-        for (let iframe of doc.getElementsByTagName("iframe")) {
+        for (let iframe of doc.querySelectorAll("iframe, browser, frame")) {
           if (iframe.contentDocument) {
             // Sometimes, iframes don't have any document, like the
             // one that are over deeply nested (bug 285395)
             documents.push(iframe.contentDocument);
           }
         }
       }
       return actors;
     }.bind(this));
   },
 
   /**
-   * Add all the stylesheets to the map and create an actor for each one
-   * if not already created.
+   * Check if we should be showing this stylesheet.
+   *
+   * @param {Document} doc
+   *        Document for which we're checking
+   * @param {DOMCSSStyleSheet} sheet
+   *        Stylesheet we're interested in
    *
-   * @param {[DOMStyleSheet]} styleSheets
-   *        Stylesheets to add
+   * @return boolean
+   *         Whether the stylesheet should be listed.
+   */
+  _shouldListSheet: function(doc, sheet) {
+    // Special case about:PreferenceStyleSheet, as it is generated on the
+    // fly and the URI is not registered with the about: handler.
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=935803#c37
+    if (sheet.href && sheet.href.toLowerCase() == "about:preferencestylesheet") {
+      return false;
+    }
+
+    return true;
+  },
+
+  /**
+   * Add all the stylesheets for this document to the map and create an actor
+   * for each one if not already created.
+   *
+   * @param {Document} doc
+   *        Document for which to add stylesheets
    *
    * @return {Promise}
    *         Promise that resolves to an array of StyleSheetActors
    */
-  _addStyleSheets: function(styleSheets)
+  _addStyleSheets: function(doc)
   {
     return Task.spawn(function*() {
+      let isChrome = Services.scriptSecurityManager.isSystemPrincipal(doc.nodePrincipal);
+      let styleSheets = isChrome ? DOMUtils.getAllStyleSheets(doc) : doc.styleSheets;
       let actors = [];
       for (let i = 0; i < styleSheets.length; i++) {
-        let actor = this.parentActor.createStyleSheetActor(styleSheets[i]);
+        let sheet = styleSheets[i];
+        if (!this._shouldListSheet(doc, sheet)) {
+          continue;
+        }
+
+        let actor = this.parentActor.createStyleSheetActor(sheet);
         actors.push(actor);
 
         // Get all sheets, including imported ones
-        let imports = yield this._getImported(actor);
+        let imports = yield this._getImported(doc, actor);
         actors = actors.concat(imports);
       }
       return actors;
     }.bind(this));
   },
 
   /**
    * Get all the stylesheets @imported from a stylesheet.
    *
+   * @param  {Document} doc
+   *         The document including the stylesheet
    * @param  {DOMStyleSheet} styleSheet
    *         Style sheet to search
    * @return {Promise}
    *         A promise that resolves with an array of StyleSheetActors
    */
-  _getImported: function(styleSheet) {
+  _getImported: function(doc, styleSheet) {
     return Task.spawn(function*() {
       let rules = yield styleSheet.getCSSRules();
       let imported = [];
 
       for (let i = 0; i < rules.length; i++) {
         let rule = rules[i];
         if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
           // Associated styleSheet may be null if it has already been seen due
           // to duplicate @imports for the same URL.
-          if (!rule.styleSheet) {
+          if (!rule.styleSheet || !this._shouldListSheet(doc, rule.styleSheet)) {
             continue;
           }
           let actor = this.parentActor.createStyleSheetActor(rule.styleSheet);
           imported.push(actor);
 
           // recurse imports in this stylesheet as well
-          let children = yield this._getImported(actor);
+          let children = yield this._getImported(doc, actor);
           imported = imported.concat(children);
         }
         else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
           // @import rules must precede all others except @charset
           break;
         }
       }
 
--- a/toolkit/devtools/webconsole/client.js
+++ b/toolkit/devtools/webconsole/client.js
@@ -149,17 +149,21 @@ WebConsoleClient.prototype = {
       text: aString,
       bindObjectActor: aOptions.bindObjectActor,
       frameActor: aOptions.frameActor,
       url: aOptions.url,
       selectedNodeActor: aOptions.selectedNodeActor,
     };
 
     this._client.request(packet, response => {
-      this.pendingEvaluationResults.set(response.resultID, aOnResponse);
+      // Null check this in case the client has been detached while waiting
+      // for a response.
+      if (this.pendingEvaluationResults) {
+        this.pendingEvaluationResults.set(response.resultID, aOnResponse);
+      }
     });
   },
 
   /**
    * Handler for the actors's unsolicited evaluationResult packet.
    */
   onEvaluationResult: function(aNotification, aPacket) {
     // Find the associated callback based on this ID, and fire it.
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -77,8 +77,22 @@ xul|description {
 }
 
 html|a:-moz-focusring,
 xul|*.text-link:-moz-focusring,
 xul|*.inline-link:-moz-focusring {
   color: #ff9500;
   text-decoration: underline;
 }
+
+xul|button:-moz-focusring,
+xul|menulist:-moz-focusring,
+xul|checkbox:-moz-focusring > .checkbox-check,
+xul|radio[focused="true"] > .radio-check,
+xul|tab:-moz-focusring > .tab-middle > .tab-text {
+	outline: 2px solid rgba(0,149,221,0.5);
+	outline-offset: 1px;
+	-moz-outline-radius: 2px;
+}
+
+xul|radio[focused="true"] > .radio-check {
+	-moz-outline-radius: 100%;
+}
\ No newline at end of file
--- a/toolkit/themes/windows/global/notification.css
+++ b/toolkit/themes/windows/global/notification.css
@@ -89,42 +89,48 @@ notification[type="critical"] {
 /*
 XXX: apply styles to all themes until bug 509642 is fixed
 @media (-moz-windows-default-theme) {
 */
   .popup-notification-menubutton {
     -moz-appearance: none;
     border-radius: 3px;
     padding: 0;
-%ifndef WINDOWS_AERO
-    background-color: rgba(250,250,250,.3);
-%endif    
+    background-color: rgba(250,250,250,.3)
   }
 
   .popup-notification-menubutton:hover:active {
     border-color: rgba(0,0,0,.5);
   }
 
   .popup-notification-menubutton:not([type="menu-button"]),
   .popup-notification-menubutton > .button-menubutton-button,
   .popup-notification-menubutton > .button-menubutton-dropmarker {
     -moz-appearance: none;
     margin: 0;
     border: 1px solid rgba(0,0,0,.35);
-%ifdef WINDOWS_AERO
-    background-image: linear-gradient(rgba(250,250,250,.6), rgba(175,175,175,.25) 49%, rgba(0,0,0,.12) 51%, rgba(0,0,0,.09) 60%, rgba(0,0,0,.05));
-    box-shadow: 0 0 1px 1px rgba(255,255,255,.75) inset;
-%else
+
     box-shadow: 0 1px 0 rgba(255,255,255,.5) inset,
                 0 2px 2px rgba(255,255,255,.35) inset,
                 0 -1px 2px rgba(0,0,0,.1) inset,
                 0 1px 0 rgba(255,255,255,.35);
-%endif
+
   }
 
+%ifdef WINDOWS_AERO
+  @media (-moz-windows-glass) {
+    .popup-notification-menubutton:not([type="menu-button"]),
+    .popup-notification-menubutton > .button-menubutton-button,
+    .popup-notification-menubutton > .button-menubutton-dropmarker {
+      background-color: transparent;
+      background-image: linear-gradient(rgba(250,250,250,.6), rgba(175,175,175,.25) 49%, rgba(0,0,0,.12) 51%, rgba(0,0,0,.09) 60%, rgba(0,0,0,.05));
+      box-shadow: 0 0 1px 1px rgba(255,255,255,.75) inset;
+    }
+  }
+%endif
   .popup-notification-menubutton > .button-menubutton-button {
     background-color: transparent;
     padding: 1px;
     -moz-border-end: none;
   }
 
   .popup-notification-menubutton:not([type="menu-button"]),
   .popup-notification-menubutton > .button-menubutton-button > .button-box {
@@ -140,49 +146,60 @@ XXX: apply styles to all themes until bu
   }
 
   .popup-notification-menubutton:-moz-focusring > .button-menubutton-dropmarker {
     outline: 1px dotted ThreeDDarkShadow;
     outline-offset: -3px;
   }
 
 %ifdef WINDOWS_AERO
-  .popup-notification-menubutton > .button-menubutton-button:-moz-locale-dir(ltr),
-  .popup-notification-menubutton > .button-menubutton-dropmarker:-moz-locale-dir(rtl) {
-    border-radius: 2px 0 0 2px;
-  }
+  @media (-moz-windows-glass) {
+    .popup-notification-menubutton > .button-menubutton-button:-moz-locale-dir(ltr),
+    .popup-notification-menubutton > .button-menubutton-dropmarker:-moz-locale-dir(rtl) {
+      border-radius: 2px 0 0 2px;
+    }
 
-  .popup-notification-menubutton > .button-menubutton-button:-moz-locale-dir(rtl),
-  .popup-notification-menubutton > .button-menubutton-dropmarker:-moz-locale-dir(ltr) {
-    border-radius: 0 2px 2px 0;
+    .popup-notification-menubutton > .button-menubutton-button:-moz-locale-dir(rtl),
+    .popup-notification-menubutton > .button-menubutton-dropmarker:-moz-locale-dir(ltr) {
+      border-radius: 0 2px 2px 0;
+    }
   }
 %endif
 
   .popup-notification-menubutton:not([type="menu-button"]):hover,
   .popup-notification-menubutton > .button-menubutton-button:hover,
   .popup-notification-menubutton > .button-menubutton-dropmarker:hover {
-%ifdef WINDOWS_AERO
-    background-image: linear-gradient(rgba(250,250,250,.9), rgba(200,200,200,.6) 49%, rgba(0,0,0,.23) 51%, rgba(0,0,0,.17) 60%, rgba(0,0,0,.05));
-    box-shadow: 0 0 0 1px white inset,
-                0 0 2px 1px rgba(255,255,255,.75) inset;
-%else
     background-color: rgba(250,250,250,.6);
-%endif
   }
 
   .popup-notification-menubutton:not([type="menu-button"]):hover:active,
   .popup-notification-menubutton > .button-menubutton-button:hover:active,
   .popup-notification-menubutton > .button-menubutton-dropmarker:hover:active,
   .popup-notification-menubutton[open="true"] > .button-menubutton-dropmarker {
-%ifdef WINDOWS_AERO
-    background-image: linear-gradient(rgba(250,250,250,.9), rgba(200,200,200,.6) 49%, rgba(0,0,0,.23) 51%, rgba(0,0,0,.17) 60%, rgba(0,0,0,.05));
-%else
     background-color: rgba(0,0,0,.05);
-%endif
     box-shadow: 0 0 2px rgba(0,0,0,.65) inset;
   }
+%ifdef WINDOWS_AERO
+@media (-moz-windows-glass) {
+  .popup-notification-menubutton:not([type="menu-button"]):hover,
+  .popup-notification-menubutton > .button-menubutton-button:hover,
+  .popup-notification-menubutton > .button-menubutton-dropmarker:hover {
+    background-color: transparent;
+    background-image: linear-gradient(rgba(250,250,250,.9), rgba(200,200,200,.6) 49%, rgba(0,0,0,.23) 51%, rgba(0,0,0,.17) 60%, rgba(0,0,0,.05));
+    box-shadow: 0 0 0 1px white inset,
+                0 0 2px 1px rgba(255,255,255,.75) inset;
+  }
+  .popup-notification-menubutton:not([type="menu-button"]):hover:active,
+  .popup-notification-menubutton > .button-menubutton-button:hover:active,
+  .popup-notification-menubutton > .button-menubutton-dropmarker:hover:active,
+  .popup-notification-menubutton[open="true"] > .button-menubutton-dropmarker {
+    background-color: transparent;
+    background-image: linear-gradient(rgba(250,250,250,.9), rgba(200,200,200,.6) 49%, rgba(0,0,0,.23) 51%, rgba(0,0,0,.17) 60%, rgba(0,0,0,.05));
+  }
+}
+%endif
 /*}*/
 %endif
 
 .popup-notification-closebutton {
   -moz-margin-end: -14px;
   margin-top: -10px;
 }
--- a/widget/android/AndroidBridge.cpp
+++ b/widget/android/AndroidBridge.cpp
@@ -45,17 +45,17 @@
 #include "MediaCodec.h"
 #include "SurfaceTexture.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 using namespace mozilla::jni;
 using namespace mozilla::widget;
 
-AndroidBridge* AndroidBridge::sBridge;
+AndroidBridge* AndroidBridge::sBridge = nullptr;
 pthread_t AndroidBridge::sJavaUiThread = -1;
 static unsigned sJavaEnvThreadIndex = 0;
 static jobject sGlobalContext = nullptr;
 static void JavaThreadDetachFunc(void *arg);
 
 // This is a dummy class that can be used in the template for android::sp
 class AndroidRefable {
     void incStrong(void* thing) { }
@@ -156,24 +156,22 @@ AndroidBridge::ConstructBridge(JNIEnv *j
      * linker lock still held.  This results in a deadlock when trying
      * to call dlclose() while we're already inside dlclose().
      * Conveniently, NSS has an env var that can prevent it from unloading.
      */
     putenv("NSS_DISABLE_UNLOAD=1");
 
     PR_NewThreadPrivateIndex(&sJavaEnvThreadIndex, JavaThreadDetachFunc);
 
-    AndroidBridge *bridge = new AndroidBridge();
-    if (!bridge->Init(jEnv, clsLoader)) {
-        delete bridge;
-    }
-    sBridge = bridge;
+    MOZ_ASSERT(!sBridge);
+    sBridge = new AndroidBridge;
+    sBridge->Init(jEnv, clsLoader); // Success or crash
 }
 
-bool
+void
 AndroidBridge::Init(JNIEnv *jEnv, Object::Param clsLoader)
 {
     ALOG_BRIDGE("AndroidBridge::Init");
     jEnv->GetJavaVM(&mJavaVM);
     if (!mJavaVM) {
         MOZ_CRASH(); // Nothing we can do here
     }
 
@@ -239,18 +237,16 @@ AndroidBridge::Init(JNIEnv *jEnv, Object
     jClose = inputStream.getMethod("close", "()V");
     jAvailable = inputStream.getMethod("available", "()I");
 
     InitAndroidJavaWrappers(jEnv);
 
     // jEnv should NOT be cached here by anything -- the jEnv here
     // is not valid for the real gecko main thread, which is set
     // at SetMainThread time.
-
-    return true;
 }
 
 bool
 AndroidBridge::SetMainThread(pthread_t thr)
 {
     ALOG_BRIDGE("AndroidBridge::SetMainThread");
     if (thr) {
         mThread = thr;
--- a/widget/android/AndroidBridge.h
+++ b/widget/android/AndroidBridge.h
@@ -359,17 +359,17 @@ protected:
 
     // the android.telephony.SmsMessage class
     jclass mAndroidSmsMessageClass;
 
     AndroidBridge();
     ~AndroidBridge();
 
     void InitStubs(JNIEnv *jEnv);
-    bool Init(JNIEnv *jEnv, jni::Object::Param clsLoader);
+    void Init(JNIEnv *jEnv, jni::Object::Param clsLoader);
 
     bool mOpenedGraphicsLibraries;
     void OpenGraphicsLibraries();
     void* GetNativeSurface(JNIEnv* env, jobject surface);
 
     bool mHasNativeBitmapAccess;
     bool mHasNativeWindowAccess;
     bool mHasNativeWindowFallback;
--- a/widget/android/GfxInfo.cpp
+++ b/widget/android/GfxInfo.cpp
@@ -36,38 +36,41 @@ public:
     : mReady(false)
   {}
 
   const nsCString& Vendor() {
     EnsureInitialized();
     return mVendor;
   }
 
+  // This spoofed value wins, even if the environment variable
+  // MOZ_GFX_SPOOF_GL_VENDOR was set.
   void SpoofVendor(const nsCString& s) {
-    EnsureInitialized();
     mVendor = s;
   }
 
   const nsCString& Renderer() {
     EnsureInitialized();
     return mRenderer;
   }
 
+  // This spoofed value wins, even if the environment variable
+  // MOZ_GFX_SPOOF_GL_RENDERER was set.
   void SpoofRenderer(const nsCString& s) {
-    EnsureInitialized();
     mRenderer = s;
   }
 
   const nsCString& Version() {
     EnsureInitialized();
     return mVersion;
   }
 
+  // This spoofed value wins, even if the environment variable
+  // MOZ_GFX_SPOOF_GL_VERSION was set.
   void SpoofVersion(const nsCString& s) {
-    EnsureInitialized();
     mVersion = s;
   }
 
   void EnsureInitialized() {
     if (mReady) {
       return;
     }
 
@@ -80,31 +83,42 @@ public:
       // remain blacklisted forever. Ideally, we would like to update that once
       // any GLContext is successfully created, like the compositor's GLContext.
       mReady = true;
       return;
     }
 
     gl->MakeCurrent();
 
-    const char *spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR");
-    if (spoofedVendor)
+    if (mVendor.IsEmpty()) {
+      const char *spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR");
+      if (spoofedVendor) {
         mVendor.Assign(spoofedVendor);
-    else
+      } else {
         mVendor.Assign((const char*)gl->fGetString(LOCAL_GL_VENDOR));
-    const char *spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
-    if (spoofedRenderer)
+      }
+    }
+
+    if (mRenderer.IsEmpty()) {
+      const char *spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
+      if (spoofedRenderer) {
         mRenderer.Assign(spoofedRenderer);
-    else
+      } else {
         mRenderer.Assign((const char*)gl->fGetString(LOCAL_GL_RENDERER));
-    const char *spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
-    if (spoofedVersion)
+      }
+    }
+
+    if (mVersion.IsEmpty()) {
+      const char *spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
+      if (spoofedVersion) {
         mVersion.Assign(spoofedVersion);
-    else
+      } else {
         mVersion.Assign((const char*)gl->fGetString(LOCAL_GL_VERSION));
+      }
+    }
 
     mReady = true;
   }
 };
 
 #ifdef DEBUG
 NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
 #endif
@@ -148,19 +162,20 @@ GfxInfo::GetCleartypeParameters(nsAStrin
 }
 
 void
 GfxInfo::EnsureInitialized()
 {
   if (mInitialized)
     return;
 
-  mGLStrings->EnsureInitialized();
-
-  MOZ_ASSERT(mozilla::AndroidBridge::Bridge());
+  if (!mozilla::AndroidBridge::Bridge()) {
+    gfxWarning() << "AndroidBridge missing during initialization";
+    return;
+  }
 
   if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "MODEL", mModel)) {
     mAdapterDescription.AppendPrintf("Model: %s",  NS_LossyConvertUTF16toASCII(mModel).get());
   }
 
   if (mozilla::AndroidBridge::Bridge()->GetStaticStringField("android/os/Build", "PRODUCT", mProduct)) {
     mAdapterDescription.AppendPrintf(", Product: %s", NS_LossyConvertUTF16toASCII(mProduct).get());
   }
@@ -596,33 +611,30 @@ GfxInfo::GetFeatureStatusImpl(int32_t aF
 
 #ifdef DEBUG
 
 // Implement nsIGfxInfoDebug
 
 /* void spoofVendorID (in DOMString aVendorID); */
 NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString & aVendorID)
 {
-  EnsureInitialized();
   mGLStrings->SpoofVendor(NS_LossyConvertUTF16toASCII(aVendorID));
   return NS_OK;
 }
 
 /* void spoofDeviceID (in unsigned long aDeviceID); */
 NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString & aDeviceID)
 {
-  EnsureInitialized();
   mGLStrings->SpoofRenderer(NS_LossyConvertUTF16toASCII(aDeviceID));
   return NS_OK;
 }
 
 /* void spoofDriverVersion (in DOMString aDriverVersion); */
 NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString & aDriverVersion)
 {
-  EnsureInitialized();
   mGLStrings->SpoofVersion(NS_LossyConvertUTF16toASCII(aDriverVersion));
   return NS_OK;
 }
 
 /* void spoofOSVersion (in unsigned long aVersion); */
 NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion)
 {
   EnsureInitialized();