Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 23 Jun 2014 10:57:40 -0400
changeset 190155 8bbe0aa7254718bc05f1dd397d2535b792862d9f
parent 190154 2bac65d5860b0139da96cd259eeba5665dac75bd (current diff)
parent 190113 31de1a84b27f7bf1a15d3dc9ea8a01f4acd7bd75 (diff)
child 190156 a43d41c1645a59ef27a1cd2e3587ed4e8e58296d
push id7411
push userkwierso@gmail.com
push dateTue, 24 Jun 2014 02:07:45 +0000
treeherderfx-team@cc4602e0c1d8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.0a1
Merge m-c to inbound.
dom/mobileconnection/tests/marionette/mobile_header.js
mobile/android/base/home/LastTabsPanel.java
mobile/android/base/home/MostRecentPanel.java
mobile/android/base/resources/drawable-large-land-v11/home_history_tabs_indicator.xml
mobile/android/base/resources/drawable-xlarge-v11/home_history_tabs_indicator.xml
mobile/android/base/resources/drawable/home_history_tabs_indicator.xml
mobile/android/base/resources/layout-large-land-v11/home_history_panel.xml
mobile/android/base/resources/layout-large-land-v11/home_history_tabs_indicator.xml
mobile/android/base/resources/layout-xlarge-v11/home_history_panel.xml
mobile/android/base/resources/layout-xlarge-v11/home_history_tabs_indicator.xml
mobile/android/base/resources/layout/home_history_tabs_indicator.xml
mobile/android/base/resources/layout/home_last_tabs_panel.xml
mobile/android/base/resources/layout/home_most_recent_panel.xml
mobile/android/base/resources/values-large-land-v11/dimens.xml
mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,23 +14,23 @@
   <!--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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
@@ -123,14 +123,14 @@
   <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"/>
   <!-- Emulator specific things -->
   <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="0e31f35a2a77301e91baa8a237aa9e9fa4076084"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="973367035a1f2545f3dad6e40e354463dc56a7f4"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="346c7694b156a3933f3d87cbc077c657e2ce571f"/>
   <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="832f4acaf481a19031e479a40b03d9ce5370ddee"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="d0aa65b140a45016975ed0ecf35f280dd336e1d3"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--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="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <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="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="3326b51017252e09ccdd715dec6c5e12a7d1ecfe"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "387f4c0123a7e82eae4c83f88f73e81f907c00e2", 
+    "revision": "aba00cfd579caaf205e05c269f0a8100f242f39c", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,22 +12,22 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,18 +10,18 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="575fdbf046e966a5915b1f1e800e5d6ad0ea14c0"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--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="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <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"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,22 +12,22 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="bd5065ced020014df5fd45259fba1ac32d65673b"/>
-  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="44b04243e31cd16f3baf54fcd4b5fecf9deb8b94"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="41cc1de26e4edbe12add0009cdc0bd292f2e94fe"/>
+  <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="bbb7659d8ea2afb396f99b3dc971ab3c42da3778"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="c510babaf88dfa2cfe2c202afb2649ee124569af"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8ba14125aba912707f44761f194339e6e59701b7"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="23db533981ee2cd04fc5d946420402aed2792381"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1402612020000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1403216209000">
   <emItems>
       <emItem  blockID="i454" id="sqlmoz@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
@@ -84,16 +84,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i105" id="{95ff02bc-ffc6-45f0-a5c8-619b8226a9de}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i626" id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
                         <versionRange  minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i506" id="/^ext@bettersurfplus/">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
@@ -299,20 +305,18 @@
               </prefs>
     </emItem>
       <emItem  blockID="i467" id="plugin@analytic-s.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
-                        <versionRange  minVersion="0" maxVersion="*" severity="1">
-                    </versionRange>
-                    <prefs>
+      <emItem  blockID="i47" id="youtube@youtube2.com">
+                          <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i360" id="ytd@mybrowserbar.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
@@ -360,18 +364,18 @@
               </prefs>
     </emItem>
       <emItem  blockID="i62" id="jid0-EcdqvFOgWLKHNJPuqAnawlykCGZ@jetpack">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i584" id="{52b0f3db-f988-4788-b9dc-861d016f4487}">
-                        <versionRange  minVersion="0" maxVersion="0.1.9999999" severity="1">
+      <emItem  blockID="i624" id="/^({b95faac1-a3d7-4d69-8943-ddd5a487d966}|{ecce0073-a837-45a2-95b9-600420505f7e}|{2713b394-286f-4d7c-89ea-4174eeab9f5a}|{da7a20cf-bef4-4342-ad78-0240fdf87055})$/">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i472" id="linksicle@linksicle.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
@@ -692,33 +696,39 @@
     </emItem>
       <emItem  blockID="i479" id="mbrsepone@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i620" id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
-                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i552" id="jid0-O6MIff3eO5dIGf5Tcv8RsJDKxrs@jetpack">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i256" id="/^[0-9a-f]+@[0-9a-f]+\.info/">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i584" id="{52b0f3db-f988-4788-b9dc-861d016f4487}">
+                        <versionRange  minVersion="0" maxVersion="0.1.9999999" severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i370" id="happylyrics@hpyproductions.net">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i440" id="{2d069a16-fca1-4e81-81ea-5d5086dcbd0c}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
@@ -1206,18 +1216,20 @@
               </prefs>
     </emItem>
       <emItem  blockID="i334" id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i47" id="youtube@youtube2.com">
-                          <prefs>
+      <emItem  blockID="i622" id="/^({ebd898f8-fcf6-4694-bc3b-eabc7271eeb1}|{46008e0d-47ac-4daa-a02a-5eb69044431a}|{213c8ed6-1d78-4d8f-8729-25006aa86a76}|{fa23121f-ee7c-4bd8-8c06-123d087282c5}|{19803860-b306-423c-bbb5-f60a7d82cde5})$/">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i518" id="/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
@@ -1503,16 +1515,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i372" id="5nc3QHFgcb@r06Ws9gvNNVRfH.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i67" id="youtube2@youtube2.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i476" id="mbroctone@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
--- a/browser/components/preferences/content.js
+++ b/browser/components/preferences/content.js
@@ -183,10 +183,16 @@ var gContentPane = {
    * Displays the translation exceptions dialog where specific site and language
    * translation preferences can be set.
    */
   showTranslationExceptions: function ()
   {
     document.documentElement.openWindow("Browser:TranslationExceptions",
                                         "chrome://browser/content/preferences/translation.xul",
                                         "", null);
+  },
+
+  openTranslationProviderAttribution: function ()
+  {
+    Components.utils.import("resource:///modules/translation/Translation.jsm");
+    Translation.openProviderAttribution();
   }
 };
--- a/browser/components/preferences/content.xul
+++ b/browser/components/preferences/content.xul
@@ -139,20 +139,26 @@
           <row id="preferredLanguageRow">
             <label flex="1" control="chooseLanguage">&chooseLanguage.label;</label>
             <button id="chooseLanguage"
                     label="&chooseButton.label;"
                     accesskey="&chooseButton.accesskey;"
                     oncommand="gContentPane.showLanguages();"/>
           </row>
           <row id="translationBox" hidden="true">
-            <checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
-                      label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
-                      onsyncfrompreference="return gContentPane.updateButtons('translateButton',
-                                            'browser.translation.detectLanguage');"/>
+            <hbox align="center">
+              <checkbox id="translate" preference="browser.translation.detectLanguage"
+                        label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
+                        onsyncfrompreference="return gContentPane.updateButtons('translateButton',
+                                              'browser.translation.detectLanguage');"/>
+              <label>Übersetzungen von</label>
+              <image id="translationAttributionImage" aria-label="Microsoft Translator"
+                     onclick="gContentPane.openTranslationProviderAttribution()"
+                     src="chrome://browser/content/microsoft-translator-attribution.png"/>
+            </hbox>
             <button id="translateButton" label="&translateExceptions.label;"
                     oncommand="gContentPane.showTranslationExceptions();"
                     accesskey="&translateExceptions.accesskey;"/>
           </row>
         </rows>
       </grid>
 
     </groupbox>
--- a/browser/components/preferences/in-content/content.js
+++ b/browser/components/preferences/in-content/content.js
@@ -181,10 +181,16 @@ var gContentPane = {
   /**
    * Displays the translation exceptions dialog where specific site and language
    * translation preferences can be set.
    */
   showTranslationExceptions: function ()
   {
     openDialog("chrome://browser/content/preferences/translation.xul",
                "Browser:TranslationExceptions", null);
+  },
+
+  openTranslationProviderAttribution: function ()
+  {
+    Components.utils.import("resource:///modules/translation/Translation.jsm");
+    Translation.openProviderAttribution();
   }
 };
--- a/browser/components/preferences/in-content/content.xul
+++ b/browser/components/preferences/in-content/content.xul
@@ -129,17 +129,24 @@
     <description flex="1" control="chooseLanguage">&chooseLanguage.label;</description>
     <button id="chooseLanguage"
             label="&chooseButton.label;"
             accesskey="&chooseButton.accesskey;"
             oncommand="gContentPane.showLanguages();"/>
   </hbox>
 
   <hbox id="translationBox" hidden="true">
-    <checkbox id="translate" preference="browser.translation.detectLanguage" flex="1"
-              label="&translateWebPages.label;" accesskey="&translateWebPages.accesskey;"
-              onsyncfrompreference="return gContentPane.updateButtons('translateButton',
-                                    'browser.translation.detectLanguage');"/>
+    <hbox align="center" flex="1">
+      <checkbox id="translate" preference="browser.translation.detectLanguage"
+                label="&translateWebPages.label;." accesskey="&translateWebPages.accesskey;"
+                onsyncfrompreference="return gContentPane.updateButtons('translateButton',
+                                              'browser.translation.detectLanguage');"/>
+      <label>Übersetzungen von</label>
+      <separator orient="vertical" class="thin"/>
+      <image id="translationAttributionImage" aria-label="Microsoft Translator"
+             onclick="gContentPane.openTranslationProviderAttribution()"
+             src="chrome://browser/content/microsoft-translator-attribution.png"/>
+    </hbox>
     <button id="translateButton" label="&translateExceptions.label;"
             oncommand="gContentPane.showTranslationExceptions();"
             accesskey="&translateExceptions.accesskey;"/>
   </hbox>
 </groupbox>
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -75,16 +75,22 @@ this.Translation = {
     trUI.translatedFrom = aData.translatedFrom;
     trUI.translatedTo = aData.translatedTo;
     trUI.originalShown = aData.originalShown;
 
     trUI.showURLBarIcon();
 
     if (trUI.shouldShowInfoBar(aBrowser.currentURI))
       trUI.showTranslationInfoBar();
+  },
+
+  openProviderAttribution: function() {
+    Cu.import("resource:///modules/RecentWindow.jsm");
+    RecentWindow.getMostRecentBrowserWindow().openUILinkIn(
+      "http://aka.ms/MicrosoftTranslatorAttribution", "tab");
   }
 };
 
 /* TranslationUI objects keep the information related to translation for
  * a specific browser.  This object is passed to the translation
  * infobar so that it can initialize itself.  The properties exposed to
  * the infobar are:
  * - detectedLanguage, code of the language detected on the web page.
@@ -301,16 +307,23 @@ let TranslationHealthReport = {
    *        the user has manually adjusted the detected language false should
    *        be passed.
    */
   recordLanguageChange: function (beforeFirstTranslation) {
     this._withProvider(provider => provider.recordLanguageChange(beforeFirstTranslation));
   },
 
   /**
+   * Record a denied translation offer.
+   */
+  recordDeniedTranslationOffer: function () {
+    this._withProvider(provider => provider.recordDeniedTranslationOffer());
+  },
+
+  /**
    * Retrieve the translation provider and pass it to the given function.
    *
    * @param callback
    *        The function that will be passed the translation provider.
    */
   _withProvider: function (callback) {
     try {
       let reporter = Cc["@mozilla.org/datareporting/service;1"]
@@ -358,16 +371,17 @@ TranslationMeasurement1.prototype = Obje
     missedTranslationOpportunityCount: DAILY_COUNTER_FIELD,
     pageTranslatedCount: DAILY_COUNTER_FIELD,
     charactersTranslatedCount: DAILY_COUNTER_FIELD,
     translationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     missedTranslationOpportunityCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     pageTranslatedCountsByLanguage: DAILY_LAST_TEXT_FIELD,
     detectedLanguageChangedBefore: DAILY_COUNTER_FIELD,
     detectedLanguageChangedAfter: DAILY_COUNTER_FIELD,
+    deniedTranslationOffer: DAILY_COUNTER_FIELD,
     detectLanguageEnabled: DAILY_LAST_NUMERIC_FIELD,
     showTranslationUI: DAILY_LAST_NUMERIC_FIELD,
   },
 
   shouldIncludeField: function (field) {
     if (!Services.prefs.getBoolPref("toolkit.telemetry.enabled")) {
       // This measurement should only be included when telemetry is
       // enabled, so we will not include any fields.
@@ -494,16 +508,25 @@ TranslationProvider.prototype = Object.f
       if (beforeFirstTranslation) {
           yield m.incrementDailyCounter("detectedLanguageChangedBefore");
         } else {
           yield m.incrementDailyCounter("detectedLanguageChangedAfter");
         }
     }.bind(this));
   },
 
+  recordDeniedTranslationOffer: function () {
+    let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
+                                TranslationMeasurement1.prototype.version);
+
+    return this._enqueueTelemetryStorageTask(function* recordTask() {
+      yield m.incrementDailyCounter("deniedTranslationOffer");
+    }.bind(this));
+  },
+
   collectDailyData: function () {
     let m = this.getMeasurement(TranslationMeasurement1.prototype.name,
                                 TranslationMeasurement1.prototype.version);
 
     return this._enqueueTelemetryStorageTask(function* recordTask() {
       let detectLanguageEnabled = Services.prefs.getBoolPref("browser.translation.detectLanguage");
       yield m.setDailyLastNumeric("detectLanguageEnabled", detectLanguageEnabled ? 1 : 0);
 
--- a/browser/components/translation/jar.mn
+++ b/browser/components/translation/jar.mn
@@ -1,5 +1,6 @@
 # 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/.
 browser.jar:
        content/browser/translation-infobar.xml
+       content/browser/microsoft-translator-attribution.png
new file mode 100644
index 0000000000000000000000000000000000000000..d9d277461b4b66b92f47e420649f8a20b5f5b780
GIT binary patch
literal 3422
zc$|G!2|Sct7biQ}QcP0s%vj4ZGYrOLDKqvNWeM@g3^SO_!py{2%P4!OBudm<NKr<M
zRvFD?i84jDB)mvj3nB82w(t9WzxVy_^SjTv=iLAQ+;h)8_xC($dtDvmWz}UxL`3AB
z9MSGVnJMf#(vrfrssrPWh=`ab$<v4FgT)|&XcQwnffh(KVo}0H)|#aaD;ysbMq~m5
zi6JDa6?p2wV=$0JumXFVVxib@6fu<K7(*v|#JG9}#e@Y}5WqIpKuZ=<=zv0G;(;s*
znaV)2tia#>B8B5MGXxC$4q=8_fp@P>0DZ7~fhZcC2sAY^H4K6p!+_=%Mle&j1q@~Y
zG={=VATTJz*uoG7L&D9GFf-uK1{SVHCj=wi(e^*r5^7f9P$n}R34ugKMHxkz7}4k<
z5SWF91q5mgF*Y_7A`BVPR3@HfNM&gL@_;5Xg6O1hCW%G`uIczd+99SD82oDsl<?nf
zsf?d#5+)47!iPg(Mo`Fq0kPQs52a9kqZv$hV(|Y*Gd!cii4b=pgLa4>L=?g`*T~^W
z6rG4?(&(Nv8u?ci_lDA#G)5>b9Ed^z^|1IL5_L`A{+)ouBAuuVCY~BZbV6H!g%%?c
ziGV~vp$KER>27<dtuWbkCKh&Pu-$NTgdN%hZj46Q{^Fu(L5C<rD)SeY@E@-2KXTV<
z;Rl;YCq)to_H-Hr_<hev(%-~lVqs<vGlg2%Lw6(oK9(O`!r#R5le_yLxe#G6kpDU9
zUnXJstPOwHtx)-0`9!L)-08x)zFAwUAiNWsnRY%*4_YuY3Qs4B{7AeL1s@_j8ayF7
zqKRlJ1Zj}adpw15oyd=-BJ6rUrLc?L_MiCFER=~(=-r;Ip7DaP&@4QlA>f=v%dIAm
zTyHf-1<Y}JHkO2X3UBDjMNK53gUB~E!g4_=>h|`hP?>s1rJqUh3WJsT_6t=yApAG)
zmE!yNWBXyT^VE-zY?j}?{d)h#Xl?PAd#f)F`F~l73A_~KmDp4_bPQ0nVV3aKSZ{XV
zBjitR{ImjwHWDj?`0G-Zcx(iswKjrGlL|YxSgM%l+opJwJ%hVFV6qM)J~07)0LfQR
z2e-Z-FX6#Ui+0~<ivk;~Qm!;=F-4Q&GX;OlP6w*3N<WE|DD<9KxxoHhBkJ@{=dMEa
zuAL9sCS^xpcb}wO-x-yp_)a<Bxz{YGEbj7(<U`32Qg5@~^g4};6tNXT){ae_`nt{2
zvJWLLk&4leWqUeb6)Du3)N6<oV6eqpO6e6sl~nGL^=%DCw;r%h@Xw0W9wCjtZ)`ku
zQPp;wc){~g_l9_P?p~<(V*5-FtLg-0EL%diOW~4IE!#0&t4QFylrdKPblzP_Vlq)G
zQ!A-%d%AUj>6teloq9SG!`A|}fBX9RJ0Pnu%d&Rhq}KuV#vbiW<ALv^(0B!3h3Dn-
zG|e5+0A<ygn9U)VKj;q>2KRkR>(4ko!`Pqsv0ZntKq|S*E;0aG&FWjodp4-e4#90c
z|8@(zyZ$|}9Q6Fzg+05iA4i#|2TOGij}LvZU25#wTDQ+$YNEJl(K2(}8P5Rg8yvY0
z4bmn#$#P_G<8<kcWLzZOK|AANTMACrbKt8A**Go%u=AW=maYKoV5gEYggL)0-AS(W
zh~0Jny*AWqDFSHv#MY~ND-t8Da(vcH^Ye2JvOU-G-xfN(zM_s^zV4QL5#!|IBW6*q
zU-+$L_C;W<PQwGyNp(TP*c3(W_Lv0Y1i$2MDYCcU^$lN(V>qVgw>U|3zUJmuk~-L?
zLNqzp4vn8JiH9vX#ww4h#IB192|WBfhPOG)Br3#pC);nrx9CyVF-jm8o`)+fdb}vH
zh)|KaI$CiV6ru-8hzqAw;tW=s@soqi_bL?;T*9ZjHlsF;!?z<uXWMqP%dp&mt7qB)
zBT{WBeUHaG1+pWn`4ovEWc84rLUIk9ixIuax;&X<d$r?9)8Jv}9?0oVdtdI|bPY({
zkcOPEhu|yPs!-2wdI<}yY1c6EJ%!uRaa`Ajvw($`ctO3O@&oy2gUBvRD~E;madkDE
z+9UizN@}cK#@4g3nAiIN4IqEV{gRiA4f&kB4&z*-vuFJ?jmRU0&sx$?Z!mk6Nq!k>
z3iCIIY}P6WKIZ1xW~;C7k9W~axvs@1@H{y@+*)Brbb3MR@1Gy<zPEDVz=3`n>!IPo
zQA^?T#p*HRt2S}26;nNJDo`*o>ne1?0G|#;<yfD%0z9BK`7-wzD0ss@p6^~1MmNT<
zhj5h!n1O%Z=dw|$Z!2s^b(*`E7w70*FOP33uXU3Qt@j;Jp4stvQOC`Z5IPP4J>3w^
z)A4Eu;+^IJ1|=QNf7sl&=b_FL-e{@ewN%-Y#~(UD23BW$&=u|pCj)``$6A7sFU_jp
zc5*LIrNsqaYCN+n%Uyi^h0X7*Q7U`x?LD~P!EaI?1lZKSanK~?g_V*O<Uy00&O1%4
zhMMw6%jVb0E9>JU^l!%czv^67k>eWR%%X-WH!QD0zTK&xM_`p~l(0Nk>{~~Ncs}o$
zt%hG}@v-6+UQ_)G7*8EvBS-1xzZ^+XYLPh$@NLuak9)YsJVrM7(G><f!l!H5k*r;-
z1qM&)4q9R#yAsUy;JiYo)^jb>mk*~>79O3?M;9LR9*1|$?@AEA(L_#l94>ix`(9E<
zcWC*P^vsroYXS=m>ytC@((S5Tjutl=K-gJR$(D0bN;BJLaMIk%nkyo1wuPw6e`2=-
zL@FzmCA}i`w)M=RlUS@y<)OB+xcdOTKr-7us=fOdA}!xpgAiF+(Jw{~T$BflQ+Nh}
zA`ywcwmf&}zxp;y_L`)QreYe@k7eg4WG$&1)^1aKB{FsSV4nGtLCqF*d%m~pZ1uvH
zWbUTaZ}8)-2fZ4IssY6&528RDPEiy}m+BIV?4Y8klH%t|nCtui?`(-HgJL7JiV|z>
z3WJPd_~4%4;?_?ee3nXzCg?Ix&1>yPP&I%`{Nj+jHJzV(ZorKO(!ZJg$n6lj;Kpa=
zFfLh9Fk|KgEw0)B!B6dz=qP8_+$V;ZDL&+){TT*w=H{B5;}^}9rq{F#OL8^LTGw$@
zTR!D%Ej#cf9_A}sb)mAZ-e5X9qome78_62Y#*fk6yb~*l1`-T#gwCB&kNt6#d7=aQ
z`FjdlJ3vbh?QcHIWp@Yf_#=$FG1e@`_zv}JR~)roC*NY*p+~Lb4xHJIt@8oLYfF4E
z_WFR^S$D-0N7O1j>UpD@0Mog6K6&Y=QYNV`g4_Po1*)xZxJ+p-IV&uwIzZWP?>XGI
z4@Yq81fF3%bn#J>F_6Tm?Vm2Cy>Nh%5LwdXm=q;US_<y!pf;da%+v2->z`u!0=xx4
zyP-aEf1$Waj;?CYa!29h_WX^4fP>e*Sw;jkF2sqyLP?i~NON0=!>RyrkMq*(0r@4#
z5V%h7nL&g6Bl_@Hp0~?zrJUJdGUxkR{#^xU2FI1y`>6<5M+CQm5cvi1q0-u!EyHq7
zEzM5BEuf12nqJL9L2nxzF61`&274&kLIbMk-rChTYeHlvB2x8hb~WxKFYPl<ksH_Y
z@<q+U1mE=vmK~ijoNcynFT0yOmr0V=XYA5&)+xj-r0P|78Om{7(!HSNK1X_+O*wQi
zEAg8}C3`pv{u3M*U(n<zGr?8+TX3Y}&Os+t*lFLq^)jIR=oadY5y^X7T-ADqwDMyN
zL|(y(lZ4)tR6v>zciL~N4}EyxGwV&rBKg`M_{DRa3nM8s?v&Kg8@lq|@+*9J7kggo
z7Ool{ei(nNe9FAJYhxA4GWYq}laZPev1H8<R6D%}{)8dexhwbU*8X*yK(*(`+0*eU
zID6@U*1Kxgp7`eUddc)YT96D;kmRXHSba*tH)p2ddh$;xOK!2QRz)uNjU#x-)IR=>
zJg=_ArN1P$u09RUXebj$t{UcLa0|Beb7zm{U}nb5WZW%Z-f8*#DphhuP!M-bGICBJ
zTPm&?T0Wc{z47%{ViuoV7oK@U+SN;OAUPJ+d$2z%rWj;2j*FML8?{}nKXE-9cy|9z
zg@LXFZR_oAi|2;lpE?1!Xj!X@QR8-?YPGGOlLfU?Rb8fYAHi=%XIS$%sWVgW%QSA4
fYv^|PK3EmWTN=|@$GmxK?QhP>&J}&#7RUZKMltZ&
--- a/browser/components/translation/test/unit/test_healthreport.js
+++ b/browser/components/translation/test/unit/test_healthreport.js
@@ -191,16 +191,38 @@ add_task(function* test_record_translati
   Assert.equal(day.get("detectedLanguageChangedBefore"), 1);
   Assert.ok(day.has("detectedLanguageChangedAfter"));
   Assert.equal(day.get("detectedLanguageChangedAfter"), 2);
 
   yield provider.shutdown();
   yield storage.close();
 });
 
+add_task(function* test_denied_translation_offer() {
+  let storage = yield Metrics.Storage("translation");
+  let provider = new TranslationProvider();
+  yield provider.init(storage);
+  let now = new Date();
+
+  yield provider.recordDeniedTranslationOffer();
+  yield provider.recordDeniedTranslationOffer();
+
+  let m = provider.getMeasurement("translation", 1);
+  let values = yield m.getValues();
+  Assert.equal(values.days.size, 1);
+  Assert.ok(values.days.hasDay(now));
+  let day = values.days.getDay(now);
+
+  Assert.ok(day.has("deniedTranslationOffer"));
+  Assert.equal(day.get("deniedTranslationOffer"), 2);
+
+  yield provider.shutdown();
+  yield storage.close();
+});
+
 add_task(function* test_collect_daily() {
   let storage = yield Metrics.Storage("translation");
   let provider = new TranslationProvider();
   yield provider.init(storage);
   let now = new Date();
 
   // Set the prefs we test here to `false` initially.
   const kPrefDetectLanguage = "browser.translation.detectLanguage";
@@ -248,16 +270,18 @@ add_task(function* test_healthreporter_j
     yield provider.recordTranslationOpportunity("fr", now);
     yield provider.recordLanguageChange(true);
     yield provider.recordTranslation("fr", "en", 1000, now);
     yield provider.recordLanguageChange(false);
 
     yield provider.recordTranslationOpportunity("es", now);
     yield provider.recordTranslation("es", "en", 1000, now);
 
+    yield provider.recordDeniedTranslationOffer();
+
     yield reporter.collectMeasurements();
     let payload = yield reporter.getJSONPayload(true);
     let today = reporter._formatDate(now);
 
     Assert.ok(today in payload.data.days);
     let day = payload.data.days[today];
 
     Assert.ok("org.mozilla.translation.translation" in day);
@@ -280,16 +304,19 @@ add_task(function* test_healthreporter_j
     Assert.ok("es" in translations["pageTranslatedCountsByLanguage"]);
     Assert.equal(translations["pageTranslatedCountsByLanguage"]["es"]["total"], 1);
     Assert.equal(translations["pageTranslatedCountsByLanguage"]["es"]["en"], 1);
 
     Assert.ok("detectedLanguageChangedBefore" in translations);
     Assert.equal(translations["detectedLanguageChangedBefore"], 1);
     Assert.ok("detectedLanguageChangedAfter" in translations);
     Assert.equal(translations["detectedLanguageChangedAfter"], 1);
+    
+    Assert.ok("deniedTranslationOffer" in translations);
+    Assert.equal(translations["deniedTranslationOffer"], 1);
   } finally {
     reporter._shutdown();
   }
 });
 
 // Test the payload after recording with telemetry disabled.
 add_task(function* test_healthreporter_json2() {
   Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
@@ -304,16 +331,18 @@ add_task(function* test_healthreporter_j
     yield provider.recordTranslationOpportunity("fr", now);
     yield provider.recordLanguageChange(true);
     yield provider.recordTranslation("fr", "en", 1000, now);
     yield provider.recordLanguageChange(false);
 
     yield provider.recordTranslationOpportunity("es", now);
     yield provider.recordTranslation("es", "en", 1000, now);
 
+    yield provider.recordDeniedTranslationOffer();
+
     yield reporter.collectMeasurements();
     let payload = yield reporter.getJSONPayload(true);
     let today = reporter._formatDate(now);
 
     Assert.ok(today in payload.data.days);
     let day = payload.data.days[today];
 
     Assert.ok("org.mozilla.translation.translation" in day);
@@ -322,12 +351,13 @@ add_task(function* test_healthreporter_j
 
     Assert.ok(!("translationOpportunityCount" in translations));
     Assert.ok(!("pageTranslatedCount" in translations));
     Assert.ok(!("charactersTranslatedCount" in translations));
     Assert.ok(!("translationOpportunityCountsByLanguage" in translations));
     Assert.ok(!("pageTranslatedCountsByLanguage" in translations));
     Assert.ok(!("detectedLanguageChangedBefore" in translations));
     Assert.ok(!("detectedLanguageChangedAfter" in translations));
+    Assert.ok(!("deniedTranslationOffer" in translations));
   } finally {
     reporter._shutdown();
   }
 });
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/translation-infobar.xml
@@ -93,27 +93,35 @@
 
           </xul:deck>
           <xul:spacer flex="1"/>
 
           <xul:button type="menu"
                       class="translate-infobar-element options-menu-button"
                       anonid="options"
                       label="&translation.options.menu;">
-            <xul:menupopup onpopupshowing="document.getBindingParent(this).optionsShowing();">
+            <xul:menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
+                                  cui-widget-panelWithFooter PanelUI-subView"
+                           onpopupshowing="document.getBindingParent(this).optionsShowing();">
               <xul:menuitem anonid="neverForLanguage"
                             oncommand="document.getBindingParent(this).neverForLanguage();"/>
               <xul:menuitem anonid="neverForSite"
                             oncommand="document.getBindingParent(this).neverForSite();"
                             label="&translation.options.neverForSite.label;"
                             accesskey="&translation.options.neverForSite.accesskey;"/>
               <xul:menuseparator/>
               <xul:menuitem oncommand="openPreferences('paneContent');"
                             label="&translation.options.preferences.label;"
                             accesskey="&translation.options.preferences.accesskey;"/>
+              <xul:menuitem class="translation-attribution subviewbutton panel-subview-footer"
+                            oncommand="document.getBindingParent(this).openProviderAttribution();">
+                <xul:label>Übersetzungen von</xul:label>
+                <xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
+                           aria-label="Microsoft Translator"/>
+              </xul:menuitem>
             </xul:menupopup>
           </xul:button>
 
         </xul:hbox>
         <xul:toolbarbutton ondblclick="event.stopPropagation();"
                            class="messageCloseButton close-icon tabbable"
                            xbl:inherits="hidden=hideclose"
                            tooltiptext="&closeNotification.tooltip;"
@@ -305,11 +313,19 @@
             let perms = Services.perms;
             perms.add(uri, "translate", perms.DENY_ACTION);
 
             this.close();
           ]]>
         </body>
       </method>
 
+      <method name="openProviderAttribution">
+        <body>
+          <![CDATA[
+            Translation.openProviderAttribution();
+          ]]>
+        </body>
+      </method>
+
     </implementation>
   </binding>
 </bindings>
--- a/browser/themes/linux/preferences/preferences.css
+++ b/browser/themes/linux/preferences/preferences.css
@@ -49,16 +49,22 @@ radio[pane=paneSync] {
 label.small {
   font-size: smaller;
 }
 
 #tabPrefsBox {
   margin: 5px;
 }
 
+/* Content Pane */
+#translationAttributionImage {
+  width: 70px;
+  cursor: pointer;
+}
+
 /* Applications Pane */
 #BrowserPreferences[animated="true"] #handlersView {
   height: 25em;
 }
 
 #BrowserPreferences[animated="false"] #handlersView {
   -moz-box-flex: 1;
 }
--- a/browser/themes/osx/preferences/preferences.css
+++ b/browser/themes/osx/preferences/preferences.css
@@ -135,16 +135,21 @@ caption {
 }
 
 #popupPolicyRow {
   margin-bottom: 4px !important;
   padding-bottom: 4px !important;
   border-bottom: 1px solid #ccc;
 }
 
+#translationAttributionImage {
+  width: 70px;
+  cursor: pointer;
+}
+
 #browserUseCurrent,
 #browserUseBookmark,
 #browserUseBlank {
   margin-top: 10px;
 }
 
 #advancedPrefs {
   margin: 0 8px;
--- a/browser/themes/shared/translation/infobar.inc.css
+++ b/browser/themes/shared/translation/infobar.inc.css
@@ -41,8 +41,26 @@ notification[value="translation"] menuli
 
 notification[value="translation"] menulist > .menulist-dropmarker {
   -moz-appearance: toolbarbutton-dropdown;
   border: none;
   background-color: transparent;
   margin: auto;
   padding: 5px 0;
 }
+
+.translation-menupopup arrowscrollbox {
+  padding-bottom: 0;
+}
+
+.translation-attribution {
+  cursor: pointer;
+  -moz-box-align: end;
+  font-size: small;
+}
+
+.translation-attribution > label {
+  margin-bottom: 0;
+}
+
+.translation-attribution > image {
+  width: 70px;
+}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2438,16 +2438,20 @@ notification[value="translation"] {
 }
 
 .translated-notification-icon,
 #translated-notification-icon {
   list-style-image: url(chrome://browser/skin/translation-16.png);
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 
+.translation-menupopup {
+  -moz-appearance: none;
+}
+
 /* Bookmarks roots menu-items */
 #subscribeToPageMenuitem:not([disabled]),
 #subscribeToPageMenupopup,
 #BMB_subscribeToPageMenuitem:not([disabled]),
 #BMB_subscribeToPageMenupopup {
   list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
 }
 
--- a/browser/themes/windows/preferences/preferences.css
+++ b/browser/themes/windows/preferences/preferences.css
@@ -48,16 +48,22 @@ radio[pane=paneSync] {
 label.small {
   font-size: smaller;
 }
 
 #tabPrefsBox {
   margin: 6px;
 }
 
+/* Content Pane */
+#translationAttributionImage {
+  width: 70px;
+  cursor: pointer;
+}
+
 /* Applications Pane */
 #BrowserPreferences[animated="true"] #handlersView {
   height: 25em;
 }
 
 #BrowserPreferences[animated="false"] #handlersView {
   -moz-box-flex: 1;
 }
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -33,16 +33,17 @@ SEARCH_PATHS = [
     'python/configobj',
     'python/jsmin',
     'python/psutil',
     'python/which',
     'build/pymake',
     'config',
     'dom/bindings',
     'dom/bindings/parser',
+    'layout/tools/reftest',
     'other-licenses/ply',
     'xpcom/idl-parser',
     'testing',
     'testing/xpcshell',
     'testing/marionette/client',
     'testing/marionette/client/marionette',
     'testing/marionette/transport',
     'testing/mozbase/mozcrash',
--- a/build/virtualenv_packages.txt
+++ b/build/virtualenv_packages.txt
@@ -12,13 +12,14 @@ which.pth:python/which
 ply.pth:other-licenses/ply/
 codegen.pth:python/codegen/
 mock.pth:python/mock-1.0.0
 mozilla.pth:build
 mozilla.pth:config
 mozilla.pth:xpcom/typelib/xpt/tools
 mozilla.pth:dom/bindings
 mozilla.pth:dom/bindings/parser
+mozilla.pth:layout/tools/reftest
 moztreedocs.pth:tools/docs
 copy:build/buildconfig.py
 packages.txt:testing/mozbase/packages.txt
 objdir:build
 gyp.pth:media/webrtc/trunk/tools/gyp/pylib
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1311,16 +1311,22 @@ this.DOMApplicationRegistry = {
     } else if (download.channel) {
       try {
         download.channel.cancel(Cr.NS_BINDING_ABORTED);
       } catch(e) { }
     } else {
       return;
     }
 
+    // Ensure we don't send additional errors for this download
+    app.isCanceling = true;
+
+    // Ensure this app can be downloaded again after canceling
+    app.downloading = false;
+
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: {
           progress: 0,
           installState: download.previousState,
           downloading: false
         },
         error: error,
@@ -1334,16 +1340,17 @@ this.DOMApplicationRegistry = {
     AppDownloadManager.remove(aManifestURL);
   },
 
   startDownload: Task.async(function*(aManifestURL) {
     debug("startDownload for " + aManifestURL);
 
     let id = this._appIdForManifestURL(aManifestURL);
     let app = this.webapps[id];
+
     if (!app) {
       debug("startDownload: No app found for " + aManifestURL);
       throw new Error("NO_SUCH_APP");
     }
 
     if (app.downloading) {
       debug("app is already downloading. Ignoring.");
       throw new Error("APP_IS_DOWNLOADING");
@@ -2765,16 +2772,24 @@ this.DOMApplicationRegistry = {
       // We determine the app's 'installState' according to its previous
       // state. Cancelled download should remain as 'pending'. Successfully
       // installed apps should morph to 'updating'.
       oldApp.installState = aIsUpdate ? "updating" : "pending";
 
       // initialize the progress to 0 right now
       oldApp.progress = 0;
 
+      // Save the current state of the app to handle cases where we may be
+      // retrying a past download.
+      yield DOMApplicationRegistry._saveApps();
+      DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+        app: oldApp,
+        manifestURL: aNewApp.manifestURL
+      });
+
       let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp);
       let hash = yield this._computeFileHash(zipFile.path);
 
       let responseStatus = requestChannel.responseStatus;
       let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash);
 
       if (oldPackage) {
         debug("package's etag or hash unchanged; sending 'applied' event");
@@ -4078,26 +4093,34 @@ AppcacheObserver.prototype = {
         eventType: ["downloadsuccess", "downloadapplied"],
         manifestURL: app.manifestURL
       });
     }
 
     let setError = function appObs_setError(aError) {
       debug("Offlinecache setError to " + aError);
       app.downloading = false;
+      mustSave = true;
+
+      // If we are canceling the download, we already send a DOWNLOAD_CANCELED
+      // error.
+      if (app.isCanceling) {
+        delete app.isCanceling;
+        return;
+      }
+
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: app,
         error: aError,
         manifestURL: app.manifestURL
       });
       DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
-      mustSave = true;
     }
 
     switch (aState) {
       case Ci.nsIOfflineCacheUpdateObserver.STATE_ERROR:
         aUpdate.removeObserver(this);
         AppDownloadManager.remove(app.manifestURL);
         setError("APP_CACHE_DOWNLOAD_ERROR");
         break;
--- a/dom/bluetooth2/BluetoothAdapter.cpp
+++ b/dom/bluetooth2/BluetoothAdapter.cpp
@@ -694,17 +694,20 @@ BluetoothAdapter::EnableDisable(bool aEn
       promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
       return promise.forget();
     }
     methodName.AssignLiteral("Disable");
     mState = BluetoothAdapterState::Disabling;
   }
 
   nsTArray<nsString> types;
-  types.AppendElement(NS_LITERAL_STRING("State"));
+  BT_APPEND_ENUM_STRING(types,
+                        BluetoothAdapterAttribute,
+                        BluetoothAdapterAttribute::State);
+
   DispatchAttributeEvent(types);
 
   nsRefPtr<BluetoothReplyRunnable> result =
     new BluetoothVoidReplyRunnable(nullptr, /* DOMRequest */
                                    promise,
                                    methodName);
 
   if(NS_FAILED(bs->EnableDisable(aEnable, result))) {
@@ -787,17 +790,17 @@ BluetoothAdapter::HandlePropertyChanged(
     if (type == BluetoothAdapterAttribute::Unknown) {
       SetPropertyByValue(arr[i]);
       continue;
     }
 
     // BluetoothAdapterAttribute properties
     if (IsAdapterAttributeChanged(type, arr[i].value())) {
       SetPropertyByValue(arr[i]);
-      types.AppendElement(arr[i].name());
+      BT_APPEND_ENUM_STRING(types, BluetoothAdapterAttribute, type);
     }
   }
 
   DispatchAttributeEvent(types);
 }
 
 void
 BluetoothAdapter::DispatchAttributeEvent(const nsTArray<nsString>& aTypes)
--- a/dom/bluetooth2/BluetoothCommon.h
+++ b/dom/bluetooth2/BluetoothCommon.h
@@ -78,16 +78,28 @@ extern bool gBluetoothDebugFlag;
   do {                                                               \
     if (!BroadcastSystemMessage(type, parameters)) {                 \
       BT_WARNING("Failed to broadcast [%s]",                         \
                  NS_ConvertUTF16toUTF8(type).get());                 \
       return;                                                        \
     }                                                                \
   } while(0)
 
+/**
+ * Convert an enum value to string then append it to an array.
+ */
+#define BT_APPEND_ENUM_STRING(array, enumType, enumValue)            \
+  do {                                                               \
+    uint32_t index = uint32_t(enumValue);                            \
+    nsAutoString name;                                               \
+    name.AssignASCII(enumType##Values::strings[index].value,         \
+                     enumType##Values::strings[index].length);       \
+    array.AppendElement(name);                                       \
+  } while(0)                                                         \
+
 #define BEGIN_BLUETOOTH_NAMESPACE \
   namespace mozilla { namespace dom { namespace bluetooth {
 #define END_BLUETOOTH_NAMESPACE \
   } /* namespace bluetooth */ } /* namespace dom */ } /* namespace mozilla */
 #define USING_BLUETOOTH_NAMESPACE \
   using namespace mozilla::dom::bluetooth;
 
 #define KEY_LOCAL_AGENT  "/B2G/bluetooth/agent"
--- a/dom/bluetooth2/BluetoothManager.cpp
+++ b/dom/bluetooth2/BluetoothManager.cpp
@@ -255,17 +255,19 @@ BluetoothManager::DispatchAttributeEvent
   NS_ENSURE_TRUE_VOID(global);
 
   JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject());
   NS_ENSURE_TRUE_VOID(scope);
 
   JSAutoCompartment ac(cx, scope);
 
   nsTArray<nsString> types;
-  types.AppendElement(NS_LITERAL_STRING("DefaultAdapter"));
+  BT_APPEND_ENUM_STRING(types,
+                        BluetoothManagerAttribute,
+                        BluetoothManagerAttribute::DefaultAdapter);
 
   if (!ToJSValue(cx, types, &value)) {
     JS_ClearPendingException(cx);
     return;
   }
 
   // Notify application of default adapter change
   RootedDictionary<BluetoothAttributeEventInit> init(cx);
--- a/dom/mobileconnection/tests/marionette/head.js
+++ b/dom/mobileconnection/tests/marionette/head.js
@@ -561,29 +561,77 @@ function sendMMI(aMmi) {
   return wrapDomRequestAsPromise(request)
     .then(null, () => { throw request.error });
 }
 
 /**
  * Query current voice privacy mode.
  *
  * Fulfill params:
-     A boolean indicates the current voice privacy mode.
+ *   A boolean indicates the current voice privacy mode.
  * Reject params:
  *   'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
  *
  * @return A deferred promise.
  */
  function getVoicePrivacyMode() {
   let request = mobileConnection.getVoicePrivacyMode();
   return wrapDomRequestAsPromise(request)
     .then(() => request.result, () => { throw request.error });
 }
 
 /**
+ * Configures call barring options.
+ *
+ * Fulfill params: (none)
+ * Reject params:
+ *   'RadioNotAvailable', 'RequestNotSupported', 'InvalidParameter' or
+ *   'GenericFailure'.
+ *
+ * @return A deferred promise.
+ */
+ function setCallBarringOption(aOptions) {
+  let request = mobileConnection.setCallBarringOption(aOptions);
+  return wrapDomRequestAsPromise(request)
+    .then(null, () => { throw request.error });
+}
+
+/**
+ * Queries current call barring status.
+ *
+ * Fulfill params:
+ *   An object contains call barring status.
+ * Reject params:
+ *   'RadioNotAvailable', 'RequestNotSupported', 'InvalidParameter' or
+ *   'GenericFailure'.
+ *
+ * @return A deferred promise.
+ */
+ function getCallBarringOption(aOptions) {
+  let request = mobileConnection.getCallBarringOption(aOptions);
+  return wrapDomRequestAsPromise(request)
+    .then(() => request.result, () => { throw request.error });
+}
+
+/**
+ * Change call barring facility password.
+ *
+ * Fulfill params: (none)
+ * Reject params:
+ *   'RadioNotAvailable', 'RequestNotSupported', or 'GenericFailure'.
+ *
+ * @return A deferred promise.
+ */
+ function changeCallBarringPassword(aOptions) {
+  let request = mobileConnection.changeCallBarringPassword(aOptions);
+  return wrapDomRequestAsPromise(request)
+    .then(null, () => { throw request.error });
+}
+
+/**
  * Set data connection enabling state and wait for "datachange" event.
  *
  * Resolve if data connection state changed to the expected one.  Never reject.
  *
  * Fulfill params: (none)
  *
  * @param aEnabled
  *        A boolean state.
@@ -954,16 +1002,104 @@ function setEmulatorOperatorNamesAndWait
   if (aWaitData) {
     promises.push(waitForManagerEvent("datachange"));
   }
   promises.push(setEmulatorOperatorNames(aOperator, aLongName, aShortName,
                                          aMcc, aMnc));
   return Promise.all(promises);
 }
 
+/**
+ * Set GSM signal strength.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aRssi
+ *
+ * @return A deferred promise.
+ */
+function setEmulatorGsmSignalStrength(aRssi) {
+  let cmd = "gsm signal " + aRssi;
+  return runEmulatorCmdSafe(cmd);
+}
+
+/**
+ * Set emulator GSM signal strength and wait for voice and/or data state change.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aRssi
+ * @param aWaitVoice [optional]
+ *        A boolean value.  Default true.
+ * @param aWaitData [optional]
+ *        A boolean value.  Default false.
+ *
+ * @return A deferred promise.
+ */
+function setEmulatorGsmSignalStrengthAndWait(aRssi,
+                                             aWaitVoice = true,
+                                             aWaitData = false) {
+  let promises = [];
+  if (aWaitVoice) {
+    promises.push(waitForManagerEvent("voicechange"));
+  }
+  if (aWaitData) {
+    promises.push(waitForManagerEvent("datachange"));
+  }
+  promises.push(setEmulatorGsmSignalStrength(aRssi));
+  return Promise.all(promises);
+}
+
+/**
+ * Set LTE signal strength.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aRxlev
+ * @param aRsrp
+ * @param aRssnr
+ *
+ * @return A deferred promise.
+ */
+function setEmulatorLteSignalStrength(aRxlev, aRsrp, aRssnr) {
+  let cmd = "gsm lte_signal " + aRxlev + " " + aRsrp + " " + aRssnr;
+  return runEmulatorCmdSafe(cmd);
+}
+
+/**
+ * Set emulator LTE signal strength and wait for voice and/or data state change.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aRxlev
+ * @param aRsrp
+ * @param aRssnr
+ * @param aWaitVoice [optional]
+ *        A boolean value.  Default true.
+ * @param aWaitData [optional]
+ *        A boolean value.  Default false.
+ *
+ * @return A deferred promise.
+ */
+function setEmulatorLteSignalStrengthAndWait(aRxlev, aRsrp, aRssnr,
+                                             aWaitVoice = true,
+                                             aWaitData = false) {
+  let promises = [];
+  if (aWaitVoice) {
+    promises.push(waitForManagerEvent("voicechange"));
+  }
+  if (aWaitData) {
+    promises.push(waitForManagerEvent("datachange"));
+  }
+  promises.push(setEmulatorLteSignalStrength(aRxlev, aRsrp, aRssnr));
+  return Promise.all(promises);
+}
+
 let _networkManager;
 
 /**
  * Get internal NetworkManager service.
  */
 function getNetworkManager() {
   if (!_networkManager) {
     _networkManager = Cc["@mozilla.org/network/manager;1"]
deleted file mode 100644
--- a/dom/mobileconnection/tests/marionette/mobile_header.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-// In single sim scenario, there is only one mobileConnection, we can always use
-// the first instance.
-let mobileConnection = window.navigator.mozMobileConnections[0];
-ok(mobileConnection instanceof MozMobileConnection,
-   "mobileConnection is instanceof " + mobileConnection.constructor);
-
-let _pendingEmulatorCmdCount = 0;
-
-/* Remove permission and execute finish() */
-let cleanUp = function() {
-  waitFor(function() {
-    SpecialPowers.removePermission("mobileconnection", document);
-    finish();
-  }, function() {
-    return _pendingEmulatorCmdCount === 0;
-  });
-};
-
-/* Helper for tasks */
-let taskHelper = {
-  tasks: [],
-
-  push: function(task) {
-    this.tasks.push(task);
-  },
-
-  runNext: function() {
-    let task = this.tasks.shift();
-    if (!task) {
-      cleanUp();
-      return;
-    }
-
-    if (typeof task === "function") {
-      task();
-    }
-  },
-};
-
-/* Helper for emulator console command */
-let emulatorHelper = {
-  sendCommand: function(cmd, callback) {
-    _pendingEmulatorCmdCount++;
-    runEmulatorCmd(cmd, function(results) {
-      _pendingEmulatorCmdCount--;
-
-      let result = results[results.length - 1];
-      is(result, "OK", "'"+ cmd +"' returns '" + result + "'");
-
-      if (callback && typeof callback === "function") {
-        callback(results);
-      }
-    });
-  },
-};
--- a/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js
+++ b/dom/mobileconnection/tests/marionette/test_call_barring_change_password.js
@@ -1,63 +1,50 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
 
-SpecialPowers.addPermission("mobileconnection", true, document);
+const TEST_DATA = [
+  // [<pin>, <new pin>, <expected error>]
 
-// Permission changes can't change existing Navigator.prototype
-// objects, so grab our objects from a new Navigator
-let ifr = document.createElement("iframe");
-let connection;
-ifr.onload = function() {
-  connection = ifr.contentWindow.navigator.mozMobileConnections[0];
+  // Test passing an invalid pin or newPin.
+  [null, "0000", "InvalidPassword"],
+  ["0000", null, "InvalidPassword"],
+  [null, null, "InvalidPassword"],
 
-  ok(connection instanceof ifr.contentWindow.MozMobileConnection,
-     "connection is instanceof " + connection.constructor);
-
-  setTimeout(testChangeCallBarringPasswordWithFailure, 0);
-};
-document.body.appendChild(ifr);
+  // Test passing mismatched newPin.
+  ["000", "0000", "InvalidPassword"],
+  ["00000", "1111", "InvalidPassword"],
+  ["abcd", "efgh", "InvalidPassword"],
 
-function testChangeCallBarringPasswordWithFailure() {
-  // Incorrect parameters, expect onerror callback.
-  let options = [
-    {pin: null, newPin: '0000'},
-    {pin: '0000', newPin: null},
-    {pin: null, newPin: null},
-    {pin: '000', newPin: '0000'},
-    {pin: '00000', newPin: '1111'},
-    {pin: 'abcd', newPin: 'efgh'},
-  ];
+  // TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on Emulator.
+  // Currently emulator doesn't support REQUEST_CHANGE_BARRING_PASSWORD, so we
+  // expect to get a 'RequestNotSupported' error here.
+  ["1234", "1234", "RequestNotSupported"]
+];
 
-  function do_test() {
-    for (let i = 0; i < options.length; i++) {
-      let request = connection.changeCallBarringPassword(options[i]);
+function testChangeCallBarringPassword(aPin, aNewPin, aExpectedError) {
+  log("Test changing call barring password to " + aPin + "/" + aNewPin);
 
-      request.onsuccess = function() {
-        ok(false, 'Unexpected result.');
-        setTimeout(cleanUp , 0);
-      };
-
-      request.onerror = function() {
-        ok(request.error.name === 'InvalidPassword', 'InvalidPassword');
-        if (i >= options.length) {
-          setTimeout(testChangeCallBarringPasswordWithSuccess, 0);
-        }
-      };
-    }
-  }
-
-  do_test();
+  let options = {
+    pin: aPin,
+    newPin: aNewPin
+  };
+  return changeCallBarringPassword(options)
+    .then(function resolve() {
+      ok(!aExpectedError, "changeCallBarringPassword success");
+    }, function reject(aError) {
+      is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
+    });
 }
 
-function testChangeCallBarringPasswordWithSuccess() {
-  // TODO: Bug 906603 - B2G RIL: Support Change Call Barring Password on
-  // Emulator.
-  setTimeout(cleanUp , 0);
-}
-
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  finish();
-}
+// Start tests
+startTestCommon(function() {
+  let promise = Promise.resolve();
+  for (let i = 0; i < TEST_DATA.length; i++) {
+    let data = TEST_DATA[i];
+    promise =
+      promise.then(() => testChangeCallBarringPassword(data[0], data[1], data[2]));
+  }
+  return promise;
+});
--- a/dom/mobileconnection/tests/marionette/test_call_barring_get_option.js
+++ b/dom/mobileconnection/tests/marionette/test_call_barring_get_option.js
@@ -1,39 +1,74 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-// Permission changes can't change existing Navigator.prototype
-// objects, so grab our objects from a new Navigator
-let ifr = document.createElement("iframe");
-let connection;
-ifr.onload = function() {
-  connection = ifr.contentWindow.navigator.mozMobileConnections[0];
-
-  ok(connection instanceof ifr.contentWindow.MozMobileConnection,
-     "connection is instanceof " + connection.constructor);
+MARIONETTE_HEAD_JS = "head.js";
 
-  testGetCallBarringOption();
-};
-document.body.appendChild(ifr);
+const TEST_DATA = [
+  // Test passing invalid program.
+  {
+    options: {
+      program: 5, /* Invalid program */
+      serviceClass: 0
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      program: null,
+      serviceClass: 0
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      /* Undefined program */
+      serviceClass: 0
+    },
+    expectedError: "InvalidParameter"
+  },
+  // Test passing invalid serviceClass.
+  {
+    options: {
+      program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      serviceClass: null
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      /* Undefined serviceClass */
+    },
+    expectedError: "InvalidParameter"
+  },
+  // TODO: Bug 1027546 - [B2G][Emulator] Support call barring
+  // Currently emulator doesn't support call barring, so we expect to get a
+  // 'RequestNotSupported' error here.
+  {
+    options: {
+      program: MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      serviceClass: 0
+    },
+    expectedError: "RequestNotSupported"
+  }
+];
 
-function testGetCallBarringOption() {
-  let option = {'program': 0, 'password': '', 'serviceClass': 0};
-  let request = connection.getCallBarringOption(option);
-  request.onsuccess = function() {
-    ok(request.result);
-    ok('enabled' in request.result, 'should have "enabled" field');
-    cleanUp();
-  };
-  request.onerror = function() {
-    // Call barring is not supported by current emulator.
-    cleanUp();
-  };
+function testGetCallBarringOption(aOptions, aExpectedError) {
+  log("Test getting call barring to " + JSON.stringify(aOptions));
+
+  return getCallBarringOption(aOptions)
+    .then(function resolve(aResult) {
+      ok(false, "should not success");
+    }, function reject(aError) {
+      is(aError.name, aExpectedError, "failed to getCallBarringOption");
+    });
 }
 
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  finish();
-}
+// Start tests
+startTestCommon(function() {
+  let promise = Promise.resolve();
+  for (let i = 0; i < TEST_DATA.length; i++) {
+    let data = TEST_DATA[i];
+    promise = promise.then(() => testGetCallBarringOption(data.options,
+                                                          data.expectedError));
+  }
+  return promise;
+});
--- a/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js
+++ b/dom/mobileconnection/tests/marionette/test_call_barring_set_error.js
@@ -1,74 +1,122 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-// Permission changes can't change existing Navigator.prototype
-// objects, so grab our objects from a new Navigator
-let ifr = document.createElement("iframe");
-let connection;
-ifr.onload = function() {
-  connection = ifr.contentWindow.navigator.mozMobileConnections[0];
-
-  ok(connection instanceof ifr.contentWindow.MozMobileConnection,
-     "connection is instanceof " + connection.constructor);
+MARIONETTE_HEAD_JS = "head.js";
 
-  nextTest();
-};
-document.body.appendChild(ifr);
-
-let caseId = 0;
-let options = [
-  buildOption(5, true, '0000', 0),  // invalid program.
-
-  // test null.
-  buildOption(null, true, '0000', 0),
-  buildOption(0, null, '0000', 0),
-  buildOption(0, true, null, 0),
-  buildOption(0, true, '0000', null),
-
-  // test undefined.
-  {'enabled': true, 'password': '0000', 'serviceClass': 0},
-  {'program': 0, 'password': '0000', 'serviceClass': 0},
-  {'program': 0, 'enabled': true, 'serviceClass': 0},
-  {'program': 0, 'enabled': true, 'password': '0000'},
+const TEST_DATA = [
+  // Test passing invalid program.
+  {
+    options: {
+      "program": 5, /* Invalid program */
+      "enabled": true,
+      "password": "0000",
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      "program": null,
+      "enabled": true,
+      "password": "0000",
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      /* Undefined program */
+      "enabled": true,
+      "password": "0000",
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  },
+  // Test passing invalid enabled.
+  {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      "enabled": null,
+      "password": "0000",
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      /* Undefined enabled */
+      "password": "0000",
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  },
+  // Test passing invalid password.
+  {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      "enabled": true,
+      "password": null,
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      "enabled": true,
+      /* Undefined password */
+      "serviceClass": 0
+    },
+    expectedError: "InvalidParameter"
+  },
+  // Test passing invalid serviceClass.
+  {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      "enabled": true,
+      "password": "0000",
+      "serviceClass": null
+    },
+    expectedError: "InvalidParameter"
+  }, {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      "enabled": true,
+      "password": "0000",
+      /* Undefined serviceClass */
+    },
+    expectedError: "InvalidParameter"
+  },
+  // TODO: Bug 1027546 - [B2G][Emulator] Support call barring
+  // Currently emulator doesn't support call barring, so we expect to get a
+  // 'RequestNotSupported' error here.
+  {
+    options: {
+      "program": MozMobileConnection.CALL_BARRING_PROGRAM_ALL_OUTGOING,
+      "enabled": true,
+      "password": "0000",
+      "serviceClass": 0
+    },
+    expectedError: "RequestNotSupported"
+  }
 ];
 
-function buildOption(program, enabled, password, serviceClass) {
-  return {
-    'program': program,
-    'enabled': enabled,
-    'password': password,
-    'serviceClass': serviceClass
-  };
+function testSetCallBarringOption(aOptions, aExpectedError) {
+  log("Test setting call barring to " + JSON.stringify(aOptions));
+
+  return setCallBarringOption(aOptions)
+    .then(function resolve() {
+      ok(false, "changeCallBarringPassword success");
+    }, function reject(aError) {
+      is(aError.name, aExpectedError, "failed to changeCallBarringPassword");
+    });
 }
 
-function testSetCallBarringOptionError(option) {
-  let request = connection.setCallBarringOption(option);
-  request.onsuccess = function() {
-    ok(false,
-       'should not fire onsuccess for invaild call barring option: '
-       + JSON.stringify(option));
-  };
-  request.onerror = function(event) {
-    is(event.target.error.name, 'InvalidParameter', JSON.stringify(option));
-    nextTest();
-  };
-}
-
-function nextTest() {
-  if (caseId >= options.length) {
-    cleanUp();
-  } else {
-    let option = options[caseId++];
-    log('test for ' + JSON.stringify(option));
-    testSetCallBarringOptionError(option);
+// Start tests
+startTestCommon(function() {
+  let promise = Promise.resolve();
+  for (let i = 0; i < TEST_DATA.length; i++) {
+    let data = TEST_DATA[i];
+    promise = promise.then(() => testSetCallBarringOption(data.options,
+                                                          data.expectedError));
   }
-}
-
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  finish();
-}
+  return promise;
+});
--- a/dom/mobileconnection/tests/marionette/test_mobile_icc_change.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_icc_change.js
@@ -1,84 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
 
-// Permission changes can't change existing Navigator.prototype
-// objects, so grab our objects from a new Navigator
-let ifr = document.createElement("iframe");
-let connection;
-ifr.onload = function() {
-  connection = ifr.contentWindow.navigator.mozMobileConnections[0];
-  ok(connection instanceof ifr.contentWindow.MozMobileConnection,
-     "connection is instanceof " + connection.constructor);
-
-  // The emulator's hard coded iccid value.
-  // See it here {B2G_HOME}/external/qemu/telephony/sim_card.c.
-  is(connection.iccId, 89014103211118510720);
+// The emulator's hard coded iccid value.
+const ICCID = "89014103211118510720";
 
-  runNextTest();
-};
-document.body.appendChild(ifr);
-
-function waitForIccChange(callback) {
-  connection.addEventListener("iccchange", function handler() {
-    connection.removeEventListener("iccchange", handler);
-    callback();
-  });
-}
+function setRadioEnabledAndWaitIccChange(aEnabled) {
+  let promises = [];
+  promises.push(waitForManagerEvent("iccchange"));
+  promises.push(setRadioEnabled(aEnabled));
 
-function setRadioEnabled(enabled) {
-  let request  = connection.setRadioEnabled(enabled);
-
-  request.onsuccess = function onsuccess() {
-    log('setRadioEnabled: ' + enabled);
-  };
-
-  request.onerror = function onerror() {
-    ok(false, "setRadioEnabled should be ok");
-  };
+  return Promise.all(promises);
 }
 
-function testIccChangeOnRadioPowerOff() {
-  // Turn off radio
-  setRadioEnabled(false);
-
-  waitForIccChange(function() {
-    is(connection.iccId, null);
-    runNextTest();
-  });
-}
-
-function testIccChangeOnRadioPowerOn() {
-  // Turn on radio
-  setRadioEnabled(true);
-
-  waitForIccChange(function() {
-    // The emulator's hard coded iccid value.
-    is(connection.iccId, 89014103211118510720);
-    runNextTest();
-  });
-}
+// Start tests
+startTestCommon(function() {
+  log("Test initial iccId");
+  is(mobileConnection.iccId, ICCID);
 
-let tests = [
-  testIccChangeOnRadioPowerOff,
-  testIccChangeOnRadioPowerOn
-];
+  return setRadioEnabledAndWaitIccChange(false)
+    .then(() => {
+      is(mobileConnection.iccId, null);
+    })
 
-function runNextTest() {
-  let test = tests.shift();
-  if (!test) {
-    cleanUp();
-    return;
-  }
-
-  test();
-}
-
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-
-  finish();
-}
+    // Restore radio state.
+    .then(() => setRadioEnabledAndWaitIccChange(true))
+    .then(() => {
+      is(mobileConnection.iccId, ICCID);
+    });
+});
--- a/dom/mobileconnection/tests/marionette/test_mobile_last_known_network.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_last_known_network.js
@@ -1,47 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobilenetwork", true, document);
-
-let connection = navigator.mozMobileConnections[0];
-ok(connection instanceof MozMobileConnection,
-   "connection is instanceof " + connection.constructor);
-
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
 
-function testLastKnownNetwork() {
-  log("testLastKnownNetwork: " + connection.lastKnownNetwork);
+// Start tests
+startTestCommon(function() {
   // The emulator's hard coded operatoer's mcc and mnc codes.
-  is(connection.lastKnownNetwork, "310-260");
-  runNextTest();
-}
-
-function testLastKnownHomeNetwork() {
-  log("testLastKnownHomeNetwork: " + connection.lastKnownHomeNetwork);
+  is(mobileConnection.lastKnownNetwork, "310-260");
   // The emulator's hard coded icc's mcc and mnc codes.
-  is(connection.lastKnownHomeNetwork, "310-260");
-  runNextTest();
-}
-
-let tests = [
-  testLastKnownNetwork,
-  testLastKnownHomeNetwork
-];
-
-function runNextTest() {
-  let test = tests.shift();
-  if (!test) {
-    cleanUp();
-    return;
-  }
-
-  test();
-}
-
-function cleanUp() {
-  SpecialPowers.removePermission("mobilenetwork", document);
-  finish();
-}
-
-runNextTest();
+  is(mobileConnection.lastKnownHomeNetwork, "310-260");
+});
--- a/dom/mobileconnection/tests/marionette/test_mobile_set_radio.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_set_radio.js
@@ -1,168 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
-
-const DATA_KEY  = "ril.data.enabled";
-const APN_KEY   = "ril.data.apnSettings";
-
-let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
+MARIONETTE_HEAD_JS = "head.js";
 
-SpecialPowers.setBoolPref("dom.mozSettings.enabled", true);
-SpecialPowers.addPermission("mobileconnection", true, document);
-SpecialPowers.addPermission("settings-read", true, document);
-SpecialPowers.addPermission("settings-write", true, document);
-
-let settings = window.navigator.mozSettings;
-let connection = window.navigator.mozMobileConnections[0];
-ok(connection instanceof MozMobileConnection,
-   "connection is instanceof " + connection.constructor);
-
-function setSetting(key, value) {
-  let deferred = Promise.defer();
+// Start tests
+startTestCommon(function() {
 
-  let setLock = settings.createLock();
-  let obj = {};
-  obj[key] = value;
+  let origApnSettings;
+  return getDataApnSettings()
+    .then(value => {
+      origApnSettings = value;
+    })
 
-  let setReq = setLock.set(obj);
-  setReq.addEventListener("success", function onSetSuccess() {
-    ok(true, "set '" + key + "' to " + obj[key]);
-    deferred.resolve();
-  });
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set '" + key + "'");
-    deferred.reject();
-  });
+    // Test disabling/enabling radio power.
+    .then(() => setRadioEnabledAndWait(false))
+    .then(() => setRadioEnabledAndWait(true))
 
-  return deferred.promise;
-}
-
-function setEmulatorAPN() {
-  let apn =
-    [
-      [
+    // Test disabling radio when data is connected.
+    .then(() => {
+      let apnSettings = [[
         {"carrier":"T-Mobile US",
          "apn":"epc.tmobile.com",
          "mmsc":"http://mms.msg.eng.t-mobile.com/mms/wapenc",
-         "types":["default","supl","mms"]}
-      ]
-    ];
-  return setSetting(APN_KEY, apn);
-}
-
-function enableData() {
-  log("Turn data on.");
-
-  let deferred = Promise.defer();
-
-  connection.addEventListener("datachange", function ondatachange() {
-    if (connection.data.connected === true) {
-      connection.removeEventListener("datachange", ondatachange);
-      log("mobileConnection.data.connected is now '"
-          + connection.data.connected + "'.");
-      deferred.resolve();
-    }
-  });
-
-  setEmulatorAPN()
-    .then(() => setSetting(DATA_KEY, true));
-
-  return deferred.promise;
-}
-
-function receivedPending(received, pending, nextAction) {
-  let index = pending.indexOf(received);
-  if (index != -1) {
-    pending.splice(index, 1);
-  }
-  if (pending.length === 0) {
-    nextAction();
-  }
-}
-
-function waitRadioState(state) {
-  let deferred = Promise.defer();
-
-  waitFor(function() {
-    deferred.resolve();
-  }, function() {
-    return connection.radioState == state;
-  });
-
-  return deferred.promise;
-}
-
-function setRadioEnabled(enabled, transientState, finalState) {
-  log("setRadioEnabled to " + enabled);
-
-  let deferred = Promise.defer();
-  let done = function() {
-    deferred.resolve();
-  };
-
-  let pending = ["onradiostatechange", "onsuccess"];
-
-  let receivedTransient = false;
-  connection.onradiostatechange = function() {
-    let state = connection.radioState;
-    log("Received 'radiostatechange' event, radioState: " + state);
-
-    if (state == transientState) {
-      receivedTransient = true;
-    } else if (state == finalState) {
-      ok(receivedTransient);
-      receivedPending("onradiostatechange", pending, done);
-    }
-  };
-
-  let req = connection.setRadioEnabled(enabled);
-
-  req.onsuccess = function() {
-    log("setRadioEnabled success");
-    receivedPending("onsuccess", pending, done);
-  };
-
-  req.onerror = function() {
-    ok(false, "setRadioEnabled should not fail");
-    deferred.reject();
-  };
-
-  return deferred.promise;
-}
-
-function testSwitchRadio() {
-  log("= testSwitchRadio =");
-  return waitRadioState("enabled")
-    .then(setRadioEnabled.bind(null, false, "disabling", "disabled"))
-    .then(setRadioEnabled.bind(null, true, "enabling", "enabled"));
-}
-
-function testDisableRadioWhenDataConnected() {
-  log("= testDisableRadioWhenDataConnected =");
-  return waitRadioState("enabled")
-    .then(enableData)
-    .then(setRadioEnabled.bind(null, false, "disabling", "disabled"))
+         "types":["default","supl","mms"]}]];
+      return setDataApnSettings(apnSettings);
+    })
+    .then(() => setDataEnabledAndWait(true))
+    .then(() => setRadioEnabledAndWait(false))
     .then(() => {
       // Data should be disconnected.
-      is(connection.data.connected, false);
+      is(mobileConnection.data.connected, false);
     })
-    .then(setRadioEnabled.bind(null, true, "enabling", "enabled"))
-    // Disable data
-    .then(setSetting.bind(null, DATA_KEY, false));
-}
 
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  SpecialPowers.removePermission("settings-write", document);
-  SpecialPowers.removePermission("settings-read", document);
-  SpecialPowers.clearUserPref("dom.mozSettings.enabled");
-  finish();
-}
+    // Restore test environment.
+    .then(() => setDataApnSettings(origApnSettings))
+    .then(() => setDataEnabled(false))
+    .then(() => setRadioEnabledAndWait(true));
 
-testSwitchRadio()
-  .then(testDisableRadioWhenDataConnected)
-  .then(null, () => {
-    ok(false, "promise reject somewhere");
-  })
-  .then(cleanUp);
+}, ["settings-read", "settings-write"]);
--- a/dom/mobileconnection/tests/marionette/test_mobile_signal_strength.js
+++ b/dom/mobileconnection/tests/marionette/test_mobile_signal_strength.js
@@ -1,132 +1,98 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-MARIONETTE_TIMEOUT = 30000;
-MARIONETTE_HEAD_JS = "mobile_header.js";
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
 
-/* Emulator command for GSM/UMTS signal strength */
-function setEmulatorGsmSignalStrength(rssi) {
-  emulatorHelper.sendCommand("gsm signal " + rssi);
-}
+// Emulator uses rssi = 7 as default value.
+const DEFAULT_RSSI = 7;
 
-/* Emulator command for LTE signal strength */
-function setEmulatorLteSignalStrength(rxlev, rsrp, rssnr) {
-  let lteSignal = rxlev + " " + rsrp + " " + rssnr;
-  emulatorHelper.sendCommand("gsm lte_signal " + lteSignal);
-}
+const TEST_DATA = [
+  // All invalid case.
+  {
+    input: {
+      rxlev: 99,
+      rsrp: 65535,
+      rssnr: 65535
+    },
+    expect: {
+      signalStrength: null,
+      relSignalStrength: null
+    }
+  },
+  // Valid rxlev with max value.
+  {
+    input: {
+      rxlev: 63,
+      rsrp: 65535,
+      rssnr: 65535
+    },
+    expect: {
+      signalStrength: -48,
+      relSignalStrength: 100
+    }
+  },
+  // Valid rxlev.
+  {
+    input: {
+      rxlev: 12,
+      rsrp: 65535,
+      rssnr: 65535
+    },
+    expect: {
+      signalStrength: -99,
+      relSignalStrength: 100
+    }
+  },
+  // Valid rxlev with min value.
+  {
+    input: {
+      rxlev: 0,
+      rsrp: 65535,
+      rssnr: 65535
+    },
+    expect: {
+      signalStrength: -111,
+      relSignalStrength: 0
+    }
+  }
+];
 
-function waitForVoiceChangeEvent(callback) {
-  mobileConnection.addEventListener("voicechange", function onvoicechange() {
-    mobileConnection.removeEventListener("voicechange", onvoicechange);
-
-    if (callback && typeof callback === "function") {
-      callback();
-    }
-  });
-}
-
-/* Test Initial Signal Strength Info */
-taskHelper.push(function testInitialSignalStrengthInfo() {
+function testInitialSignalStrengthInfo() {
   log("Test initial signal strength info");
 
   let voice = mobileConnection.voice;
   // Android emulator initializes the signal strength to -99 dBm
   is(voice.signalStrength, -99, "check voice.signalStrength");
   is(voice.relSignalStrength, 44, "check voice.relSignalStrength");
-
-  taskHelper.runNext();
-});
+}
 
-/* Test Unsolicited Signal Strength Events for LTE */
-taskHelper.push(function testLteSignalStrength() {
-  // Set emulator's LTE signal strength and wait for 'onvoicechange' event.
-  function doTestLteSignalStrength(input, expect, callback) {
-    log("Test LTE signal info with data : " + JSON.stringify(input));
+function testLteSignalStrength(aInput, aExpect) {
+  log("Test setting LTE signal strength to " + JSON.stringify(aInput));
 
-    waitForVoiceChangeEvent(function() {
+  return setEmulatorLteSignalStrengthAndWait(aInput.rxlev, aInput.rsrp, aInput.rssnr)
+    .then(() => {
       let voice = mobileConnection.voice;
-      is(voice.signalStrength, expect.signalStrength,
+      is(voice.signalStrength, aExpect.signalStrength,
          "check voice.signalStrength");
-      is(voice.relSignalStrength, expect.relSignalStrength,
+      is(voice.relSignalStrength, aExpect.relSignalStrength,
          "check voice.relSignalStrength");
+    });
+}
 
-      if (callback && typeof callback === "function") {
-        callback();
-      }
-    });
+// Start tests
+startTestCommon(function() {
+  // Test initial status
+  testInitialSignalStrengthInfo();
 
-    setEmulatorLteSignalStrength(input.rxlev, input.rsrp, input.rssnr);
+  // Test Unsolicited Signal Strength Events for LTE
+  let promise = Promise.resolve();
+  for (let i = 0; i < TEST_DATA.length; i++) {
+    let data = TEST_DATA[i];
+    promise = promise.then(() => testLteSignalStrength(data.input,
+                                                       data.expect));
   }
 
-  let testData = [
-    // All invalid case.
-    {input: {
-      rxlev: 99,
-      rsrp: 65535,
-      rssnr: 65535},
-     expect: {
-      signalStrength: null,
-      relSignalStrength: null}
-    },
-    // Valid rxlev with max value.
-    {input: {
-      rxlev: 63,
-      rsrp: 65535,
-      rssnr: 65535},
-     expect: {
-      signalStrength: -48,
-      relSignalStrength: 100}
-    },
-    // Valid rxlev.
-    {input: {
-      rxlev: 12,
-      rsrp: 65535,
-      rssnr: 65535},
-     expect: {
-      signalStrength: -99,
-      relSignalStrength: 100}
-    },
-    // Valid rxlev with min value.
-    {input: {
-      rxlev: 0,
-      rsrp: 65535,
-      rssnr: 65535},
-     expect: {
-      signalStrength: -111,
-      relSignalStrength: 0}
-    }
-  ];
-
-  // Run all test data.
-  (function do_call() {
-    let next = testData.shift();
-    if (!next) {
-      taskHelper.runNext();
-      return;
-    }
-    doTestLteSignalStrength(next.input, next.expect, do_call);
-  })();
+  // Reset Signal Strength Info to default
+  return promise.then(() => setEmulatorGsmSignalStrengthAndWait(DEFAULT_RSSI));
 });
-
-/* Reset Signal Strength Info to default, and finsih the test */
-taskHelper.push(function testResetSignalStrengthInfo() {
-  // Reset emulator's signal strength and wait for 'onvoicechange' event.
-  function doResetSignalStrength(rssi) {
-    waitForVoiceChangeEvent(function() {
-      let voice = mobileConnection.voice;
-      is(voice.signalStrength, -99, "check voice.signalStrength");
-      is(voice.relSignalStrength, 44, "check voice.relSignalStrength");
-
-      taskHelper.runNext();
-    });
-
-    setEmulatorGsmSignalStrength(rssi);
-  }
-
-  // Emulator uses rssi = 7 as default value, and we need to reset it after
-  // finishing test in case other test cases need those values for testing.
-  doResetSignalStrength(7);
-});
-
-// Start test
-taskHelper.runNext();
--- a/ipc/glue/CrossProcessMutex_posix.cpp
+++ b/ipc/glue/CrossProcessMutex_posix.cpp
@@ -14,16 +14,34 @@ struct MutexData {
   pthread_mutex_t mMutex;
   mozilla::Atomic<int32_t> mCount;
 };
 
 }
 
 namespace mozilla {
 
+static void
+InitMutex(pthread_mutex_t* mMutex)
+{
+  pthread_mutexattr_t mutexAttributes;
+  pthread_mutexattr_init(&mutexAttributes);
+  // Make the mutex reentrant so it behaves the same as a win32 mutex
+  if (pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE)) {
+    MOZ_CRASH();
+  }
+  if (pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED)) {
+    MOZ_CRASH();
+  }
+
+  if (pthread_mutex_init(mMutex, &mutexAttributes)) {
+    MOZ_CRASH();
+  }
+}
+
 CrossProcessMutex::CrossProcessMutex(const char*)
     : mSharedBuffer(nullptr)
     , mMutex(nullptr)
     , mCount(nullptr)
 {
   mSharedBuffer = new ipc::SharedMemoryBasic;
   if (!mSharedBuffer->Create(sizeof(MutexData))) {
     MOZ_CRASH();
@@ -38,30 +56,17 @@ CrossProcessMutex::CrossProcessMutex(con
   if (!data) {
     MOZ_CRASH();
   }
 
   mMutex = &(data->mMutex);
   mCount = &(data->mCount);
 
   *mCount = 1;
-
-  pthread_mutexattr_t mutexAttributes;
-  pthread_mutexattr_init(&mutexAttributes);
-  // Make the mutex reentrant so it behaves the same as a win32 mutex
-  if (pthread_mutexattr_settype(&mutexAttributes, PTHREAD_MUTEX_RECURSIVE)) {
-    MOZ_CRASH();
-  }
-  if (pthread_mutexattr_setpshared(&mutexAttributes, PTHREAD_PROCESS_SHARED)) {
-    MOZ_CRASH();
-  }
-
-  if (pthread_mutex_init(mMutex, &mutexAttributes)) {
-    MOZ_CRASH();
-  }
+  InitMutex(mMutex);
 
   MOZ_COUNT_CTOR(CrossProcessMutex);
 }
 
 CrossProcessMutex::CrossProcessMutex(CrossProcessMutexHandle aHandle)
     : mSharedBuffer(nullptr)
     , mMutex(nullptr)
     , mCount(nullptr)
@@ -79,17 +84,23 @@ CrossProcessMutex::CrossProcessMutex(Cro
   MutexData* data = static_cast<MutexData*>(mSharedBuffer->memory());
 
   if (!data) {
     MOZ_CRASH();
   }
 
   mMutex = &(data->mMutex);
   mCount = &(data->mCount);
-  (*mCount)++;
+  int32_t count = (*mCount)++;
+
+  if (count == 0) {
+    // The other side has already let go of their CrossProcessMutex, so now
+    // mMutex is garbage. We need to re-initialize it.
+    InitMutex(mMutex);
+  }
 
   MOZ_COUNT_CTOR(CrossProcessMutex);
 }
 
 CrossProcessMutex::~CrossProcessMutex()
 {
   int32_t count = --(*mCount);
 
--- a/layout/moz.build
+++ b/layout/moz.build
@@ -29,8 +29,11 @@ DIRS += ['build', 'media']
 
 if CONFIG['MOZ_DEBUG']:
     TEST_DIRS += ['tools/layout-debug']
 
 MOCHITEST_MANIFESTS += [
     'reftests/fonts/mochitest.ini',
     'reftests/fonts/mplus/mochitest.ini',
 ]
+
+REFTEST_MANIFESTS += ['reftests/reftest.list']
+CRASHTEST_MANIFESTS += ['../testing/crashtest/crashtests.list']
--- a/layout/tools/reftest/print-manifest-dirs.py
+++ b/layout/tools/reftest/print-manifest-dirs.py
@@ -1,79 +1,29 @@
 #
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import sys, os.path, re
-
-commentRE = re.compile(r"\s+#")
-conditionsRE = re.compile(r"^(fails|needs-focus|random|skip|asserts|slow|require-or|silentfail|pref|test-pref|ref-pref|fuzzy)")
-httpRE = re.compile(r"HTTP\((\.\.(\/\.\.)*)\)")
-protocolRE = re.compile(r"^\w+:")
-
-def parseManifest(manifest, dirs):
-  """Parse the reftest manifest |manifest|, adding all directories containing
-  tests (and the dirs containing the manifests themselves) to the set |dirs|."""
-  manifestdir = os.path.dirname(os.path.abspath(manifest))
-  dirs.add(manifestdir)
-  f = file(manifest)
-  urlprefix = ''
-  for line in f:
-    if line[0] == '#':
-      continue # entire line was a comment
-    m = commentRE.search(line)
-    if m:
-      line = line[:m.start()]
-    line = line.strip()
-    if not line:
-      continue
-    items = line.split()
-    while conditionsRE.match(items[0]):
-      del items[0]
-    if items[0] == "HTTP":
-      del items[0]
-    m = httpRE.match(items[0])
-    if m:
-      # need to package the dir referenced here
-      d = os.path.normpath(os.path.join(manifestdir, m.group(1)))
-      dirs.add(d)
-      del items[0]
-
-    if items[0] == "url-prefix":
-      urlprefix = items[1]
-      continue
-    elif items[0] == "default-preferences":
-      continue
-    elif items[0] == "include":
-      parseManifest(os.path.join(manifestdir, items[1]), dirs)
-      continue
-    elif items[0] == "load" or items[0] == "script":
-      testURLs = [items[1]]
-    elif items[0] == "==" or items[0] == "!=":
-      testURLs = items[1:3]
-    for u in testURLs:
-      m = protocolRE.match(u)
-      if m:
-        # can't very well package about: or data: URIs
-        continue
-      d = os.path.dirname(os.path.normpath(os.path.join(manifestdir, urlprefix + u)))
-      dirs.add(d)
-  f.close()
+import os
+import sys
+from reftest import ReftestManifest
 
 def printTestDirs(topsrcdir, topmanifests):
-  """Parse |topmanifests| and print a list of directories containing the tests
-  within (and the manifests including those tests), relative to |topsrcdir|."""
-  topsrcdir = os.path.abspath(topsrcdir)
-  dirs = set()
-  for manifest in topmanifests:
-    parseManifest(manifest, dirs)
-  for dir in sorted(dirs):
-    d = dir[len(topsrcdir):].replace('\\','/')
-    if d[0] == '/':
-      d = d[1:]
-    print d
+    """Parse |topmanifests| and print a list of directories containing the tests
+    within (and the manifests including those tests), relative to |topsrcdir|.
+    """
+    topsrcdir = os.path.abspath(topsrcdir)
+    dirs = set()
+    for path in topmanifests:
+        m = ReftestManifest()
+        m.load(path)
+        dirs |= m.dirs
+
+    for d in sorted(dirs):
+        d = d[len(topsrcdir):].replace('\\', '/').lstrip('/')
+        print(d)
 
 if __name__ == '__main__':
-  if len(sys.argv) < 3:
-    print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0]
-    sys.exit(1)
-  printTestDirs(sys.argv[1], sys.argv[2:])
+    if len(sys.argv) < 3:
+      print >>sys.stderr, "Usage: %s topsrcdir reftest.list [reftest.list]*" % sys.argv[0]
+      sys.exit(1)
+    printTestDirs(sys.argv[1], sys.argv[2:])
new file mode 100644
--- /dev/null
+++ b/layout/tools/reftest/reftest/__init__.py
@@ -0,0 +1,125 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+import os
+import re
+
+RE_COMMENT = re.compile(r'\s+#')
+RE_HTTP = re.compile(r'HTTP\((\.\.(\/\.\.)*)\)')
+RE_PROTOCOL = re.compile(r'^\w+:')
+FAILURE_TYPES = (
+    'fails',
+    'fails-if',
+    'needs-focus',
+    'random',
+    'random-if',
+    'silentfail',
+    'silentfail-if',
+    'skip',
+    'skip-if',
+    'slow',
+    'slow-if',
+    'fuzzy',
+    'fuzzy-if',
+    'require-or',
+    'asserts',
+    'asserts-if',
+)
+PREF_ITEMS = (
+    'pref',
+    'test-pref',
+    'ref-pref',
+)
+
+class ReftestManifest(object):
+    """Represents a parsed reftest manifest.
+
+    We currently only capture file information because that is the only thing
+    tools require.
+    """
+    def __init__(self):
+        self.path = None
+        self.dirs = set()
+        self.files = set()
+        self.manifests = set()
+
+    def load(self, path):
+        """Parse a reftest manifest file."""
+        normalized = os.path.normpath(os.path.abspath(path))
+        self.manifests.add(normalized)
+        if not self.path:
+            self.path = normalized
+
+        mdir = os.path.dirname(normalized)
+        self.dirs.add(mdir)
+
+        with open(path, 'r') as fh:
+            urlprefix = ''
+            for line in fh:
+                line = line.decode('utf-8')
+
+                # Entire line is a comment.
+                if line.startswith('#'):
+                    continue
+
+                # Comments can begin mid line. Strip them.
+                m = RE_COMMENT.search(line)
+                if m:
+                    line = line[:m.start()]
+
+                line = line.strip()
+                if not line:
+                    continue
+
+                items = line.split()
+                tests = []
+
+                for i in range(len(items)):
+                    item = items[i]
+
+                    if item.startswith(FAILURE_TYPES):
+                        continue
+                    if item.startswith(PREF_ITEMS):
+                        continue
+                    if item == 'HTTP':
+                        continue
+
+                    m = RE_HTTP.match(item)
+                    if m:
+                        # Need to package the referenced directory.
+                        self.dirs.add(os.path.normpath(os.path.join(
+                            mdir, m.group(1))))
+                        continue
+
+                    if item == 'url-prefix':
+                        urlprefix = items[i+1]
+                        break
+
+                    if item == 'default-preferences':
+                        break
+
+                    if item == 'include':
+                        self.load(os.path.join(mdir, items[i+1]))
+                        break
+
+                    if item == 'load' or item == 'script':
+                        tests.append(items[i+1])
+                        break
+
+                    if item == '==' or item == '!=':
+                        tests.extend(items[i+1:i+3])
+                        break
+
+                for f in tests:
+                    # We can't package about: or data: URIs.
+                    # Discarding data isn't correct for a parser. But retaining
+                    # all data isn't currently a requirement.
+                    if RE_PROTOCOL.match(f):
+                        continue
+
+                    test = os.path.normpath(os.path.join(mdir, urlprefix + f))
+                    self.files.add(test)
+                    self.dirs.add(os.path.dirname(test))
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -116,17 +116,17 @@ pref("browser.display.remotetabs.timeout
 /* session history */
 pref("browser.sessionhistory.max_total_viewers", 1);
 pref("browser.sessionhistory.max_entries", 50);
 
 /* session store */
 pref("browser.sessionstore.resume_session_once", false);
 pref("browser.sessionstore.resume_from_crash", true);
 pref("browser.sessionstore.interval", 10000); // milliseconds
-pref("browser.sessionstore.max_tabs_undo", 1);
+pref("browser.sessionstore.max_tabs_undo", 5);
 pref("browser.sessionstore.max_resumed_crashes", 1);
 pref("browser.sessionstore.recent_crashes", 0);
 
 /* these should help performance */
 pref("mozilla.widget.force-24bpp", true);
 pref("mozilla.widget.use-buffer-pixmap", true);
 pref("mozilla.widget.disable-native-theme", true);
 pref("layout.reflow.synthMouseMove", false);
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1125,16 +1125,20 @@ abstract public class BrowserApp extends
 
     @Override
     public void handleMessage(final String event, final NativeJSObject message,
                               final EventCallback callback) {
         if ("Accounts:Create".equals(event)) {
             // Do exactly the same thing as if you tapped 'Sync' in Settings.
             final Intent intent = new Intent(getContext(), FxAccountGetStartedActivity.class);
             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            final NativeJSObject extras = message.optObject("extras", null);
+            if (extras != null) {
+                intent.putExtra("extras", extras.toString());
+            }
             getContext().startActivity(intent);
 
         } else if ("CharEncoding:Data".equals(event)) {
             final NativeJSObject[] charsets = message.getObjectArray("charsets");
             final int selected = message.getInt("selected");
 
             final String[] titleArray = new String[charsets.length];
             final String[] codeArray = new String[charsets.length];
--- a/mobile/android/base/fxa/FxAccountConstants.java.in
+++ b/mobile/android/base/fxa/FxAccountConstants.java.in
@@ -10,16 +10,17 @@ import org.mozilla.gecko.background.comm
 
 public class FxAccountConstants {
   public static final String GLOBAL_LOG_TAG = "FxAccounts";
   public static final String ACCOUNT_TYPE = "@MOZ_ANDROID_SHARED_FXACCOUNT_TYPE@";
 
   public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
   public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
 
+  public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
   public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
 
   // For extra debugging.  Not final so it can be changed from Fennec, or from
   // an add-on.
   public static boolean LOG_PERSONAL_INFORMATION = false;
 
   public static void pii(String tag, String message) {
     if (LOG_PERSONAL_INFORMATION) {
--- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
@@ -18,25 +18,27 @@ import org.mozilla.gecko.background.fxa.
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
 import org.mozilla.gecko.background.fxa.QuickPasswordStretcher;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.tasks.FxAccountSetupTask.ProgressDisplay;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.Context;
 import android.content.Intent;
 import android.os.AsyncTask;
+import android.os.Bundle;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.text.method.PasswordTransformationMethod;
 import android.text.method.SingleLineTransformationMethod;
 import android.util.Patterns;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -45,62 +47,90 @@ import android.widget.ArrayAdapter;
 import android.widget.AutoCompleteTextView;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
 abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity implements ProgressDisplay {
+  public static final String EXTRA_EMAIL = "email";
+  public static final String EXTRA_PASSWORD = "password";
+  public static final String EXTRA_PASSWORD_SHOWN = "password_shown";
+  public static final String EXTRA_YEAR = "year";
+  public static final String EXTRA_EXTRAS = "extras";
+
+  public static final String JSON_KEY_AUTH = "auth";
+  public static final String JSON_KEY_SERVICES = "services";
+  public static final String JSON_KEY_SYNC = "sync";
+
   public FxAccountAbstractSetupActivity() {
     super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST | CANNOT_RESUME_WHEN_LOCKED_OUT);
   }
 
   protected FxAccountAbstractSetupActivity(int resume) {
     super(resume);
   }
 
   private static final String LOG_TAG = FxAccountAbstractSetupActivity.class.getSimpleName();
 
+  // By default, any custom server configuration is only shown when the account
+  // is configured to use a custom server.
+  private static boolean ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT = false;
+
   protected int minimumPasswordLength = 8;
 
   protected AutoCompleteTextView emailEdit;
   protected EditText passwordEdit;
   protected Button showPasswordButton;
   protected TextView remoteErrorTextView;
   protected Button button;
   protected ProgressBar progressBar;
 
+  private String authServerEndpoint;
+  private String syncServerEndpoint;
+
+  protected String getAuthServerEndpoint() {
+    return authServerEndpoint;
+  }
+
+  protected String getTokenServerEndpoint() {
+    return syncServerEndpoint;
+  }
+
   protected void createShowPasswordButton() {
     showPasswordButton.setOnClickListener(new OnClickListener() {
-      @SuppressWarnings("deprecation")
       @Override
       public void onClick(View v) {
         boolean isShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
-
-        // Changing input type loses position in edit text; let's try to maintain it.
-        int start = passwordEdit.getSelectionStart();
-        int stop = passwordEdit.getSelectionEnd();
-
-        if (isShown) {
-          passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
-          showPasswordButton.setText(R.string.fxaccount_password_show);
-          showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
-          showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
-        } else {
-          passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
-          showPasswordButton.setText(R.string.fxaccount_password_hide);
-          showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
-          showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
-        }
-        passwordEdit.setSelection(start, stop);
+        setPasswordButtonShown(!isShown);
       }
     });
   }
 
+  @SuppressWarnings("deprecation")
+  protected void setPasswordButtonShown(boolean shouldShow) {
+    // Changing input type loses position in edit text; let's try to maintain it.
+    int start = passwordEdit.getSelectionStart();
+    int stop = passwordEdit.getSelectionEnd();
+
+    if (!shouldShow) {
+      passwordEdit.setTransformationMethod(PasswordTransformationMethod.getInstance());
+      showPasswordButton.setText(R.string.fxaccount_password_show);
+      showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_show_background));
+      showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_show_textcolor));
+    } else {
+      passwordEdit.setTransformationMethod(SingleLineTransformationMethod.getInstance());
+      showPasswordButton.setText(R.string.fxaccount_password_hide);
+      showPasswordButton.setBackgroundDrawable(getResources().getDrawable(R.drawable.fxaccount_password_button_hide_background));
+      showPasswordButton.setTextColor(getResources().getColor(R.color.fxaccount_password_hide_textcolor));
+    }
+    passwordEdit.setSelection(start, stop);
+  }
+
   protected void linkifyPolicy() {
     TextView policyView = (TextView) ensureFindViewById(null, R.id.policy, "policy links");
     final String linkTerms = getString(R.string.fxaccount_link_tos);
     final String linkPrivacy = getString(R.string.fxaccount_link_pn);
     final String linkedTOS = "<a href=\"" + linkTerms + "\">" + getString(R.string.fxaccount_policy_linktos) + "</a>";
     final String linkedPN = "<a href=\"" + linkPrivacy + "\">" + getString(R.string.fxaccount_policy_linkprivacy) + "</a>";
     policyView.setText(getString(R.string.fxaccount_create_account_policy_text, linkedTOS, linkedPN));
     final boolean underlineLinks = true;
@@ -257,17 +287,17 @@ abstract public class FxAccountAbstractS
     @Override
     public void handleSuccess(LoginResponse result) {
       Logger.info(LOG_TAG, "Got success response; adding Android account.");
 
       // We're on the UI thread, but it's okay to create the account here.
       AndroidFxAccount fxAccount;
       try {
         final String profile = Constants.DEFAULT_PROFILE;
-        final String tokenServerURI = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
+        final String tokenServerURI = getTokenServerEndpoint();
         // It is crucial that we use the email address provided by the server
         // (rather than whatever the user entered), because the user's keys are
         // wrapped and salted with the initial email they provided to
         // /create/account. Of course, we want to pass through what the user
         // entered locally as much as possible, so we create the Android account
         // with their entered email address, etc.
         // The passwordStretcher should have seen this email address before, so
         // we shouldn't be calculating the expensive stretch twice.
@@ -352,22 +382,153 @@ abstract public class FxAccountAbstractS
     final String[] sortedEmails = emails.toArray(new String[0]);
     Arrays.sort(sortedEmails);
 
     final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, sortedEmails);
     emailEdit.setAdapter(adapter);
   }
 
   @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+  }
+
+  protected void updateFromIntentExtras() {
+    // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
+    if (getIntent() != null && getIntent().getExtras() != null) {
+      Bundle bundle = getIntent().getExtras();
+      emailEdit.setText(bundle.getString(EXTRA_EMAIL));
+      passwordEdit.setText(bundle.getString(EXTRA_PASSWORD));
+      setPasswordButtonShown(bundle.getBoolean(EXTRA_PASSWORD_SHOWN, false));
+    }
+
+    // This sets defaults as well as extracting from extras, so it's not conditional.
+    updateServersFromIntentExtras(getIntent());
+
+    if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
+      FxAccountConstants.pii(LOG_TAG, "Using auth server: " + authServerEndpoint);
+      FxAccountConstants.pii(LOG_TAG, "Using sync server: " + syncServerEndpoint);
+    }
+
+    updateCustomServerView();
+  }
+
+  @Override
   public void onResume() {
     super.onResume();
 
     // Getting Accounts accesses databases on disk, so needs to be done on a
     // background thread.
     final GetAccountsAsyncTask task = new GetAccountsAsyncTask(this) {
       @Override
       public void onPostExecute(Account[] accounts) {
         populateEmailAddressAutocomplete(accounts);
       }
     };
     task.execute();
   }
+
+  protected Bundle makeExtrasBundle(String email, String password) {
+    final Bundle bundle = new Bundle();
+
+    // Pass through any extras that we were started with.
+    if (getIntent() != null && getIntent().getExtras() != null) {
+      bundle.putAll(getIntent().getExtras());
+    }
+
+    // Overwrite with current settings.
+    if (email == null) {
+      email = emailEdit.getText().toString();
+    }
+    if (password == null) {
+      password = passwordEdit.getText().toString();
+    }
+    bundle.putString(EXTRA_EMAIL, email);
+    bundle.putString(EXTRA_PASSWORD, password);
+
+    boolean isPasswordShown = passwordEdit.getTransformationMethod() instanceof SingleLineTransformationMethod;
+    bundle.putBoolean(EXTRA_PASSWORD_SHOWN, isPasswordShown);
+
+    return bundle;
+  }
+
+  protected void startActivityInstead(Class<?> cls, int requestCode, Bundle extras) {
+    Intent intent = new Intent(this, cls);
+    if (extras != null) {
+      intent.putExtras(extras);
+    }
+    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
+    // the soft keyboard not being shown for the started activity. Why, Android, why?
+    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+    startActivityForResult(intent, requestCode);
+  }
+
+  protected void updateServersFromIntentExtras(Intent intent) {
+    // Start with defaults.
+    this.authServerEndpoint = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
+    this.syncServerEndpoint = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT;
+
+    if (intent == null) {
+      Logger.warn(LOG_TAG, "Intent is null; ignoring and using default servers.");
+      return;
+    }
+
+    final String extrasString = intent.getStringExtra(EXTRA_EXTRAS);
+
+    if (extrasString == null) {
+      return;
+    }
+
+    final ExtendedJSONObject extras;
+    final ExtendedJSONObject services;
+    try {
+      extras = new ExtendedJSONObject(extrasString);
+      services = extras.getObject(JSON_KEY_SERVICES);
+    } catch (Exception e) {
+      Logger.warn(LOG_TAG, "Got exception parsing extras; ignoring and using default servers.");
+      return;
+    }
+
+    String authServer = extras.getString(JSON_KEY_AUTH);
+    String syncServer = services == null ? null : services.getString(JSON_KEY_SYNC);
+
+    if (authServer != null) {
+      this.authServerEndpoint = authServer;
+    }
+    if (syncServer != null) {
+      this.syncServerEndpoint = syncServer;
+    }
+
+    if (FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint) &&
+        !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint)) {
+      // We really don't want to hard-code assumptions about server
+      // configurations into client code in such a way that if and when the
+      // situation is relaxed, the client code stops valid usage. Instead, we
+      // warn. This configuration should present itself as an auth exception at
+      // Sync time.
+      Logger.warn(LOG_TAG, "Mozilla's Sync token servers only works with Mozilla's auth servers. Sync will likely be mis-configured.");
+    }
+  }
+
+  protected void updateCustomServerView() {
+    final boolean shouldShow =
+        ALWAYS_SHOW_CUSTOM_SERVER_LAYOUT ||
+        !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServerEndpoint) ||
+        !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServerEndpoint);
+
+    if (!shouldShow) {
+      setCustomServerViewVisibility(View.GONE);
+      return;
+    }
+
+    final TextView authServerView = (TextView) ensureFindViewById(null, R.id.account_server_summary, "account server");
+    final TextView syncServerView = (TextView) ensureFindViewById(null, R.id.sync_server_summary, "Sync server");
+    authServerView.setText(authServerEndpoint);
+    syncServerView.setText(syncServerEndpoint);
+
+    setCustomServerViewVisibility(View.VISIBLE);
+  }
+
+  protected void setCustomServerViewVisibility(int visibility) {
+    ensureFindViewById(null, R.id.account_server_layout, "account server layout").setVisibility(visibility);
+    ensureFindViewById(null, R.id.sync_server_layout, "sync server layout").setVisibility(visibility);
+  }
 }
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -84,42 +84,39 @@ public class FxAccountCreateAccountActiv
     createShowPasswordButton();
     linkifyPolicy();
     createChooseCheckBox();
 
     View signInInsteadLink = ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link");
     signInInsteadLink.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        final String email = emailEdit.getText().toString();
-        final String password = passwordEdit.getText().toString();
-        doSigninInstead(email, password);
+        final Bundle extras = makeExtrasBundle(null, null);
+        startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
       }
     });
 
-    // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
-    if (getIntent() != null && getIntent().getExtras() != null) {
-      Bundle bundle = getIntent().getExtras();
-      emailEdit.setText(bundle.getString("email"));
-      passwordEdit.setText(bundle.getString("password"));
-    }
+    updateFromIntentExtras();
   }
 
-  protected void doSigninInstead(final String email, final String password) {
-    Intent intent = new Intent(this, FxAccountSignInActivity.class);
-    if (email != null) {
-      intent.putExtra("email", email);
+  @Override
+  protected Bundle makeExtrasBundle(String email, String password) {
+    final Bundle extras = super.makeExtrasBundle(email, password);
+    final String year = yearEdit.getText().toString();
+    extras.putString(EXTRA_YEAR, year);
+    return extras;
+  }
+
+  @Override
+  protected void updateFromIntentExtras() {
+    super.updateFromIntentExtras();
+
+    if (getIntent() != null) {
+      yearEdit.setText(getIntent().getStringExtra(EXTRA_YEAR));
     }
-    if (password != null) {
-      intent.putExtra("password", password);
-    }
-    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-    // the soft keyboard not being shown for the started activity. Why, Android, why?
-    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-    startActivityForResult(intent, CHILD_REQUEST_CODE);
   }
 
   @Override
   protected void showClientRemoteException(final FxAccountClientRemoteException e) {
     if (!e.isAccountAlreadyExists()) {
       super.showClientRemoteException(e);
       return;
     }
@@ -137,17 +134,19 @@ public class FxAccountCreateAccountActiv
       @Override
       public void onClick(View widget) {
         // Pass through the email address that already existed.
         String email = e.body.getString("email");
         if (email == null) {
             email = emailEdit.getText().toString();
         }
         final String password = passwordEdit.getText().toString();
-        doSigninInstead(email, password);
+
+        final Bundle extras = makeExtrasBundle(email, password);
+        startActivityInstead(FxAccountSignInActivity.class, CHILD_REQUEST_CODE, extras);
       }
     }, clickableStart, clickableEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
     remoteErrorTextView.setMovementMethod(LinkMovementMethod.getInstance());
     remoteErrorTextView.setText(span);
   }
 
   /**
    * We might have switched to the SignIn activity; if that activity
@@ -200,17 +199,17 @@ public class FxAccountCreateAccountActiv
         .create();
 
         dialog.show();
       }
     });
   }
 
   public void createAccount(String email, String password, Map<String, Boolean> engines) {
-    String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
+    String serverURI = getAuthServerEndpoint();
     PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     // This delegate creates a new Android account on success, opens the
     // appropriate "success!" activity, and finishes this activity.
     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI, engines) {
       @Override
       public void handleError(Exception e) {
         showRemoteError(e, R.string.fxaccount_create_account_unknown_error);
       }
--- a/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
@@ -45,25 +45,36 @@ public class FxAccountGetStartedActivity
     setContentView(R.layout.fxaccount_get_started);
 
     linkifyOldFirefoxLink();
 
     View button = findViewById(R.id.get_started_button);
     button.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        Intent intent = new Intent(FxAccountGetStartedActivity.this, FxAccountCreateAccountActivity.class);
-        // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-        // the soft keyboard not being shown for the started activity. Why, Android, why?
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        startActivityForResult(intent, CHILD_REQUEST_CODE);
+        Bundle extras = null; // startFlow accepts null.
+        if (getIntent() != null) {
+          extras = getIntent().getExtras();
+        }
+        startFlow(extras);
       }
     });
   }
 
+  protected void startFlow(Bundle extras) {
+    final Intent intent = new Intent(this, FxAccountCreateAccountActivity.class);
+    // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
+    // the soft keyboard not being shown for the started activity. Why, Android, why?
+    intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+    if (extras != null) {
+        intent.putExtras(extras);
+    }
+    startActivityForResult(intent, CHILD_REQUEST_CODE);
+  }
+
   @Override
   public void onResume() {
     super.onResume();
 
     Intent intent = null;
     if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) {
       intent = new Intent(this, FxAccountCreateAccountNotAllowedActivity.class);
     } else if (FirefoxAccounts.firefoxAccountsExist(this)) {
@@ -74,16 +85,30 @@ public class FxAccountGetStartedActivity
       this.setAccountAuthenticatorResult(null);
       setResult(RESULT_CANCELED);
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
       this.startActivity(intent);
       this.finish();
     }
+
+    // If we've been launched with extras (namely custom server URLs), continue
+    // past go and collect 200 dollars. If we ever get back here (for example,
+    // if the user hits the back button), forget that we had extras entirely, so
+    // that we don't enter a loop.
+    Bundle extras = null;
+    if (getIntent() != null) {
+      extras = getIntent().getExtras();
+    }
+    if (extras != null && extras.containsKey(FxAccountAbstractSetupActivity.EXTRA_EXTRAS)) {
+      getIntent().replaceExtras(Bundle.EMPTY);
+      startFlow((Bundle) extras.clone());
+      return;
+    }
   }
 
   /**
    * We started the CreateAccount activity for a result; this returns it to the
    * authenticator.
    */
   @Override
   public void onActivityResult(int requestCode, int resultCode, Intent data) {
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -10,17 +10,16 @@ import java.util.concurrent.Executors;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
-import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.AutoCompleteTextView;
@@ -60,32 +59,22 @@ public class FxAccountSignInActivity ext
     updateButtonState();
     createShowPasswordButton();
     linkifyPolicy();
 
     View createAccountInsteadLink = ensureFindViewById(null, R.id.create_account_link, "create account instead link");
     createAccountInsteadLink.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        Intent intent = new Intent(FxAccountSignInActivity.this, FxAccountCreateAccountActivity.class);
-        intent.putExtra("email", emailEdit.getText().toString());
-        intent.putExtra("password", passwordEdit.getText().toString());
-        // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
-        // the soft keyboard not being shown for the started activity. Why, Android, why?
-        intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
-        startActivityForResult(intent, CHILD_REQUEST_CODE);
+        final Bundle extras = makeExtrasBundle(null, null);
+        startActivityInstead(FxAccountCreateAccountActivity.class, CHILD_REQUEST_CODE, extras);
       }
     });
 
-    // Only set email/password in onCreate; we don't want to overwrite edited values onResume.
-    if (getIntent() != null && getIntent().getExtras() != null) {
-      Bundle bundle = getIntent().getExtras();
-      emailEdit.setText(bundle.getString("email"));
-      passwordEdit.setText(bundle.getString("password"));
-    }
+    updateFromIntentExtras();
 
     TextView view = (TextView) findViewById(R.id.forgot_password_link);
     ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
   }
 
   /**
    * We might have switched to the CreateAccount activity; if that activity
    * succeeds, feed its result back to the authenticator.
@@ -97,17 +86,17 @@ public class FxAccountSignInActivity ext
       super.onActivityResult(requestCode, resultCode, data);
       return;
     }
     this.setResult(resultCode, data);
     this.finish();
   }
 
   public void signIn(String email, String password) {
-    String serverURI = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT;
+    String serverURI = getAuthServerEndpoint();
     PasswordStretcher passwordStretcher = makePasswordStretcher(password);
     // This delegate creates a new Android account on success, opens the
     // appropriate "success!" activity, and finishes this activity.
     RequestDelegate<LoginResponse> delegate = new AddAccountDelegate(email, passwordStretcher, serverURI) {
       @Override
       public void handleError(Exception e) {
         showRemoteError(e, R.string.fxaccount_sign_in_unknown_error);
       }
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -13,16 +13,17 @@ import org.mozilla.gecko.background.comm
 import org.mozilla.gecko.background.preferences.PreferenceFragment;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 
 import android.accounts.Account;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
@@ -51,32 +52,43 @@ public class FxAccountStatusFragment
   // When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
   // before trying to sync. Should we kill off the fragment before the sync
   // request happens, that's okay: the runnable will run if the UI thread is
   // still around to service it, and since we're not updating any UI, we'll just
   // schedule the sync as usual. See also comment below about garbage
   // collection.
   private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
 
+  // By default, the auth/account server preference is only shown when the
+  // account is configured to use a custom server. In debug mode, this is set.
+  private static boolean ALWAYS_SHOW_AUTH_SERVER = false;
+
+  // By default, the Sync server preference is only shown when the account is
+  // configured to use a custom Sync server. In debug mode, this is set.
+  private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
+
+  protected PreferenceCategory accountCategory;
   protected Preference emailPreference;
+  protected Preference authServerPreference;
 
   protected Preference needsPasswordPreference;
   protected Preference needsUpgradePreference;
   protected Preference needsVerificationPreference;
   protected Preference needsMasterSyncAutomaticallyEnabledPreference;
   protected Preference needsAccountEnabledPreference;
 
   protected PreferenceCategory syncCategory;
 
   protected CheckBoxPreference bookmarksPreference;
   protected CheckBoxPreference historyPreference;
   protected CheckBoxPreference tabsPreference;
   protected CheckBoxPreference passwordsPreference;
 
   protected EditTextPreference deviceNamePreference;
+  protected Preference syncServerPreference;
 
   protected volatile AndroidFxAccount fxAccount;
   // The contract is: when fxAccount is non-null, then clientsDataDelegate is
   // non-null.  If violated then an IllegalStateException is thrown.
   protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate;
 
   // Used to post delayed sync requests.
   protected Handler handler;
@@ -100,17 +112,19 @@ public class FxAccountStatusFragment
   public void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     addPreferences();
   }
 
   protected void addPreferences() {
     addPreferencesFromResource(R.xml.fxaccount_status_prefscreen);
 
+    accountCategory = (PreferenceCategory) ensureFindPreference("signed_in_as_category");
     emailPreference = ensureFindPreference("email");
+    authServerPreference = ensureFindPreference("auth_server");
 
     needsPasswordPreference = ensureFindPreference("needs_credentials");
     needsUpgradePreference = ensureFindPreference("needs_upgrade");
     needsVerificationPreference = ensureFindPreference("needs_verification");
     needsMasterSyncAutomaticallyEnabledPreference = ensureFindPreference("needs_master_sync_automatically_enabled");
     needsAccountEnabledPreference = ensureFindPreference("needs_account_enabled");
 
     syncCategory = (PreferenceCategory) ensureFindPreference("sync_category");
@@ -119,44 +133,52 @@ public class FxAccountStatusFragment
     historyPreference = (CheckBoxPreference) ensureFindPreference("history");
     tabsPreference = (CheckBoxPreference) ensureFindPreference("tabs");
     passwordsPreference = (CheckBoxPreference) ensureFindPreference("passwords");
 
     if (!FxAccountConstants.LOG_PERSONAL_INFORMATION) {
       removeDebugButtons();
     } else {
       connectDebugButtons();
+      ALWAYS_SHOW_AUTH_SERVER = true;
+      ALWAYS_SHOW_SYNC_SERVER = true;
     }
 
     needsPasswordPreference.setOnPreferenceClickListener(this);
     needsVerificationPreference.setOnPreferenceClickListener(this);
     needsAccountEnabledPreference.setOnPreferenceClickListener(this);
 
     bookmarksPreference.setOnPreferenceClickListener(this);
     historyPreference.setOnPreferenceClickListener(this);
     tabsPreference.setOnPreferenceClickListener(this);
     passwordsPreference.setOnPreferenceClickListener(this);
 
     deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
     deviceNamePreference.setOnPreferenceChangeListener(this);
+
+    syncServerPreference = ensureFindPreference("sync_server");
   }
 
   /**
    * We intentionally don't refresh here. Our owning activity is responsible for
    * providing an AndroidFxAccount to our refresh method in its onResume method.
    */
   @Override
   public void onResume() {
     super.onResume();
   }
 
   @Override
   public boolean onPreferenceClick(Preference preference) {
     if (preference == needsPasswordPreference) {
       Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
+      final Bundle extras = getExtrasForAccount();
+      if (extras != null) {
+        intent.putExtras(extras);
+      }
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
       intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
       startActivity(intent);
 
       return true;
     }
 
@@ -185,16 +207,27 @@ public class FxAccountStatusFragment
         preference == tabsPreference) {
       saveEngineSelections();
       return true;
     }
 
     return false;
   }
 
+  protected Bundle getExtrasForAccount() {
+    final Bundle extras = new Bundle();
+    final ExtendedJSONObject o = new ExtendedJSONObject();
+    o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI());
+    final ExtendedJSONObject services = new ExtendedJSONObject();
+    services.put(FxAccountAbstractSetupActivity.JSON_KEY_SYNC, fxAccount.getTokenServerURI());
+    o.put(FxAccountAbstractSetupActivity.JSON_KEY_SERVICES, services);
+    extras.putString(FxAccountAbstractSetupActivity.EXTRA_EXTRAS, o.toJSONString());
+    return extras;
+  }
+
   protected void setCheckboxesEnabled(boolean enabled) {
     bookmarksPreference.setEnabled(enabled);
     historyPreference.setEnabled(enabled);
     tabsPreference.setEnabled(enabled);
     passwordsPreference.setEnabled(enabled);
     // Since we can't sync, we can't update our remote client record.
     deviceNamePreference.setEnabled(enabled);
   }
@@ -362,16 +395,18 @@ public class FxAccountStatusFragment
     // refresh is called from our onResume, which can happen before the owning
     // Activity tells us about an account (via our public
     // refresh(AndroidFxAccount) method).
     if (fxAccount == null) {
       throw new IllegalArgumentException("fxAccount must not be null");
     }
 
     emailPreference.setTitle(fxAccount.getEmail());
+    updateAuthServerPreference();
+    updateSyncServerPreference();
 
     try {
       // There are error states determined by Android, not the login state
       // machine, and we have a chance to present these states here.  We handle
       // them specially, since we can't surface these states as part of syncing,
       // because they generally stop syncs from happening regularly.
 
       // The action to enable syncing the Firefox Account doesn't require
@@ -412,16 +447,48 @@ public class FxAccountStatusFragment
       updateSelectedEngines();
     }
 
     final String clientName = clientsDataDelegate.getClientName();
     deviceNamePreference.setSummary(clientName);
     deviceNamePreference.setText(clientName);
   }
 
+  protected void updateAuthServerPreference() {
+    final String authServer = fxAccount.getAccountServerURI();
+    final boolean shouldBeShown = ALWAYS_SHOW_AUTH_SERVER || !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServer);
+    final boolean currentlyShown = null != findPreference(authServerPreference.getKey());
+    if (currentlyShown != shouldBeShown) {
+      if (shouldBeShown) {
+        accountCategory.addPreference(authServerPreference);
+      } else {
+        accountCategory.removePreference(authServerPreference);
+      }
+    }
+    // Always set the summary, because on first run, the preference is visible,
+    // and the above block will be skipped if there is a custom value.
+    authServerPreference.setSummary(authServer);
+  }
+
+  protected void updateSyncServerPreference() {
+    final String syncServer = fxAccount.getTokenServerURI();
+    final boolean shouldBeShown = ALWAYS_SHOW_SYNC_SERVER || !FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(syncServer);
+    final boolean currentlyShown = null != findPreference(syncServerPreference.getKey());
+    if (currentlyShown != shouldBeShown) {
+      if (shouldBeShown) {
+        syncCategory.addPreference(syncServerPreference);
+      } else {
+        syncCategory.removePreference(syncServerPreference);
+      }
+    }
+    // Always set the summary, because on first run, the preference is visible,
+    // and the above block will be skipped if there is a custom value.
+    syncServerPreference.setSummary(syncServer);
+  }
+
   /**
    * Query shared prefs for the current engine state, and update the UI
    * accordingly.
    * <p>
    * In future, we might want this to be on a background thread, or implemented
    * as a Loader.
    */
   protected void updateSelectedEngines() {
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -72,16 +72,18 @@ public class FxAccountUpdateCredentialsA
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
     emailEdit.setEnabled(false);
 
     TextView view = (TextView) findViewById(R.id.forgot_password_link);
     ActivityUtils.linkTextView(view, R.string.fxaccount_sign_in_forgot_password, R.string.fxaccount_link_forgot_password);
+
+    updateFromIntentExtras();
   }
 
   @Override
   public void onResume() {
     super.onResume();
     this.fxAccount = getAndroidFxAccount();
     if (fxAccount == null) {
       Logger.warn(LOG_TAG, "Could not get Firefox Account.");
--- a/mobile/android/base/fxa/authenticator/AccountPickler.java
+++ b/mobile/android/base/fxa/authenticator/AccountPickler.java
@@ -51,68 +51,73 @@ import android.content.Context;
  * <p>
  * See bug 768102 for more information in the context of Sync.
  */
 public class AccountPickler {
   public static final String LOG_TAG = AccountPickler.class.getSimpleName();
 
   public static final long PICKLE_VERSION = 2;
 
-  private static final String KEY_PICKLE_VERSION = "pickle_version";
-  private static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
+  public static final String KEY_PICKLE_VERSION = "pickle_version";
+  public static final String KEY_PICKLE_TIMESTAMP = "pickle_timestamp";
 
-  private static final String KEY_ACCOUNT_VERSION = "account_version";
-  private static final String KEY_ACCOUNT_TYPE = "account_type";
-  private static final String KEY_EMAIL = "email";
-  private static final String KEY_PROFILE = "profile";
-  private static final String KEY_IDP_SERVER_URI = "idpServerURI";
-  private static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
-  private static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
+  public static final String KEY_ACCOUNT_VERSION = "account_version";
+  public static final String KEY_ACCOUNT_TYPE = "account_type";
+  public static final String KEY_EMAIL = "email";
+  public static final String KEY_PROFILE = "profile";
+  public static final String KEY_IDP_SERVER_URI = "idpServerURI";
+  public static final String KEY_TOKEN_SERVER_URI = "tokenServerURI";
+  public static final String KEY_IS_SYNCING_ENABLED = "isSyncingEnabled";
 
-  private static final String KEY_BUNDLE = "bundle";
+  public static final String KEY_BUNDLE = "bundle";
 
   /**
    * Remove Firefox account persisted to disk.
    *
    * @param context Android context.
    * @param filename name of persisted pickle file; must not contain path separators.
    * @return <code>true</code> if given pickle existed and was successfully deleted.
    */
   public static boolean deletePickle(final Context context, final String filename) {
     return context.deleteFile(filename);
   }
 
-  /**
-   * Persist Firefox account to disk as a JSON object.
-   *
-   * @param AndroidFxAccount the account to persist to disk
-   * @param filename name of file to persist to; must not contain path separators.
-   */
-  public static void pickle(final AndroidFxAccount account, final String filename) {
+  public static ExtendedJSONObject toJSON(final AndroidFxAccount account, final long now) {
     final ExtendedJSONObject o = new ExtendedJSONObject();
     o.put(KEY_PICKLE_VERSION, Long.valueOf(PICKLE_VERSION));
-    o.put(KEY_PICKLE_TIMESTAMP, Long.valueOf(System.currentTimeMillis()));
+    o.put(KEY_PICKLE_TIMESTAMP, Long.valueOf(now));
 
     o.put(KEY_ACCOUNT_VERSION, AndroidFxAccount.CURRENT_ACCOUNT_VERSION);
     o.put(KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
     o.put(KEY_EMAIL, account.getEmail());
     o.put(KEY_PROFILE, account.getProfile());
     o.put(KEY_IDP_SERVER_URI, account.getAccountServerURI());
     o.put(KEY_TOKEN_SERVER_URI, account.getTokenServerURI());
     o.put(KEY_IS_SYNCING_ENABLED, account.isSyncing());
 
     // TODO: If prefs version changes under us, SyncPrefsPath will change, "clearing" prefs.
 
     final ExtendedJSONObject bundle = account.unbundle();
     if (bundle == null) {
       Logger.warn(LOG_TAG, "Unable to obtain account bundle; aborting.");
-      return;
+      return null;
     }
     o.put(KEY_BUNDLE, bundle);
 
+    return o;
+  }
+
+  /**
+   * Persist Firefox account to disk as a JSON object.
+   *
+   * @param AndroidFxAccount the account to persist to disk
+   * @param filename name of file to persist to; must not contain path separators.
+   */
+  public static void pickle(final AndroidFxAccount account, final String filename) {
+    final ExtendedJSONObject o = toJSON(account, System.currentTimeMillis());
     writeToDisk(account.context, filename, o);
   }
 
   private static void writeToDisk(final Context context, final String filename,
       final ExtendedJSONObject pickle) {
     try {
       final FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE);
       try {
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -318,16 +318,19 @@ public class AndroidFxAccount {
       final int accountVersion,
       final boolean syncEnabled,
       final boolean fromPickle,
       ExtendedJSONObject bundle)
           throws UnsupportedEncodingException, GeneralSecurityException, URISyntaxException {
     if (email == null) {
       throw new IllegalArgumentException("email must not be null");
     }
+    if (profile == null) {
+      throw new IllegalArgumentException("profile must not be null");
+    }
     if (idpServerURI == null) {
       throw new IllegalArgumentException("idpServerURI must not be null");
     }
     if (tokenServerURI == null) {
       throw new IllegalArgumentException("tokenServerURI must not be null");
     }
     if (state == null) {
       throw new IllegalArgumentException("state must not be null");
@@ -363,16 +366,25 @@ public class AndroidFxAccount {
     // We don't set an Android password, because we don't want to persist the
     // password (or anything else as powerful as the password). Instead, we
     // internally manage a sessionToken with a remotely owned lifecycle.
     boolean added = accountManager.addAccountExplicitly(account, null, userdata);
     if (!added) {
       return null;
     }
 
+    // Try to work around an intermittent issue described at
+    // http://stackoverflow.com/a/11698139.  What happens is that tests that
+    // delete and re-create the same account frequently will find the account
+    // missing all or some of the userdata bundle, possibly due to an Android
+    // AccountManager caching bug.
+    for (String key : userdata.keySet()) {
+      accountManager.setUserData(account, key, userdata.getString(key));
+    }
+
     AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
 
     if (!fromPickle) {
       fxAccount.clearSyncPrefs();
     }
 
     if (syncEnabled) {
       fxAccount.enableSyncing();
--- a/mobile/android/base/home/HistoryPanel.java
+++ b/mobile/android/base/home/HistoryPanel.java
@@ -1,104 +1,422 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.home;
 
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.widget.IconTabWidget;
+import java.util.Date;
+import java.util.EnumSet;
 
-import android.content.res.Configuration;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.BrowserDB.URLColumns;
+import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
+import org.mozilla.gecko.util.ThreadUtils;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.database.Cursor;
 import android.os.Bundle;
-import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
+import android.util.SparseArray;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.AdapterView;
+import android.widget.ImageView;
+import android.widget.TextView;
 
-public class HistoryPanel extends HomeFragment
-                          implements IconTabWidget.OnTabChangedListener {
+/**
+ * Fragment that displays recent history in a ListView.
+ */
+public class HistoryPanel extends HomeFragment {
     // Logging tag name
     private static final String LOGTAG = "GeckoHistoryPanel";
-    private IconTabWidget mTabWidget;
-    private int mSelectedTab;
-    private boolean initializeRecentPanel;
+
+    // Cursor loader ID for history query
+    private static final int LOADER_ID_HISTORY = 0;
+
+    // Adapter for the list of recent history entries.
+    private HistoryAdapter mAdapter;
+
+    // The view shown by the fragment.
+    private HomeListView mList;
+
+    // The button view for clearing browsing history.
+    private View mClearHistoryButton;
+
+    // Reference to the View to display when there are no results.
+    private View mEmptyView;
+
+    // Callbacks used for the search and favicon cursor loaders
+    private CursorLoaderCallbacks mCursorLoaderCallbacks;
+
+    // On URL open listener
+    private OnUrlOpenListener mUrlOpenListener;
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+
+        try {
+            mUrlOpenListener = (OnUrlOpenListener) activity;
+        } catch (ClassCastException e) {
+            throw new ClassCastException(activity.toString()
+                    + " must implement HomePager.OnUrlOpenListener");
+        }
+    }
+
+    @Override
+    public void onDetach() {
+        super.onDetach();
+        mUrlOpenListener = null;
+    }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
         return inflater.inflate(R.layout.home_history_panel, container, false);
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
+        mList = (HomeListView) view.findViewById(R.id.list);
+        mList.setTag(HomePager.LIST_TAG_HISTORY);
+
+        mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+            @Override
+            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+                position -= mAdapter.getMostRecentSectionsCountBefore(position);
+                final Cursor c = mAdapter.getCursor(position);
+                final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
+
+                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
+
+                // This item is a TwoLinePageRow, so we allow switch-to-tab.
+                mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
+            }
+        });
 
-        mTabWidget = (IconTabWidget) view.findViewById(R.id.tab_icon_widget);
+        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
+            @Override
+            public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
+                final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
+                info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
+                info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
+                info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
+                info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
+                final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
+                if (cursor.isNull(bookmarkIdCol)) {
+                    // If this is a combined cursor, we may get a history item without a
+                    // bookmark, in which case the bookmarks ID column value will be null.
+                    info.bookmarkId =  -1;
+                } else {
+                    info.bookmarkId = cursor.getInt(bookmarkIdCol);
+                }
+                return info;
+            }
+        });
+        registerForContextMenu(mList);
+
+        mClearHistoryButton = view.findViewById(R.id.clear_history_button);
+        mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                final Context context = getActivity();
+
+                final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
+                dialogBuilder.setMessage(R.string.home_clear_history_confirm);
 
-        mTabWidget.addTab(R.drawable.icon_most_recent, R.string.home_most_recent_title);
-        mTabWidget.addTab(R.drawable.icon_last_tabs, R.string.home_last_tabs_title);
+                dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        dialog.dismiss();
+                    }
+                });
+
+                dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
+                    @Override
+                    public void onClick(final DialogInterface dialog, final int which) {
+                        dialog.dismiss();
+                        ThreadUtils.postToBackgroundThread(new Runnable() {
+                            @Override
+                            public void run() {
+                                final ContentResolver cr = context.getContentResolver();
+                                BrowserDB.clearHistory(cr);
+                            }
+                        });
+
+                        Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
+                    }
+                });
 
-        mTabWidget.setTabSelectionListener(this);
-        mTabWidget.setCurrentTab(mSelectedTab);
+                dialogBuilder.show();
+            }
+        });
+    }
 
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        mList = null;
+        mEmptyView = null;
+        mClearHistoryButton = null;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        // Intialize adapter
+        mAdapter = new HistoryAdapter(getActivity());
+        mList.setAdapter(mAdapter);
+
+        // Create callbacks before the initial loader is started
+        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     @Override
-    public void load() {
-        // Show most recent panel as the initial panel.
-        // Since we detach/attach on config change, this prevents from replacing current fragment.
-        if (!initializeRecentPanel) {
-            showMostRecentPanel();
-            initializeRecentPanel = true;
+    protected void load() {
+        getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
+    }
+
+    private static class HistoryCursorLoader extends SimpleCursorLoader {
+        // Max number of history results
+        private static final int HISTORY_LIMIT = 100;
+
+        public HistoryCursorLoader(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Cursor loadCursor() {
+            final ContentResolver cr = getContext().getContentResolver();
+            return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
         }
     }
 
-    @Override
-    public void onTabChanged(int index) {
-        if (index == mSelectedTab) {
+    private void updateUiFromCursor(Cursor c) {
+        if (c != null && c.getCount() > 0) {
+            mClearHistoryButton.setVisibility(View.VISIBLE);
             return;
         }
 
-        if (index == 0) {
-            showMostRecentPanel();
-        } else if (index == 1) {
-            showLastTabsPanel();
-        }
-
-        mTabWidget.setCurrentTab(index);
-        mSelectedTab = index;
-    }
+        // Cursor is empty, so hide the "Clear browsing history" button,
+        // and set the empty view if it hasn't been set already.
+        mClearHistoryButton.setVisibility(View.GONE);
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
+        if (mEmptyView == null) {
+            // Set empty panel view. We delay this so that the empty view won't flash.
+            final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
+            mEmptyView = emptyViewStub.inflate();
 
-        // Rotation should detach and re-attach to use a different layout.
-        if (isVisible()) {
-            getFragmentManager().beginTransaction()
-                                .detach(this)
-                                .attach(this)
-                                .commitAllowingStateLoss();
+            final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
+            emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
+
+            final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
+            emptyText.setText(R.string.home_most_recent_empty);
+
+            mList.setEmptyView(mEmptyView);
         }
     }
 
-    private void showSubPanel(Fragment subPanel) {
-        final Bundle args = new Bundle();
-        args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
-        subPanel.setArguments(args);
+    private static class HistoryAdapter extends MultiTypeCursorAdapter {
+        private static final int ROW_HEADER = 0;
+        private static final int ROW_STANDARD = 1;
+
+        private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
+        private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
+
+        // For the time sections in history
+        private static final long MS_PER_DAY = 86400000;
+        private static final long MS_PER_WEEK = MS_PER_DAY * 7;
+
+        // The time ranges for each section
+        private static enum MostRecentSection {
+            TODAY,
+            YESTERDAY,
+            WEEK,
+            OLDER
+        };
+
+        private final Context mContext;
+
+        // Maps headers in the list with their respective sections
+        private final SparseArray<MostRecentSection> mMostRecentSections;
+
+        public HistoryAdapter(Context context) {
+            super(context, null, VIEW_TYPES, LAYOUT_TYPES);
+
+            mContext = context;
+
+            // Initialize map of history sections
+            mMostRecentSections = new SparseArray<MostRecentSection>();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            final int type = getItemViewType(position);
+
+            // Header items are not in the cursor
+            if (type == ROW_HEADER) {
+                return null;
+            }
+
+            return super.getItem(position - getMostRecentSectionsCountBefore(position));
+        }
+
+        @Override
+        public int getItemViewType(int position) {
+            if (mMostRecentSections.get(position) != null) {
+                return ROW_HEADER;
+            }
+
+            return ROW_STANDARD;
+        }
+
+        @Override
+        public boolean isEnabled(int position) {
+            return (getItemViewType(position) == ROW_STANDARD);
+        }
+
+        @Override
+        public int getCount() {
+            // Add the history section headers to the number of reported results.
+            return super.getCount() + mMostRecentSections.size();
+        }
+
+        @Override
+        public Cursor swapCursor(Cursor cursor) {
+            loadMostRecentSections(cursor);
+            Cursor oldCursor = super.swapCursor(cursor);
+            return oldCursor;
+        }
+
+        @Override
+        public void bindView(View view, Context context, int position) {
+            final int type = getItemViewType(position);
 
-        getChildFragmentManager().beginTransaction()
-                .addToBackStack(null).replace(R.id.history_panel_container, subPanel)
-                .commitAllowingStateLoss();
+            if (type == ROW_HEADER) {
+                final MostRecentSection section = mMostRecentSections.get(position);
+                final TextView row = (TextView) view;
+                row.setText(getMostRecentSectionTitle(section));
+            } else {
+                // Account for the most recent section headers
+                position -= getMostRecentSectionsCountBefore(position);
+                final Cursor c = getCursor(position);
+                final TwoLinePageRow row = (TwoLinePageRow) view;
+                row.updateFromCursor(c);
+            }
+        }
+
+        private String getMostRecentSectionTitle(MostRecentSection section) {
+            switch (section) {
+            case TODAY:
+                return mContext.getString(R.string.history_today_section);
+            case YESTERDAY:
+                return mContext.getString(R.string.history_yesterday_section);
+            case WEEK:
+                return mContext.getString(R.string.history_week_section);
+            case OLDER:
+                return mContext.getString(R.string.history_older_section);
+            }
+
+            throw new IllegalStateException("Unrecognized history section");
+        }
+
+        private int getMostRecentSectionsCountBefore(int position) {
+            // Account for the number headers before the given position
+            int sectionsBefore = 0;
+
+            final int historySectionsCount = mMostRecentSections.size();
+            for (int i = 0; i < historySectionsCount; i++) {
+                final int sectionPosition = mMostRecentSections.keyAt(i);
+                if (sectionPosition > position) {
+                    break;
+                }
+
+                sectionsBefore++;
+            }
+
+            return sectionsBefore;
+        }
+
+        private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
+            long delta = from - time;
+
+            if (delta < 0) {
+                return MostRecentSection.TODAY;
+            }
+
+            if (delta < MS_PER_DAY) {
+                return MostRecentSection.YESTERDAY;
+            }
+
+            if (delta < MS_PER_WEEK) {
+                return MostRecentSection.WEEK;
+            }
+
+            return MostRecentSection.OLDER;
+        }
+
+        private void loadMostRecentSections(Cursor c) {
+            // Clear any history sections that may have been loaded before.
+            mMostRecentSections.clear();
+
+            if (c == null || !c.moveToFirst()) {
+                return;
+            }
+
+            final Date now = new Date();
+            now.setHours(0);
+            now.setMinutes(0);
+            now.setSeconds(0);
+
+            final long today = now.getTime();
+            MostRecentSection section = null;
+
+            do {
+                final int position = c.getPosition();
+                final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
+                final MostRecentSection itemSection = HistoryAdapter.getMostRecentSectionForTime(today, time);
+
+                if (section != itemSection) {
+                    section = itemSection;
+                    mMostRecentSections.append(position + mMostRecentSections.size(), section);
+                }
+
+                // Reached the last section, no need to continue
+                if (section == MostRecentSection.OLDER) {
+                    break;
+                }
+            } while (c.moveToNext());
+        }
     }
 
-    private void showMostRecentPanel() {
-        final MostRecentPanel mostRecentPanel = MostRecentPanel.newInstance();
-        showSubPanel(mostRecentPanel);
-    }
+    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
+        @Override
+        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+            return new HistoryCursorLoader(getActivity());
+        }
 
-    private void showLastTabsPanel() {
-        final LastTabsPanel lastTabsPanel = LastTabsPanel.newInstance();
-        showSubPanel(lastTabsPanel);
+        @Override
+        public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
+            mAdapter.swapCursor(c);
+            updateUiFromCursor(c);
+        }
+
+        @Override
+        public void onLoaderReset(Loader<Cursor> loader) {
+            mAdapter.swapCursor(null);
+        }
     }
 }
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -35,16 +35,17 @@ public final class HomeConfig {
      * to a default set of built-in panels. The DYNAMIC panel type is used by
      * third-party services to create panels with varying types of content.
      */
     public static enum PanelType implements Parcelable {
         TOP_SITES("top_sites", TopSitesPanel.class),
         BOOKMARKS("bookmarks", BookmarksPanel.class),
         HISTORY("history", HistoryPanel.class),
         READING_LIST("reading_list", ReadingListPanel.class),
+        RECENT_TABS("recent_tabs", RecentTabsPanel.class),
         DYNAMIC("dynamic", DynamicPanel.class);
 
         private final String mId;
         private final Class<?> mPanelClass;
 
         PanelType(String id, Class<?> panelClass) {
             mId = id;
             mPanelClass = panelClass;
@@ -1490,16 +1491,17 @@ public final class HomeConfig {
         public void setOnReloadListener(OnReloadListener listener);
     }
 
     // UUIDs used to create PanelConfigs for default built-in panels
     private static final String TOP_SITES_PANEL_ID = "4becc86b-41eb-429a-a042-88fe8b5a094e";
     private static final String BOOKMARKS_PANEL_ID = "7f6d419a-cd6c-4e34-b26f-f68b1b551907";
     private static final String READING_LIST_PANEL_ID = "20f4549a-64ad-4c32-93e4-1dcef792733b";
     private static final String HISTORY_PANEL_ID = "f134bf20-11f7-4867-ab8b-e8e705d7fbe8";
+    private static final String RECENT_TABS_PANEL_ID = "5c2601a5-eedc-4477-b297-ce4cef52adf8";
 
     private final HomeConfigBackend mBackend;
 
     public HomeConfig(HomeConfigBackend backend) {
         mBackend = backend;
     }
 
     public State load() {
@@ -1545,16 +1547,21 @@ public final class HomeConfig {
                 id = HISTORY_PANEL_ID;
                 break;
 
             case READING_LIST:
                 titleId = R.string.reading_list_title;
                 id = READING_LIST_PANEL_ID;
                 break;
 
+            case RECENT_TABS:
+                titleId = R.string.recent_tabs_title;
+                id = RECENT_TABS_PANEL_ID;
+                break;
+
             case DYNAMIC:
                 throw new IllegalArgumentException("createBuiltinPanelConfig() is only for built-in panels");
         }
 
         return new PanelConfig(panelType, context.getString(titleId), id, flags);
     }
 
     public static HomeConfig getDefault(Context context) {
--- a/mobile/android/base/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/home/HomeConfigPrefsBackend.java
@@ -31,25 +31,39 @@ import android.content.SharedPreferences
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
 import android.support.v4.content.LocalBroadcastManager;
 import android.text.TextUtils;
 import android.util.Log;
 
 class HomeConfigPrefsBackend implements HomeConfigBackend {
     private static final String LOGTAG = "GeckoHomeConfigBackend";
 
-    private static final String PREFS_CONFIG_KEY = "home_panels";
+    // Increment this to trigger a migration.
+    private static final int VERSION = 1;
+
+    // This key was originally used to store only an array of panel configs.
+    private static final String PREFS_CONFIG_KEY_OLD = "home_panels";
+
+    // This key is now used to store a version number with the array of panel configs.
+    private static final String PREFS_CONFIG_KEY = "home_panels_with_version";
+
+    // Keys used with JSON object stored in prefs.
+    private static final String JSON_KEY_PANELS = "panels";
+    private static final String JSON_KEY_VERSION = "version";
+
     private static final String PREFS_LOCALE_KEY = "home_locale";
 
     private static final String RELOAD_BROADCAST = "HomeConfigPrefsBackend:Reload";
 
     private final Context mContext;
     private ReloadBroadcastReceiver mReloadBroadcastReceiver;
     private OnReloadListener mReloadListener;
 
+    private static boolean sMigrationDone = false;
+
     public HomeConfigPrefsBackend(Context context) {
         mContext = context;
     }
 
     private SharedPreferences getSharedPreferences() {
         return GeckoSharedPrefs.forProfile(mContext);
     }
 
@@ -63,32 +77,119 @@ class HomeConfigPrefsBackend implements 
 
         // We disable reader mode support on low memory devices. Hence the
         // reading list panel should not show up on such devices.
         if (!HardwareUtils.isLowMemoryPlatform()) {
             panelConfigs.add(createBuiltinPanelConfig(mContext, PanelType.READING_LIST));
         }
 
         final PanelConfig historyEntry = createBuiltinPanelConfig(mContext, PanelType.HISTORY);
+        final PanelConfig recentTabsEntry = createBuiltinPanelConfig(mContext, PanelType.RECENT_TABS);
 
         // On tablets, the history panel is the last.
         // On phones, the history panel is the first one.
         if (HardwareUtils.isTablet()) {
             panelConfigs.add(historyEntry);
+            panelConfigs.add(recentTabsEntry);
         } else {
             panelConfigs.add(0, historyEntry);
+            panelConfigs.add(0, recentTabsEntry);
         }
 
         return new State(panelConfigs, true);
     }
 
+    /**
+     * Migrates JSON config data storage.
+     *
+     * @param context Context used to get shared preferences and create built-in panel.
+     * @param jsonString String currently stored in preferences.
+     *
+     * @return JSONArray array representing new set of panel configs.
+     */
+    private static synchronized JSONArray maybePerformMigration(Context context, String jsonString) throws JSONException {
+        // If the migration is already done, we're at the current version.
+        if (sMigrationDone) {
+            final JSONObject json = new JSONObject(jsonString);
+            return json.getJSONArray(JSON_KEY_PANELS);
+        }
+
+        // Make sure we only do this version check once.
+        sMigrationDone = true;
+
+        final JSONArray originalJsonPanels;
+        final int version;
+
+        final SharedPreferences prefs = GeckoSharedPrefs.forProfile(context);
+        if (prefs.contains(PREFS_CONFIG_KEY_OLD)) {
+            // Our original implementation did not contain versioning, so this is implicitly version 0.
+            originalJsonPanels = new JSONArray(jsonString);
+            version = 0;
+        } else {
+            final JSONObject json = new JSONObject(jsonString);
+            originalJsonPanels = json.getJSONArray(JSON_KEY_PANELS);
+            version = json.getInt(JSON_KEY_VERSION);
+        }
+
+        if (version == VERSION) {
+            return originalJsonPanels;
+        }
+
+        Log.d(LOGTAG, "Performing migration");
+
+        final JSONArray newJsonPanels = new JSONArray();
+        final SharedPreferences.Editor prefsEditor = prefs.edit();
+
+        for (int v = version + 1; v <= VERSION; v++) {
+            Log.d(LOGTAG, "Migrating to version = " + v);
+
+            switch (v) {
+                case 1:
+                    // Add "Recent Tabs" panel
+                    final PanelConfig recentTabsConfig = createBuiltinPanelConfig(context, PanelType.RECENT_TABS);
+                    final JSONObject jsonRecentTabsConfig = recentTabsConfig.toJSON();
+
+                    // Add the new panel to the front of the array on phones.
+                    if (!HardwareUtils.isTablet()) {
+                        newJsonPanels.put(jsonRecentTabsConfig);
+                    }
+
+                    // Copy the original panel configs.
+                    final int count = originalJsonPanels.length();
+                    for (int i = 0; i < count; i++) {
+                        final JSONObject jsonPanelConfig = originalJsonPanels.getJSONObject(i);
+                        newJsonPanels.put(jsonPanelConfig);
+                    }
+
+                    // Add the new panel to the end of the array on tablets.
+                    if (HardwareUtils.isTablet()) {
+                        newJsonPanels.put(jsonRecentTabsConfig);
+                    }
+
+                    // Remove the old pref key.
+                    prefsEditor.remove(PREFS_CONFIG_KEY_OLD);
+                    break;
+            }
+        }
+
+        // Save the new panel config and the new version number.
+        final JSONObject newJson = new JSONObject();
+        newJson.put(JSON_KEY_PANELS, newJsonPanels);
+        newJson.put(JSON_KEY_VERSION, VERSION);
+
+        prefsEditor.putString(PREFS_CONFIG_KEY, newJson.toString());
+        prefsEditor.commit();
+
+        return newJsonPanels;
+    }
+
     private State loadConfigFromString(String jsonString) {
         final JSONArray jsonPanelConfigs;
         try {
-            jsonPanelConfigs = new JSONArray(jsonString);
+            jsonPanelConfigs = maybePerformMigration(mContext, jsonString);
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error loading the list of home panels from JSON prefs", e);
 
             // Fallback to default config
             return loadDefaultConfig();
         }
 
         final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
@@ -105,17 +206,19 @@ class HomeConfigPrefsBackend implements 
         }
 
         return new State(panelConfigs, false);
     }
 
     @Override
     public State load() {
         final SharedPreferences prefs = getSharedPreferences();
-        final String jsonString = prefs.getString(PREFS_CONFIG_KEY, null);
+
+        final String key = (prefs.contains(PREFS_CONFIG_KEY_OLD) ? PREFS_CONFIG_KEY_OLD : PREFS_CONFIG_KEY);
+        final String jsonString = prefs.getString(key, null);
 
         final State configState;
         if (TextUtils.isEmpty(jsonString)) {
             configState = loadDefaultConfig();
         } else {
             configState = loadConfigFromString(jsonString);
         }
 
@@ -137,17 +240,25 @@ class HomeConfigPrefsBackend implements 
                 try {
                     final JSONObject jsonPanelConfig = panelConfig.toJSON();
                     jsonPanelConfigs.put(jsonPanelConfig);
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
                 }
             }
 
-            editor.putString(PREFS_CONFIG_KEY, jsonPanelConfigs.toString());
+            try {
+                final JSONObject json = new JSONObject();
+                json.put(JSON_KEY_PANELS, jsonPanelConfigs);
+                json.put(JSON_KEY_VERSION, VERSION);
+
+                editor.putString(PREFS_CONFIG_KEY, json.toString());
+            } catch (JSONException e) {
+                Log.e(LOGTAG, "Exception saving PanelConfig state", e);
+            }
         }
 
         editor.putString(PREFS_LOCALE_KEY, Locale.getDefault().toString());
         editor.commit();
 
         // Trigger reload listeners on all live backend instances
         sendReloadBroadcast();
     }
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -63,18 +63,17 @@ public class HomePager extends ViewPager
     private OnPanelChangeListener mPanelChangedListener;
 
     // This is mostly used by UI tests to easily fetch
     // specific list views at runtime.
     static final String LIST_TAG_HISTORY = "history";
     static final String LIST_TAG_BOOKMARKS = "bookmarks";
     static final String LIST_TAG_READING_LIST = "reading_list";
     static final String LIST_TAG_TOP_SITES = "top_sites";
-    static final String LIST_TAG_MOST_RECENT = "most_recent";
-    static final String LIST_TAG_LAST_TABS = "last_tabs";
+    static final String LIST_TAG_RECENT_TABS = "recent_tabs";
     static final String LIST_TAG_BROWSER_SEARCH = "browser_search";
 
     public interface OnUrlOpenListener {
         public enum Flags {
             ALLOW_SWITCH_TO_TAB,
             OPEN_WITH_INTENT
         }
 
deleted file mode 100644
--- a/mobile/android/base/home/MostRecentPanel.java
+++ /dev/null
@@ -1,430 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * 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/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.Date;
-import java.util.EnumSet;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.BrowserDB.URLColumns;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.util.ThreadUtils;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-import android.util.SparseArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.AdapterView;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * Fragment that displays recent history in a ListView.
- */
-public class MostRecentPanel extends HomeFragment {
-    // Logging tag name
-    private static final String LOGTAG = "GeckoMostRecentPanel";
-
-    // Cursor loader ID for history query
-    private static final int LOADER_ID_HISTORY = 0;
-
-    // Adapter for the list of search results
-    private MostRecentAdapter mAdapter;
-
-    // The view shown by the fragment.
-    private HomeListView mList;
-
-    // The button view for clearing browsing history.
-    private View mClearHistoryButton;
-
-    // Reference to the View to display when there are no results.
-    private View mEmptyView;
-
-    // Callbacks used for the search and favicon cursor loaders
-    private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
-    // On URL open listener
-    private OnUrlOpenListener mUrlOpenListener;
-
-    public static MostRecentPanel newInstance() {
-        return new MostRecentPanel();
-    }
-
-    public MostRecentPanel() {
-        mUrlOpenListener = null;
-    }
-
-    @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-
-        try {
-            mUrlOpenListener = (OnUrlOpenListener) activity;
-        } catch (ClassCastException e) {
-            throw new ClassCastException(activity.toString()
-                    + " must implement HomePager.OnUrlOpenListener");
-        }
-    }
-
-    @Override
-    public void onDetach() {
-        super.onDetach();
-        mUrlOpenListener = null;
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.home_most_recent_panel, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        mList = (HomeListView) view.findViewById(R.id.list);
-        mList.setTag(HomePager.LIST_TAG_MOST_RECENT);
-
-        mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                position -= mAdapter.getMostRecentSectionsCountBefore(position);
-                final Cursor c = mAdapter.getCursor(position);
-                final String url = c.getString(c.getColumnIndexOrThrow(URLColumns.URL));
-
-                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
-
-                // This item is a TwoLinePageRow, so we allow switch-to-tab.
-                mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
-            }
-        });
-
-        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
-            @Override
-            public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
-                final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
-                info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
-                info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
-                info.display = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.DISPLAY));
-                info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
-                final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
-                if (cursor.isNull(bookmarkIdCol)) {
-                    // If this is a combined cursor, we may get a history item without a
-                    // bookmark, in which case the bookmarks ID column value will be null.
-                    info.bookmarkId =  -1;
-                } else {
-                    info.bookmarkId = cursor.getInt(bookmarkIdCol);
-                }
-                return info;
-            }
-        });
-        registerForContextMenu(mList);
-
-        mClearHistoryButton = view.findViewById(R.id.clear_history_button);
-        mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                final Context context = getActivity();
-
-                final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
-                dialogBuilder.setMessage(R.string.home_clear_history_confirm);
-
-                dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
-                    @Override
-                    public void onClick(final DialogInterface dialog, final int which) {
-                        dialog.dismiss();
-                    }
-                });
-
-                dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
-                    @Override
-                    public void onClick(final DialogInterface dialog, final int which) {
-                        dialog.dismiss();
-                        ThreadUtils.postToBackgroundThread(new Runnable() {
-                            @Override
-                            public void run() {
-                                final ContentResolver cr = context.getContentResolver();
-                                BrowserDB.clearHistory(cr);
-                            }
-                        });
-
-                        Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
-                    }
-                });
-
-                dialogBuilder.show();
-            }
-        });
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-        mList = null;
-        mEmptyView = null;
-        mClearHistoryButton = null;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // Intialize adapter
-        mAdapter = new MostRecentAdapter(getActivity());
-        mList.setAdapter(mAdapter);
-
-        // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
-        loadIfVisible();
-    }
-
-    @Override
-    protected void load() {
-        getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
-    }
-
-    private static class MostRecentCursorLoader extends SimpleCursorLoader {
-        // Max number of history results
-        private static final int HISTORY_LIMIT = 100;
-
-        public MostRecentCursorLoader(Context context) {
-            super(context);
-        }
-
-        @Override
-        public Cursor loadCursor() {
-            final ContentResolver cr = getContext().getContentResolver();
-            return BrowserDB.getRecentHistory(cr, HISTORY_LIMIT);
-        }
-    }
-
-    private void updateUiFromCursor(Cursor c) {
-        if (c != null && c.getCount() > 0) {
-            mClearHistoryButton.setVisibility(View.VISIBLE);
-            return;
-        }
-
-        // Cursor is empty, so hide the "Clear browsing history" button,
-        // and set the empty view if it hasn't been set already.
-        mClearHistoryButton.setVisibility(View.GONE);
-
-        if (mEmptyView == null) {
-            // Set empty panel view. We delay this so that the empty view won't flash.
-            final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
-            mEmptyView = emptyViewStub.inflate();
-
-            final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
-            emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
-
-            final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
-            emptyText.setText(R.string.home_most_recent_empty);
-
-            mList.setEmptyView(mEmptyView);
-        }
-    }
-
-    private static class MostRecentAdapter extends MultiTypeCursorAdapter {
-        private static final int ROW_HEADER = 0;
-        private static final int ROW_STANDARD = 1;
-
-        private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
-        private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
-
-        // For the time sections in history
-        private static final long MS_PER_DAY = 86400000;
-        private static final long MS_PER_WEEK = MS_PER_DAY * 7;
-
-        // The time ranges for each section
-        private static enum MostRecentSection {
-            TODAY,
-            YESTERDAY,
-            WEEK,
-            OLDER
-        };
-
-        private final Context mContext;
-
-        // Maps headers in the list with their respective sections
-        private final SparseArray<MostRecentSection> mMostRecentSections;
-
-        public MostRecentAdapter(Context context) {
-            super(context, null, VIEW_TYPES, LAYOUT_TYPES);
-
-            mContext = context;
-
-            // Initialize map of history sections
-            mMostRecentSections = new SparseArray<MostRecentSection>();
-        }
-
-        @Override
-        public Object getItem(int position) {
-            final int type = getItemViewType(position);
-
-            // Header items are not in the cursor
-            if (type == ROW_HEADER) {
-                return null;
-            }
-
-            return super.getItem(position - getMostRecentSectionsCountBefore(position));
-        }
-
-        @Override
-        public int getItemViewType(int position) {
-            if (mMostRecentSections.get(position) != null) {
-                return ROW_HEADER;
-            }
-
-            return ROW_STANDARD;
-        }
-
-        @Override
-        public boolean isEnabled(int position) {
-            return (getItemViewType(position) == ROW_STANDARD);
-        }
-
-        @Override
-        public int getCount() {
-            // Add the history section headers to the number of reported results.
-            return super.getCount() + mMostRecentSections.size();
-        }
-
-        @Override
-        public Cursor swapCursor(Cursor cursor) {
-            loadMostRecentSections(cursor);
-            Cursor oldCursor = super.swapCursor(cursor);
-            return oldCursor;
-        }
-
-        @Override
-        public void bindView(View view, Context context, int position) {
-            final int type = getItemViewType(position);
-
-            if (type == ROW_HEADER) {
-                final MostRecentSection section = mMostRecentSections.get(position);
-                final TextView row = (TextView) view;
-                row.setText(getMostRecentSectionTitle(section));
-            } else {
-                // Account for the most recent section headers
-                position -= getMostRecentSectionsCountBefore(position);
-                final Cursor c = getCursor(position);
-                final TwoLinePageRow row = (TwoLinePageRow) view;
-                row.updateFromCursor(c);
-            }
-        }
-
-        private String getMostRecentSectionTitle(MostRecentSection section) {
-            switch (section) {
-            case TODAY:
-                return mContext.getString(R.string.history_today_section);
-            case YESTERDAY:
-                return mContext.getString(R.string.history_yesterday_section);
-            case WEEK:
-                return mContext.getString(R.string.history_week_section);
-            case OLDER:
-                return mContext.getString(R.string.history_older_section);
-            }
-
-            throw new IllegalStateException("Unrecognized history section");
-        }
-
-        private int getMostRecentSectionsCountBefore(int position) {
-            // Account for the number headers before the given position
-            int sectionsBefore = 0;
-
-            final int historySectionsCount = mMostRecentSections.size();
-            for (int i = 0; i < historySectionsCount; i++) {
-                final int sectionPosition = mMostRecentSections.keyAt(i);
-                if (sectionPosition > position) {
-                    break;
-                }
-
-                sectionsBefore++;
-            }
-
-            return sectionsBefore;
-        }
-
-        private static MostRecentSection getMostRecentSectionForTime(long from, long time) {
-            long delta = from - time;
-
-            if (delta < 0) {
-                return MostRecentSection.TODAY;
-            }
-
-            if (delta < MS_PER_DAY) {
-                return MostRecentSection.YESTERDAY;
-            }
-
-            if (delta < MS_PER_WEEK) {
-                return MostRecentSection.WEEK;
-            }
-
-            return MostRecentSection.OLDER;
-        }
-
-        private void loadMostRecentSections(Cursor c) {
-            // Clear any history sections that may have been loaded before.
-            mMostRecentSections.clear();
-
-            if (c == null || !c.moveToFirst()) {
-                return;
-            }
-
-            final Date now = new Date();
-            now.setHours(0);
-            now.setMinutes(0);
-            now.setSeconds(0);
-
-            final long today = now.getTime();
-            MostRecentSection section = null;
-
-            do {
-                final int position = c.getPosition();
-                final long time = c.getLong(c.getColumnIndexOrThrow(URLColumns.DATE_LAST_VISITED));
-                final MostRecentSection itemSection = MostRecentAdapter.getMostRecentSectionForTime(today, time);
-
-                if (section != itemSection) {
-                    section = itemSection;
-                    mMostRecentSections.append(position + mMostRecentSections.size(), section);
-                }
-
-                // Reached the last section, no need to continue
-                if (section == MostRecentSection.OLDER) {
-                    break;
-                }
-            } while (c.moveToNext());
-        }
-    }
-
-    private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
-        @Override
-        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            return new MostRecentCursorLoader(getActivity());
-        }
-
-        @Override
-        public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            mAdapter.swapCursor(c);
-            updateUiFromCursor(c);
-        }
-
-        @Override
-        public void onLoaderReset(Loader<Cursor> loader) {
-            mAdapter.swapCursor(null);
-        }
-    }
-}
rename from mobile/android/base/home/LastTabsPanel.java
rename to mobile/android/base/home/RecentTabsPanel.java
--- a/mobile/android/base/home/LastTabsPanel.java
+++ b/mobile/android/base/home/RecentTabsPanel.java
@@ -1,78 +1,96 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.home;
 
 import org.mozilla.gecko.AboutPages;
+import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SessionParser;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract.Combined;
+import org.mozilla.gecko.db.BrowserContract.CommonColumns;
+import org.mozilla.gecko.db.BrowserContract.URLColumns;
 import org.mozilla.gecko.home.HomePager.OnNewTabsListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.NativeEventListener;
+import org.mozilla.gecko.util.NativeJSObject;
+import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MatrixCursor.RowBuilder;
 import android.os.Bundle;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.support.v4.widget.CursorAdapter;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.widget.AdapterView;
 import android.widget.ImageView;
 import android.widget.TextView;
 
 /**
  * Fragment that displays tabs from last session in a ListView.
  */
-public class LastTabsPanel extends HomeFragment {
+public class RecentTabsPanel extends HomeFragment
+                             implements NativeEventListener {
     // Logging tag name
-    private static final String LOGTAG = "GeckoLastTabsPanel";
+    private static final String LOGTAG = "GeckoRecentTabsPanel";
 
-    // Cursor loader ID for the session parser
-    private static final int LOADER_ID_LAST_TABS = 0;
+    // Cursor loader ID for the loader that loads recent tabs
+    private static final int LOADER_ID_RECENT_TABS = 0;
 
-    // Adapter for the list of search results
-    private LastTabsAdapter mAdapter;
+    // Adapter for the list of recent tabs.
+    private RecentTabsAdapter mAdapter;
 
     // The view shown by the fragment.
     private HomeListView mList;
 
-    // The title for this HomeFragment panel.
-    private TextView mTitle;
-
-    // The button view for restoring tabs from last session.
-    private View mRestoreButton;
-
     // Reference to the View to display when there are no results.
     private View mEmptyView;
 
     // Callbacks used for the search and favicon cursor loaders
     private CursorLoaderCallbacks mCursorLoaderCallbacks;
 
     // On new tabs listener
     private OnNewTabsListener mNewTabsListener;
 
-    public static LastTabsPanel newInstance() {
-        return new LastTabsPanel();
+    // Recently closed tabs from gecko
+    private ClosedTab[] mClosedTabs;
+
+    private static final class ClosedTab {
+        public final String url;
+        public final String title;
+
+        public ClosedTab(String url, String title) {
+            this.url = url;
+            this.title = title;
+        }
     }
 
-    public LastTabsPanel() {
-        mNewTabsListener = null;
+    public static final class RecentTabs implements URLColumns, CommonColumns {
+        public static final String TYPE = "type";
+
+        public static final int TYPE_HEADER = 0;
+        public static final int TYPE_LAST_TIME = 1;
+        public static final int TYPE_CLOSED = 2;
     }
 
     @Override
     public void onAttach(Activity activity) {
         super.onAttach(activity);
 
         try {
             mNewTabsListener = (OnNewTabsListener) activity;
@@ -86,103 +104,96 @@ public class LastTabsPanel extends HomeF
     public void onDetach() {
         super.onDetach();
 
         mNewTabsListener = null;
     }
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.home_last_tabs_panel, container, false);
+        return inflater.inflate(R.layout.home_recent_tabs_panel, container, false);
     }
 
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
-        mTitle = (TextView) view.findViewById(R.id.title);
-        if (mTitle != null) {
-            mTitle.setText(R.string.home_last_tabs_title);
-        }
-
         mList = (HomeListView) view.findViewById(R.id.list);
-        mList.setTag(HomePager.LIST_TAG_LAST_TABS);
+        mList.setTag(HomePager.LIST_TAG_RECENT_TABS);
 
         mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                 final Cursor c = mAdapter.getCursor();
                 if (c == null || !c.moveToPosition(position)) {
                     return;
                 }
 
                 Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL);
 
-                final String url = c.getString(c.getColumnIndexOrThrow(Combined.URL));
+                final String url = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
                 mNewTabsListener.onNewTabs(new String[] { url });
             }
         });
 
         mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
             @Override
             public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
                 final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
-                info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
-                info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
+                info.url = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.URL));
+                info.title = cursor.getString(cursor.getColumnIndexOrThrow(RecentTabs.TITLE));
                 return info;
             }
         });
 
         registerForContextMenu(mList);
 
-        mRestoreButton = view.findViewById(R.id.open_all_tabs_button);
-        mRestoreButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                openAllTabs();
-            }
-        });
+        EventDispatcher.getInstance().registerGeckoThreadListener(this, "ClosedTabs:Data");
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StartNotifications", null));
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
         mList = null;
-        mTitle = null;
         mEmptyView = null;
-        mRestoreButton = null;
+
+        EventDispatcher.getInstance().unregisterGeckoThreadListener(this, "ClosedTabs:Data");
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("ClosedTabs:StopNotifications", null));
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        // Detach and reattach the fragment as the layout changes.
+        if (isVisible()) {
+            getFragmentManager().beginTransaction()
+                                .detach(this)
+                                .attach(this)
+                                .commitAllowingStateLoss();
+        }
     }
 
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
         // Intialize adapter
-        mAdapter = new LastTabsAdapter(getActivity());
+        mAdapter = new RecentTabsAdapter(getActivity());
         mList.setAdapter(mAdapter);
 
         // Create callbacks before the initial loader is started
         mCursorLoaderCallbacks = new CursorLoaderCallbacks();
         loadIfVisible();
     }
 
     private void updateUiFromCursor(Cursor c) {
         if (c != null && c.getCount() > 0) {
-            if (mTitle != null) {
-                mTitle.setVisibility(View.VISIBLE);
-            }
-            mRestoreButton.setVisibility(View.VISIBLE);
             return;
         }
 
-        // Cursor is empty, so hide the title and set the
-        // empty view if it hasn't been set already.
-        if (mTitle != null) {
-            mTitle.setVisibility(View.GONE);
-        }
-        mRestoreButton.setVisibility(View.GONE);
-
         if (mEmptyView == null) {
             // Set empty panel view. We delay this so that the empty view won't flash.
             final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
             mEmptyView = emptyViewStub.inflate();
 
             final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
             emptyIcon.setImageResource(R.drawable.icon_last_tabs_empty);
 
@@ -190,98 +201,177 @@ public class LastTabsPanel extends HomeF
             emptyText.setText(R.string.home_last_tabs_empty);
 
             mList.setEmptyView(mEmptyView);
         }
     }
 
     @Override
     protected void load() {
-        getLoaderManager().initLoader(LOADER_ID_LAST_TABS, null, mCursorLoaderCallbacks);
+        getLoaderManager().initLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
+    }
+
+    @Override
+    public void handleMessage(String event, NativeJSObject message, EventCallback callback) {
+        final NativeJSObject[] tabs = message.getObjectArray("tabs");
+        final int length = tabs.length;
+
+        final ClosedTab[] closedTabs = new ClosedTab[length];
+        for (int i = 0; i < length; i++) {
+            final NativeJSObject tab = tabs[i];
+            closedTabs[i] = new ClosedTab(tab.getString("url"), tab.getString("title"));
+        }
+
+        // Only modify mClosedTabs on the UI thread
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mClosedTabs = closedTabs;
+
+                // Reload the cursor to show recently closed tabs.
+                if (mClosedTabs.length > 0 && canLoad()) {
+                    getLoaderManager().restartLoader(LOADER_ID_RECENT_TABS, null, mCursorLoaderCallbacks);
+                }
+            }
+        });
     }
 
     private void openAllTabs() {
         final Cursor c = mAdapter.getCursor();
         if (c == null || !c.moveToFirst()) {
             return;
         }
 
         final String[] urls = new String[c.getCount()];
 
         do {
-            urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(Combined.URL));
+            urls[c.getPosition()] = c.getString(c.getColumnIndexOrThrow(RecentTabs.URL));
         } while (c.moveToNext());
 
         Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.BUTTON);
 
         mNewTabsListener.onNewTabs(urls);
     }
 
-    private static class LastTabsCursorLoader extends SimpleCursorLoader {
-        public LastTabsCursorLoader(Context context) {
+    private static class RecentTabsCursorLoader extends SimpleCursorLoader {
+        private final ClosedTab[] closedTabs;
+
+        public RecentTabsCursorLoader(Context context, ClosedTab[] closedTabs) {
             super(context);
+            this.closedTabs = closedTabs;
+        }
+
+        private void addRow(MatrixCursor c, String url, String title, int type) {
+            final RowBuilder row = c.newRow();
+            row.add(-1);
+            row.add(url);
+            row.add(title);
+            row.add(type);
         }
 
         @Override
         public Cursor loadCursor() {
             final Context context = getContext();
 
+            final MatrixCursor c = new MatrixCursor(new String[] { RecentTabs._ID,
+                                                                   RecentTabs.URL,
+                                                                   RecentTabs.TITLE,
+                                                                   RecentTabs.TYPE });
+
+            if (closedTabs != null && closedTabs.length > 0) {
+                addRow(c, null, context.getString(R.string.home_closed_tabs_title), RecentTabs.TYPE_HEADER);
+
+                final int length = closedTabs.length;
+                for (int i = 0; i < length; i++) {
+                    final String url = closedTabs[i].url;
+
+                    // Don't show recent tabs for about:home
+                    if (!AboutPages.isAboutHome(url)) {
+                        addRow(c, url, closedTabs[i].title, RecentTabs.TYPE_CLOSED);
+                    }
+                }
+            }
+
             final String jsonString = GeckoProfile.get(context).readSessionFile(true);
             if (jsonString == null) {
                 // No previous session data
-                return null;
+                return c;
             }
 
-            final MatrixCursor c = new MatrixCursor(new String[] { Combined._ID,
-                                                                   Combined.URL,
-                                                                   Combined.TITLE });
+            final int count = c.getCount();
 
             new SessionParser() {
                 @Override
                 public void onTabRead(SessionTab tab) {
                     final String url = tab.getUrl();
 
                     // Don't show last tabs for about:home
                     if (AboutPages.isAboutHome(url)) {
                         return;
                     }
 
-                    final RowBuilder row = c.newRow();
-                    row.add(-1);
-                    row.add(url);
+                    // If this is the first tab we're reading, add a header.
+                    if (c.getCount() == count) {
+                        addRow(c, null, context.getString(R.string.home_last_tabs_title), RecentTabs.TYPE_HEADER);
+                    }
 
-                    final String title = tab.getTitle();
-                    row.add(title);
+                    addRow(c, url, tab.getTitle(), RecentTabs.TYPE_LAST_TIME);
                 }
             }.parse(jsonString);
 
             return c;
         }
     }
 
-    private static class LastTabsAdapter extends CursorAdapter {
-        public LastTabsAdapter(Context context) {
-            super(context, null, 0);
+    private static class RecentTabsAdapter extends MultiTypeCursorAdapter {
+        private static final int ROW_HEADER = 0;
+        private static final int ROW_STANDARD = 1;
+
+        private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
+        private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
+
+        public RecentTabsAdapter(Context context) {
+            super(context, null, VIEW_TYPES, LAYOUT_TYPES);
+        }
+
+        public int getItemViewType(int position) {
+            final Cursor c = getCursor(position);
+            final int type = c.getInt(c.getColumnIndexOrThrow(RecentTabs.TYPE));
+
+            if (type == RecentTabs.TYPE_HEADER) {
+                return ROW_HEADER;
+            }
+
+            return ROW_STANDARD;
+         }
+
+        public boolean isEnabled(int position) {
+            return (getItemViewType(position) != ROW_HEADER);
         }
 
         @Override
-        public void bindView(View view, Context context, Cursor cursor) {
-            ((TwoLinePageRow) view).updateFromCursor(cursor);
-        }
+        public void bindView(View view, Context context, int position) {
+            final int itemType = getItemViewType(position);
+            final Cursor c = getCursor(position);
 
-        @Override
-        public View newView(Context context, Cursor cursor, ViewGroup parent) {
-            return LayoutInflater.from(context).inflate(R.layout.home_item_row, parent, false);
-        }
+            if (itemType == ROW_HEADER) {
+                final String title = c.getString(c.getColumnIndexOrThrow(RecentTabs.TITLE));
+                final TextView textView = (TextView) view;
+                textView.setText(title);
+            } else if (itemType == ROW_STANDARD) {
+                final TwoLinePageRow pageRow = (TwoLinePageRow) view;
+                pageRow.updateFromCursor(c);
+            }
+         }
     }
 
     private class CursorLoaderCallbacks implements LoaderCallbacks<Cursor> {
         @Override
         public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            return new LastTabsCursorLoader(getActivity());
+            return new RecentTabsCursorLoader(getActivity(), mClosedTabs);
         }
 
         @Override
         public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
             mAdapter.swapCursor(c);
             updateUiFromCursor(c);
         }
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -6,16 +6,17 @@
 <!ENTITY  no_space_to_start_error "There is not enough space available for &brandShortName; to start.">
 <!ENTITY  error_loading_file "An error occurred when trying to load files required to run &brandShortName;">
 
 <!-- Localization note: These are used as the titles of different pages on the home screen.
      They are automatically converted to all caps by the Android platform. -->
 <!ENTITY  bookmarks_title "Bookmarks">
 <!ENTITY  history_title "History">
 <!ENTITY  reading_list_title "Reading List">
+<!ENTITY  recent_tabs_title "Recent Tabs">
 
 <!ENTITY  switch_to_tab "Switch to tab">
 
 <!ENTITY  crash_reporter_title "&brandShortName; Crash Reporter">
 <!ENTITY  crash_message2 "&brandShortName; had a problem and crashed. Your tabs should be listed on the &brandShortName; Start page when you restart.">
 <!ENTITY  crash_send_report_message3 "Tell &vendorShortName; about this crash so they can fix it">
 <!ENTITY  crash_include_url2 "Include the address of the page I was on">
 <!ENTITY  crash_sorry "We\'re sorry">
@@ -344,20 +345,20 @@ size. -->
 <!-- Localization note (home_top_sites_add): This string is used as placeholder
      text underneath empty thumbnails in the Top Sites page on about:home. -->
 <!ENTITY home_top_sites_add "Add a site">
 
 <!ENTITY home_history_title "History">
 <!ENTITY home_clear_history_button "Clear browsing history">
 <!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
 <!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
+<!ENTITY home_closed_tabs_title "Recently closed tabs">
 <!ENTITY home_last_tabs_title "Tabs from last time">
 <!ENTITY home_last_tabs_open "Open all tabs from last time">
 <!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
-<!ENTITY home_most_recent_title "Most recent">
 <!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
 <!ENTITY home_reading_list_empty "Articles you save for later show up here.">
 <!-- Localization note (home_reading_list_hint): The "TIP" string is synonymous to "hint", "clue", etc. This string is displayed
      as an advisory message on how to add content to the reading list when the reading list empty.
      The placeholder &formatI; will be replaced by a small image of the icon described, and can be moved to wherever
      it is applicable. -->
 <!ENTITY home_reading_list_hint2 "TIP: Save articles to your reading list by long pressing the &formatI; icon when it appears in the title bar.">
 <!-- Localization note (home_reading_list_hint_accessible): This string is used
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -104,16 +104,19 @@
 <!ENTITY sync.text.redirect.to.set.up.sync.label 'Set up &syncBrand.fullName.label; on your device to send tabs to other devices.'>
 <!ENTITY sync.text.tab.sent.label 'Your tab was sent!'>
 <!ENTITY sync.text.tab.not.sent.label 'There was a problem sending your tab.'>
 
 <!-- Firefox Account strings. -->
 
 <!ENTITY fxaccount_full_label 'Firefox Accounts'>
 
+<!ENTITY fxaccount_custom_server_account_title 'Using account on server'>
+<!ENTITY fxaccount_custom_server_sync_title 'Storing Sync data on server'>
+
 <!-- Localization note: these are shown in all screens that query the
      user for an email address and password. Hide and show are button
      labels. -->
 <!ENTITY fxaccount_email_hint 'Email'>
 <!ENTITY fxaccount_password_hint 'Password'>
 <!ENTITY fxaccount_password_hide 'Hide'>
 <!ENTITY fxaccount_password_show 'Show'>
 
@@ -170,17 +173,19 @@
 <!ENTITY fxaccount_account_verified_description2 'Your data will begin syncing momentarily.'>
 
 <!ENTITY fxaccount_update_credentials_header 'Sign in'>
 <!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
 <!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
 
 <!ENTITY fxaccount_status_header2 'Firefox Account'>
 <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
+<!ENTITY fxaccount_status_auth_server 'Account server'>
 <!ENTITY fxaccount_status_device_name 'Device name'>
+<!ENTITY fxaccount_status_sync_server 'Sync server'>
 <!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
 <!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
 <!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
 <!ENTITY fxaccount_status_needs_credentials 'Cannot connect. Tap to sign in.'>
 <!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
 <!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings &gt; Data Usage.'>
 <!ENTITY fxaccount_status_needs_account_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Tap to start syncing.'>
 <!ENTITY fxaccount_status_bookmarks 'Bookmarks'>
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -267,33 +267,32 @@ gbjar.sources += [
     'home/HomeConfigPrefsBackend.java',
     'home/HomeContextMenuInfo.java',
     'home/HomeFragment.java',
     'home/HomeListView.java',
     'home/HomePager.java',
     'home/HomePagerTabStrip.java',
     'home/HomePanelPicker.java',
     'home/HomePanelsManager.java',
-    'home/LastTabsPanel.java',
-    'home/MostRecentPanel.java',
     'home/MultiTypeCursorAdapter.java',
     'home/PanelAuthCache.java',
     'home/PanelAuthLayout.java',
     'home/PanelBackItemView.java',
     'home/PanelGridView.java',
     'home/PanelInfoManager.java',
     'home/PanelItemView.java',
     'home/PanelLayout.java',
     'home/PanelListView.java',
     'home/PanelRefreshLayout.java',
     'home/PanelViewAdapter.java',
     'home/PanelViewItemHandler.java',
     'home/PinSiteDialog.java',
     'home/ReadingListPanel.java',
     'home/ReadingListRow.java',
+    'home/RecentTabsPanel.java',
     'home/SearchEngine.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SuggestClient.java',
     'home/TabMenuStrip.java',
     'home/TabMenuStripLayout.java',
     'home/TopSitesGridItemView.java',
--- a/mobile/android/base/preferences/LocaleListPreference.java
+++ b/mobile/android/base/preferences/LocaleListPreference.java
@@ -1,39 +1,93 @@
 /* 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/. */
 
 package org.mozilla.gecko.preferences;
 
+import java.nio.ByteBuffer;
 import java.text.Collator;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Locale;
+import java.util.Set;
 
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.R;
 
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Paint;
 import android.preference.ListPreference;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 
 public class LocaleListPreference extends ListPreference {
     private static final String LOG_TAG = "GeckoLocaleList";
 
+    /**
+     * With thanks to <http://stackoverflow.com/a/22679283/22003> for the
+     * initial solution.
+     *
+     * This class encapsulates an approach to checking whether a script
+     * is usable on a device. We attempt to draw a character from the
+     * script (e.g., ব). If the fonts on the device don't have the correct
+     * glyph, Android typically renders whitespace (rather than .notdef).
+     *
+     * Pass in part of the name of the locale in its local representation,
+     * and a whitespace character; this class performs the graphical comparison.
+     *
+     * See Bug 1023451 Comment 24 for extensive explanation.
+     */
+    private static class CharacterValidator {
+        private static final int BITMAP_WIDTH = 32;
+        private static final int BITMAP_HEIGHT = 48;
+
+        private final Paint paint = new Paint();
+        private final byte[] missingCharacter;
+
+        public CharacterValidator(String missing) {
+            this.missingCharacter = getPixels(drawBitmap(missing));
+        }
+
+        private Bitmap drawBitmap(String text){
+            Bitmap b = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ALPHA_8);
+            Canvas c = new Canvas(b);
+            c.drawText(text, 0, BITMAP_HEIGHT / 2, this.paint);
+            return b;
+        }
+        private static byte[] getPixels(Bitmap b) {
+            ByteBuffer buffer = ByteBuffer.allocate(b.getByteCount());
+            b.copyPixelsToBuffer(buffer);
+            return buffer.array();
+        }
+
+        public boolean characterIsMissingInFont(String ch) {
+            byte[] rendered = getPixels(drawBitmap(ch));
+            return Arrays.equals(rendered, missingCharacter);
+        }
+    }
+
     private volatile Locale entriesLocale;
+    private final CharacterValidator characterValidator;
 
     public LocaleListPreference(Context context) {
         this(context, null);
     }
 
     public LocaleListPreference(Context context, AttributeSet attributes) {
         super(context, attributes);
+
+        // Thus far, missing glyphs are replaced by whitespace, not a box
+        // or other Unicode codepoint.
+        this.characterValidator = new CharacterValidator(" ");
         buildList();
     }
 
     private static final class LocaleDescriptor implements Comparable<LocaleDescriptor> {
         // We use Locale.US here to ensure a stable ordering of entries.
         private static final Collator COLLATOR = Collator.getInstance(Locale.US);
 
         public final String tag;
@@ -81,36 +135,88 @@ public class LocaleListPreference extend
         }
 
 
         @Override
         public int compareTo(LocaleDescriptor another) {
             // We sort by name, so we use Collator.
             return COLLATOR.compare(this.nativeName, another.nativeName);
         }
+
+        /**
+         * See Bug 1023451 Comment 10 for the research that led to
+         * this method.
+         *
+         * @return true if this locale can be used for displaying UI
+         *         on this device without known issues.
+         */
+        public boolean isUsable(CharacterValidator validator) {
+            // Oh, for Java 7 switch statements.
+            if (this.tag.equals("bn-IN")) {
+                // Bengali sometimes has an English label if the Bengali script
+                // is missing. This prevents us from simply checking character
+                // rendering for bn-IN; we'll get a false positive for "B", not "ব".
+                //
+                // This doesn't seem to affect other Bengali-script locales
+                // (below), which always have a label in native script.
+                if (!this.nativeName.startsWith("বাংলা")) {
+                    // We're on an Android version that doesn't even have
+                    // characters to say বাংলা. Definite failure.
+                    return false;
+                }
+            }
+
+            // These locales use a script that is often unavailable
+            // on common Android devices. Make sure we can show them.
+            // See documentation for CharacterValidator.
+            // Note that bn-IN is checked here even if it passed above.
+            if (this.tag.equals("or") ||
+                this.tag.equals("pa-IN") ||
+                this.tag.equals("gu-IN") ||
+                this.tag.equals("bn-IN")) {
+                if (validator.characterIsMissingInFont(this.nativeName.substring(0, 1))) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
     }
 
-    private LocaleDescriptor[] getShippingLocales() {
+    /**
+     * Not every locale we ship can be used on every device, due to
+     * font or rendering constraints.
+     *
+     * This method filters down the list before generating the descriptor array.
+     */
+    private LocaleDescriptor[] getUsableLocales() {
         Collection<String> shippingLocales = BrowserLocaleManager.getPackagedLocaleTags(getContext());
 
         // Future: single-locale builds should be specified, too.
         if (shippingLocales == null) {
             final String fallbackTag = BrowserLocaleManager.getFallbackLocaleTag();
             return new LocaleDescriptor[] { new LocaleDescriptor(fallbackTag) };
         }
 
-        final int count = shippingLocales.size();
-        final LocaleDescriptor[] descriptors = new LocaleDescriptor[count];
+        final int initialCount = shippingLocales.size();
+        final Set<LocaleDescriptor> locales = new HashSet<LocaleDescriptor>(initialCount);
+        for (String tag : shippingLocales) {
+            final LocaleDescriptor descriptor = new LocaleDescriptor(tag);
 
-        int i = 0;
-        for (String tag : shippingLocales) {
-            descriptors[i++] = new LocaleDescriptor(tag);
+            if (!descriptor.isUsable(this.characterValidator)) {
+                Log.w(LOG_TAG, "Skipping locale " + tag + " on this device.");
+                continue;
+            }
+
+            locales.add(descriptor);
         }
 
-        Arrays.sort(descriptors, 0, count);
+        final int usableCount = locales.size();
+        final LocaleDescriptor[] descriptors = locales.toArray(new LocaleDescriptor[usableCount]);
+        Arrays.sort(descriptors, 0, usableCount);
         return descriptors;
     }
 
     @Override
     protected void onDialogClosed(boolean positiveResult) {
         // The superclass will take care of persistence.
         super.onDialogClosed(positiveResult);
 
@@ -149,17 +255,17 @@ public class LocaleListPreference extend
         Log.d(LOG_TAG, "Building locales list. Current locale: " + currentLocale);
 
         if (currentLocale.equals(this.entriesLocale) &&
             getEntries() != null) {
             Log.v(LOG_TAG, "No need to build list.");
             return;
         }
 
-        final LocaleDescriptor[] descriptors = getShippingLocales();
+        final LocaleDescriptor[] descriptors = getUsableLocales();
         final int count = descriptors.length;
 
         this.entriesLocale = currentLocale;
 
         // We leave room for "System default".
         final String[] entries = new String[count + 1];
         final String[] values = new String[count + 1];
 
deleted file mode 100644
--- a/mobile/android/base/resources/drawable-large-land-v11/home_history_tabs_indicator.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_focused="false"
-          android:state_selected="false"
-          android:state_pressed="false"
-          android:drawable="@android:color/transparent"/>
-
-    <item android:state_focused="false"
-          android:state_selected="true"
-          android:state_pressed="false"
-          android:drawable="@color/background_light"/>
-
-    <item android:state_focused="true"
-          android:state_selected="false"
-          android:state_pressed="false"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="true"
-          android:state_pressed="false"
-          android:drawable="@color/background_light"/>
-
-    <item android:state_focused="false"
-          android:state_selected="false"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="false"
-          android:state_selected="true"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="false"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="true"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-</selector>
deleted file mode 100644
--- a/mobile/android/base/resources/drawable-xlarge-v11/home_history_tabs_indicator.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_focused="false"
-          android:state_selected="false"
-          android:state_pressed="false"
-          android:drawable="@android:color/transparent"/>
-
-    <item android:state_focused="false"
-          android:state_selected="true"
-          android:state_pressed="false"
-          android:drawable="@color/background_light"/>
-
-    <item android:state_focused="true"
-          android:state_selected="false"
-          android:state_pressed="false"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="true"
-          android:state_pressed="false"
-          android:drawable="@color/background_light"/>
-
-    <item android:state_focused="false"
-          android:state_selected="false"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="false"
-          android:state_selected="true"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="false"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="true"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-</selector>
deleted file mode 100644
--- a/mobile/android/base/resources/drawable/home_history_tabs_indicator.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:state_focused="false"
-          android:state_selected="false"
-          android:state_pressed="false"
-          android:drawable="@android:color/transparent"/>
-
-    <item android:state_focused="false"
-          android:state_selected="true"
-          android:state_pressed="false"
-          android:drawable="@drawable/history_tabs_indicator_selected"/>
-
-    <item android:state_focused="true"
-          android:state_selected="false"
-          android:state_pressed="false"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="true"
-          android:state_pressed="false"
-          android:drawable="@drawable/history_tabs_indicator_selected"/>
-
-    <item android:state_focused="false"
-          android:state_selected="false"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="false"
-          android:state_selected="true"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="false"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-    <item android:state_focused="true"
-          android:state_selected="true"
-          android:state_pressed="true"
-          android:drawable="@color/background_normal"/>
-
-</selector>
deleted file mode 100644
--- a/mobile/android/base/resources/layout-large-land-v11/home_history_panel.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:gecko="http://schemas.android.com/apk/res-auto"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
-
-    <org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
-                                            style="@style/Widget.Home.HistoryTabWidget"
-                                            android:layout_width="@dimen/history_tab_widget_width"
-                                            android:layout_height="@dimen/history_tab_widget_height"
-                                            android:orientation="vertical"
-                                            android:layout="@layout/home_history_tabs_indicator"
-                                            gecko:display="text"/>
-
-    <FrameLayout android:id="@+id/history_panel_container"
-                 android:layout_width="0dp"
-                 android:layout_height="match_parent"
-                 android:layout_weight="1" />
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout-large-land-v11/home_history_tabs_indicator.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          style="@style/Widget.Home.HistoryTabIndicator"
-          android:layout_width="match_parent"
-          android:layout_height="@dimen/history_tab_indicator_height"
-          android:background="@drawable/home_history_tabs_indicator"/>
deleted file mode 100644
--- a/mobile/android/base/resources/layout-xlarge-v11/home_history_panel.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              xmlns:gecko="http://schemas.android.com/apk/res-auto"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent">
-
-    <org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
-                                            style="@style/Widget.Home.HistoryTabWidget"
-                                            android:layout_width="@dimen/history_tab_widget_width"
-                                            android:layout_height="@dimen/history_tab_widget_height"
-                                            android:orientation="vertical"
-                                            android:layout="@layout/home_history_tabs_indicator"
-                                            gecko:display="text"/>
-
-    <FrameLayout android:id="@+id/history_panel_container"
-                 android:layout_width="0dp"
-                 android:layout_height="match_parent"
-                 android:layout_weight="1" />
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout-xlarge-v11/home_history_tabs_indicator.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-          style="@style/Widget.Home.HistoryTabIndicator"
-          android:layout_width="match_parent"
-          android:layout_height="@dimen/history_tab_indicator_height"
-          android:background="@drawable/home_history_tabs_indicator"/>
--- a/mobile/android/base/resources/layout/fxaccount_account_verified.xml
+++ b/mobile/android/base/resources/layout/fxaccount_account_verified.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout style="@style/FxAccountMiddle" >
 
         <TextView
             style="@style/FxAccountHeaderItem"
             android:text="@string/fxaccount_account_verified_sub_header" >
         </TextView>
--- a/mobile/android/base/resources/layout/fxaccount_confirm_account.xml
+++ b/mobile/android/base/resources/layout/fxaccount_confirm_account.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout style="@style/FxAccountMiddle" >
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <TextView
             style="@style/FxAccountHeaderItem"
--- a/mobile/android/base/resources/layout/fxaccount_create_account.xml
+++ b/mobile/android/base/resources/layout/fxaccount_create_account.xml
@@ -1,30 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout
         android:id="@+id/create_account_view"
         style="@style/FxAccountMiddle" >
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <TextView
             style="@style/FxAccountHeaderItem"
             android:text="@string/fxaccount_create_account_header" />
 
+        <include layout="@layout/fxaccount_custom_server_view" />
+
         <include layout="@layout/fxaccount_email_password_view" />
 
         <TextView
             style="@style/FxAccountTextItem"
             android:layout_marginTop="10dp"
             android:text="@string/fxaccount_create_account_password_length_restriction"
             android:textColor="@color/fxaccount_textColorSubdued" />
 
@@ -84,9 +86,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_empty_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
\ No newline at end of file
+</ScrollView>
--- a/mobile/android/base/resources/layout/fxaccount_create_account_not_allowed.xml
+++ b/mobile/android/base/resources/layout/fxaccount_create_account_not_allowed.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout
         android:id="@+id/create_account_not_allowed_view"
         style="@style/FxAccountMiddle" >
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/fxaccount_custom_server_view.xml
@@ -0,0 +1,116 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+   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/.
+-->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <LinearLayout
+        android:id="@+id/account_server_layout"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:background="@color/fxaccount_error_preference_backgroundcolor"
+        android:gravity="center_vertical"
+        android:minHeight="?android:attr/listPreferredItemHeight" >
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/fxaccount_empty_contentDescription"
+            android:gravity="center"
+            android:minWidth="48dip"
+            android:padding="10dip"
+            android:src="@drawable/fxaccount_sync_error" />
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingBottom="6dip"
+            android:paddingRight="10dip"
+            android:paddingTop="6dip" >
+
+            <TextView
+                android:id="@+android:id/account_server_title"
+                style="@style/FxAccountTextItem"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginBottom="0dp"
+                android:gravity="center_vertical"
+                android:text="@string/fxaccount_custom_server_account_title" >
+            </TextView>
+
+            <TextView
+                android:id="@+android:id/account_server_summary"
+                style="@style/FxAccountTextItem"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="0dp"
+                android:ellipsize="middle"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textSize="12sp" />
+        </LinearLayout>
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/sync_server_layout"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="10dp"
+        android:background="@color/fxaccount_error_preference_backgroundcolor"
+        android:gravity="center_vertical"
+        android:minHeight="?android:attr/listPreferredItemHeight" >
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:contentDescription="@string/fxaccount_empty_contentDescription"
+            android:gravity="center"
+            android:minWidth="48dip"
+            android:padding="10dip"
+            android:src="@drawable/fxaccount_sync_error" />
+
+        <LinearLayout
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:paddingBottom="6dip"
+            android:paddingRight="10dip"
+            android:paddingTop="6dip" >
+
+            <TextView
+                android:id="@+android:id/sync_server_title"
+                style="@style/FxAccountTextItem"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_horizontal"
+                android:layout_marginBottom="0dp"
+                android:gravity="center_vertical"
+                android:text="@string/fxaccount_custom_server_sync_title" >
+            </TextView>
+
+            <TextView
+                android:id="@+android:id/sync_server_summary"
+                style="@style/FxAccountTextItem"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginBottom="0dp"
+                android:ellipsize="marquee"
+                android:focusable="true"
+                android:focusableInTouchMode="true"
+                android:singleLine="true"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:textSize="12sp" />
+        </LinearLayout>
+    </LinearLayout>
+
+</merge>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/fxaccount_email_password_view.xml
+++ b/mobile/android/base/resources/layout/fxaccount_email_password_view.xml
@@ -3,41 +3,41 @@
    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/.
 -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android" >
 
     <LinearLayout
-        android:layout_width="match_parent"
+        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:orientation="vertical" >
 
         <AutoCompleteTextView
             android:id="@+id/email"
             style="@style/FxAccountEditItem"
             android:layout_marginBottom="10dp"
             android:completionThreshold="2"
             android:ems="10"
             android:hint="@string/fxaccount_email_hint"
             android:inputType="textEmailAddress" >
 
             <requestFocus />
         </AutoCompleteTextView>
 
         <LinearLayout
-            android:layout_width="match_parent"
+            android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:orientation="horizontal" >
 
             <EditText
                 android:id="@+id/password"
                 style="@style/FxAccountEditItem"
-                android:layout_width="match_parent"
+                android:layout_width="fill_parent"
                 android:layout_height="wrap_content"
                 android:layout_weight="1"
                 android:background="@drawable/fxaccount_password_background"
                 android:ems="10"
                 android:hint="@string/fxaccount_password_hint"
                 android:inputType="textPassword" />
 
             <!-- For the following, I beg forgiveness. The show/hide button is a
@@ -46,40 +46,40 @@
                  button's width regardless of its state. To achieve this, we
                  size the actual button to its container, and include two
                  invisible (but present for layout purposes) buttons, one of
                  each state. The container wraps the larger of the two dummy
                  buttons; the actual button sizes to the container; and we're
                  happy. Be thankful there are not three buttons! -->
             <FrameLayout
                 android:layout_width="wrap_content"
-                android:layout_height="match_parent"
+                android:layout_height="fill_parent"
                 android:layout_weight="0"
                 android:orientation="horizontal" >
 
                 <Button
                     android:id="@+id/show_password"
                     style="@style/FxAccountShowHidePasswordButton"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
                     android:text="@string/fxaccount_password_show" >
                 </Button>
 
                 <Button
                     style="@style/FxAccountShowHidePasswordButton"
                     android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
+                    android:layout_height="fill_parent"
                     android:text="@string/fxaccount_password_show"
                     android:visibility="invisible" >
                 </Button>
 
                 <Button
                     style="@style/FxAccountShowHidePasswordButton"
                     android:layout_width="wrap_content"
-                    android:layout_height="match_parent"
+                    android:layout_height="fill_parent"
                     android:text="@string/fxaccount_password_hide"
                     android:visibility="invisible" >
                 </Button>
             </FrameLayout>
         </LinearLayout>
     </LinearLayout>
 
 </merge>
--- a/mobile/android/base/resources/layout/fxaccount_get_started.xml
+++ b/mobile/android/base/resources/layout/fxaccount_get_started.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout
         android:id="@+id/intro_view"
         style="@style/FxAccountMiddle" >
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
--- a/mobile/android/base/resources/layout/fxaccount_preference_list_fragment.xml
+++ b/mobile/android/base/resources/layout/fxaccount_preference_list_fragment.xml
@@ -14,22 +14,22 @@
 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:orientation="vertical"
-    android:layout_height="match_parent"
-    android:layout_width="match_parent"
+    android:layout_height="fill_parent"
+    android:layout_width="fill_parent"
     android:background="@android:color/transparent">
 
     <ListView android:id="@android:id/list"
-        android:layout_width="match_parent"
+        android:layout_width="fill_parent"
         android:layout_height="0px"
         android:layout_weight="1"
         android:paddingTop="0dip"
         android:paddingBottom="@dimen/preference_fragment_padding_bottom"
         android:paddingLeft="@dimen/preference_fragment_padding_side"
         android:paddingRight="@dimen/preference_fragment_padding_side"
         android:scrollbarStyle="@integer/preference_fragment_scrollbarStyle"
         android:clipToPadding="false"
--- a/mobile/android/base/resources/layout/fxaccount_sign_in.xml
+++ b/mobile/android/base/resources/layout/fxaccount_sign_in.xml
@@ -1,30 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout
         android:id="@+id/sign_in_view"
         style="@style/FxAccountMiddle" >
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <TextView
             style="@style/FxAccountHeaderItem"
             android:text="@string/fxaccount_sign_in_sub_header" />
 
+        <include layout="@layout/fxaccount_custom_server_view" />
+
         <include layout="@layout/fxaccount_email_password_view" />
 
         <TextView
             android:id="@+id/remote_error"
             style="@style/FxAccountErrorItem" />
 
         <RelativeLayout style="@style/FxAccountButtonLayout" >
 
@@ -34,17 +36,17 @@
 
             <Button
                 android:id="@+id/button"
                 style="@style/FxAccountButton"
                 android:text="@string/fxaccount_sign_in_button_label" />
         </RelativeLayout>
 
         <LinearLayout
-            android:layout_width="match_parent"
+            android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="10dp"
             android:orientation="horizontal" >
 
             <TextView
                 android:id="@+id/forgot_password_link"
                 style="@style/FxAccountLinkifiedItem"
                 android:layout_gravity="left"
--- a/mobile/android/base/resources/layout/fxaccount_status_error_preference.xml
+++ b/mobile/android/base/resources/layout/fxaccount_status_error_preference.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
+    android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:background="@color/fxaccount_error_preference_backgroundcolor"
     android:gravity="center_vertical"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:paddingRight="?android:attr/scrollbarSize" >
 
     <LinearLayout
         android:layout_width="wrap_content"
@@ -30,17 +30,17 @@
         android:layout_marginLeft="15dip"
         android:layout_marginRight="6dip"
         android:layout_marginTop="6dip"
         android:layout_weight="1" >
 
         <TextView
             android:id="@+android:id/title"
             style="@style/FxAccountTextItem"
-            android:layout_width="match_parent"
+            android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_gravity="center_horizontal"
             android:gravity="center_vertical" >
         </TextView>
     </RelativeLayout>
 
     <!-- We ignore summary and widget_frame, but they still need to be present.  We set them to be gone. -->
 
--- a/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
+++ b/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
@@ -1,30 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
    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/.
 -->
 
 <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout
         android:id="@+id/update_credentials_view"
         style="@style/FxAccountMiddle" >
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <TextView
             style="@style/FxAccountHeaderItem"
             android:text="@string/fxaccount_update_credentials_header" />
 
+        <include layout="@layout/fxaccount_custom_server_view" />
+
         <include layout="@layout/fxaccount_email_password_view" />
 
         <TextView
             android:id="@+id/remote_error"
             style="@style/FxAccountErrorItem" />
 
         <RelativeLayout style="@style/FxAccountButtonLayout" >
 
@@ -36,16 +38,17 @@
                 android:id="@+id/button"
                 style="@style/FxAccountButton"
                 android:text="@string/fxaccount_update_credentials_button_label" />
         </RelativeLayout>
 
         <TextView
             android:id="@+id/forgot_password_link"
             style="@style/FxAccountLinkifiedItem"
+            android:layout_marginTop="10dp"
             android:text="@string/fxaccount_sign_in_forgot_password" />
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_empty_contentDescription" />
     </LinearLayout>
--- a/mobile/android/base/resources/layout/home_history_panel.xml
+++ b/mobile/android/base/resources/layout/home_history_panel.xml
@@ -3,22 +3,23 @@
    - 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/. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:orientation="vertical">
 
-    <FrameLayout android:id="@+id/history_panel_container"
-                 android:layout_width="match_parent"
-                 android:layout_height="0dp"
-                 android:layout_weight="1" />
+    <include layout="@layout/home_history_list"/>
+
+    <LinearLayout android:layout_width="match_parent"
+                  android:layout_height="wrap_content"
+                  android:background="@color/home_button_bar_bg">
 
-    <org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_icon_widget"
-                                            android:layout_width="match_parent"
-                                            android:layout_height="@dimen/browser_toolbar_height"
-                                            android:tabStripEnabled="false"
-                                            android:showDividers="none"
-                                            android:background="@color/background_light"
-                                            android:layout="@layout/home_history_tabs_indicator"/>
+        <Button android:id="@+id/clear_history_button"
+                style="@style/Widget.Home.ActionButton"
+                android:text="@string/home_clear_history_button"
+                android:gravity="center"
+                android:visibility="gone"/>
+
+    </LinearLayout>
 
 </LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_history_tabs_indicator.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
-             android:layout_width="0dp"
-             android:layout_height="match_parent"
-             android:layout_weight="1"
-             android:background="@drawable/home_history_tabs_indicator"/>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_most_recent_panel.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <include layout="@layout/home_history_list"/>
-
-    <LinearLayout android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:background="@color/home_button_bar_bg">
-
-        <Button android:id="@+id/clear_history_button"
-                style="@style/Widget.Home.ActionButton"
-                android:text="@string/home_clear_history_button"
-                android:gravity="center"
-                android:visibility="gone"/>
-
-    </LinearLayout>
-
-</LinearLayout>
rename from mobile/android/base/resources/layout/home_last_tabs_panel.xml
rename to mobile/android/base/resources/layout/home_recent_tabs_panel.xml
--- a/mobile/android/base/resources/layout/home_last_tabs_panel.xml
+++ b/mobile/android/base/resources/layout/home_recent_tabs_panel.xml
@@ -5,21 +5,9 @@
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:orientation="vertical">
 
     <include layout="@layout/home_history_list"/>
 
-    <LinearLayout android:layout_width="match_parent"
-                  android:layout_height="wrap_content"
-                  android:background="@color/home_button_bar_bg">
-
-        <Button android:id="@+id/open_all_tabs_button"
-                style="@style/Widget.Home.ActionButton"
-                android:text="@string/home_last_tabs_open"
-                android:gravity="center"
-                android:visibility="gone"/>
-
-    </LinearLayout>
-
 </LinearLayout>
--- a/mobile/android/base/resources/layout/sync_list_item.xml
+++ b/mobile/android/base/resources/layout/sync_list_item.xml
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:gravity="center_vertical"
-    android:layout_width="match_parent"
+    android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
     android:orientation="horizontal"
     android:padding="5dp" >
 
     <ImageView
         android:id="@+id/img"
         android:layout_width="wrap_content"
--- a/mobile/android/base/resources/layout/sync_send_tab.xml
+++ b/mobile/android/base/resources/layout/sync_send_tab.xml
@@ -6,30 +6,30 @@
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   style="@style/SyncContainer" >
 
   <TextView
       style="@style/SyncTop"
       android:text="@string/sync_title_send_tab" />
 
   <LinearLayout
-    android:layout_width="match_parent"
+    android:layout_width="fill_parent"
     android:layout_height="wrap_content"
     android:orientation="vertical"
     android:padding="@dimen/SyncSpace" >
 
     <TextView
       android:id="@+id/title"
-      android:layout_width="match_parent"
+      android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:textAppearance="?android:attr/textAppearanceMedium"
       android:text="@string/sync_title_send_tab" />
     <TextView
       android:id="@+id/uri"
-      android:layout_width="match_parent"
+      android:layout_width="fill_parent"
       android:layout_height="wrap_content"
       android:textAppearance="?android:attr/textAppearanceSmall"
       android:text="@string/sync_title_send_tab" />
   </LinearLayout>
 
   <ListView
     android:id="@+id/device_list"
     style="@style/SyncMiddle"
--- a/mobile/android/base/resources/layout/sync_setup_jpake_waiting.xml
+++ b/mobile/android/base/resources/layout/sync_setup_jpake_waiting.xml
@@ -14,17 +14,17 @@
     style="@style/SyncMiddle" >
 
     <LinearLayout
       style="@style/SyncLayout.Vertical" >
 
       <ProgressBar
         android:id="@+id/waiting_content1"
         style="@style/Widget.ProgressBar.Horizontal"
-        android:layout_width="match_parent"
+        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:indeterminateOnly="true"
         android:padding="@dimen/SyncSpace" />
 
       <TextView
         style="@style/SyncTextItem"
         android:gravity="center"
         android:text="@string/sync_jpake_subtitle_waiting" />
--- a/mobile/android/base/resources/layout/sync_setup_webview.xml
+++ b/mobile/android/base/resources/layout/sync_setup_webview.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   style="@style/SyncLayout" >
   <WebView android:id="@+id/web_engine"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" />
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent" />
 </LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/values-large-land-v11/dimens.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<resources>
-
-    <dimen name="history_tab_widget_width">360dp</dimen>
-
-</resources>
--- a/mobile/android/base/resources/values-v11/sync_styles.xml
+++ b/mobile/android/base/resources/values-v11/sync_styles.xml
@@ -2,30 +2,30 @@
 <!-- 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/. -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
   <!-- Top title bar: a text view with the Sync icon to the left. -->
   <style name="SyncTop" parent="@android:style/Widget.Holo.ActionBar">
     <item name="android:textAppearance">@android:style/TextAppearance.Holo.Widget.ActionBar.Title</item>
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:gravity">center_vertical|left</item>
     <item name="android:drawableLeft">@drawable/icon</item>
     <item name="android:padding">4dp</item>
     <item name="android:drawablePadding">4dp</item>
   </style>
 
   <!-- Bottom bar: a horizontal linear layout with buttons in it. -->
   <style name="SyncBottom" parent="@android:style/Holo.Light.ButtonBar">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
   </style>
 
   <style name="SyncButton" parent="@android:style/Widget.Holo.Light.Button.Small">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:layout_weight">1</item>
     <item name="android:background">?android:attr/selectableItemBackground</item>
   </style>
 
 </resources>
deleted file mode 100644
--- a/mobile/android/base/resources/values-xlarge-land-v11/dimens.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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/. -->
-
-<resources>
-
-    <dimen name="history_tab_widget_width">480dp</dimen>
-
-</resources>
--- a/mobile/android/base/resources/values-xlarge-v11/dimens.xml
+++ b/mobile/android/base/resources/values-xlarge-v11/dimens.xml
@@ -6,12 +6,11 @@
 <resources>
 
     <dimen name="browser_toolbar_height">56dp</dimen>
     <dimen name="remote_tab_child_row_height">56dp</dimen>
     <dimen name="remote_tab_group_row_height">34dp</dimen>
     <dimen name="tabs_counter_size">26sp</dimen>
     <dimen name="tabs_panel_indicator_width">60dp</dimen>
     <dimen name="tabs_panel_list_padding">8dip</dimen>
-    <dimen name="history_tab_widget_width">270dp</dimen>
     <dimen name="panel_grid_view_column_width">250dp</dimen>
 
 </resources>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -77,22 +77,16 @@
     <dimen name="text_selection_handle_height">58dp</dimen>
     <dimen name="text_selection_handle_shadow">11dp</dimen>
     <dimen name="validation_message_height">50dp</dimen>
     <dimen name="validation_message_margin_top">6dp</dimen>
     <dimen name="forward_default_offset">-13dip</dimen>
     <dimen name="url_bar_offset_left">32dp</dimen>
     <dimen name="history_tab_indicator_height">50dp</dimen>
 
-    <!-- We need to maintain height for the tab widget on History Page
-         since android does not add footer/header divider height to its
-         calculation for wrap_content in LinearLayout.
-         50dp * 2 Views + 30dp padding + 4dp dividers-->
-    <dimen name="history_tab_widget_height">134dp</dimen>
-
     <!-- PageActionButtons dimensions -->
     <dimen name="page_action_button_width">32dp</dimen>
 
     <!-- Banner -->
     <dimen name="home_banner_height">72dp</dimen>
 
     <!-- Icon Grid -->
     <dimen name="icongrid_columnwidth">128dp</dimen>
--- a/mobile/android/base/resources/values/fxaccount_styles.xml
+++ b/mobile/android/base/resources/values/fxaccount_styles.xml
@@ -11,40 +11,40 @@
 
     <style name="FxAccountTheme.FxAccountStatusActivity" parent="@style/FxAccountTheme">
         <item name="android:windowNoTitle">false</item>
     </style>
 
     <style name="FxAccountMiddle">
         <item name="android:background">@android:color/white</item>
         <item name="android:orientation">vertical</item>
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_weight">1</item>
         <item name="android:paddingTop">30dp</item>
         <item name="android:paddingLeft">20dp</item>
         <item name="android:paddingRight">20dp</item>
         <item name="android:paddingBottom">30dp</item>
     </style>
 
     <style name="FxAccountSpacer">
         <item name="android:orientation">vertical</item>
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">0dp</item>
         <item name="android:layout_weight">1</item>
     </style>
 
     <style name="FxAccountHeaderItem" parent="@style/FxAccountTextItem">
         <item name="android:textSize">28sp</item>
         <item name="android:layout_marginBottom">20dp</item>
     </style>
 
     <style name="FxAccountTextItem" parent="@android:style/TextAppearance.Medium">
         <item name="android:textColor">@color/fxaccount_textColor</item>
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center_horizontal</item>
         <item name="android:textSize">16sp</item>
         <item name="android:layout_marginBottom">10dp</item>
     </style>
 
     <style name="FxAccountLinkItem" parent="@style/FxAccountTextItem">
         <item name="android:clickable">true</item>
@@ -52,17 +52,17 @@
         <item name="android:textColor">@drawable/fxaccount_linkitem_textcolor</item>
     </style>
 
     <style name="FxAccountButton" parent="@android:style/Widget.Button">
         <item name="android:background">@drawable/fxaccount_button_background</item>
         <item name="android:textColor">@drawable/fxaccount_button_color</item>
         <item name="android:textSize">24sp</item>
         <item name="android:padding">20dp</item>
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">10dp</item>
     </style>
 
     <style name="FxAccountEditItem" parent="@android:style/Widget.EditText">
         <item name="android:textSize">20sp</item>
         <item name="android:paddingLeft">@dimen/fxaccount_input_padding_horizontal</item>
         <item name="android:paddingRight">@dimen/fxaccount_input_padding_horizontal</item>
@@ -76,31 +76,31 @@
         <item name="android:textColorHint">@color/fxaccount_input_textColorHint</item>
     </style>
 
     <style name="FxAccountLinkifiedItem" parent="@style/FxAccountTextItem">
         <item name="android:clickable">true</item>
         <item name="android:focusable">true</item>
         <item name="android:textColor">@color/fxaccount_linkified_textColor</item>
         <item name="android:textColorLink">@color/fxaccount_linkified_textColorLink</item>
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center</item>
     </style>
 
     <style name="FxAccountIcon">
         <item name="android:layout_marginTop">20dp</item>
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_gravity">center_horizontal</item>
         <item name="android:src">@drawable/icon</item>
     </style>
 
     <style name="FxAccountErrorItem">
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_marginBottom">10dp</item>
         <item name="android:layout_marginTop">10dp</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:gravity">center_horizontal</item>
         <item name="android:background">@drawable/fxaccount_textview_error_background</item>
         <item name="android:padding">5dp</item>
         <item name="android:text">Error</item>
         <item name="android:textColor">@android:color/white</item>
@@ -113,23 +113,23 @@
         <item name="android:layout_width">wrap_content</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_centerInParent">true</item>
         <item name="android:visibility">invisible</item>
     </style>
 
     <style name="FxAccountButtonLayout">
         <item name="android:orientation">vertical</item>
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:background">@drawable/fxaccount_button_background</item>
     </style>
 
     <style name="FxAccountCheckBox">
-        <item name="android:layout_width">match_parent</item>
+        <item name="android:layout_width">fill_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_marginBottom">10dp</item>
         <item name="android:textColor">@drawable/fxaccount_checkbox_textcolor</item>
     </style>
 
     <style name="FxAccountShowHidePasswordButton" parent="@android:style/Widget.Button">
         <item name="android:background">@drawable/fxaccount_password_button_show_background</item>
         <item name="android:minHeight">0dp</item>
--- a/mobile/android/base/resources/values/sync_styles.xml
+++ b/mobile/android/base/resources/values/sync_styles.xml
@@ -1,35 +1,35 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
 <resources>
   <style name="SyncLayout">
-    <item name="android:layout_width">match_parent</item>
-    <item name="android:layout_height">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
+    <item name="android:layout_height">fill_parent</item>
   </style>
   <style name="SyncLayout.Vertical" parent="@style/SyncLayout">
     <item name="android:orientation">vertical</item>
   </style>
   <style name="SyncLayout.Horizontal" parent="@style/SyncLayout">
     <item name="android:orientation">horizontal</item>
   </style>
 
   <!-- TextView Styles -->
   <style name="SyncTextFrame" parent="@style/TextAppearance">
-    <item name="android:layout_width">match_parent</item>
-    <item name="android:layout_height">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
+    <item name="android:layout_height">fill_parent</item>
     <item name="android:layout_gravity">center</item>
     <item name="android:padding">@dimen/SyncSpace</item>
     <item name="android:orientation">vertical</item>
   </style>
   <style name="SyncTextItem" parent="@style/TextAppearance.Medium">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:textSize">15dp</item>
   </style>
   <style name="SyncTextError" parent="@style/SyncTextItem">
     <item name="android:textSize">15dp</item>
     <item name="android:visibility">gone</item>
   </style>
   <style name="SyncLinkItem" parent="@style/SyncTextItem">
@@ -39,17 +39,17 @@
   <style name="SyncTextTitle" parent="@style/SyncTextItem">
     <item name="android:layout_gravity">center_vertical</item>
     <item name="android:paddingLeft">4dp</item>
     <item name="android:gravity">left</item>
     <item name="android:textSize">20dp</item>
   </style>
   <!-- EditView Styles -->
   <style name="SyncEditItem" parent="@style/Widget.EditText">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:singleLine">true</item>
     <item name="android:inputType">text|textNoSuggestions</item>
   </style>
   <style name="SyncEditPin" parent="@style/SyncEditItem">
     <item name="android:layout_width">wrap_content</item>
     <item name="android:gravity">center_horizontal</item>
     <item name="android:ems">4</item>
@@ -62,54 +62,54 @@
 
   <!-- Outer container: a container with top, middle, and bottom
        content in it. -->
   <style name="SyncContainer" parent="@style/SyncLayout.Vertical" />
 
   <!-- Top title bar: a text view with the Sync icon to the left. -->
   <style name="SyncTop">
     <item name="android:textAppearance">@style/TextAppearance.Large</item>
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:gravity">center_vertical|left</item>
     <item name="android:drawableLeft">@drawable/icon</item>
     <item name="android:padding">4dp</item>
     <item name="android:drawablePadding">4dp</item>
     <item name="android:background">@android:drawable/bottom_bar</item>
   </style>
 
   <dimen name="SyncSpace">20dp</dimen>
 
   <!-- Middle scroller: a scroll view with content in it. -->
   <style name="SyncMiddle">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">0dp</item>
     <item name="android:layout_weight">1</item>
     <item name="android:padding">@dimen/SyncSpace</item>
   </style>
 
   <!-- Bottom bar: a horizontal linear layout with buttons in it. -->
   <style name="SyncBottom">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:layout_gravity">center</item>
     <item name="android:gravity">center</item>
     <item name="android:background">@android:drawable/bottom_bar</item>
   </style>
 
   <style name="SyncBottomContainer">
     <item name="android:layout_width">match_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:divider">@android:drawable/divider_horizontal_bright</item>
     <item name="android:showDividers">beginning</item>
     <item name="android:orientation">vertical</item>
   </style>
 
   <style name="SyncButton" parent="@style/Widget.Button">
-    <item name="android:layout_width">match_parent</item>
+    <item name="android:layout_width">fill_parent</item>
     <item name="android:layout_height">wrap_content</item>
     <item name="android:layout_weight">1</item>
   </style>
 
   <!-- Text Display Styles -->
   <style name="SyncPinText" parent="@style/Widget.TextView">
     <item name="android:layout_width">130sp</item>
     <item name="android:layout_height">wrap_content</item>
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -5,16 +5,21 @@
     <PreferenceCategory
         android:key="signed_in_as_category"
         android:title="@string/fxaccount_status_signed_in_as" >
         <Preference
             android:editable="false"
             android:key="email"
             android:persistent="false"
             android:title="@string/fxaccount_email_hint" />
+        <Preference
+            android:editable="false"
+            android:key="auth_server"
+            android:persistent="false"
+            android:title="@string/fxaccount_status_auth_server" />
     </PreferenceCategory>
     <PreferenceCategory
         android:key="sync_category"
         android:title="@string/fxaccount_status_sync" >
         <Preference
             android:editable="false"
             android:icon="@drawable/fxaccount_sync_error"
             android:key="needs_credentials"
@@ -67,16 +72,23 @@
             android:persistent="false"
             android:title="@string/fxaccount_status_passwords" />
 
         <EditTextPreference
             android:singleLine="true"
             android:key="device_name"
             android:persistent="false"
             android:title="@string/fxaccount_status_device_name" />
+
+		<Preference
+            android:editable="false"
+            android:key="sync_server"
+            android:persistent="false"
+            android:title="@string/fxaccount_status_sync_server" />
+
     </PreferenceCategory>
     <PreferenceCategory
         android:key="legal_category"
         android:title="@string/fxaccount_status_legal" >
         <Preference android:title="@string/fxaccount_status_linktos" >
             <intent
                 android:action="android.intent.action.VIEW"
                 android:data="@string/fxaccount_link_tos"
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -31,16 +31,17 @@
   <string name="android_package_name_for_ui">@ANDROID_PACKAGE_NAME@</string>
 #include ../services/strings.xml.in
   <string name="no_space_to_start_error">&no_space_to_start_error;</string>
   <string name="error_loading_file">&error_loading_file;</string>
 
   <string name="bookmarks_title">&bookmarks_title;</string>
   <string name="history_title">&history_title;</string>
   <string name="reading_list_title">&reading_list_title;</string>
+  <string name="recent_tabs_title">&recent_tabs_title;</string>
 
   <string name="switch_to_tab">&switch_to_tab;</string>
 
   <string name="crash_reporter_title">&crash_reporter_title;</string>
   <string name="crash_message2">&crash_message2;</string>
   <string name="crash_send_report_message3">&crash_send_report_message3;</string>
   <string name="crash_include_url2">&crash_include_url2;</string>
   <string name="crash_sorry">&crash_sorry;</string>
@@ -308,20 +309,20 @@
   <string name="button_no">&button_no;</string>
 
   <string name="home_top_sites_title">&home_top_sites_title;</string>
   <string name="home_top_sites_add">&home_top_sites_add;</string>
   <string name="home_history_title">&home_history_title;</string>
   <string name="home_clear_history_button">&home_clear_history_button;</string>
   <string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
   <string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
+  <string name="home_closed_tabs_title">&home_closed_tabs_title;</string>
   <string name="home_last_tabs_title">&home_last_tabs_title;</string>
   <string name="home_last_tabs_open">&home_last_tabs_open;</string>
   <string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
-  <string name="home_most_recent_title">&home_most_recent_title;</string>
   <string name="home_most_recent_empty">&home_most_recent_empty;</string>
   <string name="home_reading_list_empty">&home_reading_list_empty;</string>
   <string name="home_reading_list_hint">&home_reading_list_hint2;</string>
   <string name="home_reading_list_hint_accessible">&home_reading_list_hint_accessible;</string>
   <string name="home_default_empty">&home_default_empty;</string>
   <string name="home_move_up_to_filter">&home_move_up_to_filter;</string>
   <string name="private_browsing_title">&private_browsing_title;</string>
   <string name="private_tabs_panel_description">&private_tabs_panel_description;</string>
--- a/mobile/android/base/sync/SyncConfiguration.java
+++ b/mobile/android/base/sync/SyncConfiguration.java
@@ -467,17 +467,17 @@ public class SyncConfiguration {
     if (syncID != null) {
       edit.putString(PREF_SYNC_ID, syncID);
     }
     if (enabledEngineNames == null) {
       edit.remove(PREF_ENABLED_ENGINE_NAMES);
     } else {
       edit.putString(PREF_ENABLED_ENGINE_NAMES, setToJSONObjectString(enabledEngineNames));
     }
-    if (declinedEngineNames.isEmpty()) {
+    if (declinedEngineNames == null || declinedEngineNames.isEmpty()) {
       edit.remove(PREF_DECLINED_ENGINE_NAMES);
     } else {
       edit.putString(PREF_DECLINED_ENGINE_NAMES, setToJSONObjectString(declinedEngineNames));
     }
     if (userSelectedEngines == null) {
       edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC);
       edit.remove(PREF_USER_SELECTED_ENGINES_TO_SYNC_TIMESTAMP);
     }
--- a/mobile/android/base/sync/jpake/stage/DecryptDataStage.java
+++ b/mobile/android/base/sync/jpake/stage/DecryptDataStage.java
@@ -39,17 +39,17 @@ public class DecryptDataStage extends JP
     } catch (NonObjectJSONException e1) {
       Logger.error(LOG_TAG, "Invalid round 3 data.", e1);
       jClient.abort(Constants.JPAKE_ERROR_WRONGMESSAGE);
       return;
     }
     Logger.debug(LOG_TAG, "Decrypting data.");
     String cleartext = null;
     try {
-      cleartext = new String(decryptPayload(iPayload, jClient.myKeyBundle), "UTF-8");
+      cleartext = new String(decryptPayload(iPayload, jClient.myKeyBundle), "UTF-8");
     } catch (UnsupportedEncodingException e) {
       Logger.error(LOG_TAG, "Failed to decrypt data.", e);
       jClient.abort(Constants.JPAKE_ERROR_INTERNAL);
       return;
     } catch (CryptoException e) {
       Logger.error(LOG_TAG, "Failed to decrypt data.", e);
       jClient.abort(Constants.JPAKE_ERROR_KEYMISMATCH);
       return;
--- a/mobile/android/base/tests/AboutHomeTest.java
+++ b/mobile/android/base/tests/AboutHomeTest.java
@@ -21,34 +21,43 @@ import com.jayway.android.robotium.solo.
 
 /**
  * This class is an extension of BaseTest that helps with interaction with about:home
  * This class contains methods that access the different tabs from about:home, methods that get information like history and bookmarks from the database, edit and remove bookmarks and history items
  * The purpose of this class is to collect all the logically connected methods that deal with about:home
  * To use any of these methods in your test make sure it extends AboutHomeTest instead of BaseTest
  */
 abstract class AboutHomeTest extends PixelTest {
-    protected enum AboutHomeTabs {HISTORY, MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST};
+    protected enum AboutHomeTabs {
+        RECENT_TABS,
+        HISTORY,
+        TOP_SITES,
+        BOOKMARKS,
+        READING_LIST
+    };
+
     private ArrayList<String> aboutHomeTabs = new ArrayList<String>() {{
                   add("TOP_SITES");
                   add("BOOKMARKS");
                   add("READING_LIST");
               }};
 
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
 
         if (aboutHomeTabs.size() < 4) {
             // Update it for tablets vs. phones.
             if (mDevice.type.equals("phone")) {
                 aboutHomeTabs.add(0, AboutHomeTabs.HISTORY.toString());
+                aboutHomeTabs.add(0, AboutHomeTabs.RECENT_TABS.toString());
             } else {
                 aboutHomeTabs.add(AboutHomeTabs.HISTORY.toString());
+                aboutHomeTabs.add(AboutHomeTabs.RECENT_TABS.toString());
             }
         }
     }
 
     /**
      * FIXME: Write new versions of these methods and update their consumers to use the new about:home pages.
      */
     protected ListView getHistoryList(String waitText, int expectedChildCount) {
@@ -218,86 +227,28 @@ abstract class AboutHomeTest extends Pix
                 mSolo.sleep(100);
             }
         }
     }
 
     /**
      * This method can be used to open the different tabs of about:home.
      *
-     * @param AboutHomeTabs enum item {MOST_RECENT, TABS_FROM_LAST_TIME, TOP_SITES, BOOKMARKS, READING_LIST}
+     * @param AboutHomeTabs enum item
      */
     protected void openAboutHomeTab(AboutHomeTabs tab) {
         focusUrlBar();
         ViewPager pager = (ViewPager)mSolo.getView(ViewPager.class, 0);
         final int currentTabIndex = pager.getCurrentItem();
         int tabOffset;
 
         // Handle tablets by just clicking the visible tab title.
         if (mDevice.type.equals("tablet")) {
-            if (AboutHomeTabs.MOST_RECENT == tab || AboutHomeTabs.TABS_FROM_LAST_TIME == tab) {
-                tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
-                swipeAboutHome(tabOffset);
-                waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
-                TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
-
-                switch (tab) {
-                    case MOST_RECENT: {
-                        mSolo.clickOnView(tabwidget.getChildAt(0));
-                        // We can determine if we are on the MOST_RECENT tab only if pages were first visited during the test
-                        mAsserter.ok(waitForText(StringHelper.TODAY_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
-                        break;
-                    }
-                    case TABS_FROM_LAST_TIME: {
-                        mSolo.clickOnView(tabwidget.getChildAt(1));
-                        mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
-                        break;
-                    }
-                }
-            } else {
-                clickAboutHomeTab(tab);
-            }
+            clickAboutHomeTab(tab);
             return;
         }
 
         // Handle phones (non-tablets).
         tabOffset = aboutHomeTabs.indexOf(tab.toString()) - currentTabIndex;
-        switch (tab) {
-            case TOP_SITES : {
-                swipeAboutHome(tabOffset);
-                waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
-                break;
-            }
-            case BOOKMARKS : {
-                swipeAboutHome(tabOffset);
-                waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
-                break;
-            }
-            case MOST_RECENT: {
-                // MOST_RECENT is contained in the HISTORY tab.
-                tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
-                swipeAboutHome(tabOffset);
-                waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
-                TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
-                mSolo.clickOnView(tabwidget.getChildAt(0));
-                // We can determine if we are on the MOST_RECENT tab only if pages were first visited during the test
-                mAsserter.ok(waitForText(StringHelper.TODAY_LABEL), "Checking that we are in the most recent tab of about:home", "We are in the most recent tab");
-                break;
-            }
-            case TABS_FROM_LAST_TIME: {
-                // TABS_FROM_LAST_TIME is contained in the HISTORY tab.
-                tabOffset = aboutHomeTabs.indexOf(AboutHomeTabs.HISTORY.toString()) - currentTabIndex;
-                swipeAboutHome(tabOffset);
-                waitForAboutHomeTab(aboutHomeTabs.indexOf(StringHelper.HISTORY_LABEL));
-                TabWidget tabwidget = (TabWidget)mSolo.getView(TabWidget.class, 0);
-                mSolo.clickOnView(tabwidget.getChildAt(1));
-                mAsserter.ok(waitForText(StringHelper.TABS_FROM_LAST_TIME_LABEL), "Checking that we are in the Tabs from last time tab of about:home", "We are in the Tabs from last time tab");
-                break;
-            }
-            case READING_LIST: {
-                swipeAboutHome(tabOffset);
-                waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
-                break;
-            }
-
-        }
+        swipeAboutHome(tabOffset);
+        waitForAboutHomeTab(aboutHomeTabs.indexOf(tab.toString()));
     }
 }
--- a/mobile/android/base/tests/components/AboutHomeComponent.java
+++ b/mobile/android/base/tests/components/AboutHomeComponent.java
@@ -25,35 +25,38 @@ import com.jayway.android.robotium.solo.
 public class AboutHomeComponent extends BaseComponent {
     private static final String LOGTAG = AboutHomeComponent.class.getSimpleName();
 
     // The different types of panels that can be present on about:home
     public enum PanelType {
         HISTORY,
         TOP_SITES,
         BOOKMARKS,
-        READING_LIST
+        READING_LIST,
+        RECENT_TABS
     }
 
     // TODO: Having a specific ordering of panels is prone to fail and thus temporary.
     // Hopefully the work in bug 940565 will alleviate the need for these enums.
     // Explicit ordering of HomePager panels on a phone.
     private enum PhonePanel {
+        RECENT_TABS,
         HISTORY,
         TOP_SITES,
         BOOKMARKS,
         READING_LIST
     }
 
     // Explicit ordering of HomePager panels on a tablet.
     private enum TabletPanel {
         TOP_SITES,
         BOOKMARKS,
         READING_LIST,
-        HISTORY
+        HISTORY,
+        RECENT_TABS
     }
 
     // The percentage of the panel to swipe between 0 and 1. This value was set through
     // testing: 0.55f was tested on try and fails on armv6 devices.
     private static final float SWIPE_PERCENTAGE = 0.70f;
 
     public AboutHomeComponent(final UITestContext testContext) {
         super(testContext);
--- a/mobile/android/base/tests/testAboutHomePageNavigation.java
+++ b/mobile/android/base/tests/testAboutHomePageNavigation.java
@@ -1,16 +1,18 @@
 package org.mozilla.gecko.tests;
 
 import org.mozilla.gecko.tests.components.AboutHomeComponent.PanelType;
 import org.mozilla.gecko.tests.helpers.DeviceHelper;
 import org.mozilla.gecko.tests.helpers.GeckoHelper;
 
 /**
  * Tests functionality related to navigating between the various about:home panels.
+ *
+ * TODO: Update this test to account for recent tabs panel (bug 1028727).
  */
 public class testAboutHomePageNavigation extends UITest {
     // TODO: Define this test dynamically by creating dynamic representations of the Page
     // enum for both phone and tablet, then swiping through the panels. This will also
     // benefit having a HomePager with custom panels.
     public void testAboutHomePageNavigation() {
         GeckoHelper.blockForDelayedStartup();
 
--- a/mobile/android/base/tests/testHistory.java
+++ b/mobile/android/base/tests/testHistory.java
@@ -16,19 +16,19 @@ public class testHistory extends AboutHo
 
         inputAndLoadUrl(url);
         verifyPageTitle("Browser Blank Page 01");
         inputAndLoadUrl(url2);
         verifyPageTitle("Browser Blank Page 02");
         inputAndLoadUrl(url3);
         verifyPageTitle("Browser Blank Page 03");
 
-        openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
+        openAboutHomeTab(AboutHomeTabs.HISTORY);
 
-        final ListView hList = findListViewWithTag("most_recent");
+        final ListView hList = findListViewWithTag("history");
         mAsserter.is(waitForNonEmptyListToLoad(hList), true, "list is properly loaded");
 
         // Click on the history item and wait for the page to load
         // wait for the history list to be populated
         mFirstChild = null;
         boolean success = waitForTest(new BooleanTest() {
             @Override
             public boolean test() {
--- a/mobile/android/base/tests/testReaderMode.java
+++ b/mobile/android/base/tests/testReaderMode.java
@@ -101,18 +101,18 @@ public class testReaderMode extends Abou
         mSolo.clickOnScreen(width,height);
         contentEventExpecter.blockForEvent();
         contentEventExpecter.unregisterListener();
 
         // Check if the page is present in the Reading List
         mAsserter.ok(mSolo.waitForText("Robocop Text Page"), "Verify if the page is added to your Reading List", "The page is present in your Reading List");
 
         // Check if the page is added in History tab like a Reading List item
-        openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
-        list = findListViewWithTag("most_recent");
+        openAboutHomeTab(AboutHomeTabs.HISTORY);
+        list = findListViewWithTag("history");
         child = list.getChildAt(1);
         mAsserter.ok(child != null, "item can be retrieved", child != null ? child.toString() : "null!");
         mSolo.clickLongOnView(child);
         mAsserter.ok(mSolo.waitForText("Open in Reader"), "Verify if the page is present in history as a Reading List item", "The page is present in history as a Reading List item");
         mActions.sendSpecialKey(Actions.SpecialKey.BACK); // Dismiss the context menu
         mSolo.waitForText("Robocop Text Page");
 
         // Verify separately the Reading List entries for tablets and phone because for tablets there is an extra child in UI design
--- a/mobile/android/base/tests/testShareLink.java
+++ b/mobile/android/base/tests/testShareLink.java
@@ -114,20 +114,20 @@ public class testShareLink extends About
         mActions.drag(width / 2, width / 2, height - 10, height / 2);
 
         ListView topSitesList = findListViewWithTag("top_sites");
         mAsserter.is(waitForNonEmptyListToLoad(topSitesList), true, "list is properly loaded");
         View mostVisitedItem = topSitesList.getChildAt(topSitesList.getHeaderViewsCount());
         mSolo.clickLongOnView(mostVisitedItem);
         verifySharePopup(shareOptions,"top_sites");
 
-        // Test the share popup in the Most Recent tab
-        openAboutHomeTab(AboutHomeTabs.MOST_RECENT);
+        // Test the share popup in the history tab
+        openAboutHomeTab(AboutHomeTabs.HISTORY);
 
-        ListView mostRecentList = findListViewWithTag("most_recent");
+        ListView mostRecentList = findListViewWithTag("history");
         mAsserter.is(waitForNonEmptyListToLoad(mostRecentList), true, "list is properly loaded");
 
         // Getting second child after header views because the first is the "Today" label
         View mostRecentItem = mostRecentList.getChildAt(mostRecentList.getHeaderViewsCount() + 1);
         mSolo.clickLongOnView(mostRecentItem);
         verifySharePopup(shareOptions,"most recent");
     }
 
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -38,23 +38,26 @@ SessionStore.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISessionStore,
                                          Ci.nsIDOMEventListener,
                                          Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
   _windows: {},
   _lastSaveTime: 0,
   _interval: 10000,
-  _maxTabsUndo: 1,
+  _maxTabsUndo: 5,
   _pendingWrite: 0,
 
   // The index where the most recently closed tab was in the tabs array
   // when it was closed.
   _lastClosedTabIndex: -1,
 
+  // Whether or not to send notifications for changes to the closed tabs.
+  _notifyClosedTabs: false,
+
   init: function ss_init() {
     // Get file references
     this._sessionFile = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
     this._sessionFileBackup = this._sessionFile.clone();
     this._sessionFile.append("sessionstore.js");
     this._sessionFileBackup.append("sessionstore.bak");
 
     this._loadState = STATE_STOPPED;
@@ -74,16 +77,18 @@ SessionStore.prototype = {
     switch (aTopic) {
       case "app-startup":
         observerService.addObserver(this, "final-ui-startup", true);
         observerService.addObserver(this, "domwindowopened", true);
         observerService.addObserver(this, "domwindowclosed", true);
         observerService.addObserver(this, "browser:purge-session-history", true);
         observerService.addObserver(this, "Session:Restore", true);
         observerService.addObserver(this, "application-background", true);
+        observerService.addObserver(this, "ClosedTabs:StartNotifications", true);
+        observerService.addObserver(this, "ClosedTabs:StopNotifications", true);
         break;
       case "final-ui-startup":
         observerService.removeObserver(this, "final-ui-startup");
         this.init();
         break;
       case "domwindowopened": {
         let window = aSubject;
         window.addEventListener("load", function() {
@@ -152,16 +157,23 @@ SessionStore.prototype = {
       }
       case "application-background":
         // We receive this notification when Android's onPause callback is
         // executed. After onPause, the application may be terminated at any
         // point without notice; therefore, we must synchronously write out any
         // pending save state to ensure that this data does not get lost.
         this.flushPendingState();
         break;
+      case "ClosedTabs:StartNotifications":
+        this._notifyClosedTabs = true;
+        this._sendClosedTabsToJava(Services.wm.getMostRecentWindow("navigator:browser"));
+        break;
+      case "ClosedTabs:StopNotifications":
+        this._notifyClosedTabs = false;
+        break;
     }
   },
 
   handleEvent: function ss_handleEvent(aEvent) {
     let window = aEvent.currentTarget.ownerDocument.defaultView;
     switch (aEvent.type) {
       case "TabOpen": {
         let browser = aEvent.target;
@@ -286,16 +298,20 @@ SessionStore.prototype = {
       data.extData = aBrowser.__SS_extdata;
 
       this._windows[aWindow.__SSID].closedTabs.unshift(data);
       let length = this._windows[aWindow.__SSID].closedTabs.length;
       if (length > this._maxTabsUndo)
         this._windows[aWindow.__SSID].closedTabs.splice(this._maxTabsUndo, length - this._maxTabsUndo);
 
       this._lastClosedTabIndex = aTabIndex;
+
+      if (this._notifyClosedTabs) {
+        this._sendClosedTabsToJava(aWindow);
+      }
     }
   },
 
   onTabLoad: function ss_onTabLoad(aWindow, aBrowser) {
     // If this browser is being restored, skip any session save activity
     if (aBrowser.__SS_restore)
       return;
 
@@ -859,16 +875,20 @@ SessionStore.prototype = {
     let tab = aWindow.BrowserApp.addTab(closedTab.entries[closedTab.index - 1].url, params);
     this._restoreHistory(closedTab, tab.browser.sessionHistory);
 
     this._lastClosedTabIndex = -1;
 
     // Put back the extra data
     tab.browser.__SS_extdata = closedTab.extData;
 
+    if (this._notifyClosedTabs) {
+      this._sendClosedTabsToJava(aWindow);
+    }
+
     return tab.browser;
   },
 
   forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
     if (!aWindow.__SSID)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     let closedTabs = this._windows[aWindow.__SSID].closedTabs;
@@ -880,16 +900,40 @@ SessionStore.prototype = {
 
     // remove closed tab from the array
     closedTabs.splice(aIndex, 1);
 
     // Forget the last closed tab index if we're forgetting the last closed tab.
     if (aIndex == 0) {
       this._lastClosedTabIndex = -1;
     }
+    if (this._notifyClosedTabs) {
+      this._sendClosedTabsToJava(aWindow);
+    }
+  },
+
+  _sendClosedTabsToJava: function ss_sendClosedTabsToJava(aWindow) {
+    if (!aWindow.__SSID)
+      throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+    let closedTabs = this._windows[aWindow.__SSID].closedTabs;
+
+    let tabs = closedTabs.map(function (tab) {
+      // Get the url and title for the last entry in the session history.
+      let lastEntry = tab.entries[tab.entries.length - 1];
+      return {
+        url: lastEntry.url,
+        title: lastEntry.title || ""
+      };
+    });
+
+    sendMessageToJava({
+      type: "ClosedTabs:Data",
+      tabs: tabs
+    });
   },
 
   getTabValue: function ss_getTabValue(aTab, aKey) {
     let browser = aTab.browser;
     let data = browser.__SS_extdata || {};
     return data[aKey] || "";
   },
 
--- a/mobile/android/modules/Accounts.jsm
+++ b/mobile/android/modules/Accounts.jsm
@@ -60,17 +60,21 @@ let Accounts = Object.freeze({
   anySyncAccountsExist: function () {
     return this._accountsExist("any");
   },
 
   /**
    * Fire-and-forget: open the Firefox accounts activity, which
    * will be the Getting Started screen if FxA isn't yet set up.
    *
+   * Optional extras are passed, as a JSON string, to the Firefox
+   * Account Getting Started activity in the extras bundle of the
+   * activity launch intent, under the key "extras".
+   *
    * There is no return value from this method.
    */
-  launchSetup: function () {
+  launchSetup: function (extras) {
     sendMessageToJava({
       type: "Accounts:Create",
+      extras: extras,
     });
   },
 });
-
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -112,16 +112,19 @@
 <string name="fxaccount_link_tos">https://accounts.firefox.com/legal/terms</string>
 <string name="fxaccount_link_pn">https://accounts.firefox.com/legal/privacy</string>
 <string name="fxaccount_link_forgot_password">https://accounts.firefox.com/reset_password</string>
 
 <!-- Per Bug 974627, decorative images should not have non-empty
      contentDescription text, and it should not be translated. -->
 <string name="fxaccount_empty_contentDescription"></string>
 
+<string name="fxaccount_custom_server_account_title">&fxaccount_custom_server_account_title;</string>
+<string name="fxaccount_custom_server_sync_title">&fxaccount_custom_server_sync_title;</string>
+
 <string name="fxaccount_email_hint">&fxaccount_email_hint;</string>
 <string name="fxaccount_password_hint">&fxaccount_password_hint;</string>
 <string name="fxaccount_password_hide">&fxaccount_password_hide;</string>
 <string name="fxaccount_password_show">&fxaccount_password_show;</string>
 
 <string name="fxaccount_back_to_browsing">&fxaccount_back_to_browsing;</string>
 
 <string name="fxaccount_policy_linktos">&fxaccount_policy_linktos;</string>
@@ -164,17 +167,19 @@
 
 <string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
 <string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
 <string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
 
 <string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
 <string name="fxaccount_status_header">&fxaccount_status_header2;</string>
 <string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
+<string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
 <string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
+<string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
 <string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
 <string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
 <string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
 <string name="fxaccount_status_needs_credentials">&fxaccount_status_needs_credentials;</string>
 <string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
 <string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
 <string name="fxaccount_status_needs_account_enabled">&fxaccount_status_needs_account_enabled;</string>
 <string name="fxaccount_status_bookmarks">&fxaccount_status_bookmarks;</string>
--- a/mobile/android/tests/background/junit3/android-services-files.mk
+++ b/mobile/android/tests/background/junit3/android-services-files.mk
@@ -42,16 +42,17 @@ BACKGROUND_TESTS_JAVA_FILES := \
   src/healthreport/upload/TestAndroidSubmissionClient.java \
   src/healthreport/upload/TestHealthReportUploadService.java \
   src/helpers/AndroidSyncTestCase.java \
   src/helpers/BackgroundServiceTestCase.java \
   src/helpers/DBHelpers.java \
   src/helpers/DBProviderTestCase.java \
   src/helpers/FakeProfileTestCase.java \
   src/nativecode/test/TestNativeCrypto.java \
+  src/sync/AndroidSyncTestCaseWithAccounts.java \
   src/sync/helpers/BookmarkHelpers.java \
   src/sync/helpers/DefaultBeginDelegate.java \
   src/sync/helpers/DefaultCleanDelegate.java \
   src/sync/helpers/DefaultDelegate.java \
   src/sync/helpers/DefaultFetchDelegate.java \
   src/sync/helpers/DefaultFinishDelegate.java \
   src/sync/helpers/DefaultGuidsSinceDelegate.java \
   src/sync/helpers/DefaultSessionCreationDelegate.java \
--- a/mobile/android/tests/background/junit3/res/layout/main.xml
+++ b/mobile/android/tests/background/junit3/res/layout/main.xml
@@ -1,12 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
     android:orientation="vertical" >
 
     <TextView
-        android:layout_width="match_parent"
+        android:layout_width="fill_parent"
         android:layout_height="wrap_content"
         android:text="@string/app_name" />
 
 </LinearLayout>
--- a/mobile/android/tests/background/junit3/src/fxa/authenticator/TestAccountPickler.java
+++ b/mobile/android/tests/background/junit3/src/fxa/authenticator/TestAccountPickler.java
@@ -1,118 +1,132 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.fxa.authenticator;
 
-import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
-import org.mozilla.gecko.background.sync.TestSyncAccounts;
+import org.mozilla.gecko.background.sync.AndroidSyncTestCaseWithAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Separated;
 import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.test.InstrumentationTestCase;
 import android.test.RenamingDelegatingContext;
 
-public class TestAccountPickler extends AndroidSyncTestCase {
+public class TestAccountPickler extends AndroidSyncTestCaseWithAccounts {
+  private static final String TEST_TOKEN_SERVER_URI = "tokenServerURI";
+  private static final String TEST_AUTH_SERVER_URI = "serverURI";
+  private static final String TEST_PROFILE = "profile";
   private final static String FILENAME_PREFIX = "TestAccountPickler-";
   private final static String PICKLE_FILENAME = "pickle";
 
+  private final static String TEST_ACCOUNTTYPE = FxAccountConstants.ACCOUNT_TYPE;
+
+  // Test account names must start with TEST_USERNAME in order to be recognized
+  // as test accounts and deleted in tearDown.
+  public static final String TEST_USERNAME   = "testFirefoxAccount@mozilla.com";
+
   public Account account;
   public RenamingDelegatingContext context;
-  public AccountManager accountManager;
+
+  public TestAccountPickler() {
+    super(TEST_ACCOUNTTYPE, TEST_USERNAME);
+  }
 
   @Override
   public void setUp() {
+    super.setUp();
     this.account = null;
     // Randomize the filename prefix in case we don't clean up correctly.
     this.context = new RenamingDelegatingContext(getApplicationContext(), FILENAME_PREFIX +
         Math.random() * 1000001 + "-");
     this.accountManager = AccountManager.get(context);
   }
 
   @Override
   public void tearDown() {
-    if (this.account != null) {
-      deleteAccount(this, this.accountManager, this.account);
-      this.account = null;
-    }
+    super.tearDown();
     this.context.deleteFile(PICKLE_FILENAME);
   }
 
-  public static void deleteAccount(final InstrumentationTestCase test,
-      final AccountManager accountManager, final Account account) {
-    TestSyncAccounts.deleteAccount(test, accountManager, account);
-  }
-
-  private boolean accountsExist() {
-    // Note that we don't use FirefoxAccounts.firefoxAccountsExist because it unpickles.
-    return AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE).length > 0;
-  }
-
-  public AndroidFxAccount addDummyAccount() throws Exception {
-    final String email = "iu@fakedomain.io";
-    final State state = new Separated(email, "uid", false); // State choice is arbitrary.
-    final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, email,
-        "profile", "serverURI", "tokenServerURI", state);
+  public AndroidFxAccount addTestAccount() throws Exception {
+    final State state = new Separated(TEST_USERNAME, "uid", false); // State choice is arbitrary.
+    final AndroidFxAccount account = AndroidFxAccount.addAndroidAccount(context, TEST_USERNAME,
+        TEST_PROFILE, TEST_AUTH_SERVER_URI, TEST_TOKEN_SERVER_URI, state);
     assertNotNull(account);
-    assertTrue(accountsExist()); // Sanity check.
+    assertNotNull(account.getProfile());
+    assertTrue(testAccountsExist()); // Sanity check.
     this.account = account.getAndroidAccount(); // To remove in tearDown() if we throw.
     return account;
   }
 
+  public void testPickle() throws Exception {
+    final AndroidFxAccount account = addTestAccount();
+    // Sync is enabled by default so we do a more thorough test by disabling it.
+    account.disableSyncing();
+
+    final long now = System.currentTimeMillis();
+    final ExtendedJSONObject o = AccountPickler.toJSON(account, now);
+
+    assertEquals(2, o.getLong(AccountPickler.KEY_PICKLE_VERSION).longValue());
+    assertTrue(o.getLong(AccountPickler.KEY_PICKLE_TIMESTAMP).longValue() < System.currentTimeMillis());
+
+    assertEquals(AndroidFxAccount.CURRENT_ACCOUNT_VERSION, o.getIntegerSafely(AccountPickler.KEY_ACCOUNT_VERSION).intValue());
+    assertEquals(FxAccountConstants.ACCOUNT_TYPE, o.getString(AccountPickler.KEY_ACCOUNT_TYPE));
+
+    assertEquals(TEST_USERNAME, o.getString(AccountPickler.KEY_EMAIL));
+    assertEquals(TEST_PROFILE, o.getString(AccountPickler.KEY_PROFILE));
+    assertEquals(TEST_AUTH_SERVER_URI, o.getString(AccountPickler.KEY_IDP_SERVER_URI));
+    assertEquals(TEST_TOKEN_SERVER_URI, o.getString(AccountPickler.KEY_TOKEN_SERVER_URI));
+
+    assertFalse(o.getBoolean(AccountPickler.KEY_IS_SYNCING_ENABLED));
+    assertNotNull(o.get(AccountPickler.KEY_BUNDLE));
+  }
+
   public void testPickleAndUnpickle() throws Exception {
-    final AndroidFxAccount inputAccount = addDummyAccount();
+    final AndroidFxAccount inputAccount = addTestAccount();
     // Sync is enabled by default so we do a more thorough test by disabling it.
     inputAccount.disableSyncing();
 
     AccountPickler.pickle(inputAccount, PICKLE_FILENAME);
+    final ExtendedJSONObject inputJSON = AccountPickler.toJSON(inputAccount, 0);
+    final State inputState = inputAccount.getState();
+    assertNotNull(inputJSON);
+    assertNotNull(inputState);
 
     // unpickle adds an account to the AccountManager so delete it first.
-    deleteAccount(this, this.accountManager, inputAccount.getAndroidAccount());
-    assertFalse(accountsExist());
+    deleteTestAccounts();
+    assertFalse(testAccountsExist());
 
-    final AndroidFxAccount unpickledAccount =
-        AccountPickler.unpickle(context, PICKLE_FILENAME);
+    final AndroidFxAccount unpickledAccount = AccountPickler.unpickle(context, PICKLE_FILENAME);
     assertNotNull(unpickledAccount);
-    this.account = unpickledAccount.getAndroidAccount(); // To remove in tearDown().
-    assertAccountsEquals(inputAccount, unpickledAccount);
+    final ExtendedJSONObject unpickledJSON = AccountPickler.toJSON(unpickledAccount, 0);
+    final State unpickledState = unpickledAccount.getState();
+    assertNotNull(unpickledJSON);
+    assertNotNull(unpickledState);
+
+    assertEquals(inputJSON, unpickledJSON);
+    assertStateEquals(inputState, unpickledState);
   }
 
   public void testDeletePickle() throws Exception {
-    final AndroidFxAccount account = addDummyAccount();
+    final AndroidFxAccount account = addTestAccount();
     AccountPickler.pickle(account, PICKLE_FILENAME);
 
     final String s = Utils.readFile(context, PICKLE_FILENAME);
     assertNotNull(s);
     assertTrue(s.length() > 0);
 
     AccountPickler.deletePickle(context, PICKLE_FILENAME);
-    org.mozilla.gecko.background.sync.TestAccountPickler.assertFileNotPresent(
-        context, PICKLE_FILENAME);
-  }
-
-  private void assertAccountsEquals(final AndroidFxAccount expected,
-      final AndroidFxAccount actual) throws Exception {
-    // TODO: Write and use AndroidFxAccount.equals
-    // TODO: protected.
-    //assertEquals(expected.getAccountVersion(), actual.getAccountVersion());
-    assertEquals(expected.getProfile(), actual.getProfile());
-    assertEquals(expected.getAccountServerURI(), actual.getAccountServerURI());
-    assertEquals(expected.getAudience(), actual.getAudience());
-    assertEquals(expected.getTokenServerURI(), actual.getTokenServerURI());
-    assertEquals(expected.getSyncPrefsPath(), actual.getSyncPrefsPath());
-    assertEquals(expected.isSyncing(), actual.isSyncing());
-    assertEquals(expected.getEmail(), actual.getEmail());
-    assertStateEquals(expected.getState(), actual.getState());
+    assertFileNotPresent(context, PICKLE_FILENAME);
   }
 
   private void assertStateEquals(final State expected, final State actual) throws Exception {
     // TODO: Write and use State.equals. Thus, this is only thorough for the State base class.
     assertEquals(expected.getStateLabel(), actual.getStateLabel());
     assertEquals(expected.email, actual.email);
     assertEquals(expected.uid, actual.uid);
     assertEquals(expected.verified, actual.verified);
--- a/mobile/android/tests/background/junit3/src/healthreport/prune/TestHealthReportPruneService.java
+++ b/mobile/android/tests/background/junit3/src/healthreport/prune/TestHealthReportPruneService.java
@@ -5,17 +5,16 @@ package org.mozilla.gecko.background.hea
 
 import java.util.concurrent.BrokenBarrierException;
 
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.helpers.BackgroundServiceTestCase;
 
 import android.content.Intent;
 import android.content.SharedPreferences;
-import android.test.mock.MockContext;
 
 public class TestHealthReportPruneService
     extends BackgroundServiceTestCase<TestHealthReportPruneService.MockHealthReportPruneService> {
   public static class MockHealthReportPruneService extends HealthReportPruneService {
     protected MockPrunePolicy prunePolicy;
 
     @Override
     protected SharedPreferences getSharedPreferences() {
@@ -32,17 +31,20 @@ public class TestHealthReportPruneServic
         fail("Awaiting thread should not be interrupted.");
       } catch (BrokenBarrierException e) {
         // This will happen on timeout - do nothing.
       }
     }
 
     @Override
     public PrunePolicy getPrunePolicy(final String profilePath) {
-      final PrunePolicyStorage storage = new PrunePolicyDatabaseStorage(new MockContext(), profilePath);
+      // We don't actually need any storage, so just make it null. Actually
+      // creating storage requires a valid context; here, we only have a
+      // MockContext.
+      final PrunePolicyStorage storage = null;
       prunePolicy = new MockPrunePolicy(storage, getSharedPreferences());
       return prunePolicy;
     }
 
     public boolean wasTickCalled() {
       if (prunePolicy == null) {
         return false;
       }
--- a/mobile/android/tests/background/junit3/src/healthreport/upload/TestHealthReportUploadService.java
+++ b/mobile/android/tests/background/junit3/src/healthreport/upload/TestHealthReportUploadService.java
@@ -17,16 +17,22 @@ public class TestHealthReportUploadServi
   public static class MockHealthReportUploadService extends HealthReportUploadService {
     @Override
     protected SharedPreferences getSharedPreferences() {
       return this.getSharedPreferences(sharedPrefsName,
           GlobalConstants.SHARED_PREFERENCES_MODE);
     }
 
     @Override
+    public boolean backgroundDataIsEnabled() {
+      // When testing, we always want to say we can upload.
+      return true;
+    }
+
+    @Override
     public void onHandleIntent(Intent intent) {
       super.onHandleIntent(intent);
       try {
         barrier.await();
       } catch (InterruptedException e) {
         fail("Awaiting thread should not be interrupted.");
       } catch (BrokenBarrierException e) {
         // This will happen on timeout - do nothing.
--- a/mobile/android/tests/background/junit3/src/helpers/AndroidSyncTestCase.java
+++ b/mobile/android/tests/background/junit3/src/helpers/AndroidSyncTestCase.java
@@ -18,17 +18,17 @@ public class AndroidSyncTestCase extends
   protected static String LOG_TAG = "AndroidSyncTestCase";
 
   public AndroidSyncTestCase() {
     super(Activity.class);
     WaitHelper.resetTestWaiter();
   }
 
   public Context getApplicationContext() {
-    return this.getInstrumentation().getTargetContext().getApplicationContext();
+    return this.getInstrumentation().getTargetContext();
   }
 
   public static void performWait(Runnable runnable) {
     try {
       WaitHelper.getTestWaiter().performWait(runnable);
     } catch (WaitHelper.InnerError e) {
       AssertionFailedError inner = new AssertionFailedError("Caught error in performWait");
       inner.initCause(e.innerError);
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit3/src/sync/AndroidSyncTestCaseWithAccounts.java
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.gecko.background.sync;
+
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.Context;
+
+public class AndroidSyncTestCaseWithAccounts extends AndroidSyncTestCase {
+  public final String testAccountType;
+  public final String testAccountPrefix;
+
+  protected Context context;
+  protected AccountManager accountManager;
+  protected int numAccounts;
+
+  public AndroidSyncTestCaseWithAccounts(String accountType, String accountPrefix) {
+    super();
+    this.testAccountType = accountType;
+    this.testAccountPrefix = accountPrefix;
+  }
+
+  @Override
+  public void setUp() {
+    context = getApplicationContext();
+    accountManager = AccountManager.get(context);
+    deleteTestAccounts(); // Always start with no test accounts.
+    numAccounts = accountManager.getAccountsByType(testAccountType).length;
+  }
+
+  public List<Account> getTestAccounts() {
+    final List<Account> testAccounts = new ArrayList<Account>();
+
+    final Account[] accounts = accountManager.getAccountsByType(testAccountType);
+    for (Account account : accounts) {
+      if (account.name.startsWith(testAccountPrefix)) {
+        testAccounts.add(account);
+      }
+    }
+
+    return testAccounts;
+  }
+
+  public void deleteTestAccounts() {
+    for (Account account : getTestAccounts()) {
+      TestSyncAccounts.deleteAccount(this, accountManager, account);
+    }
+  }
+
+  public boolean testAccountsExist() {
+    // Note that we don't use FirefoxAccounts.firefoxAccountsExist because it unpickles.
+    return !getTestAccounts().isEmpty();
+  }
+
+  @Override
+  public void tearDown() {
+    deleteTestAccounts();
+    assertEquals(numAccounts, accountManager.getAccountsByType(testAccountType).length);
+  }
+
+  public static void assertFileNotPresent(final Context context, final String filename) throws Exception {
+    // Verify file is not present.
+    FileInputStream fis = null;
+    try {
+      fis = context.openFileInput(filename);
+      fail("Should get FileNotFoundException.");
+    } catch (FileNotFoundException e) {
+      // Do nothing; file should not exist.
+    } finally {
+      if (fis != null) {
+        fis.close();
+      }
+    }
+  }
+}
--- a/mobile/android/tests/background/junit3/src/sync/TestAccountPickler.java
+++ b/mobile/android/tests/background/junit3/src/sync/TestAccountPickler.java
@@ -1,39 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.sync;
 
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.List;
 
 import org.mozilla.gecko.background.common.GlobalConstants;
-import org.mozilla.gecko.background.helpers.AndroidSyncTestCase;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConstants;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.config.AccountPickler;
 import org.mozilla.gecko.sync.setup.Constants;
 import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 
-public class TestAccountPickler extends AndroidSyncTestCase {
+public class TestAccountPickler extends AndroidSyncTestCaseWithAccounts {
   public static final String TEST_FILENAME = "test.json";
   public static final String TEST_ACCOUNTTYPE = SyncConstants.ACCOUNTTYPE_SYNC;
 
   // Test account names must start with TEST_USERNAME in order to be recognized
   // as test accounts and deleted in tearDown.
   public static final String TEST_USERNAME   = "testAccount@mozilla.com";
   public static final String TEST_USERNAME2  = TEST_USERNAME + "2";
 
@@ -44,66 +39,27 @@ public class TestAccountPickler extends 
   public static final String TEST_CLIENT_NAME = "testClientName";
   public static final String TEST_CLIENT_GUID = "testClientGuid";
 
   public static final String TEST_PRODUCT = GlobalConstants.BROWSER_INTENT_PACKAGE;
   public static final String TEST_PROFILE = "default";
   public static final long TEST_VERSION = SyncConfiguration.CURRENT_PREFS_VERSION;
 
   protected SyncAccountParameters params;
-  protected Context context;
-  protected AccountManager accountManager;
-  protected int numAccounts;
 
+  public TestAccountPickler() {
+    super(TEST_ACCOUNTTYPE, TEST_USERNAME);
+  }
+
+  @Override
   public void setUp() {
-    context = getApplicationContext();
-    accountManager = AccountManager.get(context);
+    super.setUp();
     params = new SyncAccountParameters(context, accountManager,
         TEST_USERNAME, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVER_URL,
         TEST_CLUSTER_URL, TEST_CLIENT_NAME, TEST_CLIENT_GUID);
-    numAccounts = accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length;
-  }
-
-  public static List<Account> getTestAccounts(final AccountManager accountManager) {
-    final List<Account> testAccounts = new ArrayList<Account>();
-
-    final Account[] accounts = accountManager.getAccountsByType(TEST_ACCOUNTTYPE);
-    for (Account account : accounts) {
-      if (account.name.startsWith(TEST_USERNAME)) {
-        testAccounts.add(account);
-      }
-    }
-
-    return testAccounts;
-  }
-
-  public void deleteTestAccounts() {
-    for (Account account : getTestAccounts(accountManager)) {
-      TestSyncAccounts.deleteAccount(this, accountManager, account);
-    }
-  }
-
-  public void tearDown() {
-    deleteTestAccounts();
-    assertEquals(numAccounts, accountManager.getAccountsByType(TEST_ACCOUNTTYPE).length);
-  }
-
-  public static void assertFileNotPresent(final Context context, final String filename) throws Exception {
-    // Verify file is not present.
-    FileInputStream fis = null;
-    try {
-      fis = context.openFileInput(TEST_FILENAME);
-      fail("Should get FileNotFoundException.");
-    } catch (FileNotFoundException e) {
-      // Do nothing; file should not exist.
-    } finally {
-      if (fis != null) {
-        fis.close();
-      }
-    }
   }
 
   public void testPersist() throws Exception {
     context.deleteFile(TEST_FILENAME);
     assertFileNotPresent(context, TEST_FILENAME);
 
     AccountPickler.pickle(context, TEST_FILENAME, params, true);
 
@@ -132,30 +88,30 @@ public class TestAccountPickler extends 
     assertTrue(s.length() > 0);
 
     AccountPickler.deletePickle(context, TEST_FILENAME);
     assertFileNotPresent(context, TEST_FILENAME);
   }
 
   public Account deleteAccountsAndUnpickle(final Context context, final AccountManager accountManager, final String filename) {
     deleteTestAccounts();
-    assertEquals(0, getTestAccounts(accountManager).size());
+    assertEquals(0, getTestAccounts().size());
 
     return AccountPickler.unpickle(context, filename);
   }
 
   public void testUnpickleSuccess() throws Exception {
     AccountPickler.pickle(context, TEST_FILENAME, params, true);
 
     // Make sure we have no accounts hanging around.
     final Account account = deleteAccountsAndUnpickle(context, accountManager, TEST_FILENAME);
     assertNotNull(account);
 
     try {
-      assertEquals(1, getTestAccounts(accountManager).size());
+      assertEquals(1, getTestAccounts().size());
       assertTrue(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
       assertEquals(account.name, TEST_USERNAME);
 
       // Verify Account parameters are in place.  Not testing clusterURL since it's stored in
       // shared prefs and it's less critical.
       final String password = accountManager.getPassword(account);
       final String serverURL  = accountManager.getUserData(account, Constants.OPTION_SERVER);
       final String syncKey    = accountManager.getUserData(account, Constants.OPTION_SYNCKEY);
@@ -181,17 +137,17 @@ public class TestAccountPickler extends 
   public void testUnpickleNoAutomatic() throws Exception {
     AccountPickler.pickle(context, TEST_FILENAME, params, false);
 
     // Make sure we have no accounts hanging around.
     final Account account = deleteAccountsAndUnpickle(context, accountManager, TEST_FILENAME);
     assertNotNull(account);
 
     try {
-      assertEquals(1, getTestAccounts(accountManager).size());
+      assertEquals(1, getTestAccounts().size());
       assertFalse(ContentResolver.getSyncAutomatically(account, BrowserContract.AUTHORITY));
     } finally {
       TestSyncAccounts.deleteAccount(this, accountManager, account);
     }
   }
 
   public void testUnpickleNoFile() {
     // Just in case file is hanging around.
@@ -236,13 +192,13 @@ public class TestAccountPickler extends 
         TEST_USERNAME2, TEST_SYNCKEY, TEST_PASSWORD, TEST_SERVER_URL, null, TEST_CLIENT_NAME, TEST_CLIENT_GUID);
     AccountPickler.pickle(context, TEST_FILENAME, params, false);
 
     // Checking if sync accounts exist could try to unpickle. That unpickle
     // would load an account with a different username, so we can check that
     // nothing was unpickled by verifying that the username has not changed.
     assertTrue(SyncAccounts.syncAccountsExist(context));
 
-    for (Account a : getTestAccounts(accountManager)) {
+    for (Account a : getTestAccounts()) {
       assertEquals(TEST_USERNAME, a.name);
     }
   }
 }
--- a/mobile/android/tests/background/junit3/src/sync/TestSyncConfiguration.java
+++ b/mobile/android/tests/background/junit3/src/sync/TestSyncConfiguration.java
@@ -39,17 +39,18 @@ public class TestSyncConfiguration exten
       expected.add(name);
     }
     assertEquals(expected, config.declinedEngineNames);
 
     config.declinedEngineNames = null;
     config.persistToPrefs();
     assertFalse(prefs.contains(SyncConfiguration.PREF_DECLINED_ENGINE_NAMES));
     config = newSyncConfiguration();
-    assertNull(config.declinedEngineNames);
+    assertNotNull(config.declinedEngineNames);
+    assertTrue(config.declinedEngineNames.isEmpty());
   }
 
   public void testEnabledEngineNames() {
     SyncConfiguration config = null;
     SharedPreferences prefs = getPrefs(TEST_PREFS_NAME, 0);
 
     config = newSyncConfiguration();
     config.enabledEngineNames = new HashSet<String>();
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -8,16 +8,17 @@ import itertools
 import json
 import logging
 import os
 import types
 
 from collections import namedtuple
 
 import mozwebidlcodegen
+from reftest import ReftestManifest
 
 import mozbuild.makeutil as mozmakeutil
 from mozpack.copier import FilePurger
 from mozpack.manifests import (
     InstallManifest,
 )
 import mozpack.path as mozpath
 
@@ -1070,16 +1071,21 @@ class RecursiveMakeBackend(CommonBackend
             except ValueError:
                 if not obj.dupe_manifest:
                     raise
 
         m = self._test_manifests.setdefault(obj.flavor,
             (obj.install_prefix, set()))
         m[1].add(obj.manifest_obj_relpath)
 
+        if isinstance(obj.manifest, ReftestManifest):
+            # Mark included files as part of the build backend so changes
+            # result in re-config.
+            self.backend_input_files |= obj.manifest.manifests
+
     def _process_local_include(self, local_include, backend_file):
         if local_include.startswith('/'):
             path = '$(topsrcdir)'
         else:
             path = '$(srcdir)/'
         backend_file.write('LOCAL_INCLUDES += -I%s%s\n' % (path, local_include))
 
     def _process_generated_include(self, generated_include, backend_file):
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -10,17 +10,17 @@ import os
 import traceback
 import sys
 import time
 
 from mach.mixin.logging import LoggingMixin
 
 import mozpack.path as mozpath
 import manifestparser
-
+import reftest
 import mozinfo
 
 from .data import (
     ConfigFileSubstitution,
     Defines,
     DirectoryTraversal,
     Exports,
     GeneratedEventWebIDLFile,
@@ -414,16 +414,21 @@ class TreeMetadataEmitter(LoggingMixin):
             XPCSHELL_TESTS=('xpcshell', 'xpcshell', '.', False),
         )
 
         for prefix, info in test_manifests.items():
             for path in sandbox.get('%s_MANIFESTS' % prefix, []):
                 for obj in self._process_test_manifest(sandbox, info, path):
                     yield obj
 
+        for flavor in ('crashtest', 'reftest'):
+            for path in sandbox.get('%s_MANIFESTS' % flavor.upper(), []):
+                for obj in self._process_reftest_manifest(sandbox, flavor, path):
+                    yield obj
+
         jar_manifests = sandbox.get('JAR_MANIFESTS', [])
         if len(jar_manifests) > 1:
             raise SandboxValidationError('While JAR_MANIFESTS is a list, '
                 'it is currently limited to one value.')
 
         for path in jar_manifests:
             yield JARManifest(sandbox, mozpath.join(sandbox['SRCDIR'], path))
 
@@ -596,16 +601,48 @@ class TreeMetadataEmitter(LoggingMixin):
                 obj.external_installs.add(mozpath.join(out_dir, f))
 
             yield obj
         except (AssertionError, Exception):
             raise SandboxValidationError('Error processing test '
                 'manifest file %s: %s' % (path,
                     '\n'.join(traceback.format_exception(*sys.exc_info()))))
 
+    def _process_reftest_manifest(self, sandbox, flavor, manifest_path):
+        manifest_path = mozpath.normpath(manifest_path)
+        manifest_full_path = mozpath.normpath(mozpath.join(
+            sandbox['SRCDIR'], manifest_path))
+        manifest_reldir = mozpath.dirname(mozpath.relpath(manifest_full_path,
+            sandbox['TOPSRCDIR']))
+
+        manifest = reftest.ReftestManifest()
+        manifest.load(manifest_full_path)
+
+        # reftest manifests don't come from manifest parser. But they are
+        # similar enough that we can use the same emitted objects. Note
+        # that we don't perform any installs for reftests.
+        obj = TestManifest(sandbox, manifest_full_path, manifest,
+                flavor=flavor, install_prefix='%s/' % flavor,
+                relpath=mozpath.join(manifest_reldir,
+                    mozpath.basename(manifest_path)))
+
+        for test in sorted(manifest.files):
+            obj.tests.append({
+                'path': test,
+                'here': mozpath.dirname(test),
+                'manifest': manifest_full_path,
+                'name': mozpath.basename(test),
+                'head': '',
+                'tail': '',
+                'support-files': '',
+                'subsuite': '',
+            })
+
+        yield obj
+
     def _emit_directory_traversal_from_sandbox(self, sandbox):
         o = DirectoryTraversal(sandbox)
         o.dirs = sandbox.get('DIRS', [])
         o.parallel_dirs = sandbox.get('PARALLEL_DIRS', [])
         o.tool_dirs = sandbox.get('TOOL_DIRS', [])
         o.test_dirs = sandbox.get('TEST_DIRS', [])
         o.test_tool_dirs = sandbox.get('TEST_TOOL_DIRS', [])
         o.is_tool_dir = sandbox.get('IS_TOOL_DIR', False)
--- a/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
+++ b/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
@@ -594,32 +594,44 @@ VARIABLES = {
     'A11Y_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining a11y tests.
         """, None),
 
     'BROWSER_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining browser chrome tests.
         """, None),
 
+    'CRASHTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
+        """List of manifest files defining crashtests.
+
+        These are commonly named crashtests.list.
+        """, None),
+
     'METRO_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining metro browser chrome tests.
         """, None),
 
     'MOCHITEST_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining mochitest chrome tests.
         """, None),
 
     'MOCHITEST_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining mochitest tests.
         """, None),
 
     'MOCHITEST_WEBAPPRT_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining webapprt mochitest chrome tests.
         """, None),
 
+    'REFTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
+        """List of manifest files defining reftests.
+
+        These are commonly named reftest.list.
+        """, None),
+
     'WEBRTC_SIGNALLING_TEST_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining WebRTC signalling tests.
         """, None),
 
     'XPCSHELL_TESTS_MANIFESTS': (StrictOrderingOnAppendList, list,
         """List of manifest files defining xpcshell tests.
         """, None),
 
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/crashtest.list
@@ -0,0 +1,1 @@
+== crashtest1.html crashtest1-ref.html
--- a/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/moz.build
@@ -2,8 +2,10 @@
 # http://creativecommons.org/publicdomain/zero/1.0/
 
 A11Y_MANIFESTS += ['a11y.ini']
 BROWSER_CHROME_MANIFESTS += ['browser.ini']
 METRO_CHROME_MANIFESTS += ['metro.ini']
 MOCHITEST_MANIFESTS += ['mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['xpcshell.ini']
+REFTEST_MANIFESTS += ['reftest.list']
+CRASHTEST_MANIFESTS += ['crashtest.list']
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/test/frontend/data/test-manifest-keys-extracted/reftest.list
@@ -0,0 +1,1 @@
+== reftest1.html reftest1-ref.html
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -374,17 +374,17 @@ class TestEmitterBasic(unittest.TestCase
 
     def test_test_manifest_keys_extracted(self):
         """Ensure all metadata from test manifests is extracted."""
         reader = self.reader('test-manifest-keys-extracted')
 
         objs = [o for o in self.read_topsrcdir(reader)
                 if isinstance(o, TestManifest)]
 
-        self.assertEqual(len(objs), 6)
+        self.assertEqual(len(objs), 8)
 
         metadata = {
             'a11y.ini': {
                 'flavor': 'a11y',
                 'installs': {
                     'a11y.ini': False,
                     'test_a11y.js': True,
                 },
@@ -431,16 +431,24 @@ class TestEmitterBasic(unittest.TestCase
                     'xpcshell.ini': False,
                     'test_xpcshell.js': True,
                     'head1': False,
                     'head2': False,
                     'tail1': False,
                     'tail2': False,
                 },
             },
+            'reftest.list': {
+                'flavor': 'reftest',
+                'installs': {},
+            },
+            'crashtest.list': {
+                'flavor': 'crashtest',
+                'installs': {},
+            },
         }
 
         for o in objs:
             m = metadata[mozpath.basename(o.manifest_relpath)]
 
             self.assertTrue(o.path.startswith(o.directory))
             self.assertEqual(o.flavor, m['flavor'])
             self.assertEqual(o.dupe_manifest, m.get('dupe', False))
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -1,30 +1,29 @@
 admin.google.com: did not receive HSTS header (error ignored - included regardless)
 adsfund.org: could not connect to host
 airbnb.com: did not receive HSTS header
-alpha.irccloud.com: did not receive HSTS header
 anycoin.me: did not receive HSTS header
 api.lookout.com: could not connect to host
 api.mega.co.nz: could not connect to host
 api.recurly.com: did not receive HSTS header
 apis.google.com: did not receive HSTS header (error ignored - included regardless)
 app.lookout.com: could not connect to host
 appengine.google.com: did not receive HSTS header (error ignored - included regardless)
 appseccalifornia.org: did not receive HSTS header
 bassh.net: did not receive HSTS header
-bcrook.com: max-age too low: 86400
+bccx.com: could not connect to host
 betnet.fr: could not connect to host
 bigshinylock.minazo.net: could not connect to host
 blacklane.com: did not receive HSTS header
 braintreegateway.com: did not receive HSTS header
 braintreepayments.com: did not receive HSTS header
 browserid.org: did not receive HSTS header
 business.medbank.com.mt: did not receive HSTS header
-calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125"  data: no]
+calyxinstitute.org: could not connect to host
 carlolly.co.uk: did not receive HSTS header
 cert.se: max-age too low: 2628001
 checkout.google.com: did not receive HSTS header (error ignored - included regardless)
 chrome-devtools-frontend.appspot.com: did not receive HSTS header (error ignored - included regardless)
 chrome.google.com: did not receive HSTS header (error ignored - included regardless)
 cloud.google.com: did not receive HSTS header (error ignored - included regardless)
 code.google.com: did not receive HSTS header (error ignored - included regardless)
 codereview.chromium.org: did not receive HSTS header (error ignored - included regardless)
@@ -42,29 +41,30 @@ edmodo.com: did not receive HSTS header
 email.lookout.com: could not connect to host
 emailprivacytester.com: did not receive HSTS header
 encrypted.google.com: did not receive HSTS header (error ignored - included regardless)
 epoxate.com: did not receive HSTS header
 errors.zenpayroll.com: could not connect to host
 espra.com: could not connect to host
 fatzebra.com.au: did not receive HSTS header
 get.zenpayroll.com: did not receive HSTS header
+getlantern.org: did not receive HSTS header
 glass.google.com: did not receive HSTS header (error ignored - included regardless)
 gmail.com: did not receive HSTS header (error ignored - included regardless)
 googlemail.com: did not receive HSTS header (error ignored - included regardless)
 googleplex.com: could not connect to host
 googleplex.com: could not connect to host (error ignored - included regardless)
 goto.google.com: did not receive HSTS header (error ignored - included regardless)
 greplin.com: did not receive HSTS header
 groups.google.com: did not receive HSTS header (error ignored - included regardless)
-haste.ch: could not connect to host
 history.google.com: did not receive HSTS header (error ignored - included regardless)
 hostedtalkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 id.atlassian.com: did not receive HSTS header
 in.xero.com: max-age too low: 3600
+intercom.io: did not receive HSTS header
 iop.intuit.com: max-age too low: 86400
 irccloud.com: did not receive HSTS header
 jitsi.org: did not receive HSTS header
 jottit.com: did not receive HSTS header
 keymaster.lookout.com: did not receive HSTS header
 kiwiirc.com: max-age too low: 5256000
 ledgerscope.net: did not receive HSTS header
 liberty.lavabit.com: could not connect to host
@@ -106,28 +106,27 @@ sites.google.com: did not receive HSTS h
 sol.io: could not connect to host
 souyar.de: could not connect to host
 souyar.net: could not connect to host
 souyar.us: could not connect to host
 spreadsheets.google.com: did not receive HSTS header (error ignored - included regardless)
 square.com: did not receive HSTS header
 ssl.google-analytics.com: did not receive HSTS header (error ignored - included regardless)
 ssl.panoramio.com: did not receive HSTS header
-static.wepay.com: did not receive HSTS header
 stocktrade.de: could not connect to host
 sunshinepress.org: could not connect to host
 surfeasy.com: did not receive HSTS header
 talk.google.com: did not receive HSTS header (error ignored - included regardless)
 talkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 translate.googleapis.com: did not receive HSTS header (error ignored - included regardless)
 uprotect.it: could not connect to host
 wallet.google.com: did not receive HSTS header (error ignored - included regardless)
 webmail.mayfirst.org: did not receive HSTS header
 whonix.org: did not receive HSTS header
-www.calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125"  data: no]
+www.calyxinstitute.org: could not connect to host
 www.cueup.com: did not receive HSTS header
 www.developer.mydigipass.com: could not connect to host
 www.elanex.biz: did not receive HSTS header
 www.gmail.com: did not receive HSTS header (error ignored - included regardless)
 www.googlemail.com: did not receive HSTS header (error ignored - included regardless)
 www.gov.uk: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125"  data: no]
 www.greplin.com: could not connect to host
 www.intercom.io: did not receive HSTS header
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,43 +3,45 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1413626820110000);
+const PRTime gPreloadListExpirationTime = INT64_C(1414231604157000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
 static const nsSTSPreload kSTSPreloadList[] = {
   { "accounts.google.com", true },
   { "aclu.org", false },
   { "activiti.alfresco.com", false },
   { "admin.google.com", true },
   { "adsfund.org", true },
   { "aladdinschools.appspot.com", false },
+  { "alpha.irccloud.com", false },
   { "api.intercom.io", false },
   { "api.simple.com", false },
   { "api.xero.com", false },
   { "apis.google.com", true },
   { "app.manilla.com", true },
   { "app.recurly.com", false },
   { "app.yinxiang.com", false },
   { "appengine.google.com", true },
   { "arivo.com.br", true },
   { "bank.simple.com", false },
   { "bccx.com", true },
+  { "bcrook.com", false },
   { "bitbucket.org", false },
   { "blog.cyveillance.com", true },
   { "blog.linode.com", false },
   { "blog.lookout.com", false },
   { "blog.torproject.org", false },
   { "boxcryptor.com", true },
   { "bugzilla.mozilla.org", true },
   { "business.lookout.com", false },
@@ -92,17 +94,16 @@ static const nsSTSPreload kSTSPreloadLis
   { "faq.lookout.com", false },
   { "feedbin.com", false },
   { "fiken.no", true },
   { "fj.simple.com", false },
   { "forum.linode.com", false },
   { "forum.quantifiedself.com", true },
   { "gernert-server.de", true },
   { "getcloak.com", false },
-  { "getlantern.org", false },
   { "glass.google.com", true },
   { "gmail.com", false },
   { "go.xero.com", false },
   { "gocardless.com", true },
   { "googlemail.com", false },
   { "googleplex.com", true },
   { "goto.google.com", true },
   { "grc.com", false },
@@ -112,17 +113,16 @@ static const nsSTSPreload kSTSPreloadLis
   { "haste.ch", true },
   { "heha.co", true },
   { "history.google.com", true },
   { "hostedtalkgadget.google.com", true },
   { "howrandom.org", true },
   { "id.mayfirst.org", false },
   { "imouto.my", false },
   { "inertianetworks.com", true },
-  { "intercom.io", false },
   { "itriskltd.com", true },
   { "keeperapp.com", true },
   { "keepersecurity.com", true },
   { "keyerror.com", true },
   { "kinsights.com", false },
   { "lastpass.com", false },
   { "launchkey.com", true },
   { "library.linode.com", false },
@@ -207,16 +207,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "simbolo.co.uk", false },
   { "simple.com", false },
   { "sites.google.com", true },
   { "skydrive.live.com", false },
   { "spreadsheets.google.com", true },
   { "squareup.com", false },
   { "ssl.google-analytics.com", true },
   { "stage.wepay.com", false },
+  { "static.wepay.com", false },
   { "stocktrade.de", false },
   { "stripe.com", true },
   { "strongest-privacy.com", true },
   { "subrosa.io", true },
   { "support.mayfirst.org", false },
   { "surkatty.org", true },
   { "talk.google.com", true },
   { "talkgadget.google.com", true },
@@ -252,16 +253,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "wiki.python.org", true },
   { "wiz.biz", true },
   { "writeapp.me", false },
   { "www.aclu.org", false },
   { "www.airbnb.com", true },
   { "www.apollo-auto.com", true },
   { "www.banking.co.at", false },
   { "www.braintreepayments.com", false },
+  { "www.capitainetrain.com", false },
   { "www.cyveillance.com", true },
   { "www.dropcam.com", false },
   { "www.entropia.de", false },
   { "www.eternalgoth.co.uk", true },
   { "www.evernote.com", false },
   { "www.getcloak.com", false },
   { "www.gmail.com", false },
   { "www.googlemail.com", false },
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1551,16 +1551,20 @@ pageTranslatedCount
 charactersTranslatedCount
     Integer count of the number of characters translated.
 detectedLanguageChangedBefore
     Integer count of the number of times the user manually adjusted the detected
     language before translating.
 detectedLanguageChangedAfter
     Integer count of the number of times the user manually adjusted the detected
     language after having first translated the page.
+deniedTranslationOffer
+    Integer count of the numbers of times the user opted-out offered
+    page translation, either by the Not Now button or by the notification's
+    close button in the "offer" state.
 
 Additional daily counts broken down by language are reported in the following
 properties:
 
 translationOpportunityCountsByLanguage
     A mapping from language to count of opportunities to translate that
     language.
 missedTranslationOpportunityCountsByLanguage
@@ -1587,16 +1591,17 @@ Example
       "_v": 1,
       "detectLanguageEnabled": 1,
       "showTranslationUI": 1,
       "translationOpportunityCount": 134,
       "pageTranslatedCount": 6,
       "charactersTranslatedCount": "1126",
       "detectedLanguageChangedBefore": 1,
       "detectedLanguageChangedAfter": 2,
+      "deniedTranslationOffer": 3
       "translationOpportunityCountsByLanguage": {
         "fr": 100,
         "es": 34
       },
       "pageTranslatedCountsByLanguage": {
         "fr": {
           "total": 6,
           "es": 5,
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -700,22 +700,20 @@ var LoginManagerContent = {
 
             if (usernameField) {
                 // Don't modify the username field if it's disabled or readOnly so we preserve its case.
                 let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly;
 
                 // Don't replace the username if it differs only in case, and the user triggered
                 // this autocomplete. We assume that if it was user-triggered the entered text
                 // is desired.
-                dump("field value: " + usernameField.value + "\n");
-                dump("selectedLogin value: " + selectedLogin.username + "\n");
                 let userEnteredDifferentCase = userTriggered &&
                       (usernameField.value != selectedLogin.username &&
                        usernameField.value.toLowerCase() == selectedLogin.username.toLowerCase());
-    
+
                 if (!disabledOrReadOnly && !userEnteredDifferentCase) {
                     usernameField.setUserInput(selectedLogin.username);
                 }
             }
             passwordField.setUserInput(selectedLogin.password);
             didFillForm = true;
         } else if (selectedLogin && !autofillForm) {
             // For when autofillForm is false, but we still have the information