Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 21 Jan 2014 15:20:25 -0500
changeset 164511 00585fe8196f651297a493b542ac34421ff7a3d2
parent 164510 62f929fab468e7b69e08e4480cc4b46eb748e4cd (current diff)
parent 164461 dcb0a6baeadb2d9ee606dae6c6323ccca6bdce9b (diff)
child 164512 1eb2405c8c186338dbff254de216a4dca28f86af
push id26048
push userkwierso@gmail.com
push dateWed, 22 Jan 2014 01:11:21 +0000
treeherdermozilla-central@92cb91891880 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
CLOBBER
browser/components/sessionstore/src/PrivacyLevelFilter.jsm
browser/components/sessionstore/src/SessionStore.jsm
content/media/omx/OmxDecoder.cpp
dom/network/src/MobileConnection.cpp
dom/network/src/MobileConnection.h
dom/network/src/MobileConnectionArray.cpp
dom/network/src/MobileConnectionArray.h
mobile/android/base/fxa/activities/FxAccountCreateSuccessActivity.java
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,10 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 953381 requires a clobber due to bug 961339.
+CLOBBER due to recent changes to JS build files that were causing
+"STOP! configure has changed and needs to be run in this build directory." bustage
--- a/README.txt
+++ b/README.txt
@@ -20,8 +20,9 @@ are accessible on Google Groups, or news
 
 You can download nightly development builds from the Mozilla FTP server.
 Keep in mind that nightly builds, which are used by Mozilla developers for
 testing, may be buggy. Firefox nightlies, for example, can be found at:
 
     ftp://ftp.mozilla.org/pub/firefox/nightly/latest-trunk/
             - or -
     http://nightly.mozilla.org/
+
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -7,23 +7,23 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <!-- 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"/>
@@ -93,9 +93,9 @@
   <project name="android-development" path="development" remote="b2g" revision="4e236e65a5d652a66ac32590f69f2123d17cb4ad"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="5be0a9c4b3c6c004786917fdb5bee248960d045b"/>
   <project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
   <project 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="d2685281e2e54ca14d1df304867aa82c37b27162"/>
   <project 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="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>
   <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="a3daa50e9b5db558696951ae724f913e4e5c7489"/>
   <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="83dbccadb19d0d3c07828c2bb0e5c2be12c0f980"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="5701d3cb45c2e848cc57003cda2e1141288ecae4"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -6,30 +6,23 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="fce1a137746dbd354bca1918f02f96d51c40bad2">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
-  <project groups="darwin" name="platform/prebuilts/clang/darwin-x86/3.1" path="prebuilts/clang/darwin-x86/3.1" revision="8a10d50e8caab8c18224588f0531f1c9363965b5"/>
-  <project groups="darwin" name="platform/prebuilts/clang/darwin-x86/3.2" path="prebuilts/clang/darwin-x86/3.2" revision="2d96fcbab6efee560c2004725b21bdc06d090933"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/darwin-x86/arm/arm-eabi-4.7" revision="37c334e567086e7925107c346ae8158369591711"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.7" revision="56ea799d883b6f58f77d907e43cfb32f79269ca6"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/host/headers" path="prebuilts/gcc/darwin-x86/host/headers" revision="3b329c54c157eb42d9add1abc6df3fe49bcca570"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" path="prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" revision="ccec3e9c52e0575ed6c9f40ab63e74909ec71f03"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/darwin-x86/x86/i686-linux-android-4.7" revision="5481d408fc8e245abbc0096037ed9a44fe8179c2"/>
   <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"/>
   <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="1342fd7b4b000ac3e76a5dfe111a0de9d710b4c8"/>
   <project name="device/common" path="device/common" revision="4e1a38704dcfadef60ed2da3cfeba02a56b069d2"/>
   <project name="device/sample" path="device/sample" revision="b045905b46c8b4ee630d0c2aee7db63eaec722d9"/>
@@ -126,9 +119,9 @@
   <!-- Emulator specific things -->
   <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="9e1ff573f5669a9e0756e199cb4237ab18546388"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87e1478ffc36b0b446119ae0a1ea7a02e1baea5e"/>
   <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="2838a77ce4b8c09fa6a46fe25410bb3a4474cbd4"/>
   <project name="platform/development" path="development" revision="1f18cfe031ce23b7fb838fe3d4379dd802b49e71"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -7,23 +7,23 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="eda08beb3ba9a159843c70ffde0f9660ec351eb9"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="87aa8679560ce09f6445621d6f370d9de722cdba"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <!-- 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"/>
@@ -93,9 +93,9 @@
   <project name="android-development" path="development" remote="b2g" revision="4e236e65a5d652a66ac32590f69f2123d17cb4ad"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="5be0a9c4b3c6c004786917fdb5bee248960d045b"/>
   <project name="platform/external/iproute2" path="external/iproute2" revision="c66c5716d5335e450f7a7b71ccc6a604fb2f41d2"/>
   <project 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="d2685281e2e54ca14d1df304867aa82c37b27162"/>
   <project 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="627f9b20fc518937b93747a7ff1ed4f5ed46e06f"/>
   <project name="platform/prebuilts/tools" path="prebuilts/tools" revision="a3daa50e9b5db558696951ae724f913e4e5c7489"/>
   <project name="platform_prebuilts_qemu-kernel" path="prebuilts/qemu-kernel" remote="b2g" revision="83dbccadb19d0d3c07828c2bb0e5c2be12c0f980"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="5701d3cb45c2e848cc57003cda2e1141288ecae4"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "18e2adbabcfa967611077df5a9bff7f855041324", 
+    "revision": "a43f6c119d80637dd3e36975e8f38b7f19b5c95c", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -6,22 +6,22 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <!-- 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"/>
@@ -98,9 +98,9 @@
   <project name="kernel/msm" path="kernel" revision="8072055e7094023e2cac8eea425bb785fe1d4066"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="19933e5d182a4799c6217b19a18562193a419298"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="5a58382180c70d0c446badc9c9837918ab69ec60"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="20d83ab382a1f813702421e76c2f9f994585990e"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="1698e6e9ed7cf1d543508845fa05ed86c7e5e241"/>
   <project name="platform/hardware/msm7k" path="hardware/msm7k" revision="693e65da9905d88c23653b45800e6509143f6a78"/>
   <project name="platform/vendor/qcom-opensource/omx/mm-core" path="vendor/qcom/opensource/omx/mm-core" revision="0365db6af2d4df11184a421f97c5043db47a0c0d"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="ec40c0aee736052fc4fe01c1b8dc16929da5dc45"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -5,17 +5,17 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
   <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"/>
@@ -95,9 +95,9 @@
   <project name="device-helix" path="device/qcom/helix" remote="b2g" revision="47f22c59edb13379a6995554cb4a473558929653"/>
   <project name="kernel/msm" path="kernel" revision="e9c1e0c6a0ca33f5741a48288de4b46453031210"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="c4e3e6cf938f1bdde78bc39f38350f72b6fc3a21"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="32905dde6a66296c7e5843e9664c5c6444deb38c"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="b2ac43193f3d3a44171bdb50ea3c2aeb558511d3"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="1698e6e9ed7cf1d543508845fa05ed86c7e5e241"/>
   <project name="platform/hardware/msm7k" path="hardware/msm7k" revision="669815aaca47afee95b4a95908dc87bff267a815"/>
   <project name="platform/vendor/qcom-opensource/omx/mm-core" path="vendor/qcom/opensource/omx/mm-core" revision="0365db6af2d4df11184a421f97c5043db47a0c0d"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -7,22 +7,22 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <!-- 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"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
@@ -100,9 +100,9 @@
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="0a01247e4b0880f93424b27251cd3a1f6b19dbb2"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="1acf77a75e30f3fc8b1eed2057c97adf1cb1633f"/>
   <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="6405d30f2fac7d8a1f2cb17b99fb7dd0a8bcfdac"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="552c3ddb7174a01f3508782d40c4d8c845ab441a"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="23d5707b320d7fc69f8ba3b7d84d78a1c5681708"/>
   <project name="platform/hardware/msm7k" path="hardware/msm7k" revision="8892d46805c5639b55dd07547745c5180da861e7"/>
   <project name="platform/vendor/qcom-opensource/omx/mm-core" path="vendor/qcom/opensource/omx/mm-core" revision="ab17ac9a074b4bb69986a8436336bdfbbaf9cd39"/>
   <project name="platform/hardware/ril" path="hardware/ril" remote="caf" revision="fe9a3f63922143b57e79ed570bab2328df8c83a5"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -6,22 +6,22 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <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"/>
   <project name="platform/development" path="development" revision="b1025ec93beeb480caaf3049d171283c3846461d"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
@@ -96,9 +96,9 @@
   <project name="device-leo" path="device/qcom/leo" remote="b2g" revision="4e53c9ba4afcea9dd0c36c9464042abcf16c228d"/>
   <project name="kernel/msm" path="kernel" revision="e9c1e0c6a0ca33f5741a48288de4b46453031210"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="c4e3e6cf938f1bdde78bc39f38350f72b6fc3a21"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="32905dde6a66296c7e5843e9664c5c6444deb38c"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="b2ac43193f3d3a44171bdb50ea3c2aeb558511d3"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="1698e6e9ed7cf1d543508845fa05ed86c7e5e241"/>
   <project name="platform/hardware/msm7k" path="hardware/msm7k" revision="669815aaca47afee95b4a95908dc87bff267a815"/>
   <project name="platform/vendor/qcom-opensource/omx/mm-core" path="vendor/qcom/opensource/omx/mm-core" revision="0365db6af2d4df11184a421f97c5043db47a0c0d"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -6,30 +6,23 @@
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="fce1a137746dbd354bca1918f02f96d51c40bad2">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="905bfa3548eb75cf1792d0d8412b92113bbd4318"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="c3d7efc45414f1b44cd9c479bb2758c91c4707c0"/>
   <!-- Stock Android things -->
-  <project groups="darwin" name="platform/prebuilts/clang/darwin-x86/3.1" path="prebuilts/clang/darwin-x86/3.1" revision="8a10d50e8caab8c18224588f0531f1c9363965b5"/>
-  <project groups="darwin" name="platform/prebuilts/clang/darwin-x86/3.2" path="prebuilts/clang/darwin-x86/3.2" revision="2d96fcbab6efee560c2004725b21bdc06d090933"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/darwin-x86/arm/arm-eabi-4.7" revision="37c334e567086e7925107c346ae8158369591711"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/darwin-x86/arm/arm-linux-androideabi-4.7" revision="56ea799d883b6f58f77d907e43cfb32f79269ca6"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/host/headers" path="prebuilts/gcc/darwin-x86/host/headers" revision="3b329c54c157eb42d9add1abc6df3fe49bcca570"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" path="prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1" revision="ccec3e9c52e0575ed6c9f40ab63e74909ec71f03"/>
-  <project groups="darwin" name="platform/prebuilts/gcc/darwin-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/darwin-x86/x86/i686-linux-android-4.7" revision="5481d408fc8e245abbc0096037ed9a44fe8179c2"/>
   <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"/>
   <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="1342fd7b4b000ac3e76a5dfe111a0de9d710b4c8"/>
   <project name="device/common" path="device/common" revision="4e1a38704dcfadef60ed2da3cfeba02a56b069d2"/>
   <project name="device/sample" path="device/sample" revision="b045905b46c8b4ee630d0c2aee7db63eaec722d9"/>
@@ -136,9 +129,9 @@
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
   <project name="platform/hardware/qcom/msm8960" path="hardware/qcom/msm8960" revision="0ba0e0e06410e048b1cd57ce985f0376b6e4c84d"/>
   <project name="platform/hardware/qcom/power" path="hardware/qcom/power" revision="acbdde094799f61b4b8cb6ec116f2bc5f37d2afd"/>
   <project name="platform/hardware/qcom/sensors" path="hardware/qcom/sensors" revision="15488e251d83ad881a61f7045c64c711f5eaf082"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="7bc1339234f8b8387df491c0ced88fffd7d505f0"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="d9f3acd2ac2db63697a24f29dbf04083aedbccf8"/>
-</manifest>
\ No newline at end of file
+</manifest>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -6,22 +6,22 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <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="59605a7c026ff06cc1613af3938579b1dddc6cfe">
     <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="4c29941ae4adf800bac335e33e11a30a602c0e4a"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e0b7c709ddc21c407ee3360d3203e9eb84535b66"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="e9b6626eddbc85873eaa2a9174a9bd5101e5c05f"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="84f2f2fce22605e17d511ff1767e54770067b5b5"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96d2d00165f4561fbde62d1062706eab74b3a01f"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="221bcaecbbbc9d185f691471b64aed9e75b0c11d"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="e2f73049f8d52fb06cb9b5d923c1280557aa9238"/>
   <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"/>
@@ -97,9 +97,9 @@
   <project name="platform/vendor/qcom/msm8960" path="device/qcom/msm8960" revision="58786a6a6f16384a7770f92081a5d8f7b1a067ae"/>
   <project name="device-wasabi" path="device/qcom/wasabi" remote="b2g" revision="75c4cf201326edcd909502aa52febc96bf553104"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="0a01247e4b0880f93424b27251cd3a1f6b19dbb2"/>
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="1acf77a75e30f3fc8b1eed2057c97adf1cb1633f"/>
   <project name="hardware_qcom_display" path="hardware/qcom/display" remote="b2g" revision="6405d30f2fac7d8a1f2cb17b99fb7dd0a8bcfdac"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="552c3ddb7174a01f3508782d40c4d8c845ab441a"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="23d5707b320d7fc69f8ba3b7d84d78a1c5681708"/>
   <project name="platform/vendor/qcom-opensource/omx/mm-core" path="vendor/qcom/opensource/omx/mm-core" revision="ab17ac9a074b4bb69986a8436336bdfbbaf9cd39"/>
-</manifest>
\ No newline at end of file
+</manifest>
rename from browser/components/sessionstore/src/PrivacyLevelFilter.jsm
rename to browser/components/sessionstore/src/PrivacyFilter.jsm
--- a/browser/components/sessionstore/src/PrivacyLevelFilter.jsm
+++ b/browser/components/sessionstore/src/PrivacyFilter.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.EXPORTED_SYMBOLS = ["PrivacyLevelFilter"];
+this.EXPORTED_SYMBOLS = ["PrivacyFilter"];
 
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevel",
   "resource:///modules/sessionstore/PrivacyLevel.jsm");
 
@@ -25,17 +25,17 @@ function checkPrivacyLevel(url, isPinned
   let isHttps = url.startsWith("https:");
   return PrivacyLevel.canSave({isHttps: isHttps, isPinned: isPinned});
 }
 
 /**
  * A module that provides methods to filter various kinds of data collected
  * from a tab by the current privacy level as set by the user.
  */
-this.PrivacyLevelFilter = Object.freeze({
+this.PrivacyFilter = Object.freeze({
   /**
    * Filters the given (serialized) session storage |data| according to the
    * current privacy level and returns a new object containing only data that
    * we're allowed to store.
    *
    * @param data The session storage data as collected from a tab.
    * @param isPinned Whether the tab we collected from is pinned.
    * @return object
@@ -82,10 +82,69 @@ this.PrivacyLevelFilter = Object.freeze(
       // Only copy keys other than "children" if we have a valid URL in
       // data.url and we thus passed the privacy level check.
       } else if (data.url) {
         retval[key] = data[key];
       }
     }
 
     return Object.keys(retval).length ? retval : null;
+  },
+
+  /**
+   * Removes any private windows and tabs from a given browser state object.
+   *
+   * @param browserState (object)
+   *        The browser state for which we remove any private windows and tabs.
+   *        The given object will be modified.
+   */
+  filterPrivateWindowsAndTabs: function (browserState) {
+    // Remove private opened windows.
+    for (let i = browserState.windows.length - 1; i >= 0; i--) {
+      let win = browserState.windows[i];
+
+      if (win.isPrivate) {
+        browserState.windows.splice(i, 1);
+
+        if (browserState.selectedWindow >= i) {
+          browserState.selectedWindow--;
+        }
+      } else {
+        // Remove private tabs from all open non-private windows.
+        this.filterPrivateTabs(win);
+      }
+    }
+
+    // Remove private closed windows.
+    browserState._closedWindows =
+      browserState._closedWindows.filter(win => !win.isPrivate);
+
+    // Remove private tabs from all remaining closed windows.
+    browserState._closedWindows.forEach(win => this.filterPrivateTabs(win));
+  },
+
+  /**
+   * Removes open private tabs from a given window state object.
+   *
+   * @param winState (object)
+   *        The window state for which we remove any private tabs.
+   *        The given object will be modified.
+   */
+  filterPrivateTabs: function (winState) {
+    // Remove open private tabs.
+    for (let i = winState.tabs.length - 1; i >= 0 ; i--) {
+      let tab = winState.tabs[i];
+
+      if (tab.isPrivate) {
+        winState.tabs.splice(i, 1);
+
+        if (winState.selected >= i) {
+          winState.selected--;
+        }
+      }
+    }
+
+    // Note that closed private tabs are only stored for private windows.
+    // There is no need to call this function for private windows as the
+    // whole window state should just be discarded so we explicitly don't
+    // try to remove closed private tabs as an optimization.
   }
 });
--- a/browser/components/sessionstore/src/SessionSaver.jsm
+++ b/browser/components/sessionstore/src/SessionSaver.jsm
@@ -10,20 +10,22 @@ const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 Cu.import("resource://gre/modules/Timer.jsm", this);
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/TelemetryStopwatch.jsm", this);
 
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+  "resource://gre/modules/devtools/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
+  "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
   "resource:///modules/sessionstore/SessionStore.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "console",
-  "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionFile",
   "resource:///modules/sessionstore/SessionFile.jsm");
 
 // Minimal interval between two save operations (in milliseconds).
 XPCOMUtils.defineLazyGetter(this, "gInterval", function () {
   const PREF = "browser.sessionstore.interval";
 
   // Observer that updates the cached value when the preference changes.
@@ -187,49 +189,17 @@ let SessionSaverInternal = {
    *        update the corresponding caches.
    */
   _saveState: function (forceUpdateAllWindows = false) {
     // Cancel any pending timeouts.
     this.cancel();
 
     stopWatchStart("COLLECT_DATA_MS", "COLLECT_DATA_LONGEST_OP_MS");
     let state = SessionStore.getCurrentState(forceUpdateAllWindows);
-
-    // Forget about private windows and tabs.
-    for (let i = state.windows.length - 1; i >= 0; i--) {
-      let win = state.windows[i];
-      if (win.isPrivate || false) { // The whole window is private, remove it
-         state.windows.splice(i, 1);
-         if (state.selectedWindow >= i) {
-           state.selectedWindow--;
-         }
-        continue;
-      }
-      // The window is not private, but its tabs still might
-      for (let j = win.tabs.length - 1; j >= 0 ; --j) {
-        let tab = win.tabs[j];
-        if (tab.isPrivate || false) {
-          win.tabs.splice(j, 1);
-          if (win.selected >= j) {
-            win.selected--;
-          }
-        }
-      }
-    }
-
-    // Remove private windows from the list of closed windows.
-    for (let i = state._closedWindows.length - 1; i >= 0; i--) {
-      if (state._closedWindows[i].isPrivate) {
-        state._closedWindows.splice(i, 1);
-      }
-    }
-
-    // Note that closed private tabs are never stored (see
-    // SessionStoreInternal.onTabClose), so we do not need to remove
-    // them.
+    PrivacyFilter.filterPrivateWindowsAndTabs(state);
 
     // Make sure that we keep the previous session if we started with a single
     // private window and no non-private windows have been opened, yet.
     if (state.deferredInitialState) {
       state.windows = state.deferredInitialState.windows || [];
       delete state.deferredInitialState;
     }
 
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -120,16 +120,18 @@ XPCOMUtils.defineLazyServiceGetter(this,
   "@mozilla.org/base/telemetry;1", "nsITelemetry");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "GlobalState",
   "resource:///modules/sessionstore/GlobalState.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
   "resource:///modules/sessionstore/Messenger.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
+  "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
   "resource:///modules/devtools/scratchpad-manager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionSaver",
   "resource:///modules/sessionstore/SessionSaver.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SessionCookies",
   "resource:///modules/sessionstore/SessionCookies.jsm");
@@ -1112,23 +1114,30 @@ let SessionStoreInternal = {
 
       // Store the window's close date to figure out when each individual tab
       // was closed. This timestamp should allow re-arranging data based on how
       // recently something was closed.
       winData.closedAt = Date.now();
 
       // Save the window if it has multiple tabs or a single saveable tab and
       // it's not private.
-      if (!winData.isPrivate && (winData.tabs.length > 1 ||
-          (winData.tabs.length == 1 && this._shouldSaveTabState(winData.tabs[0])))) {
-        // we don't want to save the busy state
-        delete winData.busy;
-
-        this._closedWindows.unshift(winData);
-        this._capClosedWindows();
+      if (!winData.isPrivate) {
+        // Remove any open private tabs the window may contain.
+        PrivacyFilter.filterPrivateTabs(winData);
+
+        let hasSingleTabToSave =
+          winData.tabs.length == 1 && this._shouldSaveTabState(winData.tabs[0]);
+
+        if (hasSingleTabToSave || winData.tabs.length > 1) {
+          // we don't want to save the busy state
+          delete winData.busy;
+
+          this._closedWindows.unshift(winData);
+          this._capClosedWindows();
+        }
       }
 
       // clear this window from the list
       delete this._windows[aWindow.__SSi];
 
       // save the state without this window to disk
       this.saveStateDelayed();
     }
@@ -1403,17 +1412,18 @@ let SessionStoreInternal = {
 
     // Flush all data queued in the content script before the tab is gone.
     TabState.flush(aTab.linkedBrowser);
 
     // Get the latest data for this tab (generally, from the cache)
     let tabState = TabState.collectSync(aTab);
 
     // Don't save private tabs
-    if (tabState.isPrivate || false) {
+    let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
+    if (!isPrivateWindow && tabState.isPrivate) {
       return;
     }
 
     // store closed-tab data for undo
     if (this._shouldSaveTabState(tabState)) {
       let tabTitle = aTab.label;
       let tabbrowser = aWindow.gBrowser;
       tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
--- a/browser/components/sessionstore/src/TabState.jsm
+++ b/browser/components/sessionstore/src/TabState.jsm
@@ -11,18 +11,18 @@ const Cu = Components.utils;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Messenger",
   "resource:///modules/sessionstore/Messenger.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivacyLevelFilter",
-  "resource:///modules/sessionstore/PrivacyLevelFilter.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivacyFilter",
+  "resource:///modules/sessionstore/PrivacyFilter.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabStateCache",
   "resource:///modules/sessionstore/TabStateCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabAttributes",
   "resource:///modules/sessionstore/TabAttributes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Utils",
   "resource:///modules/sessionstore/Utils.jsm");
 
 /**
@@ -361,19 +361,19 @@ let TabStateInternal = {
     let includePrivateData = options && options.includePrivateData;
 
     for (let key of Object.keys(data)) {
       let value = data[key];
 
       // Filter sensitive data according to the current privacy level.
       if (!includePrivateData) {
         if (key === "storage") {
-          value = PrivacyLevelFilter.filterSessionStorageData(value, tab.pinned);
+          value = PrivacyFilter.filterSessionStorageData(value, tab.pinned);
         } else if (key === "formdata") {
-          value = PrivacyLevelFilter.filterFormData(value, tab.pinned);
+          value = PrivacyFilter.filterFormData(value, tab.pinned);
         }
       }
 
       if (value) {
         tabData[key] = value;
       }
     }
   },
--- a/browser/components/sessionstore/src/moz.build
+++ b/browser/components/sessionstore/src/moz.build
@@ -15,18 +15,18 @@ JS_MODULES_PATH = 'modules/sessionstore'
 EXTRA_JS_MODULES = [
     'ContentRestore.jsm',
     'DocShellCapabilities.jsm',
     'FormData.jsm',
     'FrameTree.jsm',
     'GlobalState.jsm',
     'Messenger.jsm',
     'PageStyle.jsm',
+    'PrivacyFilter.jsm',
     'PrivacyLevel.jsm',
-    'PrivacyLevelFilter.jsm',
     'RecentlyClosedTabsAndWindowsMenuUtils.jsm',
     'ScrollPosition.jsm',
     'SessionCookies.jsm',
     'SessionFile.jsm',
     'SessionHistory.jsm',
     'SessionMigration.jsm',
     'SessionStorage.jsm',
     'SessionWorker.js',
--- a/browser/components/sessionstore/test/browser_privatetabs.js
+++ b/browser/components/sessionstore/test/browser_privatetabs.js
@@ -63,30 +63,79 @@ add_task(function() {
     }
   }
 });
 
 add_task(function () {
   const FRAME_SCRIPT = "data:," +
     "docShell.QueryInterface%28Ci.nsILoadContext%29.usePrivateBrowsing%3Dtrue";
 
+  // Clear the list of closed windows.
+  while (ss.getClosedWindowCount()) {
+    ss.forgetClosedWindow(0);
+  }
+
   // Create a new window to attach our frame script to.
   let win = yield promiseNewWindowLoaded();
   win.messageManager.loadFrameScript(FRAME_SCRIPT, true);
 
   // Create a new tab in the new window that will load the frame script.
   let tab = win.gBrowser.addTab("about:mozilla");
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
   SyncHandlers.get(browser).flush();
 
   // Check that we consider the tab as private.
   let state = JSON.parse(ss.getTabState(tab));
   ok(state.isPrivate, "tab considered private");
 
-  // Cleanup.
+  // Ensure we don't allow restoring closed private tabs in non-private windows.
+  win.gBrowser.removeTab(tab);
+  is(ss.getClosedTabCount(win), 0, "no tabs to restore");
+
+  // Create a new tab in the new window that will load the frame script.
+  let tab = win.gBrowser.addTab("about:mozilla");
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+  SyncHandlers.get(browser).flush();
+
+  // Check that we consider the tab as private.
+  let state = JSON.parse(ss.getTabState(tab));
+  ok(state.isPrivate, "tab considered private");
+
+  // Check that all private tabs are removed when the non-private
+  // window is closed and we don't save windows without any tabs.
   yield promiseWindowClosed(win);
+  is(ss.getClosedWindowCount(), 0, "no windows to restore");
+});
+
+add_task(function () {
+  // Clear the list of closed windows.
+  while (ss.getClosedWindowCount()) {
+    ss.forgetClosedWindow(0);
+  }
+
+  // Create a new window to attach our frame script to.
+  let win = yield promiseNewWindowLoaded({private: true});
+
+  // Create a new tab in the new window that will load the frame script.
+  let tab = win.gBrowser.addTab("about:mozilla");
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+  SyncHandlers.get(browser).flush();
+
+  // Check that we consider the tab as private.
+  let state = JSON.parse(ss.getTabState(tab));
+  ok(state.isPrivate, "tab considered private");
+
+  // Ensure that closed tabs in a private windows can be restored.
+  win.gBrowser.removeTab(tab);
+  is(ss.getClosedTabCount(win), 1, "there is a single tab to restore");
+
+  // Ensure that closed private windows can never be restored.
+  yield promiseWindowClosed(win);
+  is(ss.getClosedWindowCount(), 0, "no windows to restore");
 });
 
 function setUsePrivateBrowsing(browser, val) {
   return sendMessage(browser, "ss-test:setUsePrivateBrowsing", val);
 }
 
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -2,16 +2,19 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 Cu.import("resource://gre/modules/devtools/Console.jsm");
 
 const promise = devtools.require("sdk/core/promise");
 
 function LayoutView(aInspector, aWindow)
 {
@@ -233,8 +236,51 @@ LayoutView.prototype = {
       this.inspector.emit("layoutview-updated");
       return null;
     });
 
     this._lastRequest = lastRequest;
     return this._lastRequest;
   }
 }
+
+let elts;
+let tooltip;
+
+window.setPanel = function(panel) {
+  this.layoutview = new LayoutView(panel, window);
+
+  // Tooltip mechanism
+  elts = document.querySelectorAll("*[tooltip]");
+  tooltip = document.querySelector(".tooltip");
+  for (let i = 0; i < elts.length; i++) {
+    let elt = elts[i];
+    elt.addEventListener("mouseover", onmouseover, true);
+    elt.addEventListener("mouseout", onmouseout, true);
+  }
+
+  // Mark document as RTL or LTR:
+  let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
+    getService(Ci.nsIXULChromeRegistry);
+  let dir = chromeReg.isLocaleRTL("global");
+  document.body.setAttribute("dir", dir ? "rtl" : "ltr");
+
+  window.parent.postMessage("layoutview-ready", "*");
+};
+
+window.onunload = function() {
+  this.layoutview.destroy();
+  if (elts) {
+    for (let i = 0; i < elts.length; i++) {
+      let elt = elts[i];
+      elt.removeEventListener("mouseover", onmouseover, true);
+      elt.removeEventListener("mouseout", onmouseout, true);
+    }
+  }
+};
+
+function onmouseover(e) {
+  tooltip.textContent = e.target.getAttribute("tooltip");
+}
+
+function onmouseout(e) {
+  tooltip.textContent = "";
+}
\ No newline at end of file
--- a/browser/devtools/layoutview/view.xhtml
+++ b/browser/devtools/layoutview/view.xhtml
@@ -11,65 +11,16 @@
       xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <head>
     <title>&title;</title>
 
     <script type="application/javascript;version=1.8"
             src="chrome://browser/content/devtools/theme-switching.js"/>
 
     <script type="application/javascript;version=1.8" src="view.js"></script>
-    <script type="application/javascript;version=1.8">
-    <![CDATA[
-      let elts;
-      let tooltip;
-
-      const Ci = Components.interfaces;
-      const Cc = Components.classes;
-
-      window.setPanel = function(panel) {
-        this.layoutview = new LayoutView(panel, window);
-
-        // Tooltip mechanism
-        elts = document.querySelectorAll("*[tooltip]");
-        tooltip = document.querySelector(".tooltip");
-        for (let i = 0; i < elts.length; i++) {
-          let elt = elts[i];
-          elt.addEventListener("mouseover", onmouseover, true);
-          elt.addEventListener("mouseout", onmouseout, true);
-        }
-
-        // Mark document as RTL or LTR:
-        let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
-          getService(Ci.nsIXULChromeRegistry);
-        let dir = chromeReg.isLocaleRTL("global");
-        document.body.setAttribute("dir", dir ? "rtl" : "ltr");
-
-        window.parent.postMessage("layoutview-ready", "*");
-      }
-
-      window.onunload = function() {
-        this.layoutview.destroy();
-        if (elts) {
-          for (let i = 0; i < elts.length; i++) {
-            let elt = elts[i];
-            elt.removeEventListener("mouseover", onmouseover, true);
-            elt.removeEventListener("mouseout", onmouseout, true);
-          }
-        }
-      }
-
-      function onmouseover(e) {
-        tooltip.textContent = e.target.getAttribute("tooltip");
-      }
-
-      function onmouseout(e) {
-        tooltip.textContent = "";
-      }
-    ]]>
-    </script>
 
     <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://browser/skin/devtools/layoutview.css" type="text/css"/>
     <link rel="stylesheet" href="view.css" type="text/css"/>
 
   </head>
   <body class="theme-body devtools-monospace">
 
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -233,17 +233,20 @@ ResponsiveUI.prototype = {
    */
    onPageLoad: function() {
      this.touchEventHandler = new TouchEventHandler(this.browser);
      if (this.touchEnableBefore) {
        this.enableTouch();
      }
    },
 
-   onPageUnload: function() {
+   onPageUnload: function(evt) {
+     // Ignore sub frames unload events
+     if (evt.target != this.browser.contentDocument)
+       return;
      if (this.closing)
        return;
      if (this.touchEventHandler) {
        this.touchEnableBefore = this.touchEventHandler.enabled;
        this.disableTouch();
        delete this.touchEventHandler;
      }
    },
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -532,21 +532,31 @@ Rule.prototype = {
    * Create a new TextProperty to include in the rule.
    *
    * @param {string} aName
    *        The text property name (such as "background" or "border-top").
    * @param {string} aValue
    *        The property's value (not including priority).
    * @param {string} aPriority
    *        The property's priority (either "important" or an empty string).
+   * @param {TextProperty} aSiblingProp
+   *        Optional, property next to which the new property will be added.
    */
-  createProperty: function Rule_createProperty(aName, aValue, aPriority)
+  createProperty: function Rule_createProperty(aName, aValue, aPriority, aSiblingProp)
   {
     let prop = new TextProperty(this, aName, aValue, aPriority);
-    this.textProps.push(prop);
+
+    if (aSiblingProp) {
+      let ind = this.textProps.indexOf(aSiblingProp);
+      this.textProps.splice(ind + 1, 0, prop);
+    }
+    else {
+      this.textProps.push(prop);
+    }
+
     this.applyProperties();
     return prop;
   },
 
   /**
    * Reapply all the properties in this rule, and update their
    * computed styles.  Store disabled properties in the element
    * style's store.  Will re-mark overridden properties.
@@ -589,17 +599,17 @@ Rule.prototype = {
     if (disabledProps.length > 0) {
       disabled.set(this.style, disabledProps);
     } else {
       disabled.delete(this.style);
     }
 
     let promise = aModifications.apply().then(() => {
       let cssProps = {};
-      for (let cssProp of this._parseCSSText(this.style.cssText)) {
+      for (let cssProp of parseCSSText(this.style.cssText)) {
         cssProps[cssProp.name] = cssProp;
       }
 
       for (let textProp of this.textProps) {
         if (!textProp.enabled) {
           continue;
         }
         let cssProp = cssProps[textProp.name];
@@ -696,45 +706,25 @@ Rule.prototype = {
     this.textProps = this.textProps.filter(function(prop) prop != aProperty);
     let modifications = this.style.startModifyingProperties();
     modifications.removeProperty(aProperty.name);
     // Need to re-apply properties in case removing this TextProperty
     // exposes another one.
     this.applyProperties(modifications);
   },
 
-  _parseCSSText: function Rule_parseProperties(aCssText)
-  {
-    let lines = aCssText.match(CSS_LINE_RE);
-    let props = [];
-
-    for (let line of lines) {
-      let [, name, value, priority] = CSS_PROP_RE.exec(line) || [];
-      if (!name || !value) {
-        continue;
-      }
-
-      props.push({
-        name: name,
-        value: value,
-        priority: priority || ""
-      });
-    }
-    return props;
-  },
-
   /**
    * Get the list of TextProperties from the style.  Needs
    * to parse the style's cssText.
    */
   _getTextProperties: function Rule_getTextProperties()
   {
     let textProps = [];
     let store = this.elementStyle.store;
-    let props = this._parseCSSText(this.style.cssText);
+    let props = parseCSSText(this.style.cssText);
     for (let prop of props) {
       let name = prop.name;
       if (this.inherited && !domUtils.isInheritedProperty(name)) {
         continue;
       }
       let value = store.userProperties.getProperty(this.style, name, prop.value);
       let textProp = new TextProperty(this, name, value, prop.priority);
       textProps.push(textProp);
@@ -1725,19 +1715,19 @@ RuleEditor.prototype = {
 
     this.closeBrace = createChild(code, "div", {
       class: "ruleview-ruleclose",
       tabindex: "0",
       textContent: "}"
     });
 
     // Create a property editor when the close brace is clicked.
-    editableItem({ element: this.closeBrace }, function(aElement) {
+    editableItem({ element: this.closeBrace }, (aElement) => {
       this.newProperty();
-    }.bind(this));
+    });
   },
 
   updateSourceLink: function RuleEditor_updateSourceLink()
   {
     let sourceLabel = this.element.querySelector(".source-link-label");
     sourceLabel.setAttribute("value", this.rule.title);
     sourceLabel.setAttribute("tooltiptext", this.rule.title);
 
@@ -1798,28 +1788,73 @@ RuleEditor.prototype = {
    * Programatically add a new property to the rule.
    *
    * @param {string} aName
    *        Property name.
    * @param {string} aValue
    *        Property value.
    * @param {string} aPriority
    *        Property priority.
+   * @param {TextProperty} aSiblingProp
+   *        Optional, property next to which the new property will be added.
    * @return {TextProperty}
    *        The new property
    */
-  addProperty: function RuleEditor_addProperty(aName, aValue, aPriority)
+  addProperty: function RuleEditor_addProperty(aName, aValue, aPriority, aSiblingProp)
   {
-    let prop = this.rule.createProperty(aName, aValue, aPriority);
+    let prop = this.rule.createProperty(aName, aValue, aPriority, aSiblingProp);
+    let index = this.rule.textProps.indexOf(prop);
     let editor = new TextPropertyEditor(this, prop);
-    this.propertyList.appendChild(editor.element);
+
+    // Insert this node before the DOM node that is currently at its new index
+    // in the property list.  There is currently one less node in the DOM than
+    // in the property list, so this causes it to appear after aSiblingProp.
+    // If there is no node at its index, as is the case where this is the last
+    // node being inserted, then this behaves as appendChild.
+    this.propertyList.insertBefore(editor.element,
+      this.propertyList.children[index]);
+
     return prop;
   },
 
   /**
+   * Programatically add a list of new properties to the rule.  Focus the UI
+   * to the proper location after adding (either focus the value on the
+   * last property if it is empty, or create a new property and focus it).
+   *
+   * @param {Array} aProperties
+   *        Array of properties, which are objects with this signature:
+   *        {
+   *          name: {string},
+   *          value: {string},
+   *          priority: {string}
+   *        }
+   * @param {TextProperty} aSiblingProp
+   *        Optional, the property next to which all new props should be added.
+   */
+  addProperties: function RuleEditor_addProperties(aProperties, aSiblingProp)
+  {
+    if (!aProperties || !aProperties.length) {
+      return;
+    }
+
+    let lastProp = aSiblingProp;
+    for (let p of aProperties) {
+      lastProp = this.addProperty(p.name, p.value, p.priority, lastProp);
+    }
+
+    // Either focus on the last value if incomplete, or start a new one.
+    if (lastProp && lastProp.value.trim() === "") {
+      lastProp.editor.valueSpan.click();
+    } else {
+      this.newProperty();
+    }
+  },
+
+  /**
    * Create a text input for a property name.  If a non-empty property
    * name is given, we'll create a real TextProperty and add it to the
    * rule.
    */
   newProperty: function RuleEditor_newProperty()
   {
     // If we're already creating a new property, ignore this.
     if (!this.closeBrace.hasAttribute("tabindex")) {
@@ -1835,59 +1870,81 @@ RuleEditor.prototype = {
       class: "ruleview-property ruleview-newproperty",
     });
 
     this.newPropSpan = createChild(this.newPropItem, "span", {
       class: "ruleview-propertyname",
       tabindex: "0"
     });
 
-    new InplaceEditor({
+    this.multipleAddedProperties = null;
+
+    this.editor = new InplaceEditor({
       element: this.newPropSpan,
       done: this._onNewProperty,
       destroy: this._newPropertyDestroy,
       advanceChars: ":",
       contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
       popup: this.ruleView.popup
     });
+
+    // Auto-close the input if multiple rules get pasted into new property.
+    this.editor.input.addEventListener("paste",
+      blurOnMultipleProperties, false);
   },
 
   /**
    * Called when the new property input has been dismissed.
-   * Will create a new TextProperty if necessary.
    *
    * @param {string} aValue
    *        The value in the editor.
    * @param {bool} aCommit
    *        True if the value should be committed.
    */
   _onNewProperty: function RuleEditor__onNewProperty(aValue, aCommit)
   {
     if (!aValue || !aCommit) {
       return;
     }
 
-    // Create an empty-valued property and start editing it.
-    let prop = this.rule.createProperty(aValue, "", "");
-    let editor = new TextPropertyEditor(this, prop);
-    this.propertyList.appendChild(editor.element);
-    editor.valueSpan.click();
+    // Deal with adding declarations later (once editor has been destroyed).
+    // If aValue is just a name, will make a new property with empty value.
+    this.multipleAddedProperties = parseCSSText(aValue);
+    if (!this.multipleAddedProperties.length) {
+      this.multipleAddedProperties = [{
+        name: aValue,
+        value: "",
+        priority: ""
+      }];
+    }
+
+    this.editor.input.blur();
   },
 
   /**
    * Called when the new property editor is destroyed.
+   * This is where the properties (type TextProperty) are actually being
+   * added, since we want to wait until after the inplace editor `destroy`
+   * event has been fired to keep consistent UI state.
    */
   _newPropertyDestroy: function RuleEditor__newPropertyDestroy()
   {
     // We're done, make the close brace focusable again.
     this.closeBrace.setAttribute("tabindex", "0");
 
     this.propertyList.removeChild(this.newPropItem);
     delete this.newPropItem;
     delete this.newPropSpan;
+
+    // If properties were added, we want to focus the proper element.
+    // If the last new property has no value, focus the value on it.
+    // Otherwise, start a new property and focus that field.
+    if (this.multipleAddedProperties && this.multipleAddedProperties.length) {
+      this.addProperties(this.multipleAddedProperties);
+    }
   }
 };
 
 /**
  * Create a TextPropertyEditor.
  *
  * @param {RuleEditor} aRuleEditor
  *        The rule editor that owns this TextPropertyEditor.
@@ -1911,17 +1968,18 @@ function TextPropertyEditor(aRuleEditor,
     this.sheetURI = IOService.newURI(href, null, null);
   }
 
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onValueDone = this._onValueDone.bind(this);
-  this._onValidate = throttle(this._livePreview, 10, this, this.browserWindow);
+  this._onValidate = throttle(this._livePreview, 10, this);
+  this.update = this.update.bind(this);
 
   this._create();
   this.update();
 }
 
 TextPropertyEditor.prototype = {
   /**
    * Boolean indicating if the name or value is being currently edited.
@@ -1969,22 +2027,26 @@ TextPropertyEditor.prototype = {
       class: "ruleview-propertyname theme-fg-color5",
       tabindex: "0",
     });
 
     editableField({
       start: this._onStartEditing,
       element: this.nameSpan,
       done: this._onNameDone,
-      destroy: this.update.bind(this),
+      destroy: this.update,
       advanceChars: ':',
       contentType: InplaceEditor.CONTENT_TYPES.CSS_PROPERTY,
       popup: this.popup
     });
 
+    // Auto blur name field on multiple CSS rules get pasted in.
+    this.nameContainer.addEventListener("paste",
+      blurOnMultipleProperties, false);
+
     appendText(this.nameContainer, ": ");
 
     // Create a span that will hold the property and semicolon.
     // Use this span to create a slightly larger click target
     // for the value.
     let propertyContainer = createChild(this.element, "span", {
       class: "ruleview-propertycontainer"
     });
@@ -2039,17 +2101,17 @@ TextPropertyEditor.prototype = {
     this.computed = createChild(this.element, "ul", {
       class: "ruleview-computedlist",
     });
 
     editableField({
       start: this._onStartEditing,
       element: this.valueSpan,
       done: this._onValueDone,
-      destroy: this.update.bind(this),
+      destroy: this.update,
       validate: this._onValidate,
       advanceChars: ';',
       contentType: InplaceEditor.CONTENT_TYPES.CSS_VALUE,
       property: this.prop,
       popup: this.popup
     });
   },
 
@@ -2257,40 +2319,37 @@ TextPropertyEditor.prototype = {
    * @param {string} aValue
    *        The value contained in the editor.
    * @param {boolean} aCommit
    *        True if the change should be applied.
    */
   _onNameDone: function TextPropertyEditor_onNameDone(aValue, aCommit)
   {
     if (aCommit) {
+      // Unlike the value editor, if a name is empty the entire property
+      // should always be removed.
       if (aValue.trim() === "") {
         this.remove();
       } else {
-        this.prop.setName(aValue);
+
+        // Adding multiple rules inside of name field overwrites the current
+        // property with the first, then adds any more onto the property list.
+        let properties = parseCSSText(aValue);
+        if (properties.length > 0) {
+          this.prop.setName(properties[0].name);
+          this.prop.setValue(properties[0].value, properties[0].priority);
+
+          this.ruleEditor.addProperties(properties.slice(1), this.prop);
+        } else {
+          this.prop.setName(aValue);
+        }
       }
     }
   },
 
-  /**
-   * Pull priority (!important) out of the value provided by a
-   * value editor.
-   *
-   * @param {string} aValue
-   *        The value from the text editor.
-   * @return {object} an object with 'value' and 'priority' properties.
-   */
-  _parseValue: function TextPropertyEditor_parseValue(aValue)
-  {
-    let pieces = aValue.split("!", 2);
-    return {
-      value: pieces[0].trim(),
-      priority: (pieces.length > 1 ? pieces[1].trim() : "")
-    };
-  },
 
   /**
    * Remove property from style and the editors from DOM.
    * Begin editing next available property.
    */
   remove: function TextPropertyEditor_remove()
   {
     if (this._swatchSpans && this._swatchSpans.length) {
@@ -2311,34 +2370,107 @@ TextPropertyEditor.prototype = {
    *
    * @param {string} aValue
    *        The value contained in the editor.
    * @param {bool} aCommit
    *        True if the change should be applied.
    */
    _onValueDone: function PropertyEditor_onValueDone(aValue, aCommit)
   {
-    if (aCommit) {
-      this._applyNewValue(aValue);
-    } else {
-      // A new property should be removed when escape is pressed.
-      if (this.removeOnRevert) {
-        this.remove();
-      } else {
-        // We use this.valueSpan.textContent instead of this.committed.value
-        // because otherwise pressing escape to revert a color value will result
-        // in an unparsed property value.
-        this.prop.setValue(this.valueSpan.textContent, this.committed.priority);
+    if (!aCommit) {
+       // A new property should be removed when escape is pressed.
+       if (this.removeOnRevert) {
+         this.remove();
+       } else {
+         this.prop.setValue(this.committed.value, this.committed.priority);
+       }
+       return;
+    }
+
+    let {propertiesToAdd,firstValue} = this._getValueAndExtraProperties(aValue);
+
+    // First, set this property value (common case, only modified a property)
+    let val = parseCSSValue(firstValue);
+    this.prop.setValue(val.value, val.priority);
+    this.removeOnRevert = false;
+    this.committed.value = this.prop.value;
+    this.committed.priority = this.prop.priority;
+
+    // If needed, add any new properties after this.prop.
+    this.ruleEditor.addProperties(propertiesToAdd, this.prop);
+
+    // If the name or value is not actively being edited, and the value is
+    // empty, then remove the whole property.
+    // A timeout is used here to accurately check the state, since the inplace
+    // editor `done` and `destroy` events fire before the next editor
+    // is focused.
+    if (val.value.trim() === "") {
+      setTimeout(() => {
+        if (!this.editing) {
+          this.remove();
+        }
+      }, 0);
+    }
+  },
+
+  /**
+   * Parse a value string and break it into pieces, starting with the
+   * first value, and into an array of additional properties (if any).
+   *
+   * Example: Calling with "red; width: 100px" would return
+   * { firstValue: "red", propertiesToAdd: [{ name: "width", value: "100px" }] }
+   *
+   * @param {string} aValue
+   *        The string to parse
+   * @return {object} An object with the following properties:
+   *        firstValue: A string containing a simple value, like
+   *                    "red" or "100px!important"
+   *        propertiesToAdd: An array with additional properties, following the
+   *                         parseCSSText format of {name,value,priority}
+   */
+  _getValueAndExtraProperties: function PropetyEditor_getValueAndExtraProperties(aValue) {
+
+    // The inplace editor will prevent manual typing of multiple properties,
+    // but we need to deal with the case during a paste event.
+    // Adding multiple properties inside of value editor sets value with the
+    // first, then adds any more onto the property list (below this property).
+    let properties = parseCSSText(aValue);
+    let propertiesToAdd = [];
+    let firstValue = aValue;
+
+    if (properties.length > 0) {
+      // If text like "red; width: 1px;" was entered in, handle this as two
+      // separate properties (setting value here to red and adding a new prop).
+      let propertiesNoName = parseCSSText("a:" + aValue);
+      let enteredValueFirst = propertiesNoName.length > properties.length;
+
+      let firstProp = properties[0];
+      propertiesToAdd = properties.slice(1);
+
+      if (enteredValueFirst) {
+        firstProp = propertiesNoName[0];
+        propertiesToAdd = propertiesNoName.slice(1);
       }
+
+      // If "red; width: 1px", then set value to "red"
+      // If "color: red; width: 1px;", then set value to "color: red;"
+      firstValue = enteredValueFirst ?
+        firstProp.value + "!" + firstProp.priority :
+        firstProp.name + ": " + firstProp.value + "!" + firstProp.priority;
     }
+
+    return {
+      propertiesToAdd: propertiesToAdd,
+      firstValue: firstValue
+    };
   },
 
   _applyNewValue: function PropetyEditor_applyNewValue(aValue)
   {
-    let val = this._parseValue(aValue);
+    let val = parseCSSValue(aValue);
     // Any property should be removed if has an empty value.
     if (val.value.trim() === "") {
       this.remove();
     } else {
       this.prop.setValue(val.value, val.priority);
       this.removeOnRevert = false;
       this.committed.value = this.prop.value;
       this.committed.priority = this.prop.priority;
@@ -2353,17 +2485,17 @@ TextPropertyEditor.prototype = {
    */
   _livePreview: function TextPropertyEditor_livePreview(aValue)
   {
     // Since function call is throttled, we need to make sure we are still editing
     if (!this.editing) {
       return;
     }
 
-    let val = this._parseValue(aValue);
+    let val = parseCSSValue(aValue);
 
     // Live previewing the change without committing just yet, that'll be done in _onValueDone
     // If it was not a valid value, apply an empty string to reset the live preview
     this.ruleEditor.rule.setPropertyValue(this.prop, val.value, val.priority);
   },
 
   /**
    * Validate this property. Does it make sense for this value to be assigned
@@ -2374,17 +2506,17 @@ TextPropertyEditor.prototype = {
    *        Defaults to the current value for this.prop
    *
    * @return {bool} true if the property value is valid, false otherwise.
    */
   isValid: function TextPropertyEditor_isValid(aValue)
   {
     let name = this.prop.name;
     let value = typeof aValue == "undefined" ? this.prop.value : aValue;
-    let val = this._parseValue(value);
+    let val = parseCSSValue(value);
 
     let style = this.doc.createElementNS(HTML_NS, "div").style;
     let prefs = Services.prefs;
 
     // We toggle output of errors whilst the user is typing a property value.
     let prefVal = prefs.getBoolPref("layout.css.report_errors");
     prefs.setBoolPref("layout.css.report_errors", false);
 
@@ -2519,31 +2651,108 @@ function createMenuItem(aMenu, aAttribut
   item.setAttribute("accesskey", _strings.GetStringFromName(aAttributes.accesskey));
   item.addEventListener("command", aAttributes.command);
 
   aMenu.appendChild(item);
 
   return item;
 }
 
-
-function throttle(func, wait, scope, window) {
+function setTimeout()
+{
+  let window = Services.appShell.hiddenDOMWindow;
+  return window.setTimeout.apply(window, arguments);
+}
+
+function clearTimeout()
+{
+  let window = Services.appShell.hiddenDOMWindow;
+  return window.clearTimeout.apply(window, arguments);
+}
+
+function throttle(func, wait, scope)
+{
   var timer = null;
   return function() {
     if(timer) {
-      window.clearTimeout(timer);
+      clearTimeout(timer);
     }
     var args = arguments;
-    timer = window.setTimeout(function() {
+    timer = setTimeout(function() {
       timer = null;
       func.apply(scope, args);
     }, wait);
   };
 }
 
+/**
+ * Pull priority (!important) out of the value provided by a
+ * value editor.
+ *
+ * @param {string} aValue
+ *        The value from the text editor.
+ * @return {object} an object with 'value' and 'priority' properties.
+ */
+function parseCSSValue(aValue)
+{
+  let pieces = aValue.split("!", 2);
+  return {
+    value: pieces[0].trim(),
+    priority: (pieces.length > 1 ? pieces[1].trim() : "")
+  };
+}
+
+/**
+ * Return an array of CSS properties given an input string
+ * For example, parseCSSText("width: 1px; height: 1px") would return
+ * [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
+ *
+ * @param {string} aCssText
+ *        An input string of CSS
+ * @return {Array} an array of objects with the following signature:
+ *         [{"name": string, "value": string, "priority": string}, ...]
+ */
+function parseCSSText(aCssText)
+{
+  let lines = aCssText.match(CSS_LINE_RE);
+  let props = [];
+
+  [].forEach.call(lines, (line, i) => {
+    let [, name, value, priority] = CSS_PROP_RE.exec(line) || [];
+
+    // If this is ending with an unfinished line, add it onto the end
+    // with an empty value
+    if (!name && line && i > 0) {
+      name = line;
+    }
+
+    if (name) {
+      props.push({
+        name: name.trim(),
+        value: value || "",
+        priority: priority || ""
+      });
+    }
+  });
+
+  return props;
+}
+
+/**
+ * Event handler that causes a blur on the target if the input has
+ * multiple CSS properties as the value.
+ */
+function blurOnMultipleProperties(e)
+{
+  setTimeout(() => {
+    if (parseCSSText(e.target.value).length) {
+      e.target.blur();
+    }
+  }, 0);
+}
 
 /**
  * Append a text node to an element.
  */
 function appendText(aParent, aText)
 {
   aParent.appendChild(aParent.ownerDocument.createTextNode(aText));
 }
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -14,16 +14,17 @@ skip-if = true # awaiting promise-based 
 [browser_csslogic_inherited.js]
 [browser_ruleview_734259_style_editor_link.js]
 [browser_ruleview_editor.js]
 [browser_ruleview_editor_changedvalues.js]
 [browser_ruleview_copy.js]
 [browser_ruleview_focus.js]
 [browser_ruleview_inherit.js]
 [browser_ruleview_manipulation.js]
+[browser_ruleview_multiple_properties.js]
 [browser_ruleview_override.js]
 [browser_ruleview_ui.js]
 [browser_ruleview_update.js]
 [browser_ruleview_livepreview.js]
 [browser_bug705707_is_content_stylesheet.js]
 support-files =
   browser_bug705707_is_content_stylesheet.html
   browser_bug705707_is_content_stylesheet_imported.css
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_902966_revert_value_on_ESC.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_902966_revert_value_on_ESC.js
@@ -16,16 +16,17 @@ let originalValue = "#00F";
 //  value: what char sequence to type,
 //  commitKey: what key to type to "commit" the change,
 //  modifiers: commitKey modifiers,
 //  expected: what value is expected as a result
 // }
 let testData = [
   {value: "red", commitKey: "VK_ESCAPE", modifiers: {}, expected: originalValue},
   {value: "red", commitKey: "VK_RETURN", modifiers: {}, expected: "#F00"},
+  {value: "invalid", commitKey: "VK_RETURN", modifiers: {}, expected: "invalid"},
   {value: "blue", commitKey: "VK_TAB", modifiers: {shiftKey: true}, expected: "blue"}
 ];
 
 function startTests()
 {
   let style = '' +
     '#testid {' +
     '  color: ' + originalValue + ';' +
@@ -54,21 +55,32 @@ function runTestData(index)
   let idRuleEditor = ruleView.element.children[1]._ruleEditor;
   let propEditor = idRuleEditor.rule.textProps[0].editor;
   waitForEditorFocus(propEditor.element, function(aEditor) {
     is(inplaceEditor(propEditor.valueSpan), aEditor, "Focused editor should be the value span.");
 
     for (let ch of testData[index].value) {
       EventUtils.sendChar(ch, ruleWindow);
     }
-    EventUtils.synthesizeKey(testData[index].commitKey, testData[index].modifiers);
 
-    is(propEditor.valueSpan.textContent, testData[index].expected);
-
-    runTestData(index + 1);
+    // Need to wait for the change to be finished before the next test starts
+    // if not cancelling the change (the previous modification can change which
+    // color format is shown).
+    if (testData[index].commitKey === "VK_ESCAPE") {
+      EventUtils.synthesizeKey(testData[index].commitKey, testData[index].modifiers);
+      is(propEditor.valueSpan.textContent, testData[index].expected, "Value is same as expected: " + testData[index].expected);
+      runTestData(index + 1);
+    } else {
+      ruleView.element.addEventListener("CssRuleViewChanged", function nextTest() {
+        ruleView.element.removeEventListener("CssRuleViewChanged", nextTest);
+        is(propEditor.valueSpan.textContent, testData[index].expected, "Value is same as expected: " + testData[index].expected);
+        runTestData(index + 1);
+      });
+      EventUtils.synthesizeKey(testData[index].commitKey, testData[index].modifiers);
+    }
   });
 
   EventUtils.synthesizeMouse(propEditor.valueSpan, 1, 1, {}, ruleWindow);
 }
 
 function finishTest()
 {
   inspector = ruleWindow = ruleView = null;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_multiple_properties.js
@@ -0,0 +1,274 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let doc;
+let ruleWindow;
+let ruleView;
+let inspector;
+let elementRuleEditor;
+
+function startTest()
+{
+  doc.body.innerHTML = '<h1>Testing Multiple Properties</h1>';
+
+  openRuleView((aInspector, aRuleView) => {
+    inspector = aInspector;
+    ruleView = aRuleView;
+    ruleWindow = aRuleView.doc.defaultView;
+    selectNewElement().then(testCreateNewMulti);
+  });
+}
+
+/*
+ * Add a new node to the DOM and resolve the promise once it is ready to use
+ */
+function selectNewElement()
+{
+  let newElement = doc.createElement("div");
+  newElement.textContent = "Test Element";
+  doc.body.appendChild(newElement);
+  inspector.selection.setNode(newElement);
+  let def = promise.defer();
+  ruleView.element.addEventListener("CssRuleViewRefreshed", function changed() {
+    ruleView.element.removeEventListener("CssRuleViewRefreshed", changed);
+    elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+    def.resolve();
+  });
+  return def.promise;
+}
+
+/*
+ * Begin the creation of a new property, resolving after the editor
+ * has been created.
+ */
+function beginNewProp()
+{
+  let def = promise.defer();
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+
+    is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
+    is(elementRuleEditor.rule.textProps.length,  0, "Should be starting with one new text property.");
+    is(elementRuleEditor.propertyList.children.length, 1, "Should be starting with two property editors.");
+
+    def.resolve(aEditor);
+  });
+  elementRuleEditor.closeBrace.scrollIntoView();
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleWindow);
+  return def.promise;
+}
+
+/*
+ * Fully create a new property, given some text input
+ */
+function createNewProp(inputValue)
+{
+  let def = promise.defer();
+  beginNewProp().then((aEditor)=>{
+    aEditor.input.value = inputValue;
+
+    waitForEditorFocus(elementRuleEditor.element, function onNewValue(aEditor) {
+      promiseDone(expectRuleChange(elementRuleEditor.rule).then(() => {
+        def.resolve();
+      }));
+    });
+
+    EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
+  });
+
+  return def.promise;
+}
+
+function testCreateNewMulti()
+{
+  createNewProp(
+    "color:blue;background : orange   ; text-align:center; border-color: green;"
+  ).then(()=>{
+    is(elementRuleEditor.rule.textProps.length, 4, "Should have created a new text property.");
+    is(elementRuleEditor.propertyList.children.length, 5, "Should have created a new property editor.");
+
+    is(elementRuleEditor.rule.textProps[0].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[0].value, "blue", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[1].name, "background", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[1].value, "orange", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[2].name, "text-align", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[2].value, "center", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[3].name, "border-color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[3].value, "green", "Should have correct property value");
+
+    selectNewElement().then(testCreateNewMultiDuplicates);
+  });
+}
+
+function testCreateNewMultiDuplicates()
+{
+  createNewProp(
+    "color:red;color:orange;color:yellow;color:green;color:blue;color:indigo;color:violet;"
+  ).then(()=>{
+    is(elementRuleEditor.rule.textProps.length, 7, "Should have created new text properties.");
+    is(elementRuleEditor.propertyList.children.length, 8, "Should have created new property editors.");
+
+    is(elementRuleEditor.rule.textProps[0].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[0].value, "red", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[1].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[1].value, "orange", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[2].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[2].value, "yellow", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[3].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[3].value, "green", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[4].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[4].value, "blue", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[5].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[5].value, "indigo", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[6].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[6].value, "violet", "Should have correct property value");
+
+    selectNewElement().then(testCreateNewMultiPriority);
+  });
+}
+
+function testCreateNewMultiPriority()
+{
+  createNewProp(
+    "color:red;width:100px;height: 100px;"
+  ).then(()=>{
+    is(elementRuleEditor.rule.textProps.length, 3, "Should have created new text properties.");
+    is(elementRuleEditor.propertyList.children.length, 4, "Should have created new property editors.");
+
+    is(elementRuleEditor.rule.textProps[0].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[0].value, "red", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[1].name, "width", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[1].value, "100px", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[2].name, "height", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[2].value, "100px", "Should have correct property value");
+
+    selectNewElement().then(testCreateNewMultiUnfinished);
+  });
+}
+
+function testCreateNewMultiUnfinished()
+{
+  createNewProp(
+    "color:blue;background : orange   ; text-align:center; border-color: "
+  ).then(()=>{
+    is(elementRuleEditor.rule.textProps.length, 4, "Should have created new text properties.");
+    is(elementRuleEditor.propertyList.children.length, 4, "Should have created property editors.");
+
+    for (let ch of "red") {
+      EventUtils.sendChar(ch, ruleWindow);
+    }
+    EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
+
+    is(elementRuleEditor.rule.textProps.length, 4, "Should have the same number of text properties.");
+    is(elementRuleEditor.propertyList.children.length, 5, "Should have added the changed value editor.");
+
+    is(elementRuleEditor.rule.textProps[0].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[0].value, "blue", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[1].name, "background", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[1].value, "orange", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[2].name, "text-align", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[2].value, "center", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[3].name, "border-color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[3].value, "red", "Should have correct property value");
+
+    selectNewElement().then(testCreateNewMultiPartialUnfinished);
+  });
+}
+
+
+function testCreateNewMultiPartialUnfinished()
+{
+  createNewProp(
+    "width: 100px; heig"
+  ).then(()=>{
+    is(elementRuleEditor.rule.textProps.length, 2, "Should have created a new text property.");
+    is(elementRuleEditor.propertyList.children.length, 2, "Should have created a property editor.");
+
+    // Value is focused, lets add multiple rules here and make sure they get added
+    let valueEditor = elementRuleEditor.propertyList.children[1].querySelector("input");
+    valueEditor.value = "10px;background:orangered;color: black;";
+    EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
+
+    is(elementRuleEditor.rule.textProps.length, 4, "Should have added the changed value.");
+    is(elementRuleEditor.propertyList.children.length, 5, "Should have added the changed value editor.");
+
+    is(elementRuleEditor.rule.textProps[0].name, "width", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[0].value, "100px", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[1].name, "heig", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[1].value, "10px", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[2].name, "background", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[2].value, "orangered", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[3].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[3].value, "black", "Should have correct property value");
+
+    selectNewElement().then(testMultiValues);
+  });
+}
+
+function testMultiValues()
+{
+  createNewProp(
+    "width:"
+  ).then(()=>{
+    is(elementRuleEditor.rule.textProps.length, 1, "Should have created a new text property.");
+    is(elementRuleEditor.propertyList.children.length, 1, "Should have created a property editor.");
+
+    // Value is focused, lets add multiple rules here and make sure they get added
+    let valueEditor = elementRuleEditor.propertyList.children[0].querySelector("input");
+    valueEditor.value = "height: 10px;color:blue"
+    EventUtils.synthesizeKey("VK_RETURN", {}, ruleWindow);
+
+    is(elementRuleEditor.rule.textProps.length, 2, "Should have added the changed value.");
+    is(elementRuleEditor.propertyList.children.length, 3, "Should have added the changed value editor.");
+
+    EventUtils.synthesizeKey("VK_ESCAPE", {}, ruleWindow);
+    is(elementRuleEditor.propertyList.children.length, 2, "Should have removed the value editor.");
+
+    is(elementRuleEditor.rule.textProps[0].name, "width", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[0].value, "height: 10px", "Should have correct property value");
+
+    is(elementRuleEditor.rule.textProps[1].name, "color", "Should have correct property name");
+    is(elementRuleEditor.rule.textProps[1].value, "blue", "Should have correct property value");
+
+    finishTest();
+  });
+}
+
+function finishTest()
+{
+  inspector = ruleWindow = ruleView = doc = elementRuleEditor = null;
+  gBrowser.removeCurrentTab();
+  finish();
+}
+
+function test()
+{
+  waitForExplicitFinish();
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function changedValues_load(evt) {
+    gBrowser.selectedBrowser.removeEventListener(evt.type, changedValues_load, true);
+    doc = content.document;
+    waitForFocus(startTest, content);
+  }, true);
+
+  content.location = "data:text/html,test rule view user changes";
+}
--- a/browser/devtools/styleinspector/test/browser_ruleview_ui.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_ui.js
@@ -50,19 +50,44 @@ function testCancelNew()
 
   let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
   waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
     is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
     waitForEditorBlur(aEditor, function () {
       ok(!elementRuleEditor.rule._applyingModifications, "Shouldn't have an outstanding modification request after a cancel.");
       is(elementRuleEditor.rule.textProps.length,  0, "Should have canceled creating a new text property.");
       ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
+      testCancelNewOnEscape();
+    });
+    aEditor.input.blur();
+  });
+
+  EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
+                             { },
+                             ruleWindow);
+}
+
+function testCancelNewOnEscape()
+{
+  // Start at the beginning: start to add a rule to the element's style
+  // declaration, add some text, then press escape.
+
+  let elementRuleEditor = ruleView.element.children[0]._ruleEditor;
+  waitForEditorFocus(elementRuleEditor.element, function onNewElement(aEditor) {
+    is(inplaceEditor(elementRuleEditor.newPropSpan), aEditor, "Next focused editor should be the new property editor.");
+    for (let ch of "background") {
+      EventUtils.sendChar(ch, ruleWindow);
+    }
+    waitForEditorBlur(aEditor, function () {
+      ok(!elementRuleEditor.rule._applyingModifications, "Shouldn't have an outstanding modification request after a cancel.");
+      is(elementRuleEditor.rule.textProps.length,  0, "Should have canceled creating a new text property.");
+      ok(!elementRuleEditor.propertyList.hasChildNodes(), "Should not have any properties.");
       testCreateNew();
     });
-    aEditor.input.blur();
+    EventUtils.synthesizeKey("VK_ESCAPE", { });
   });
 
   EventUtils.synthesizeMouse(elementRuleEditor.closeBrace, 1, 1,
                              { },
                              ruleWindow);
 }
 
 function testCreateNew()
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -499,17 +499,20 @@ WebConsole.prototype = {
     let toolbox = gDevTools.getToolbox(this.target);
     if (!toolbox) {
       this.viewSource(aSourceURL, aSourceLine);
       return;
     }
 
     let showSource = ({ DebuggerView }) => {
       if (DebuggerView.Sources.containsValue(aSourceURL)) {
-        DebuggerView.setEditorLocation(aSourceURL, aSourceLine, { noDebug: true });
+        DebuggerView.setEditorLocation(aSourceURL, aSourceLine,
+                                       { noDebug: true }).then(() => {
+          this.ui.emit("source-in-debugger-opened");
+        });
         return;
       }
       toolbox.selectTool("webconsole");
       this.viewSource(aSourceURL, aSourceLine);
     }
 
     // If the Debugger was already open, switch to it and try to show the
     // source immediately. Otherwise, initialize it and wait for the sources
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_766001_JS_Console_in_Debugger.js
@@ -2,115 +2,72 @@
 /* ***** BEGIN LICENSE BLOCK *****
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  * ***** END LICENSE BLOCK ***** */
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
                  "/test-bug-766001-js-console-links.html";
 
-let nodes, dbg, toolbox, target, index = 0, src, line;
+function test() {
+  let hud;
 
-function test()
-{
-  expectUncaughtException();
   requestLongerTimeout(2);
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    openConsole(null, testViewSource);
-  }, true);
-}
+  Task.spawn(runner).then(finishTest);
+
+  function* runner() {
+    expectUncaughtException();
+    let {tab} = yield loadTab(TEST_URI);
+    hud = yield openConsole(tab);
 
-function testViewSource(aHud)
-{
-  registerCleanupFunction(function() {
-    nodes = dbg = toolbox = target = index = src = line = null;
-  });
+    let [exceptionRule, consoleRule] = yield waitForMessages({
+      webconsole: hud,
+      messages: [{
+        text: "document.bar",
+        category: CATEGORY_JS,
+        severity: SEVERITY_ERROR,
+      },
+      {
+        text: "Blah Blah",
+        category: CATEGORY_WEBDEV,
+        severity: SEVERITY_LOG,
+      }],
+    });
 
-  waitForMessages({
-    webconsole: aHud,
-    messages: [{
-      text: "document.bar",
-      category: CATEGORY_JS,
-      severity: SEVERITY_ERROR,
-    },
-    {
-      text: "Blah Blah",
-      category: CATEGORY_WEBDEV,
-      severity: SEVERITY_LOG,
-    }],
-  }).then(([exceptionRule, consoleRule]) => {
     let exceptionMsg = [...exceptionRule.matched][0];
     let consoleMsg = [...consoleRule.matched][0];
-    nodes = [exceptionMsg.querySelector(".location"),
-             consoleMsg.querySelector(".location")];
+    let nodes = [exceptionMsg.querySelector(".location"),
+                 consoleMsg.querySelector(".location")];
     ok(nodes[0], ".location node for the exception message");
     ok(nodes[1], ".location node for the console message");
 
-    target = TargetFactory.forTab(gBrowser.selectedTab);
-    toolbox = gDevTools.getToolbox(target);
-    toolbox.once("jsdebugger-selected", checkLineAndClickNext);
-
-    EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
-  });
-}
-
-function checkLineAndClickNext(aEvent, aPanel)
-{
-  if (index == 3) {
-    finishTest();
-    return;
-  }
-  info(aEvent + " event fired for index " + index);
+    for (let i = 0; i < nodes.length; i++) {
+      yield checkClickOnNode(i, nodes[i]);
+      yield gDevTools.showToolbox(hud.target, "webconsole");
+    }
 
-  dbg = aPanel;
-
-  src = nodes[index%2].getAttribute("title");
-  ok(src, "source url found for index " + index);
-  line = nodes[index%2].sourceLine;
-  ok(line, "found source line for index " + index);
+    // check again the first node.
+    yield checkClickOnNode(0, nodes[0]);
+  }
 
-  info("Waiting for the correct script to be selected for index " + index);
-  dbg.panelWin.on(dbg.panelWin.EVENTS.SOURCE_SHOWN, onSource);
-}
+  function* checkClickOnNode(index, node) {
+    info("checking click on node index " + index);
 
-function onSource(aEvent, aSource) {
-  if (aSource.url != src) {
-    return;
-  }
-  dbg.panelWin.off(dbg.panelWin.EVENTS.SOURCE_SHOWN, onSource);
+    let url = node.getAttribute("title");
+    ok(url, "source url found for index " + index);
 
-  ok(true, "Correct script is selected for index " + index);
-
-  checkCorrectLine(function() {
-    gDevTools.showToolbox(target, "webconsole").then(function() {
-      index++;
-      info("webconsole selected for index " + index);
+    let line = node.sourceLine;
+    ok(line, "found source line for index " + index);
 
-      toolbox.once("jsdebugger-selected", checkLineAndClickNext);
-
-      EventUtils.sendMouseEvent({ type: "click" }, nodes[index%2]);
+    executeSoon(() => {
+      EventUtils.sendMouseEvent({ type: "click" }, node);
     });
-  });
-}
 
-function checkCorrectLine(aCallback)
-{
-  waitForSuccess({
-    name: "correct source and line test for debugger for index " + index,
-    validatorFn: function()
-    {
-      let debuggerView = dbg.panelWin.DebuggerView;
-      if (debuggerView.editor &&
-          debuggerView.editor.getCursor().line == line - 1) {
-        return true;
-      }
-      return false;
-    },
-    successFn: function()
-    {
-      aCallback && executeSoon(aCallback);
-    },
-    failureFn: finishTest,
-    timeout: 10000,
-  });
+    yield hud.ui.once("source-in-debugger-opened", checkLine.bind(null, url, line));
+  }
+
+  function* checkLine(url, line) {
+    let toolbox = yield gDevTools.getToolbox(hud.target);
+    let {panelWin: { DebuggerView: view }} = toolbox.getPanel("jsdebugger");
+    is(view.Sources.selectedValue, url, "expected source url");
+    is(view.editor.getCursor().line, line - 1, "expected source line");
+  }
 }
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -62,16 +62,30 @@ let tab, browser, hudId, hud, hudBox, fi
 
 function addTab(aURL)
 {
   gBrowser.selectedTab = gBrowser.addTab(aURL);
   tab = gBrowser.selectedTab;
   browser = gBrowser.getBrowserForTab(tab);
 }
 
+function loadTab(url) {
+  let deferred = promise.defer();
+
+  let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+  let browser = gBrowser.getBrowserForTab(tab);
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    deferred.resolve({tab: tab, browser: browser});
+  }, true);
+
+  return deferred.promise;
+}
+
 function afterAllTabsLoaded(callback, win) {
   win = win || window;
 
   let stillToLoad = 0;
 
   function onLoad() {
     this.removeEventListener("load", onLoad, true);
     stillToLoad--;
--- a/browser/metro/base/content/ContextUI.js
+++ b/browser/metro/base/content/ContextUI.js
@@ -311,17 +311,19 @@ var ContextUI = {
       this._delayedHide = false;
       this._resetDelayedTimeout();
     }
   },
 
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "URLChanged":
-        if (aEvent.target == Browser.selectedBrowser) {
+        // "aEvent.detail" is a boolean value that indicates whether actual URL
+        // has changed ignoring URL fragment changes.
+        if (aEvent.target == Browser.selectedBrowser && aEvent.detail) {
           this.displayNavbar();
         }
         break;
       case "MozEdgeUIStarted":
         this._onEdgeUIStarted(aEvent);
         break;
       case "MozEdgeUICanceled":
         this._onEdgeUICanceled(aEvent);
--- a/browser/metro/base/tests/mochitest/browser_context_ui.js
+++ b/browser/metro/base/tests/mochitest/browser_context_ui.js
@@ -10,16 +10,23 @@ function test() {
 }
 
 function doEdgeUIGesture() {
   let event = document.createEvent("Events");
   event.initEvent("MozEdgeUICompleted", true, false);
   window.dispatchEvent(event);
 }
 
+function fireTabURLChanged(tab, hasLocationChanged) {
+  let urlChangedEvent = document.createEvent("UIEvents");
+  urlChangedEvent.initUIEvent("URLChanged", true, false, window,
+      hasLocationChanged);
+  tab.browser.dispatchEvent(urlChangedEvent);
+}
+
 function getpage(idx) {
   return "http://mochi.test:8888/metro/browser/metro/base/tests/mochitest/" + "res/blankpage" + idx + ".html";
 }
 
 gTests.push({
   desc: "Context UI on about:start",
   run: function testAboutStart() {
     let tab = yield addTab("about:start");
@@ -251,8 +258,31 @@ gTests.push({
     let edit = document.getElementById("urlbar-edit");
 
     ok(!edit.focused, "Edit is not focused");
 
     Browser.closeTab(newTab, { forceClose: true });
     Browser.closeTab(mozTab, { forceClose: true });
   }
 });
+
+gTests.push({
+  desc: "Bug 956576 - Location app bar pops up when fragment identifier " +
+        "changes (URL stuff after hash / number sign).",
+  run: function () {
+    let tab = yield addTab("about:mozilla");
+    yield showNavBar();
+    ok(ContextUI.navbarVisible, "Navbar is initially visible.");
+
+    ContextUI.dismiss();
+    ok(!ContextUI.navbarVisible, "Navbar is dismissed and hidden.");
+
+    let locationHasChanged = false;
+    fireTabURLChanged(tab, locationHasChanged);
+    ok(!ContextUI.navbarVisible, "Navbar isn't shown on URL fragment change.");
+
+    locationHasChanged = true;
+    fireTabURLChanged(tab, locationHasChanged);
+    ok(ContextUI.navbarVisible, "Navbar is shown on actual URL change.");
+
+    Browser.closeTab(tab, { forceClose: true });
+  }
+});
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -1,14 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 %filter substitution
-%define menuPanelWidth 21em
+
+%define menuPanelWidth 22.35em
+%define panelTextSize 1rem
 %define exitSubviewGutterWidth 38px
 %define buttonStateHover :not(:-moz-any([disabled],[checked="true"],[open],:active)):hover
 %define buttonStateActive :not([disabled]):-moz-any([open],[checked="true"],:hover:active)
 
 %include ../browser.inc
 
 .panel-subviews {
   background-image: linear-gradient(to bottom, white 1px, rgba(255, 255, 255, 0) 15px);
@@ -66,17 +68,17 @@
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text,
 #bookmarks-menu-button > toolbarbutton > .toolbarbutton-text,
 :-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbaritem > toolbarbutton > .toolbarbutton-text,
 :-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbaritem > toolbarbutton > .toolbarbutton-text,
 :-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarpaletteitem > toolbarbutton > .toolbarbutton-text,
 :-moz-any(#PanelUI-contents,#widget-overflow-list) > toolbarbutton > .toolbarbutton-text {
-  font-size: 10px;
+  font-size: @panelTextSize@;
 }
 
 #wrapper-edit-controls:-moz-any([place="palette"],[place="panel"]) > #edit-controls,
 #wrapper-zoom-controls:-moz-any([place="palette"],[place="panel"]) > #zoom-controls {
   -moz-margin-start: 0;
 }
 
 #PanelUI-contents,
@@ -119,18 +121,18 @@ toolbaritem[cui-areatype="menu-panel"][s
 #PanelUI-contents toolbarbutton,
 toolbarpaletteitem[place="panel"] > toolbarbutton,
 toolbarpaletteitem[place="palette"] > toolbarbutton,
 toolbarpaletteitem[place="panel"] > toolbaritem > toolbarbutton,
 toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton,
 .panel-customization-placeholder {
   -moz-appearance: none;
   -moz-box-orient: vertical;
-  min-width: 7em;
-  max-width: 7em;
+  min-width: calc(@menuPanelWidth@ / 3);
+  max-width: calc(@menuPanelWidth@ / 3);
   height: calc(40px + 2.4em);
   max-height: calc(40px + 2.4em);
 }
 
 .panel-customization-placeholder[expand],
 .panel-customization-placeholder[contract] {
   transition-property: width;
   transition-duration: 170ms;
@@ -140,33 +142,34 @@ toolbarpaletteitem[place="palette"] > to
   max-width: none;
 }
 
 .panel-combined-button[disabled] > .toolbarbutton-icon {
   opacity: .5;
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] {
-  width: 7em;
+  width: calc(@menuPanelWidth@ / 3);
   margin: 0 !important;
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-widget) {
   -moz-box-align: center;
   -moz-box-pack: center;
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"] > iframe {
   margin: 4px auto;
 }
 
 toolbaritem[cui-areatype="menu-panel"][sdkstylewidget="true"]:not(.panel-wide-item) > .toolbarbutton-text {
   text-align: center;
 }
 
+#PanelUI-contents .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
 .panel-customization-placeholder-child > .toolbarbutton-icon,
 #bookmarks-menu-button[cui-areatype="menu-panel"] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 toolbarbutton[cui-areatype="menu-panel"] > .toolbarbutton-icon,
 toolbaritem[cui-areatype="menu-panel"] > toolbarbutton > .toolbarbutton-icon,
 toolbarpaletteitem[place="palette"] > #bookmarks-menu-button > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
 toolbarpaletteitem[place="palette"] > toolbarbutton > .toolbarbutton-icon,
 toolbarpaletteitem[place="palette"] > toolbaritem > toolbarbutton > .toolbarbutton-icon {
   min-width: 32px;
@@ -407,18 +410,18 @@ toolbarpaletteitem[place="palette"] > #b
   display: none;
 }
 
 #search-container[cui-areatype="menu-panel"] {
   width: @menuPanelWidth@;
 }
 
 toolbarpaletteitem[place="palette"] > #search-container {
-  min-width: 7em;
-  width: 7em;
+  min-width: calc(@menuPanelWidth@ / 3);
+  width: calc(@menuPanelWidth@ / 3);
 }
 
 #edit-controls@inAnyPanel@,
 #zoom-controls@inAnyPanel@ {
   background-color: hsla(210,4%,10%,0);
   border-radius: 2px;
   border: 1px solid;
   border-color: hsla(210,4%,10%,0);
@@ -432,18 +435,18 @@ toolbarpaletteitem[place="palette"] > #s
 #edit-controls@inAnyPanel@ > toolbarbutton,
 #zoom-controls@inAnyPanel@ > toolbarbutton {
   border: 0;
   padding: .5em;
   margin: 0;
   -moz-box-flex: 1;
   /* reduce the width with 2px for each button to compensate for two separators
      of 3px. */
-  min-width: calc(7em - 2px);
-  max-width: calc(7em - 2px);
+  min-width: calc(@menuPanelWidth@ / 3 - 2px);
+  max-width: calc(@menuPanelWidth@ / 3 - 2px);
   height: auto;
   max-height: none;
   -moz-box-orient: horizontal;
 }
 
 #edit-controls@inAnyPanel@ > toolbarbutton[disabled] > .toolbarbutton-icon,
 #zoom-controls@inAnyPanel@ > toolbarbutton[disabled] > .toolbarbutton-icon {
   opacity: .25;
--- a/content/base/public/nsIContentSecurityPolicy.idl
+++ b/content/base/public/nsIContentSecurityPolicy.idl
@@ -1,26 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIURI;
-interface nsIHttpChannel;
+interface nsIChannel;
 interface nsIDocShell;
 
 /**
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
-[scriptable, uuid(ff46c14e-5b2d-4aca-8961-d0b0d987cb81)]
+[scriptable, uuid(2e7875a3-8cb5-4ebb-905b-af0a90dae594)]
 interface nsIContentSecurityPolicy : nsISupports
 {
 
   /**
    * Set to true when the CSP has been read in and parsed and is ready to
    * enforce.  This is a barrier for the nsDocument so it doesn't load any
    * sub-content until either it knows that a CSP is ready or will not be used.
    */
@@ -178,17 +178,17 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short VIOLATION_TYPE_NONCE_STYLE   = 5;
   const unsigned short VIOLATION_TYPE_HASH_SCRIPT   = 6;
   const unsigned short VIOLATION_TYPE_HASH_STYLE    = 7;
 
   /**
    * Called after the CSP object is created to fill in the appropriate request
    * and request header information needed in case a report needs to be sent.
    */
-  void scanRequestData(in nsIHttpChannel aChannel);
+  void scanRequestData(in nsIChannel aChannel);
 
   /**
    * Verifies ancestry as permitted by the policy.
    *
    * NOTE: Calls to this may trigger violation reports when queried, so this
    * value should not be cached.
    *
    * @param docShell
--- a/content/base/src/contentSecurityPolicy.js
+++ b/content/base/src/contentSecurityPolicy.js
@@ -359,17 +359,17 @@ ContentSecurityPolicy.prototype = {
     this._request = uri.asciiSpec;
     this._requestOrigin = uri;
 
     //store a reference to the principal, that can later be used in shouldLoad
     this._weakRequestPrincipal = Cu.getWeakReference(Cc["@mozilla.org/scriptsecuritymanager;1"]
                                                        .getService(Ci.nsIScriptSecurityManager)
                                                        .getChannelPrincipal(aChannel));
 
-    if (aChannel.referrer) {
+    if (aChannel instanceof Ci.nsIHttpChannel && aChannel.referrer) {
       let referrer = aChannel.referrer.cloneIgnoringRef();
       try { // GetUserPass throws for some protocols without userPass
         referrer.userPass = '';
       } catch (ex) {}
       this._referrer = referrer.asciiSpec;
     }
   },
 
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -2686,17 +2686,17 @@ nsDocument::InitCSP(nsIChannel* aChannel
     return rv;
   }
 
   // used as a "self" identifier for the CSP.
   nsCOMPtr<nsIURI> selfURI;
   aChannel->GetURI(getter_AddRefs(selfURI));
 
   // Store the request context for violation reports
-  csp->ScanRequestData(httpChannel);
+  csp->ScanRequestData(aChannel);
 
   // ----- if the doc is an app and we want a default CSP, apply it.
   if (applyAppDefaultCSP) {
     nsAdoptingString appCSP;
     if (appStatus ==  nsIPrincipal::APP_STATUS_PRIVILEGED) {
       appCSP = Preferences::GetString("security.apps.privileged.CSP.default");
       NS_ASSERTION(appCSP, "App, but no default CSP in security.apps.privileged.CSP.default");
     } else if (appStatus == nsIPrincipal::APP_STATUS_CERTIFIED) {
--- a/content/base/test/csp/mochitest.ini
+++ b/content/base/test/csp/mochitest.ini
@@ -127,8 +127,9 @@ support-files =
 [test_CSP_bug910139.html]
 [test_CSP_bug909029.html]
 [test_policyuri_regression_from_multipolicy.html]
 [test_nonce_source.html]
 [test_CSP_bug941404.html]
 [test_hash_source.html]
 [test_dual_headers_warning.html]
 [test_self_none_as_hostname_confusion.html]
+[test_bug949549.html]
new file mode 100644
--- /dev/null
+++ b/content/base/test/csp/test_bug949549.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Bug 949549</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=949549">Mozilla Bug 949549</a>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+  "use strict";
+
+  // Ensure that `scanRequestData` doesn't throw with app:// URLs
+
+  const csp = SpecialPowers.Cc["@mozilla.org/contentsecuritypolicy;1"]
+              .createInstance(SpecialPowers.Ci.nsIContentSecurityPolicy);
+
+  const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
+
+  SimpleTest.waitForExplicitFinish();
+  var launchableValue, app;
+
+  function setupTest() {
+    // We have to install an app in order for the app URL to be valid
+    // (otherwise we get a "DummyChannel" that throws NS_NOT_IMPLEMENTED)
+    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.addPermission("webapps-manage", true, document);
+    SpecialPowers.autoConfirmAppInstall(function () {
+      let req = navigator.mozApps.install(gManifestURL);
+      req.onsuccess = function () {
+        app = this.result;
+        runTest();
+      }
+    });
+  }
+
+  function runTest() {
+    // We have to use a mochitest to test app:// urls,
+    // as app channels can't be instanciated in xpcshell.
+    // Because app protocol depends on webapps.jsm,
+    // which doesn't instanciate properly on xpcshell without many hacks
+    let appchan = SpecialPowers.Services.io.newChannel(gManifestURL, null, null);
+
+    try {
+      csp.scanRequestData(appchan);
+      ok(true, "scanRequestData hasn't thown");
+    } catch(e) {
+      ok(false, "scanRequestData throws");
+    }
+
+    cleanup()
+  }
+
+  function cleanup() {
+    SpecialPowers.setAllAppsLaunchable(launchableValue);
+    let req = navigator.mozApps.mgmt.uninstall(app);
+    req.onsuccess = function () {
+      SimpleTest.finish();
+    };
+  }
+
+  setupTest();
+</script>
+</pre>
+</body>
+</html>
--- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -186,17 +186,28 @@ MobileMessageDB.prototype = {
             self.upgradeSchema11(event.target.transaction, next);
             break;
           case 12:
             if (DEBUG) debug("Upgrade to version 13. Replaced deliveryStatus by deliveryInfo.");
             self.upgradeSchema12(event.target.transaction, next);
             break;
           case 13:
             if (DEBUG) debug("Upgrade to version 14. Fix the wrong participants.");
-            self.upgradeSchema13(event.target.transaction, next);
+            // A workaround to check if we need to re-upgrade the DB schema 12. We missed this
+            // because we didn't properly uplift that logic to b2g_v1.2 and errors could happen
+            // when migrating b2g_v1.2 to b2g_v1.3. Please see Bug 960741 for details.
+            self.needReUpgradeSchema12(event.target.transaction, function(isNeeded) {
+              if (isNeeded) {
+                self.upgradeSchema12(event.target.transaction, function() {
+                  self.upgradeSchema13(event.target.transaction, next);
+                });
+              } else {
+                self.upgradeSchema13(event.target.transaction, next);
+              }
+            });
             break;
           case 14:
             if (DEBUG) debug("Upgrade to version 15. Add deliveryTimestamp.");
             self.upgradeSchema14(event.target.transaction, next);
             break;
           case 15:
             if (DEBUG) debug("Upgrade to version 16. Add ICC ID for each message.");
             self.upgradeSchema15(event.target.transaction, next);
@@ -898,16 +909,39 @@ MobileMessageDB.prototype = {
         delete messageRecord.deliveryStatus;
         cursor.update(messageRecord);
       }
       cursor.continue();
     };
   },
 
   /**
+   * Check if we need to re-upgrade the DB schema 12.
+   */
+  needReUpgradeSchema12: function(transaction, callback) {
+    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
+
+    messageStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (!cursor) {
+        callback(false);
+        return;
+      }
+
+      let messageRecord = cursor.value;
+      if (messageRecord.type == "mms" &&
+          messageRecord.deliveryInfo === undefined) {
+        callback(true);
+        return;
+      }
+      cursor.continue();
+    };
+  },
+
+  /**
    * Fix the wrong participants.
    */
   upgradeSchema13: function(transaction, next) {
     let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME);
     let threadStore = transaction.objectStore(THREAD_STORE_NAME);
     let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
     let self = this;
 
--- a/dom/mobilemessage/tests/marionette/test_getsegmentinfofortext.js
+++ b/dom/mobilemessage/tests/marionette/test_getsegmentinfofortext.js
@@ -107,17 +107,17 @@ addTest(1.0, 1, PDU_MAX_USER_DATA_7BIT, 
 addTest({}, 1, PDU_MAX_USER_DATA_7BIT,
         PDU_MAX_USER_DATA_7BIT - (("" + {}).length + 2));
 
 // Testing Date object.
 let date = new Date();
 addTest(date, 1, PDU_MAX_USER_DATA_7BIT,
         PDU_MAX_USER_DATA_7BIT - ("" + date).length);
 
-addTest("", 0, PDU_MAX_USER_DATA_7BIT,
+addTest("", 1, PDU_MAX_USER_DATA_7BIT,
         PDU_MAX_USER_DATA_7BIT - "".length);
 
 // WARNING: All tasks should be pushed before this!!!
 tasks.push(function cleanUp() {
   SpecialPowers.removePermission("sms", document);
   SpecialPowers.clearUserPref("dom.sms.enabled");
   finish();
 });
deleted file mode 100644
--- a/dom/network/src/MobileConnection.cpp
+++ /dev/null
@@ -1,738 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/dom/network/MobileConnection.h"
-
-#include "GeneratedEvents.h"
-#include "mozilla/dom/CFStateChangeEvent.h"
-#include "mozilla/dom/DataErrorEvent.h"
-#include "mozilla/dom/MozEmergencyCbModeEvent.h"
-#include "mozilla/dom/MozOtaStatusEvent.h"
-#include "mozilla/dom/USSDReceivedEvent.h"
-#include "mozilla/Preferences.h"
-#include "nsDOMEvent.h"
-#include "nsIDOMClassInfo.h"
-#include "nsIDOMDOMRequest.h"
-#include "nsIPermissionManager.h"
-#include "nsIVariant.h"
-
-#include "nsJSUtils.h"
-#include "nsJSON.h"
-#include "mozilla/Services.h"
-
-#define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
-
-using namespace mozilla::dom::network;
-
-class MobileConnection::Listener MOZ_FINAL : public nsIMobileConnectionListener
-{
-  MobileConnection* mMobileConnection;
-
-public:
-  NS_DECL_ISUPPORTS
-  NS_FORWARD_SAFE_NSIMOBILECONNECTIONLISTENER(mMobileConnection)
-
-  Listener(MobileConnection* aMobileConnection)
-    : mMobileConnection(aMobileConnection)
-  {
-    MOZ_ASSERT(mMobileConnection);
-  }
-
-  void Disconnect()
-  {
-    MOZ_ASSERT(mMobileConnection);
-    mMobileConnection = nullptr;
-  }
-};
-
-NS_IMPL_ISUPPORTS1(MobileConnection::Listener, nsIMobileConnectionListener)
-
-DOMCI_DATA(MozMobileConnection, MobileConnection)
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(MobileConnection)
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MobileConnection,
-                                                  nsDOMEventTargetHelper)
-  // Don't traverse mListener because it doesn't keep any reference to
-  // MobileConnection but a raw pointer instead. Neither does mProvider because
-  // it's an xpcom service and is only released at shutting down.
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(MobileConnection,
-                                                nsDOMEventTargetHelper)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(MobileConnection)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMMozMobileConnection)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozMobileConnection)
-NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
-
-NS_IMPL_ADDREF_INHERITED(MobileConnection, nsDOMEventTargetHelper)
-NS_IMPL_RELEASE_INHERITED(MobileConnection, nsDOMEventTargetHelper)
-
-NS_IMPL_EVENT_HANDLER(MobileConnection, voicechange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, datachange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, ussdreceived)
-NS_IMPL_EVENT_HANDLER(MobileConnection, dataerror)
-NS_IMPL_EVENT_HANDLER(MobileConnection, cfstatechange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, emergencycbmodechange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, otastatuschange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, iccchange)
-NS_IMPL_EVENT_HANDLER(MobileConnection, radiostatechange)
-
-MobileConnection::MobileConnection(uint32_t aClientId)
-: mClientId(aClientId)
-{
-  mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
-  mWindow = nullptr;
-
-  // Not being able to acquire the provider isn't fatal since we check
-  // for it explicitly below.
-  if (!mProvider) {
-    NS_WARNING("Could not acquire nsIMobileConnectionProvider!");
-    return;
-  }
-}
-
-void
-MobileConnection::Init(nsPIDOMWindow* aWindow)
-{
-  BindToOwner(aWindow);
-
-  mWindow = do_GetWeakReference(aWindow);
-  mListener = new Listener(this);
-
-  if (CheckPermission("mobileconnection")) {
-    DebugOnly<nsresult> rv = mProvider->RegisterMobileConnectionMsg(mClientId, mListener);
-    NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
-                     "Failed registering mobile connection messages with provider");
-
-    printf_stderr("MobileConnection initialized");
-  }
-}
-
-void
-MobileConnection::Shutdown()
-{
-  if (mProvider && mListener) {
-    mListener->Disconnect();
-    mProvider->UnregisterMobileConnectionMsg(mClientId, mListener);
-    mProvider = nullptr;
-    mListener = nullptr;
-  }
-}
-
-// nsIDOMMozMobileConnection
-
-NS_IMETHODIMP
-MobileConnection::GetLastKnownNetwork(nsAString& aNetwork)
-{
-  aNetwork.SetIsVoid(true);
-
-  if (!CheckPermission("mobilenetwork")) {
-    return NS_OK;
-  }
-
-  return mProvider->GetLastKnownNetwork(mClientId, aNetwork);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetLastKnownHomeNetwork(nsAString& aNetwork)
-{
-  aNetwork.SetIsVoid(true);
-
-  if (!CheckPermission("mobilenetwork")) {
-    return NS_OK;
-  }
-
-  return mProvider->GetLastKnownHomeNetwork(mClientId, aNetwork);
-}
-
-// All fields below require the "mobileconnection" permission.
-
-bool
-MobileConnection::CheckPermission(const char* aType)
-{
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
-  NS_ENSURE_TRUE(window, false);
-
-  nsCOMPtr<nsIPermissionManager> permMgr =
-    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
-  NS_ENSURE_TRUE(permMgr, false);
-
-  uint32_t permission = nsIPermissionManager::DENY_ACTION;
-  permMgr->TestPermissionFromWindow(window, aType, &permission);
-  return permission == nsIPermissionManager::ALLOW_ACTION;
-}
-
-NS_IMETHODIMP
-MobileConnection::GetVoice(nsIDOMMozMobileConnectionInfo** aVoice)
-{
-  *aVoice = nullptr;
-
-  if (!mProvider || !CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-  return mProvider->GetVoiceConnectionInfo(mClientId, aVoice);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetData(nsIDOMMozMobileConnectionInfo** aData)
-{
-  *aData = nullptr;
-
-  if (!mProvider || !CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-  return mProvider->GetDataConnectionInfo(mClientId, aData);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetIccId(nsAString& aIccId)
-{
-  aIccId.SetIsVoid(true);
-
-  if (!mProvider || !CheckPermission("mobileconnection")) {
-     return NS_OK;
-  }
-  return mProvider->GetIccId(mClientId, aIccId);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetNetworkSelectionMode(nsAString& aNetworkSelectionMode)
-{
-  aNetworkSelectionMode.SetIsVoid(true);
-
-  if (!mProvider || !CheckPermission("mobileconnection")) {
-     return NS_OK;
-  }
-  return mProvider->GetNetworkSelectionMode(mClientId, aNetworkSelectionMode);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetRadioState(nsAString& aRadioState)
-{
-  aRadioState.SetIsVoid(true);
-
-  if (!mProvider || !CheckPermission("mobileconnection")) {
-     return NS_OK;
-  }
-  return mProvider->GetRadioState(mClientId, aRadioState);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetSupportedNetworkTypes(nsIVariant** aSupportedNetworkTypes)
-{
-  *aSupportedNetworkTypes = nullptr;
-
-  if (!mProvider || !CheckPermission("mobileconnection")) {
-     return NS_OK;
-  }
-
-  return mProvider->GetSupportedNetworkTypes(mClientId, aSupportedNetworkTypes);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetNetworks(nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetNetworks(mClientId, GetOwner(), aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SelectNetwork(nsIDOMMozMobileNetworkInfo* aNetwork, nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SelectNetwork(mClientId, GetOwner(), aNetwork, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SelectNetworkAutomatically(nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SelectNetworkAutomatically(mClientId, GetOwner(), aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetPreferredNetworkType(const nsAString& aType,
-                                          nsIDOMDOMRequest** aDomRequest)
-{
-  *aDomRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetPreferredNetworkType(mClientId, GetOwner(), aType, aDomRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetPreferredNetworkType(nsIDOMDOMRequest** aDomRequest)
-{
-  *aDomRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetPreferredNetworkType(mClientId, GetOwner(), aDomRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetRoamingPreference(const nsAString& aMode, nsIDOMDOMRequest** aDomRequest)
-{
-  *aDomRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetRoamingPreference(mClientId, GetOwner(), aMode, aDomRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetRoamingPreference(nsIDOMDOMRequest** aDomRequest)
-{
-  *aDomRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetRoamingPreference(mClientId, GetOwner(), aDomRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetVoicePrivacyMode(bool aEnabled, nsIDOMDOMRequest** aDomRequest)
-{
-  *aDomRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetVoicePrivacyMode(mClientId, GetOwner(), aEnabled, aDomRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetVoicePrivacyMode(nsIDOMDOMRequest** aDomRequest)
-{
-  *aDomRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetVoicePrivacyMode(mClientId, GetOwner(), aDomRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SendMMI(const nsAString& aMMIString,
-                          nsIDOMDOMRequest** aRequest)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SendMMI(mClientId, GetOwner(), aMMIString, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::CancelMMI(nsIDOMDOMRequest** aRequest)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->CancelMMI(mClientId, GetOwner(),aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallForwardingOption(uint16_t aReason,
-                                          nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCallForwardingOption(mClientId, GetOwner(), aReason, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallForwardingOption(nsIDOMMozMobileCFInfo* aCFInfo,
-                                          nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetCallForwardingOption(mClientId, GetOwner(), aCFInfo, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallBarringOption(JS::Handle<JS::Value> aOption,
-                                       nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCallBarringOption(mClientId, GetOwner(), aOption, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallBarringOption(JS::Handle<JS::Value> aOption,
-                                       nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetCallBarringOption(mClientId, GetOwner(), aOption, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::ChangeCallBarringPassword(JS::Handle<JS::Value> aInfo,
-                                            nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->ChangeCallBarringPassword(mClientId, GetOwner(), aInfo, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallWaitingOption(nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCallWaitingOption(mClientId, GetOwner(), aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallWaitingOption(bool aEnabled,
-                                       nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetCallWaitingOption(mClientId, GetOwner(), aEnabled, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::GetCallingLineIdRestriction(nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCallingLineIdRestriction(mClientId, GetOwner(), aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetCallingLineIdRestriction(unsigned short aClirMode,
-                                              nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetCallingLineIdRestriction(mClientId, GetOwner(), aClirMode, aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::ExitEmergencyCbMode(nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->ExitEmergencyCbMode(mClientId, GetOwner(), aRequest);
-}
-
-NS_IMETHODIMP
-MobileConnection::SetRadioEnabled(bool aEnabled,
-                                  nsIDOMDOMRequest** aRequest)
-{
-  *aRequest = nullptr;
-
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->SetRadioEnabled(mClientId, GetOwner(), aEnabled, aRequest);
-}
-
-// nsIMobileConnectionListener
-
-NS_IMETHODIMP
-MobileConnection::NotifyVoiceChanged()
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  return DispatchTrustedEvent(NS_LITERAL_STRING("voicechange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyDataChanged()
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  return DispatchTrustedEvent(NS_LITERAL_STRING("datachange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyUssdReceived(const nsAString& aMessage,
-                                     bool aSessionEnded)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  USSDReceivedEventInit init;
-  init.mBubbles = false;
-  init.mCancelable = false;
-  init.mMessage = aMessage;
-  init.mSessionEnded = aSessionEnded;
-
-  nsRefPtr<USSDReceivedEvent> event =
-    USSDReceivedEvent::Constructor(this, NS_LITERAL_STRING("ussdreceived"), init);
-
-  return DispatchTrustedEvent(event);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyDataError(const nsAString& aMessage)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  DataErrorEventInit init;
-  init.mBubbles = false;
-  init.mCancelable = false;
-  init.mMessage = aMessage;
-
-  nsRefPtr<DataErrorEvent> event =
-    DataErrorEvent::Constructor(this, NS_LITERAL_STRING("dataerror"), init);
-
-  return DispatchTrustedEvent(event);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyCFStateChange(bool aSuccess,
-                                      unsigned short aAction,
-                                      unsigned short aReason,
-                                      const nsAString& aNumber,
-                                      unsigned short aSeconds,
-                                      unsigned short aServiceClass)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  CFStateChangeEventInit init;
-  init.mBubbles = false;
-  init.mCancelable = false;
-  init.mSuccess = aSuccess;
-  init.mAction = aAction;
-  init.mReason = aReason;
-  init.mNumber = aNumber;
-  init.mTimeSeconds = aSeconds;
-  init.mServiceClass = aServiceClass;
-
-  nsRefPtr<CFStateChangeEvent> event =
-    CFStateChangeEvent::Constructor(this, NS_LITERAL_STRING("cfstatechange"), init);
-
-  return DispatchTrustedEvent(event);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyEmergencyCbModeChanged(bool aActive,
-                                               uint32_t aTimeoutMs)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  MozEmergencyCbModeEventInit init;
-  init.mBubbles = false;
-  init.mCancelable = false;
-  init.mActive = aActive;
-  init.mTimeoutMs = aTimeoutMs;
-
-  nsRefPtr<MozEmergencyCbModeEvent> event =
-    MozEmergencyCbModeEvent::Constructor(this, NS_LITERAL_STRING("emergencycbmodechange"), init);
-
-  return DispatchTrustedEvent(event);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyOtaStatusChanged(const nsAString& aStatus)
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  MozOtaStatusEventInit init;
-  init.mBubbles = false;
-  init.mCancelable = false;
-  init.mStatus = aStatus;
-
-  nsRefPtr<MozOtaStatusEvent> event =
-    MozOtaStatusEvent::Constructor(this, NS_LITERAL_STRING("otastatuschange"), init);
-
-  return DispatchTrustedEvent(event);
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyIccChanged()
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  return DispatchTrustedEvent(NS_LITERAL_STRING("iccchange"));
-}
-
-NS_IMETHODIMP
-MobileConnection::NotifyRadioStateChanged()
-{
-  if (!CheckPermission("mobileconnection")) {
-    return NS_OK;
-  }
-
-  return DispatchTrustedEvent(NS_LITERAL_STRING("radiostatechange"));
-}
deleted file mode 100644
--- a/dom/network/src/MobileConnection.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_network_MobileConnection_h
-#define mozilla_dom_network_MobileConnection_h
-
-#include "nsIDOMMobileConnection.h"
-#include "nsIMobileConnectionProvider.h"
-#include "nsDOMEventTargetHelper.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsWeakPtr.h"
-
-namespace mozilla {
-namespace dom {
-namespace network {
-
-class MobileConnection : public nsDOMEventTargetHelper
-                       , public nsIDOMMozMobileConnection
-{
-  /**
-   * Class MobileConnection doesn't actually inherit
-   * nsIMobileConnectionListener. Instead, it owns an
-   * nsIMobileConnectionListener derived instance mListener and passes it to
-   * nsIMobileConnectionProvider. The onreceived events are first delivered to
-   * mListener and then forwarded to its owner, MobileConnection. See also bug
-   * 775997 comment #51.
-   */
-  class Listener;
-
-public:
-  NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_NSIDOMMOZMOBILECONNECTION
-  NS_DECL_NSIMOBILECONNECTIONLISTENER
-
-  NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
-
-  MobileConnection(uint32_t aClientId);
-
-  void Init(nsPIDOMWindow *aWindow);
-  void Shutdown();
-
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MobileConnection,
-                                           nsDOMEventTargetHelper)
-
-private:
-  nsCOMPtr<nsIMobileConnectionProvider> mProvider;
-  nsRefPtr<Listener> mListener;
-  nsWeakPtr mWindow;
-
-  uint32_t mClientId;
-
-  bool CheckPermission(const char* aType);
-};
-
-} // namespace network
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_network_MobileConnection_h
deleted file mode 100644
--- a/dom/network/src/MobileConnectionArray.cpp
+++ /dev/null
@@ -1,113 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "MobileConnectionArray.h"
-#include "mozilla/dom/MozMobileConnectionArrayBinding.h"
-#include "mozilla/Preferences.h"
-
-using namespace mozilla::dom::network;
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(MobileConnectionArray)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MobileConnectionArray)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
-  // Notify our mobile connections that we're going away.
-  tmp->DropConnections();
-  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(MobileConnectionArray)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMobileConnections)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(MobileConnectionArray)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(MobileConnectionArray)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(MobileConnectionArray)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MobileConnectionArray)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-MobileConnectionArray::MobileConnectionArray(nsPIDOMWindow* aWindow)
-: mWindow(aWindow), mInitialized(false)
-{
-  uint32_t numRil = mozilla::Preferences::GetUint("ril.numRadioInterfaces", 1);
-  MOZ_ASSERT(numRil > 0);
-
-  bool ret = mMobileConnections.SetLength(numRil);
-  MOZ_ASSERT(ret);
-
-  SetIsDOMBinding();
-}
-
-MobileConnectionArray::~MobileConnectionArray()
-{
-  DropConnections();
-}
-
-void
-MobileConnectionArray::Init()
-{
-  mInitialized = true;
-
-  for (uint32_t id = 0; id < mMobileConnections.Length(); id++) {
-    nsRefPtr<MobileConnection> mobileConnection = new MobileConnection(id);
-    mobileConnection->Init(mWindow);
-    mMobileConnections[id] = mobileConnection;
-  }
-}
-
-void
-MobileConnectionArray::DropConnections()
-{
-  if (mInitialized) {
-    for (uint32_t i = 0; i < mMobileConnections.Length(); i++) {
-      mMobileConnections[i]->Shutdown();
-    }
-  }
-
-  mMobileConnections.Clear();
-}
-
-nsPIDOMWindow*
-MobileConnectionArray::GetParentObject() const
-{
-  MOZ_ASSERT(mWindow);
-  return mWindow;
-}
-
-JSObject*
-MobileConnectionArray::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
-{
-  return MozMobileConnectionArrayBinding::Wrap(aCx, aScope, this);
-}
-
-nsIDOMMozMobileConnection*
-MobileConnectionArray::Item(uint32_t aIndex)
-{
-  bool unused;
-  return IndexedGetter(aIndex, unused);
-}
-
-uint32_t
-MobileConnectionArray::Length() const
-{
-  return mMobileConnections.Length();
-}
-
-nsIDOMMozMobileConnection*
-MobileConnectionArray::IndexedGetter(uint32_t aIndex, bool& aFound)
-{
-  if (!mInitialized) {
-    Init();
-  }
-
-  aFound = false;
-  aFound = aIndex < mMobileConnections.Length();
-
-  return aFound ? mMobileConnections[aIndex] : nullptr;
-}
deleted file mode 100644
--- a/dom/network/src/MobileConnectionArray.h
+++ /dev/null
@@ -1,64 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_network_MobileConnectionArray_h__
-#define mozilla_dom_network_MobileConnectionArray_h__
-
-#include "nsWrapperCache.h"
-#include "mozilla/dom/network/MobileConnection.h"
-
-class nsIDOMMozMobileConnection;
-
-namespace mozilla {
-namespace dom {
-namespace network {
-
-class MobileConnectionArray MOZ_FINAL : public nsISupports,
-                                        public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MobileConnectionArray)
-
-  MobileConnectionArray(nsPIDOMWindow* aWindow);
-
-  nsPIDOMWindow*
-  GetParentObject() const;
-
-  // WrapperCache
-  virtual JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
-
-  //  WebIDL
-  nsIDOMMozMobileConnection*
-  Item(uint32_t aIndex);
-
-  uint32_t
-  Length() const;
-
-  nsIDOMMozMobileConnection*
-  IndexedGetter(uint32_t aIndex, bool& aFound);
-
-private:
-  ~MobileConnectionArray();
-
-  void
-  Init();
-
-  void
-  DropConnections();
-
-  bool mInitialized;
-
-  nsCOMPtr<nsPIDOMWindow> mWindow;
-  nsTArray<nsRefPtr<MobileConnection>> mMobileConnections;
-};
-
-} // namespace network
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_network_MobileConnectionArray_h__
--- a/dom/system/OSFileConstants.cpp
+++ b/dom/system/OSFileConstants.cpp
@@ -824,32 +824,31 @@ bool DefineOSFileConstants(JSContext *cx
   JS::Rooted<JSObject*> objPath(cx);
   if (!(objPath = GetOrCreateObjectProperty(cx, objConstants, "Path"))) {
     return false;
   }
 
   // Locate libxul
   // Note that we don't actually provide the full path, only the name of the
   // library, which is sufficient to link to the library using js-ctypes.
-  {
+
 #if defined(XP_MACOSX)
-    // Under MacOS X, for some reason, libxul is called simply "XUL"
-    nsAutoString xulPath(NS_LITERAL_STRING("XUL"));
+  // Under MacOS X, for some reason, libxul is called simply "XUL"
+  nsAutoString libxul(NS_LITERAL_STRING("XUL"));
 #else
-    // On other platforms, libxul is a library "xul" with regular
-    // library prefix/suffix
-    nsAutoString xulPath;
-    xulPath.Append(NS_LITERAL_STRING(DLL_PREFIX));
-    xulPath.Append(NS_LITERAL_STRING("xul"));
-    xulPath.Append(NS_LITERAL_STRING(DLL_SUFFIX));
+  // On other platforms, libxul is a library "xul" with regular
+  // library prefix/suffix
+  nsAutoString libxul;
+  libxul.Append(NS_LITERAL_STRING(DLL_PREFIX));
+  libxul.Append(NS_LITERAL_STRING("xul"));
+  libxul.Append(NS_LITERAL_STRING(DLL_SUFFIX));
 #endif // defined(XP_MACOSX)
 
-    if (!SetStringProperty(cx, objPath, "libxul", xulPath)) {
-      return false;
-    }
+  if (!SetStringProperty(cx, objPath, "libxul", libxul)) {
+    return false;
   }
 
   if (!SetStringProperty(cx, objPath, "libDir", gPaths->libDir)) {
     return false;
   }
 
   if (!SetStringProperty(cx, objPath, "tmpDir", gPaths->tmpDir)) {
     return false;
@@ -894,16 +893,37 @@ bool DefineOSFileConstants(JSContext *cx
     return false;
   }
 
   if (!SetStringProperty(cx, objPath, "macLocalApplicationsDir", gPaths->macLocalApplicationsDir)) {
     return false;
   }
 #endif // defined(XP_MACOSX)
 
+  // sqlite3 is linked from different places depending on the platform
+  nsAutoString libsqlite3;
+#if defined(ANDROID)
+  // On Android, we use the system's libsqlite3
+  libsqlite3.Append(NS_LITERAL_STRING(DLL_PREFIX));
+  libsqlite3.Append(NS_LITERAL_STRING("sqlite3"));
+  libsqlite3.Append(NS_LITERAL_STRING(DLL_SUFFIX));
+#elif defined(XP_WIN)
+  // On Windows, for some reason, this is part of nss3.dll
+  libsqlite3.Append(NS_LITERAL_STRING(DLL_PREFIX));
+  libsqlite3.Append(NS_LITERAL_STRING("nss3"));
+  libsqlite3.Append(NS_LITERAL_STRING(DLL_SUFFIX));
+#else
+    // On other platforms, we link sqlite3 into libxul
+  libsqlite3 = libxul;
+#endif // defined(ANDROID) || defined(XP_WIN)
+
+  if (!SetStringProperty(cx, objPath, "libsqlite3", libsqlite3)) {
+    return false;
+  }
+
   return true;
 }
 
 NS_IMPL_ISUPPORTS1(OSFileConstantsService, nsIOSFileConstantsService)
 
 OSFileConstantsService::OSFileConstantsService()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -121,17 +121,17 @@ NetworkService.prototype = {
         success: true,  // netd always return success even interface doesn't exist.
         rxBytes: 0,
         txBytes: 0
       };
       result.date = new Date();
 
       if (Components.isSuccessCode(status)) {
         // Find record for corresponding interface.
-        let statExpr = / +(\S+): +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+/;
+        let statExpr = /(\S+): +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +(\d+) +\d+ +\d+ +\d+ +\d+ +\d+ +\d+ +\d+/;
         let data = NetUtil.readInputStreamToString(inputStream,
                     inputStream.available()).split("\n");
         for (let i = 2; i < data.length; i++) {
           let parseResult = statExpr.exec(data[i]);
           if (parseResult && parseResult[1] === networkName) {
             result.rxBytes = parseInt(parseResult[2], 10);
             result.txBytes = parseInt(parseResult[3], 10);
             break;
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -3328,16 +3328,25 @@ RadioInterface.prototype = {
    *        Optional. Enable Latin characters replacement with corresponding
    *        ones in GSM SMS 7-bit default alphabet.
    *
    * @return an array of objects. See #_fragmentText() for detailed definition.
    */
   _fragmentText7Bit: function(text, langTable, langShiftTable, segmentSeptets, strict7BitEncoding) {
     let ret = [];
     let body = "", len = 0;
+    // If the message is empty, we only push the empty message to ret.
+    if (text.length === 0) {
+      ret.push({
+        body: text,
+        encodedBodyLength: text.length,
+      });
+      return ret;
+    }
+
     for (let i = 0, inc = 0; i < text.length; i++) {
       let c = text.charAt(i);
       if (strict7BitEncoding) {
         c = RIL.GSM_SMS_STRICT_7BIT_CHARMAP[c] || c;
       }
 
       let septet = langTable.indexOf(c);
       if (septet == RIL.PDU_NL_EXTENDED_ESCAPE) {
@@ -3397,16 +3406,25 @@ RadioInterface.prototype = {
    *        text string to be fragmented.
    * @param segmentChars
    *        Number of available characters per segment.
    *
    * @return an array of objects. See #_fragmentText() for detailed definition.
    */
   _fragmentTextUCS2: function(text, segmentChars) {
     let ret = [];
+    // If the message is empty, we only push the empty message to ret.
+    if (text.length === 0) {
+      ret.push({
+        body: text,
+        encodedBodyLength: text.length,
+      });
+      return ret;
+    }
+
     for (let offset = 0; offset < text.length; offset += segmentChars) {
       let str = text.substr(offset, segmentChars);
       ret.push({
         body: str,
         encodedBodyLength: str.length * 2,
       });
     }
 
--- a/dom/system/gonk/nfc_worker.js
+++ b/dom/system/gonk/nfc_worker.js
@@ -363,33 +363,36 @@ NfcWorker[NFC_NOTIFICATION_INITIALIZED] 
             NFC_MAJOR_VERSION + "." + NFC_MINOR_VERSION  +
            " Received Version : " + majorVersion + "." + minorVersion);
   }
 };
 
 NfcWorker[NFC_NOTIFICATION_TECH_DISCOVERED] = function NFC_NOTIFICATION_TECH_DISCOVERED() {
   debug("NFC_NOTIFICATION_TECH_DISCOVERED");
   let techList  = [];
+  let records   = null;
 
   let sessionId = Buf.readInt32();
   let techCount = Buf.readInt32();
   for (let count = 0; count < techCount; count++) {
     let tech = NFC_TECHS[Buf.readUint8()];
     if (tech) {
       techList.push(tech);
     }
   }
 
   let padding   = getPaddingLen(techCount);
   for (let i = 0; i < padding; i++) {
     Buf.readUint8();
   }
 
   let ndefMsgCount = Buf.readInt32();
-  let records = this.unMarshallNdefMessage();
+  if (ndefMsgCount > 0) {
+    records = this.unMarshallNdefMessage();
+  }
   this.sendDOMMessage({type: "techDiscovered",
                        sessionId: sessionId,
                        techList: techList,
                        records: records});
 };
 
 NfcWorker[NFC_NOTIFICATION_TECH_LOST] = function NFC_NOTIFICATION_TECH_LOST() {
   debug("NFC_NOTIFICATION_TECH_LOST");
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -9024,17 +9024,26 @@ let CdmaPDUHelper = {
           }
           result += msgDigit;
           msgBodySize--;
         }
         break;
       case PDU_CDMA_MSG_CODING_SHIFT_JIS:
         // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
         //             http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
-        // Fall through.
+        let shift_jis_message = [];
+
+        while (msgBodySize > 0) {
+          shift_jis_message.push(BitBufferHelper.readBits(8));
+          msgBodySize--;
+        }
+
+        let decoder = new TextDecoder("shift_jis");
+        result = decoder.decode(new Uint8Array(shift_jis_message));
+        break;
       case PDU_CDMA_MSG_CODING_KOREAN:
       case PDU_CDMA_MSG_CODING_GSM_DCS:
         // Fall through.
       default:
         break;
     }
     return result;
   },
--- a/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js
+++ b/dom/system/gonk/tests/test_ril_worker_sms_cdmapduhelper.js
@@ -66,8 +66,142 @@ add_test(function test_CdmaPDUHelper_dec
   // 10|000100: temporary condition|Network congestion
   test_MsgStatus(0x84);
 
   // 11|000101: permanent condition|Network error
   test_MsgStatus(0xC5);
 
   run_next_test();
 });
+
+/**
+ * Verify CdmaPDUHelper#decodeCdmaPDUMsg.
+ *  - encoding by shift-jis
+ */
+add_test(function test_CdmaPDUHelper_decodeCdmaPDUMsg_Shift_jis() {
+  let worker = newWorker({
+    postRILMessage: function(data) {
+      // Do nothing
+    },
+    postMessage: function(message) {
+      // Do nothing
+    }
+  });
+
+  let helper = worker.CdmaPDUHelper;
+  function test_decodePDUMsg(testDataBuffer, expected, encoding, msgType, msgBodySize) {
+    worker.BitBufferHelper.startRead(testDataBuffer);
+    let result = helper.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
+    do_check_eq(result, expected);
+  }
+
+  // Shift-JIS has 1 byte and 2 byte code for one character and has some types of characters:
+  //   Hiragana, Kanji, Katakana(fullwidth, halfwidth)...
+  // This test is a combination of 1 byte and 2 byte code and types of characters.
+
+  // test case 1
+  let testDataBuffer1 = [0x82, 0x58, 0x33, 0x41, 0x61, 0x33, 0x82, 0x60,
+                         0x82, 0x81, 0x33, 0xB1, 0xAF, 0x33, 0x83, 0x41,
+                         0x83, 0x96, 0x33, 0x82, 0xA0, 0x33, 0x93, 0xFA,
+                         0x33, 0x3A, 0x3C, 0x33, 0x81, 0x80, 0x81, 0x8E,
+                         0x33, 0x31, 0x82, 0x51, 0x41, 0x61, 0x82, 0x51,
+                         0x82, 0x60, 0x82, 0x81, 0x82, 0x51, 0xB1, 0xAF,
+                         0x82, 0x51, 0x83, 0x41, 0x83, 0x96, 0x82, 0x51,
+                         0x82, 0xA0, 0x82, 0x51, 0x93, 0xFA, 0x82, 0x51,
+                         0x3A, 0x3C, 0x82, 0x51, 0x81, 0x80, 0x81, 0x8E,
+                         0x82, 0x51];
+
+  test_decodePDUMsg(
+      testDataBuffer1,
+      "\uFF19\u0033\u0041\u0061\u0033\uFF21\uFF41\u0033\uFF71\uFF6F\u0033\u30A2\u30F6\u0033\u3042\u0033\u65E5\u0033\u003A\u003C\u0033\u00F7\u2103\u0033\u0031\uFF12\u0041\u0061\uFF12\uFF21\uFF41\uFF12\uFF71\uFF6F\uFF12\u30A2\u30F6\uFF12\u3042\uFF12\u65E5\uFF12\u003A\u003C\uFF12\u00F7\u2103\uFF12",
+      PDU_CDMA_MSG_CODING_SHIFT_JIS,
+      undefined,
+      testDataBuffer1.length
+  );
+
+  // test case 2
+  let testDataBuffer2 = [0x31, 0x51, 0x63, 0x82, 0x58, 0x51, 0x63, 0x82,
+                         0x60, 0x82, 0x81, 0x51, 0x63, 0xB1, 0xAF, 0x51,
+                         0x63, 0x83, 0x41, 0x83, 0x96, 0x51, 0x63, 0x82,
+                         0xA0, 0x51, 0x63, 0x93, 0xFA, 0x51, 0x63, 0x3A,
+                         0x3C, 0x51, 0x63, 0x81, 0x80, 0x81, 0x8E, 0x51,
+                         0x63, 0x31, 0x82, 0x70, 0x82, 0x85, 0x82, 0x58,
+                         0x82, 0x70, 0x82, 0x85, 0x41, 0x61, 0x82, 0x70,
+                         0x82, 0x85, 0xB1, 0xAF, 0x82, 0x70, 0x82, 0x85,
+                         0x83, 0x41, 0x83, 0x96, 0x82, 0x70, 0x82, 0x85,
+                         0x82, 0xA0, 0x82, 0x70, 0x82, 0x85, 0x93, 0xFA,
+                         0x82, 0x70, 0x82, 0x85, 0x3A, 0x3C, 0x82, 0x70,
+                         0x82, 0x85, 0x81, 0x80, 0x81, 0x8E, 0x82, 0x70,
+                         0x82, 0x85];
+
+  test_decodePDUMsg(
+      testDataBuffer2,
+      "\u0031\u0051\u0063\uFF19\u0051\u0063\uFF21\uFF41\u0051\u0063\uFF71\uFF6F\u0051\u0063\u30A2\u30F6\u0051\u0063\u3042\u0051\u0063\u65E5\u0051\u0063\u003A\u003C\u0051\u0063\u00F7\u2103\u0051\u0063\u0031\uFF31\uFF45\uFF19\uFF31\uFF45\u0041\u0061\uFF31\uFF45\uFF71\uFF6F\uFF31\uFF45\u30A2\u30F6\uFF31\uFF45\u3042\uFF31\uFF45\u65E5\uFF31\uFF45\u003A\u003C\uFF31\uFF45\u00F7\u2103\uFF31\uFF45",
+      PDU_CDMA_MSG_CODING_SHIFT_JIS,
+      undefined,
+      testDataBuffer2.length
+  );
+
+  // test case 3
+  let testDataBuffer3 = [0x31, 0xC2, 0xDF, 0x82, 0x58, 0xC2, 0xDF, 0x41,
+                         0x61, 0xC2, 0xDF, 0x82, 0x60, 0x82, 0x81, 0xC2,
+                         0xDF, 0x83, 0x41, 0x83, 0x96, 0xC2, 0xDF, 0x82,
+                         0xA0, 0xC2, 0xDF, 0x93, 0xFA, 0xC2, 0xDF, 0x3A,
+                         0x3C, 0xC2, 0xDF, 0x81, 0x80, 0x81, 0x8E, 0xC2,
+                         0xDF, 0x31, 0x83, 0x51, 0x83, 0x87, 0x82, 0x58,
+                         0x83, 0x51, 0x83, 0x87, 0x41, 0x61, 0x83, 0x51,
+                         0x83, 0x87, 0x82, 0x60, 0x82, 0x81, 0x83, 0x51,
+                         0x83, 0x87, 0xB1, 0xAF, 0x83, 0x51, 0x83, 0x87,
+                         0x82, 0xA0, 0x83, 0x51, 0x83, 0x87, 0x93, 0xFA,
+                         0x83, 0x51, 0x83, 0x87, 0x3A, 0x3C, 0x83, 0x51,
+                         0x83, 0x87, 0x81, 0x80, 0x81, 0x8E, 0x83, 0x51,
+                         0x83, 0x87];
+
+  test_decodePDUMsg(
+      testDataBuffer3,
+      "\u0031\uFF82\uFF9F\uFF19\uFF82\uFF9F\u0041\u0061\uFF82\uFF9F\uFF21\uFF41\uFF82\uFF9F\u30A2\u30F6\uFF82\uFF9F\u3042\uFF82\uFF9F\u65E5\uFF82\uFF9F\u003A\u003C\uFF82\uFF9F\u00F7\u2103\uFF82\uFF9F\u0031\u30B2\u30E7\uFF19\u30B2\u30E7\u0041\u0061\u30B2\u30E7\uFF21\uFF41\u30B2\u30E7\uFF71\uFF6F\u30B2\u30E7\u3042\u30B2\u30E7\u65E5\u30B2\u30E7\u003A\u003C\u30B2\u30E7\u00F7\u2103\u30B2\u30E7",
+      PDU_CDMA_MSG_CODING_SHIFT_JIS,
+      undefined,
+      testDataBuffer3.length
+  );
+
+  // test case 4
+  let testDataBuffer4 = [0x31, 0x82, 0xB0, 0x82, 0x58, 0x82, 0xB0, 0x41,
+                         0x61, 0x82, 0xB0, 0x82, 0x60, 0x82, 0x81, 0x82,
+                         0xB0, 0xB1, 0xAF, 0x82, 0xB0, 0x83, 0x41, 0x83,
+                         0x96, 0x82, 0xB0, 0x93, 0xFA, 0x82, 0xB0, 0x3A,
+                         0x3C, 0x82, 0xB0, 0x81, 0x80, 0x81, 0x8E, 0x82,
+                         0xB0, 0x31, 0x88, 0xA4, 0x82, 0x58, 0x88, 0xA4,
+                         0x41, 0x61, 0x88, 0xA4, 0x82, 0x60, 0x82, 0x81,
+                         0x88, 0xA4, 0xB1, 0xAF, 0x88, 0xA4, 0x83, 0x41,
+                         0x83, 0x96, 0x88, 0xA4, 0x82, 0xA0, 0x88, 0xA4,
+                         0x3A, 0x3C, 0x88, 0xA4, 0x81, 0x80, 0x81, 0x8E,
+                         0x88, 0xA4];
+
+  test_decodePDUMsg(
+      testDataBuffer4,
+      "\u0031\u3052\uFF19\u3052\u0041\u0061\u3052\uFF21\uFF41\u3052\uFF71\uFF6F\u3052\u30A2\u30F6\u3052\u65E5\u3052\u003A\u003C\u3052\u00F7\u2103\u3052\u0031\u611B\uFF19\u611B\u0041\u0061\u611B\uFF21\uFF41\u611B\uFF71\uFF6F\u611B\u30A2\u30F6\u611B\u3042\u611B\u003A\u003C\u611B\u00F7\u2103\u611B",
+      PDU_CDMA_MSG_CODING_SHIFT_JIS,
+      undefined,
+      testDataBuffer4.length
+  );
+
+  // test case 5
+  let testDataBuffer5 = [0x31, 0x40, 0x82, 0x58, 0x40, 0x41, 0x61, 0x40,
+                         0x82, 0x60, 0x82, 0x81, 0x40, 0xB1, 0xAF, 0x40,
+                         0x83, 0x41, 0x83, 0x96, 0x40, 0x82, 0xA0, 0x40,
+                         0x93, 0xFA, 0x40, 0x81, 0x80, 0x81, 0x8E, 0x40,
+                         0x31, 0x81, 0x9B, 0x82, 0x58, 0x81, 0x9B, 0x41,
+                         0x61, 0x81, 0x9B, 0x82, 0x60, 0x82, 0x81, 0x81,
+                         0x9B, 0xB1, 0xAF, 0x81, 0x9B, 0x83, 0x41, 0x83,
+                         0x96, 0x81, 0x9B, 0x82, 0xA0, 0x81, 0x9B, 0x93,
+                         0xFA, 0x81, 0x9B, 0x3A, 0x3C, 0x81, 0x9B];
+
+  test_decodePDUMsg(
+      testDataBuffer5,
+      "\u0031\u0040\uFF19\u0040\u0041\u0061\u0040\uFF21\uFF41\u0040\uFF71\uFF6F\u0040\u30A2\u30F6\u0040\u3042\u0040\u65E5\u0040\u00F7\u2103\u0040\u0031\u25CB\uFF19\u25CB\u0041\u0061\u25CB\uFF21\uFF41\u25CB\uFF71\uFF6F\u25CB\u30A2\u30F6\u25CB\u3042\u25CB\u65E5\u25CB\u003A\u003C\u25CB",
+      PDU_CDMA_MSG_CODING_SHIFT_JIS,
+      undefined,
+      testDataBuffer5.length
+  );
+
+  run_next_test();
+});
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -483,17 +483,17 @@ abstract public class BrowserApp extends
         mBrowserToolbar.setOnCommitListener(new BrowserToolbar.OnCommitListener() {
             public void onCommit() {
                 commitEditingMode();
             }
         });
 
         mBrowserToolbar.setOnDismissListener(new BrowserToolbar.OnDismissListener() {
             public void onDismiss() {
-                dismissEditingMode();
+                mBrowserToolbar.cancelEdit();
             }
         });
 
         mBrowserToolbar.setOnFilterListener(new BrowserToolbar.OnFilterListener() {
             public void onFilter(String searchText, AutocompleteHandler handler) {
                 filterEditingMode(searchText, handler);
             }
         });
@@ -605,20 +605,16 @@ abstract public class BrowserApp extends
 
     @Override
     public void onBackPressed() {
         if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
             super.onBackPressed();
             return;
         }
 
-        if (dismissEditingMode()) {
-            return;
-        }
-
         if (mBrowserToolbar.onBackPressed()) {
             return;
         }
 
         if (mActionMode != null) {
             endActionModeCompat();
             return;
         }
@@ -1569,26 +1565,16 @@ abstract public class BrowserApp extends
             message.put("location", where);
             message.put("identifier", identifier);
             GeckoAppShell.getEventDispatcher().dispatchEvent(message);
         } catch (Exception e) {
             Log.w(LOGTAG, "Error recording search.", e);
         }
     }
 
-    private boolean dismissEditingMode() {
-        if (!mBrowserToolbar.isEditing()) {
-            return false;
-        }
-
-        mBrowserToolbar.cancelEdit();
-
-        return true;
-    }
-
     void filterEditingMode(String searchTerm, AutocompleteHandler handler) {
         if (TextUtils.isEmpty(searchTerm)) {
             hideBrowserSearch();
         } else {
             showBrowserSearch();
             mBrowserSearch.filter(searchTerm, handler);
         }
     }
@@ -2395,17 +2381,17 @@ abstract public class BrowserApp extends
         }
 
         if (!mInitialized) {
             return;
         }
 
         // Dismiss editing mode if the user is loading a URL from an external app.
         if (Intent.ACTION_VIEW.equals(action)) {
-            dismissEditingMode();
+            mBrowserToolbar.cancelEdit();
             return;
         }
 
         // Only solicit feedback when the app has been launched from the icon shortcut.
         if (!Intent.ACTION_MAIN.equals(action)) {
             return;
         }
 
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -498,21 +498,23 @@ sync_java_files = [
     'background/common/log/writers/ThreadLocalTagLogWriter.java',
     'background/datareporting/TelemetryRecorder.java',
     'background/db/CursorDumper.java',
     'background/db/Tab.java',
     'background/fxa/FxAccount10AuthDelegate.java',
     'background/fxa/FxAccount10CreateDelegate.java',
     'background/fxa/FxAccount20CreateDelegate.java',
     'background/fxa/FxAccount20LoginDelegate.java',
+    'background/fxa/FxAccountAgeLockoutHelper.java',
     'background/fxa/FxAccountClient.java',
     'background/fxa/FxAccountClient10.java',
     'background/fxa/FxAccountClient20.java',
     'background/fxa/FxAccountClientException.java',
     'background/fxa/FxAccountUtils.java',
+    'background/fxa/SkewHandler.java',
     'background/healthreport/Environment.java',
     'background/healthreport/EnvironmentBuilder.java',
     'background/healthreport/EnvironmentV1.java',
     'background/healthreport/HealthReportBroadcastReceiver.java',
     'background/healthreport/HealthReportBroadcastService.java',
     'background/healthreport/HealthReportDatabases.java',
     'background/healthreport/HealthReportDatabaseStorage.java',
     'background/healthreport/HealthReportGenerator.java',
@@ -538,24 +540,26 @@ sync_java_files = [
     'browserid/SigningPrivateKey.java',
     'browserid/verifier/BrowserIDRemoteVerifierClient.java',
     'browserid/verifier/BrowserIDVerifierClient.java',
     'browserid/verifier/BrowserIDVerifierDelegate.java',
     'browserid/verifier/BrowserIDVerifierException.java',
     'browserid/VerifyingPublicKey.java',
     'fxa/activities/FxAccountAbstractActivity.java',
     'fxa/activities/FxAccountAbstractSetupActivity.java',
+    'fxa/activities/FxAccountConfirmAccountActivity.java',
     'fxa/activities/FxAccountCreateAccountActivity.java',
     'fxa/activities/FxAccountCreateAccountFragment.java',
-    'fxa/activities/FxAccountCreateSuccessActivity.java',
+    'fxa/activities/FxAccountCreateAccountNotAllowedActivity.java',
     'fxa/activities/FxAccountGetStartedActivity.java',
     'fxa/activities/FxAccountSetupTask.java',
     'fxa/activities/FxAccountSignInActivity.java',
     'fxa/activities/FxAccountStatusActivity.java',
     'fxa/activities/FxAccountUpdateCredentialsActivity.java',
+    'fxa/activities/FxAccountVerifiedAccountActivity.java',
     'fxa/authenticator/AbstractFxAccount.java',
     'fxa/authenticator/AndroidFxAccount.java',
     'fxa/authenticator/FxAccountAuthenticator.java',
     'fxa/authenticator/FxAccountAuthenticatorService.java',
     'fxa/authenticator/FxAccountLoginDelegate.java',
     'fxa/authenticator/FxAccountLoginException.java',
     'fxa/authenticator/FxAccountLoginPolicy.java',
     'fxa/sync/FxAccountGlobalSession.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/fxa/FxAccountAgeLockoutHelper.java
@@ -0,0 +1,90 @@
+/* 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.background.fxa;
+
+import java.util.Arrays;
+import java.util.Calendar;
+
+import org.mozilla.gecko.fxa.FxAccountConstants;
+
+/**
+ * Utility to manage COPPA age verification requirements.
+ * <p>
+ * A user who fails an age verification check when trying to create an account
+ * is denied the ability to make an account for a period of time. We refer to
+ * this state as being "locked out".
+ * <p>
+ * For now we maintain "locked out" state as a static variable. In the future we
+ * might need to persist this state across process restarts, so we'll force
+ * consumers to create an instance of this class. Then, we can drop in a class
+ * backed by shared preferences.
+ */
+public class FxAccountAgeLockoutHelper {
+  private static final String LOG_TAG = FxAccountAgeLockoutHelper.class.getSimpleName();
+
+  protected static long ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = 0;
+
+  public static synchronized boolean isLockedOut(long elapsedRealtime) {
+    long millsecondsSinceLastFailedAgeCheck = elapsedRealtime - ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK;
+    boolean isLockedOut = millsecondsSinceLastFailedAgeCheck < FxAccountConstants.MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS;
+    FxAccountConstants.pii(LOG_TAG, "Checking if locked out: it's been " + millsecondsSinceLastFailedAgeCheck + "ms " +
+        "since last lockout, so " + (isLockedOut ? "yes." : "no."));
+    return isLockedOut;
+  }
+
+  public static synchronized void lockOut(long elapsedRealtime) {
+      FxAccountConstants.pii(LOG_TAG, "Locking out at time: " + elapsedRealtime);
+      ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK = Math.max(elapsedRealtime, ELAPSED_REALTIME_OF_LAST_FAILED_AGE_CHECK);
+  }
+
+  /**
+   * Return true if the age of somebody born in <code>yearOfBirth</code> is
+   * definitely old enough to create an account.
+   * <p>
+   * This errs towards locking out users who might be old enough, but are not
+   * definitely old enough.
+   *
+   * @param yearOfBirth
+   * @return true if somebody born in <code>yearOfBirth</code> is definitely old
+   *         enough.
+   */
+  public static boolean passesAgeCheck(int yearOfBirth) {
+    int thisYear = Calendar.getInstance().get(Calendar.YEAR);
+    int approximateAge = thisYear - yearOfBirth;
+    boolean oldEnough = approximateAge >= FxAccountConstants.MINIMUM_AGE_TO_CREATE_AN_ACCOUNT;
+    if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
+      FxAccountConstants.pii(LOG_TAG, "Age check " + (oldEnough ? "passes" : "fails") +
+          ": age is " + approximateAge + " = " + thisYear + " - " + yearOfBirth);
+    }
+    return oldEnough;
+  }
+
+  /**
+   * Custom function for UI use only.
+   */
+  public static boolean passesAgeCheck(String yearText, String[] yearItems) {
+    if (yearText == null) {
+      throw new IllegalArgumentException("yearText must not be null");
+    }
+    if (yearItems == null) {
+      throw new IllegalArgumentException("yearItems must not be null");
+    }
+    if (!Arrays.asList(yearItems).contains(yearText)) {
+      // This should never happen, but let's be careful.
+      FxAccountConstants.pii(LOG_TAG, "Failed age check: year text was not found in item list.");
+      return false;
+    }
+    Integer yearOfBirth;
+    try {
+      yearOfBirth = Integer.valueOf(yearText, 10);
+    } catch (NumberFormatException e) {
+      // Any non-numbers in the list are ranges (and we say as much to
+      // translators in the resource file), so these people pass the age check.
+      FxAccountConstants.pii(LOG_TAG, "Passed age check: year text was found in item list but was not a number.");
+      return true;
+    }
+    return passesAgeCheck(yearOfBirth.intValue());
+  }
+}
--- a/mobile/android/base/background/fxa/FxAccountClient10.java
+++ b/mobile/android/base/background/fxa/FxAccountClient10.java
@@ -145,16 +145,17 @@ public class FxAccountClient10 {
   protected abstract class ResourceDelegate<T> extends BaseResourceDelegate {
     protected abstract void handleSuccess(final int status, HttpResponse response, final ExtendedJSONObject body);
 
     protected final RequestDelegate<T> delegate;
 
     protected final byte[] tokenId;
     protected final byte[] reqHMACKey;
     protected final boolean payload;
+    protected final SkewHandler skewHandler;
 
     /**
      * Create a delegate for an un-authenticated resource.
      */
     public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate) {
       this(resource, delegate, null, null, false);
     }
 
@@ -162,34 +163,40 @@ public class FxAccountClient10 {
      * Create a delegate for a Hawk-authenticated resource.
      */
     public ResourceDelegate(final Resource resource, final RequestDelegate<T> delegate, final byte[] tokenId, final byte[] reqHMACKey, final boolean authenticatePayload) {
       super(resource);
       this.delegate = delegate;
       this.reqHMACKey = reqHMACKey;
       this.tokenId = tokenId;
       this.payload = authenticatePayload;
+      this.skewHandler = SkewHandler.getSkewHandlerForResource(resource);
     }
 
     @Override
     public AuthHeaderProvider getAuthHeaderProvider() {
       if (tokenId != null && reqHMACKey != null) {
-        return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, payload);
+        return new HawkAuthHeaderProvider(Utils.byte2Hex(tokenId), reqHMACKey, payload, skewHandler.getSkewInSeconds());
       }
       return super.getAuthHeaderProvider();
     }
 
     @Override
     public void handleHttpResponse(HttpResponse response) {
       final int status = response.getStatusLine().getStatusCode();
       switch (status) {
       case 200:
+        skewHandler.updateSkew(response, now());
         invokeHandleSuccess(status, response);
         return;
       default:
+        if (!skewHandler.updateSkew(response, now())) {
+          // If we couldn't update skew, but we got a failure, let's try clearing the skew.
+          skewHandler.resetSkew();
+        }
         invokeHandleFailure(status, response);
         return;
       }
     }
 
     protected void invokeHandleFailure(final int status, final HttpResponse response) {
       executor.execute(new Runnable() {
         @Override
@@ -237,16 +244,21 @@ public class FxAccountClient10 {
         resource.post(requestBody);
       }
     } catch (UnsupportedEncodingException e) {
       invokeHandleError(delegate, e);
       return;
     }
   }
 
+  @SuppressWarnings("static-method")
+  public long now() {
+    return System.currentTimeMillis();
+  }
+
   public void createAccount(final String email, final byte[] stretchedPWBytes,
       final String srpSalt, final String mainSalt,
       final RequestDelegate<String> delegate) {
     try {
       createAccount(new FxAccount10CreateDelegate(email, stretchedPWBytes, srpSalt, mainSalt), delegate);
     } catch (final Exception e) {
       invokeHandleError(delegate, e);
       return;
@@ -634,9 +646,51 @@ public class FxAccountClient10 {
           delegate.handleError(new FxAccountClientException("cert must be a non-null string"));
           return;
         }
         delegate.handleSuccess(cert);
       }
     };
     post(resource, body, delegate);
   }
+
+  /**
+   * Request a verification link be sent to the account email, given a valid session token.
+   *
+   * @param sessionToken
+   *          to authenticate with.
+   * @param delegate
+   *          to invoke callbacks.
+   */
+  public void resendCode(byte[] sessionToken, final RequestDelegate<Void> delegate) {
+    final byte[] tokenId = new byte[32];
+    final byte[] reqHMACKey = new byte[32];
+    final byte[] requestKey = new byte[32];
+    try {
+      HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
+    } catch (Exception e) {
+      invokeHandleError(delegate, e);
+      return;
+    }
+
+    BaseResource resource;
+    try {
+      resource = new BaseResource(new URI(serverURI + "recovery_email/resend_code"));
+    } catch (URISyntaxException e) {
+      invokeHandleError(delegate, e);
+      return;
+    }
+
+    resource.delegate = new ResourceDelegate<Void>(resource, delegate, tokenId, reqHMACKey, false) {
+      @Override
+      public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
+        try {
+          delegate.handleSuccess(null);
+          return;
+        } catch (Exception e) {
+          delegate.handleError(e);
+          return;
+        }
+      }
+    };
+    post(resource, new JSONObject(), delegate);
+  }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/fxa/SkewHandler.java
@@ -0,0 +1,111 @@
+/* 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.background.fxa;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.sync.net.Resource;
+
+import ch.boye.httpclientandroidlib.Header;
+import ch.boye.httpclientandroidlib.HttpHeaders;
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
+import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
+
+public class SkewHandler {
+  private static final String LOG_TAG = "SkewHandler";
+  protected volatile long skewMillis = 0L;
+  protected final String hostname;
+
+  private static final HashMap<String, SkewHandler> skewHandlers = new HashMap<String, SkewHandler>();
+
+  public static SkewHandler getSkewHandlerForResource(final Resource resource) {
+    return getSkewHandlerForHostname(resource.getHostname());
+  }
+
+  public static SkewHandler getSkewHandlerFromEndpointString(final String url) throws URISyntaxException {
+    if (url == null) {
+      throw new IllegalArgumentException("url must not be null.");
+    }
+    URI u = new URI(url);
+    return getSkewHandlerForHostname(u.getHost());
+  }
+
+  public static synchronized SkewHandler getSkewHandlerForHostname(final String hostname) {
+    SkewHandler handler = skewHandlers.get(hostname);
+    if (handler == null) {
+      handler = new SkewHandler(hostname);
+      skewHandlers.put(hostname, handler);
+    }
+    return handler;
+  }
+
+  public static synchronized void clearSkewHandlers() {
+    skewHandlers.clear();
+  }
+
+  public SkewHandler(final String hostname) {
+    this.hostname = hostname;
+  }
+
+  public boolean updateSkewFromServerMillis(long millis, long now) {
+    skewMillis = millis - now;
+    Logger.debug(LOG_TAG, "Updated skew: " + skewMillis + "ms for hostname " + this.hostname);
+    return true;
+  }
+
+  public boolean updateSkewFromHTTPDateString(String date, long now) {
+    try {
+      final long millis = DateUtils.parseDate(date).getTime();
+      return updateSkewFromServerMillis(millis, now);
+    } catch (DateParseException e) {
+      Logger.warn(LOG_TAG, "Unexpected: invalid Date header from " + this.hostname);
+      return false;
+    }
+  }
+
+  public boolean updateSkewFromDateHeader(Header header, long now) {
+    String date = header.getValue();
+    if (null == date) {
+      Logger.warn(LOG_TAG, "Unexpected: null Date header from " + this.hostname);
+      return false;
+    }
+    return updateSkewFromHTTPDateString(date, now);
+  }
+
+  /**
+   * Update our tracked skew value to account for the local clock differing from
+   * the server's.
+   * 
+   * @param response
+   *          the received HTTP response.
+   * @param now
+   *          the current time in milliseconds.
+   * @return true if the skew value was updated, false otherwise.
+   */
+  public boolean updateSkew(HttpResponse response, long now) {
+    Header header = response.getFirstHeader(HttpHeaders.DATE);
+    if (null == header) {
+      Logger.warn(LOG_TAG, "Unexpected: missing Date header from " + this.hostname);
+      return false;
+    }
+    return updateSkewFromDateHeader(header, now);
+  }
+
+  public long getSkewInMillis() {
+    return skewMillis;
+  }
+
+  public long getSkewInSeconds() {
+    return skewMillis / 1000;
+  }
+
+  public void resetSkew() {
+    skewMillis = 0L;
+  }
+}
\ No newline at end of file
--- a/mobile/android/base/fxa/FxAccountConstants.java.in
+++ b/mobile/android/base/fxa/FxAccountConstants.java.in
@@ -20,9 +20,15 @@ public class FxAccountConstants {
   // an add-on.
   public static boolean LOG_PERSONAL_INFORMATION = true;
 
   public static void pii(String tag, String message) {
     if (LOG_PERSONAL_INFORMATION) {
       Logger.info(tag, "$$FxA PII$$: " + message);
     }
   }
+
+  // You must be at least 14 years old to create a Firefox Account.
+  public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 14;
+
+  // You must wait 15 minutes after failing an age check before trying to create a different account.
+  public static final long MINIMUM_TIME_TO_WAIT_AFTER_AGE_CHECK_FAILED_IN_MILLISECONDS = 15 * 60 * 1000;
 }
--- a/mobile/android/base/fxa/activities/FxAccountAbstractActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractActivity.java
@@ -1,27 +1,80 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.fxa.activities;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
+import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 
+import android.accounts.Account;
 import android.app.Activity;
 import android.content.Intent;
+import android.os.SystemClock;
 import android.text.Html;
 import android.text.method.LinkMovementMethod;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.TextView;
 
 public abstract class FxAccountAbstractActivity extends Activity {
   private static final String LOG_TAG = FxAccountAbstractActivity.class.getSimpleName();
 
+  protected final boolean cannotResumeWhenAccountsExist;
+  protected final boolean cannotResumeWhenNoAccountsExist;
+  protected final boolean cannotResumeWhenLockedOut;
+
+  public static final int CAN_ALWAYS_RESUME = 0;
+  public static final int CANNOT_RESUME_WHEN_ACCOUNTS_EXIST = 1 << 0;
+  public static final int CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST = 1 << 1;
+  public static final int CANNOT_RESUME_WHEN_LOCKED_OUT = 1 << 2;
+
+  public FxAccountAbstractActivity(int resume) {
+    super();
+    this.cannotResumeWhenAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_ACCOUNTS_EXIST);
+    this.cannotResumeWhenNoAccountsExist = 0 != (resume & CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
+    this.cannotResumeWhenLockedOut = 0 != (resume & CANNOT_RESUME_WHEN_LOCKED_OUT);
+  }
+
+  /**
+   * Many Firefox Accounts activities shouldn't display if an account already
+   * exists or if account creation is locked out due to an age verification
+   * check failing (getting started, create account, sign in). This function
+   * redirects as appropriate.
+   */
+  protected void redirectIfAppropriate() {
+    if (cannotResumeWhenAccountsExist || cannotResumeWhenNoAccountsExist) {
+      Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
+      if (cannotResumeWhenAccountsExist && accounts.length > 0) {
+        redirectToActivity(FxAccountStatusActivity.class);
+        return;
+      }
+      if (cannotResumeWhenNoAccountsExist && accounts.length < 1) {
+        redirectToActivity(FxAccountGetStartedActivity.class);
+        return;
+      }
+    }
+    if (cannotResumeWhenLockedOut) {
+      if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) {
+        this.setResult(RESULT_CANCELED);
+        redirectToActivity(FxAccountCreateAccountNotAllowedActivity.class);
+        return;
+      }
+    }
+  }
+
+  @Override
+  public void onResume() {
+    super.onResume();
+    redirectIfAppropriate();
+  }
+
   protected void launchActivity(Class<? extends Activity> activityClass) {
     Intent intent = new Intent(this, activityClass);
     // 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);
   }
 
--- a/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractSetupActivity.java
@@ -17,16 +17,24 @@ import android.view.View;
 import android.view.View.OnClickListener;
 import android.view.View.OnFocusChangeListener;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 
 abstract public class FxAccountAbstractSetupActivity extends FxAccountAbstractActivity {
+  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();
 
   protected int minimumPasswordLength = 8;
 
   protected TextView localErrorTextView;
   protected EditText emailEdit;
   protected EditText passwordEdit;
   protected Button showPasswordButton;
@@ -41,17 +49,17 @@ abstract public class FxAccountAbstractS
         int start = passwordEdit.getSelectionStart();
         int stop = passwordEdit.getSelectionEnd();
         passwordEdit.setInputType(passwordEdit.getInputType() ^ InputType.TYPE_TEXT_VARIATION_PASSWORD);
         passwordEdit.setSelection(start, stop);
         if (isShown) {
           showPasswordButton.setText(R.string.fxaccount_password_show);
         } else {
           showPasswordButton.setText(R.string.fxaccount_password_hide);
-        } 
+        }
       }
     });
   }
 
   protected void showRemoteError(Exception e) {
     new AlertDialog.Builder(this).setTitle("Remote error!").setMessage(e.toString()).show();
   }
 
@@ -98,25 +106,29 @@ abstract public class FxAccountAbstractS
     }
 
     @Override
     public void onTextChanged(CharSequence s, int start, int before, int count) {
       // Do nothing.
     }
   }
 
-  protected boolean updateButtonState() {
+  protected boolean shouldButtonBeEnabled() {
     final String email = emailEdit.getText().toString();
     final String password = passwordEdit.getText().toString();
 
     boolean enabled =
         (email.length() > 0) &&
         Patterns.EMAIL_ADDRESS.matcher(email).matches() &&
-        (password.length() >= minimumPasswordLength); 
+        (password.length() >= minimumPasswordLength);
+    return enabled;
+  }
 
+  protected boolean updateButtonState() {
+    boolean enabled = shouldButtonBeEnabled();
     if (enabled != button.isEnabled()) {
       Logger.debug(LOG_TAG, (enabled ? "En" : "Dis") + "abling button.");
       button.setEnabled(enabled);
     }
 
     return enabled;
   }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
@@ -0,0 +1,139 @@
+/* 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.fxa.activities;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.sync.HTTPFailureException;
+import org.mozilla.gecko.sync.net.SyncStorageResponse;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+import android.widget.Toast;
+import ch.boye.httpclientandroidlib.HttpResponse;
+
+/**
+ * Activity which displays account created successfully screen to the user, and
+ * starts them on the email verification path.
+ */
+public class FxAccountConfirmAccountActivity extends Activity implements OnClickListener {
+  protected static final String LOG_TAG = FxAccountConfirmAccountActivity.class.getSimpleName();
+
+  protected byte[] sessionToken;
+  protected TextView emailText;
+
+  /**
+   * Helper to find view or error if it is missing.
+   *
+   * @param id of view to find.
+   * @param description to print in error.
+   * @return non-null <code>View</code> instance.
+   */
+  public View ensureFindViewById(View v, int id, String description) {
+    View view;
+    if (v != null) {
+      view = v.findViewById(id);
+    } else {
+      view = findViewById(id);
+    }
+    if (view == null) {
+      String message = "Could not find view " + description + ".";
+      Logger.error(LOG_TAG, message);
+      throw new RuntimeException(message);
+    }
+    return view;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onCreate(Bundle icicle) {
+    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
+
+    super.onCreate(icicle);
+    setContentView(R.layout.fxaccount_confirm_account);
+
+    emailText = (TextView) ensureFindViewById(null, R.id.email, "email text");
+    if (getIntent() != null && getIntent().getExtras() != null) {
+      Bundle extras = getIntent().getExtras();
+      emailText.setText(extras.getString("email"));
+      sessionToken = extras.getByteArray("sessionToken");
+    }
+
+    View resendLink = ensureFindViewById(null, R.id.resend_confirmation_email_link, "resend confirmation email link");
+    resendLink.setOnClickListener(this);
+
+    if (sessionToken == null) {
+      resendLink.setEnabled(false);
+      resendLink.setClickable(false);
+    }
+  }
+
+  public static class FxAccountResendCodeTask extends FxAccountSetupTask<Void> {
+    protected static final String LOG_TAG = FxAccountResendCodeTask.class.getSimpleName();
+
+    protected final byte[] sessionToken;
+
+    public FxAccountResendCodeTask(Context context, byte[] sessionToken, FxAccountClient20 client, RequestDelegate<Void> delegate) {
+      super(context, false, client, delegate);
+      this.sessionToken = sessionToken;
+    }
+
+    @Override
+    protected InnerRequestDelegate<Void> doInBackground(Void... arg0) {
+      try {
+        client.resendCode(sessionToken, innerDelegate);
+        latch.await();
+        return innerDelegate;
+      } catch (Exception e) {
+        Logger.error(LOG_TAG, "Got exception signing in.", e);
+        delegate.handleError(e);
+      }
+      return null;
+    }
+  }
+
+  protected class ResendCodeDelegate implements RequestDelegate<Void> {
+    @Override
+    public void handleError(Exception e) {
+      Logger.warn(LOG_TAG, "Got exception requesting fresh confirmation link; ignoring.", e);
+      Toast.makeText(getApplicationContext(), R.string.fxaccount_confirm_verification_link_not_sent, Toast.LENGTH_LONG).show();
+    }
+
+    @Override
+    public void handleFailure(int status, HttpResponse response) {
+      handleError(new HTTPFailureException(new SyncStorageResponse(response)));
+    }
+
+    @Override
+    public void handleSuccess(Void result) {
+      Toast.makeText(getApplicationContext(), R.string.fxaccount_confirm_verification_link_sent, Toast.LENGTH_SHORT).show();
+    }
+  }
+
+  protected void resendCode(byte[] sessionToken) {
+    String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
+    RequestDelegate<Void> delegate = new ResendCodeDelegate();
+    Executor executor = Executors.newSingleThreadExecutor();
+    FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
+    new FxAccountResendCodeTask(this, sessionToken, client, delegate).execute();
+  }
+
+  @Override
+  public void onClick(View v) {
+    resendCode(sessionToken);
+  }
+}
--- a/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountActivity.java
@@ -4,45 +4,50 @@
 
 package org.mozilla.gecko.fxa.activities;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignUpTask;
+import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountCreateAccountTask;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 
 import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
-import android.widget.Toast;
 import ch.boye.httpclientandroidlib.HttpResponse;
 
 /**
  * Activity which displays create account screen to the user.
  */
 public class FxAccountCreateAccountActivity extends FxAccountAbstractSetupActivity {
   protected static final String LOG_TAG = FxAccountCreateAccountActivity.class.getSimpleName();
 
+  private static final int CHILD_REQUEST_CODE = 2;
+
+  protected String[] yearItems;
   protected EditText yearEdit;
 
   /**
    * {@inheritDoc}
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
@@ -60,50 +65,69 @@ public class FxAccountCreateAccountActiv
     button = (Button) ensureFindViewById(null, R.id.create_account_button, "create account button");
 
     createCreateAccountButton();
     createYearEdit();
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
-    launchActivityOnClick(ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link"), FxAccountSignInActivity.class);
+    View signInInsteadLink = ensureFindViewById(null, R.id.sign_in_instead_link, "sign in instead link");
+    signInInsteadLink.setOnClickListener(new OnClickListener() {
+      @Override
+      public void onClick(View v) {
+        Intent intent = new Intent(FxAccountCreateAccountActivity.this, FxAccountSignInActivity.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);
+      }
+    });
+
+    // 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"));
+    }
   }
 
   /**
-   * {@inheritDoc}
+   * We might have switched to the SignIn activity; if that activity
+   * succeeds, feed its result back to the authenticator.
    */
   @Override
-  public void onResume() {
-    super.onResume();
-    if (FxAccountAuthenticator.getFirefoxAccounts(this).length > 0) {
-      redirectToActivity(FxAccountStatusActivity.class);
+  public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    Logger.debug(LOG_TAG, "onActivityResult: " + requestCode);
+    if (requestCode != CHILD_REQUEST_CODE || resultCode != RESULT_OK) {
+      super.onActivityResult(requestCode, resultCode, data);
       return;
     }
+    this.setResult(resultCode, data);
+    this.finish();
   }
 
   protected void createYearEdit() {
+    yearItems = getResources().getStringArray(R.array.fxaccount_create_account_ages_array);
+
     yearEdit.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
-        final String[] years = new String[20];
-        for (int i = 0; i < years.length; i++) {
-          years[i] = Integer.toString(2014 - i);
-        }
-
         android.content.DialogInterface.OnClickListener listener = new Dialog.OnClickListener() {
           @Override
           public void onClick(DialogInterface dialog, int which) {
-            yearEdit.setText(years[which]);
+            yearEdit.setText(yearItems[which]);
+            updateButtonState();
           }
         };
-
-        AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
+        final AlertDialog dialog = new AlertDialog.Builder(FxAccountCreateAccountActivity.this)
         .setTitle(R.string.fxaccount_when_were_you_born)
-        .setItems(years, listener)
+        .setItems(yearItems, listener)
         .setIcon(R.drawable.fxaccount_icon)
         .create();
 
         dialog.show();
       }
     });
   }
 
@@ -146,39 +170,66 @@ public class FxAccountCreateAccountActiv
         return;
       }
 
       // For great debugging.
       if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
         new AndroidFxAccount(activity, account).dump();
       }
 
-      Toast.makeText(getApplicationContext(), "Got success creating account.", Toast.LENGTH_LONG).show();
-      redirectToActivity(FxAccountStatusActivity.class);
+      // The GetStarted activity has called us and needs to return a result to the authenticator.
+      final Intent intent = new Intent();
+      intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email);
+      intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
+      // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
+      setResult(RESULT_OK, intent);
+      finish();
+
+      // Show success activity.
+      Intent successIntent = new Intent(FxAccountCreateAccountActivity.this, FxAccountConfirmAccountActivity.class);
+      successIntent.putExtra("email", email);
+      // 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?
+      successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+      startActivity(successIntent);
     }
   }
 
   public void createAccount(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
     RequestDelegate<String> delegate = new CreateAccountDelegate(email, password, serverURI);
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
     try {
-      new FxAccountSignUpTask(this, email, password, client, delegate).execute();
+      new FxAccountCreateAccountTask(this, email, password, client, delegate).execute();
     } catch (Exception e) {
       showRemoteError(e);
     }
   }
 
+  @Override
+  protected boolean shouldButtonBeEnabled() {
+    return super.shouldButtonBeEnabled() &&
+        (yearEdit.length() > 0);
+  }
+
   protected void createCreateAccountButton() {
     button.setOnClickListener(new OnClickListener() {
       @Override
       public void onClick(View v) {
         if (!updateButtonState()) {
           return;
         }
         final String email = emailEdit.getText().toString();
         final String password = passwordEdit.getText().toString();
-        createAccount(email, password);
+        if (FxAccountAgeLockoutHelper.passesAgeCheck(yearEdit.getText().toString(), yearItems)) {
+          FxAccountConstants.pii(LOG_TAG, "Passed age check.");
+          createAccount(email, password);
+        } else {
+          FxAccountConstants.pii(LOG_TAG, "Failed age check!");
+          FxAccountAgeLockoutHelper.lockOut(SystemClock.elapsedRealtime());
+          setResult(RESULT_CANCELED);
+          redirectToActivity(FxAccountCreateAccountNotAllowedActivity.class);
+        }
       }
     });
   }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/activities/FxAccountCreateAccountNotAllowedActivity.java
@@ -0,0 +1,34 @@
+/* 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.fxa.activities;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.background.common.log.Logger;
+
+import android.os.Bundle;
+
+/**
+ * Activity which displays sign up/sign in screen to the user.
+ */
+public class FxAccountCreateAccountNotAllowedActivity extends FxAccountAbstractActivity {
+  protected static final String LOG_TAG = FxAccountCreateAccountNotAllowedActivity.class.getSimpleName();
+
+  public FxAccountCreateAccountNotAllowedActivity() {
+    super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onCreate(Bundle icicle) {
+    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
+
+    super.onCreate(icicle);
+    setContentView(R.layout.fxaccount_create_account_not_allowed);
+
+    linkifyTextViews(null, new int[] { R.id.learn_more_link });
+  }
+}
deleted file mode 100644
--- a/mobile/android/base/fxa/activities/FxAccountCreateSuccessActivity.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.fxa.activities;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.background.common.log.Logger;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
-
-/**
- * Activity which displays sign up/sign in screen to the user.
- */
-public class FxAccountCreateSuccessActivity extends Activity {
-  protected static final String LOG_TAG = FxAccountCreateSuccessActivity.class.getSimpleName();
-
-  protected TextView emailText;
-
-  /**
-   * Helper to find view or error if it is missing.
-   *
-   * @param id of view to find.
-   * @param description to print in error.
-   * @return non-null <code>View</code> instance.
-   */
-  public View ensureFindViewById(View v, int id, String description) {
-    View view;
-    if (v != null) {
-      view = v.findViewById(id);
-    } else {
-      view = findViewById(id);
-    }
-    if (view == null) {
-      String message = "Could not find view " + description + ".";
-      Logger.error(LOG_TAG, message);
-      throw new RuntimeException(message);
-    }
-    return view;
-  }
-
-  /**
-   * {@inheritDoc}
-   */
-  @Override
-  public void onCreate(Bundle icicle) {
-    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
-
-    super.onCreate(icicle);
-    setContentView(R.layout.fxaccount_create_success);
-
-    emailText = (TextView) ensureFindViewById(null, R.id.email, "email text");
-    if (getIntent() != null && getIntent().getExtras() != null) {
-      emailText.setText(getIntent().getStringExtra("email"));
-    }
-  }
-}
--- a/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountGetStartedActivity.java
@@ -1,32 +1,91 @@
 /* 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.fxa.activities;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountAgeLockoutHelper;
+import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 
+import android.accounts.AccountAuthenticatorActivity;
+import android.content.Intent;
 import android.os.Bundle;
+import android.os.SystemClock;
+import android.view.View;
+import android.view.View.OnClickListener;
 
 /**
  * Activity which displays sign up/sign in screen to the user.
  */
-public class FxAccountGetStartedActivity extends FxAccountAbstractActivity {
+public class FxAccountGetStartedActivity extends AccountAuthenticatorActivity {
   protected static final String LOG_TAG = FxAccountGetStartedActivity.class.getSimpleName();
 
+  private static final int CHILD_REQUEST_CODE = 1;
+
   /**
    * {@inheritDoc}
    */
   @Override
   public void onCreate(Bundle icicle) {
+    Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_get_started);
 
-    linkifyTextViews(null, new int[] { R.id.old_firefox });
+    // linkifyTextViews(null, new int[] { R.id.old_firefox });
+
+    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);
+      }
+    });
+  }
+
+  public void onResume() {
+    super.onResume();
 
-    launchActivityOnClick(ensureFindViewById(null, R.id.get_started_button, "get started button"), FxAccountCreateAccountActivity.class);
+    Intent intent = null;
+    if (FxAccountAgeLockoutHelper.isLockedOut(SystemClock.elapsedRealtime())) {
+      intent = new Intent(this, FxAccountCreateAccountNotAllowedActivity.class);
+    } else if (FxAccountAuthenticator.firefoxAccountsExist(this)) {
+      intent = new Intent(this, FxAccountStatusActivity.class);
+    }
+    if (intent != null) {
+      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);
+      startActivity(intent);
+      finish();
+      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) {
+    Logger.debug(LOG_TAG, "onActivityResult: " + requestCode);
+    if (requestCode != CHILD_REQUEST_CODE) {
+      super.onActivityResult(requestCode, resultCode, data);
+      return;
+    }
+    if (data != null) {
+      this.setAccountAuthenticatorResult(data.getExtras());
+    }
+    this.setResult(requestCode, data);
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountSetupTask.java
+++ b/mobile/android/base/fxa/activities/FxAccountSetupTask.java
@@ -30,66 +30,63 @@ import ch.boye.httpclientandroidlib.Http
  * it here to take advantage of Android's built in support for background work.
  * We really want to avoid making a threading mistake that brings down the whole
  * process.
  */
 abstract class FxAccountSetupTask<T> extends AsyncTask<Void, Void, InnerRequestDelegate<T>> {
   protected static final String LOG_TAG = FxAccountSetupTask.class.getSimpleName();
 
   protected final Context context;
-  protected final String email;
-  protected final byte[] emailUTF8;
-  protected final String password;
-  public final byte[] quickStretchedPW;
   protected final FxAccountClient20 client;
 
   protected ProgressDialog progressDialog = null;
 
+  // Initialized lazily.
+  protected byte[] quickStretchedPW;
+
   // AsyncTask's are one-time-use, so final members are fine.
   protected final CountDownLatch latch = new CountDownLatch(1);
   protected final InnerRequestDelegate<T> innerDelegate = new InnerRequestDelegate<T>(latch);
 
   protected final RequestDelegate<T> delegate;
 
-  public FxAccountSetupTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<T> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
+  public FxAccountSetupTask(Context context, boolean shouldShowProgressDialog, FxAccountClient20 client, RequestDelegate<T> delegate) {
     this.context = context;
-    this.email = email;
-    this.emailUTF8 = email.getBytes("UTF-8");
-    this.password = password;
-    this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, password.getBytes("UTF-8"));
     this.client = client;
     this.delegate = delegate;
+    if (shouldShowProgressDialog) {
+      progressDialog = new ProgressDialog(context);
+    }
   }
 
   @Override
   protected void onPreExecute() {
-    progressDialog = new ProgressDialog(context);
-    progressDialog.setTitle("Firefox Account..."); // XXX.
-    progressDialog.setMessage("Please wait.");
-    progressDialog.setCancelable(false);
-    progressDialog.setIndeterminate(true);
-    progressDialog.show();
+    if (progressDialog != null) {
+      progressDialog.setTitle("Firefox Account..."); // XXX.
+      progressDialog.setMessage("Please wait.");
+      progressDialog.setCancelable(false);
+      progressDialog.setIndeterminate(true);
+      progressDialog.show();
+    }
   }
 
   @Override
   protected void onPostExecute(InnerRequestDelegate<T> result) {
     if (progressDialog != null) {
       progressDialog.dismiss();
     }
 
     // We are on the UI thread, and need to invoke these callbacks here to allow UI updating.
-    if (result.response != null) {
-      delegate.handleSuccess(result.response);
-    } else if (result.exception instanceof HTTPFailureException) {
+    if (result.exception instanceof HTTPFailureException) {
       HTTPFailureException e = (HTTPFailureException) result.exception;
       delegate.handleFailure(e.response.getStatusCode(), e.response.httpResponse());
     } else if (innerDelegate.exception != null) {
       delegate.handleError(innerDelegate.exception);
     } else {
-      delegate.handleError(new IllegalStateException("Got bad state."));
+      delegate.handleSuccess(result.response);
     }
   }
 
   @Override
   protected void onCancelled(InnerRequestDelegate<T> result) {
     if (progressDialog != null) {
       progressDialog.dismiss();
     }
@@ -122,50 +119,88 @@ abstract class FxAccountSetupTask<T> ext
     @Override
     public void handleSuccess(T result) {
       Logger.info(LOG_TAG, "Got success.");
       this.response = result;
       latch.countDown();
     }
   }
 
-  public static class FxAccountSignUpTask extends FxAccountSetupTask<String> {
-    protected static final String LOG_TAG = FxAccountSignUpTask.class.getSimpleName();
+  public static class FxAccountCreateAccountTask extends FxAccountSetupTask<String> {
+    protected static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
+
+    protected final byte[] emailUTF8;
+    protected final byte[] passwordUTF8;
+
+    public FxAccountCreateAccountTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException {
+      super(context, true, client, delegate);
+      this.emailUTF8 = email.getBytes("UTF-8");
+      this.passwordUTF8 = password.getBytes("UTF-8");
+    }
 
-    public FxAccountSignUpTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<String> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
-      super(context, email, password, client, delegate);
+    /**
+     * Stretching the password is expensive, so we compute the stretched value lazily.
+     *
+     * @return stretched password.
+     * @throws GeneralSecurityException
+     * @throws UnsupportedEncodingException
+     */
+    public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
+      if (this.quickStretchedPW == null) {
+        this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
+      }
+      return this.quickStretchedPW;
     }
 
     @Override
     protected InnerRequestDelegate<String> doInBackground(Void... arg0) {
       try {
-        client.createAccount(emailUTF8, quickStretchedPW, false, innerDelegate);
+        client.createAccount(emailUTF8, generateQuickStretchedPW(), false, innerDelegate);
         latch.await();
         return innerDelegate;
-      } catch (InterruptedException e) {
+      } catch (Exception e) {
         Logger.error(LOG_TAG, "Got exception logging in.", e);
         delegate.handleError(e);
       }
       return null;
     }
   }
 
   public static class FxAccountSignInTask extends FxAccountSetupTask<LoginResponse> {
-    protected static final String LOG_TAG = FxAccountSignUpTask.class.getSimpleName();
+    protected static final String LOG_TAG = FxAccountCreateAccountTask.class.getSimpleName();
+
+    protected final byte[] emailUTF8;
+    protected final byte[] passwordUTF8;
+
+    public FxAccountSignInTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException {
+      super(context, true, client, delegate);
+      this.emailUTF8 = email.getBytes("UTF-8");
+      this.passwordUTF8 = password.getBytes("UTF-8");
+    }
 
-    public FxAccountSignInTask(Context context, String email, String password, FxAccountClient20 client, RequestDelegate<LoginResponse> delegate) throws UnsupportedEncodingException, GeneralSecurityException {
-      super(context, email, password, client, delegate);
+    /**
+     * Stretching the password is expensive, so we compute the stretched value lazily.
+     *
+     * @return stretched password.
+     * @throws GeneralSecurityException
+     * @throws UnsupportedEncodingException
+     */
+    public byte[] generateQuickStretchedPW() throws UnsupportedEncodingException, GeneralSecurityException {
+      if (this.quickStretchedPW == null) {
+        this.quickStretchedPW = FxAccountUtils.generateQuickStretchedPW(emailUTF8, passwordUTF8);
+      }
+      return this.quickStretchedPW;
     }
 
     @Override
     protected InnerRequestDelegate<LoginResponse> doInBackground(Void... arg0) {
       try {
-        client.loginAndGetKeys(emailUTF8, quickStretchedPW, innerDelegate);
+        client.loginAndGetKeys(emailUTF8, generateQuickStretchedPW(), innerDelegate);
         latch.await();
         return innerDelegate;
-      } catch (InterruptedException e) {
+      } catch (Exception e) {
         Logger.error(LOG_TAG, "Got exception signing in.", e);
         delegate.handleError(e);
       }
       return null;
     }
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountSignInActivity.java
@@ -10,37 +10,39 @@ import java.util.concurrent.Executors;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
 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.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountSetupTask.FxAccountSignInTask;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 
 import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
-import android.widget.Toast;
 import ch.boye.httpclientandroidlib.HttpResponse;
 
 /**
  * Activity which displays sign in screen to the user.
  */
 public class FxAccountSignInActivity extends FxAccountAbstractSetupActivity {
   protected static final String LOG_TAG = FxAccountSignInActivity.class.getSimpleName();
 
+  private static final int CHILD_REQUEST_CODE = 3;
+
   /**
    * {@inheritDoc}
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
@@ -53,31 +55,54 @@ public class FxAccountSignInActivity ext
     button = (Button) ensureFindViewById(null, R.id.sign_in_button, "sign in button");
 
     minimumPasswordLength = 1; // Minimal restriction on passwords entered to sign in.
     createSignInButton();
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
-    this.launchActivityOnClick(ensureFindViewById(null, R.id.create_account_link, "create account instead link"), FxAccountCreateAccountActivity.class);
+    View signInInsteadLink = ensureFindViewById(null, R.id.create_account_link, "create account instead link");
+    signInInsteadLink.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);
+      }
+    });
+
+    // 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"));
+    }
+
     // Not yet implemented.
-    this.launchActivityOnClick(ensureFindViewById(null, R.id.forgot_password_link, "forgot password link"), null);
+    // this.launchActivityOnClick(ensureFindViewById(null, R.id.forgot_password_link, "forgot password link"), null);
   }
 
   /**
-   * {@inheritDoc}
+   * We might have switched to the CreateAccount activity; if that activity
+   * succeeds, feed its result back to the authenticator.
    */
   @Override
-  public void onResume() {
-    super.onResume();
-    if (FxAccountAuthenticator.getFirefoxAccounts(this).length > 0) {
-      redirectToActivity(FxAccountStatusActivity.class);
+  public void onActivityResult(int requestCode, int resultCode, Intent data) {
+    Logger.debug(LOG_TAG, "onActivityResult: " + requestCode);
+    if (requestCode != CHILD_REQUEST_CODE || resultCode != RESULT_OK) {
+      super.onActivityResult(requestCode, resultCode, data);
       return;
     }
+    this.setResult(resultCode, data);
+    this.finish();
   }
 
   protected class SignInDelegate implements RequestDelegate<LoginResponse> {
     public final String email;
     public final String password;
     public final String serverURI;
 
     public SignInDelegate(String email, String password, String serverURI) {
@@ -114,18 +139,37 @@ public class FxAccountSignInActivity ext
         return;
       }
 
       // For great debugging.
       if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
         new AndroidFxAccount(activity, account).dump();
       }
 
-      Toast.makeText(getApplicationContext(), "Got success creating account.", Toast.LENGTH_LONG).show();
-      redirectToActivity(FxAccountStatusActivity.class);
+      // The GetStarted activity has called us and needs to return a result to the authenticator.
+      final Intent intent = new Intent();
+      intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, email);
+      intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, FxAccountConstants.ACCOUNT_TYPE);
+      // intent.putExtra(AccountManager.KEY_AUTHTOKEN, accountType);
+      setResult(RESULT_OK, intent);
+      finish();
+
+      // Show success activity depending on verification status.
+      Intent successIntent;
+      if (result.verified) {
+        successIntent = new Intent(FxAccountSignInActivity.this, FxAccountVerifiedAccountActivity.class);
+      } else {
+        successIntent = new Intent(FxAccountSignInActivity.this, FxAccountConfirmAccountActivity.class);
+        successIntent.putExtra("sessionToken", result.sessionToken);
+      }
+      successIntent.putExtra("email", email);
+      // 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?
+      successIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+      startActivity(successIntent);
     }
   }
 
   public void signIn(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
     RequestDelegate<LoginResponse> delegate = new SignInDelegate(email, password, serverURI);
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
--- a/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusActivity.java
@@ -1,16 +1,17 @@
 /* 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.fxa.activities;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 
 import android.accounts.Account;
 import android.os.Bundle;
 import android.view.View;
 import android.widget.TextView;
 
@@ -19,21 +20,26 @@ import android.widget.TextView;
  */
 public class FxAccountStatusActivity extends FxAccountAbstractActivity {
   protected static final String LOG_TAG = FxAccountStatusActivity.class.getSimpleName();
 
   protected View connectionStatusUnverifiedView;
   protected View connectionStatusSignInView;
   protected View connectionStatusSyncingView;
 
+  public FxAccountStatusActivity() {
+    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
+  }
+
   /**
    * {@inheritDoc}
    */
   @Override
   public void onCreate(Bundle icicle) {
+    Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
     setContentView(R.layout.fxaccount_status);
 
     connectionStatusUnverifiedView = ensureFindViewById(null, R.id.unverified_view, "unverified view");
     connectionStatusSignInView = ensureFindViewById(null, R.id.sign_in_view, "sign in view");
     connectionStatusSyncingView = ensureFindViewById(null, R.id.syncing_view, "syncing view");
--- a/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountUpdateCredentialsActivity.java
@@ -25,27 +25,34 @@ import org.mozilla.gecko.sync.net.SyncSt
 import android.accounts.Account;
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.TextView;
-import android.widget.Toast;
 import ch.boye.httpclientandroidlib.HttpResponse;
 
 /**
  * Activity which displays a screen for updating the local password.
  */
 public class FxAccountUpdateCredentialsActivity extends FxAccountAbstractSetupActivity {
   protected static final String LOG_TAG = FxAccountUpdateCredentialsActivity.class.getSimpleName();
 
   protected Account account;
 
+  public FxAccountUpdateCredentialsActivity() {
+    // We want to share code with the other setup activities, but this activity
+    // doesn't create a new Android Account, it modifies an existing one. If you
+    // manage to get an account, and somehow be locked out too, we'll let you
+    // update it.
+    super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
+  }
+
   /**
    * {@inheritDoc}
    */
   @Override
   public void onCreate(Bundle icicle) {
     Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
 
     super.onCreate(icicle);
@@ -61,28 +68,29 @@ public class FxAccountUpdateCredentialsA
     createButton();
     addListeners();
     updateButtonState();
     createShowPasswordButton();
 
     emailEdit.setEnabled(false);
 
     // Not yet implemented.
-    this.launchActivityOnClick(ensureFindViewById(null, R.id.forgot_password_link, "forgot password link"), null);
+    // this.launchActivityOnClick(ensureFindViewById(null, R.id.forgot_password_link, "forgot password link"), null);
   }
 
   @Override
   public void onResume() {
     super.onResume();
     Account accounts[] = FxAccountAuthenticator.getFirefoxAccounts(this);
-    if (accounts.length < 1) {
-      redirectToActivity(FxAccountGetStartedActivity.class);
+    account = accounts[0];
+    if (account == null) {
+      setResult(RESULT_CANCELED);
       finish();
+      return;
     }
-    account = accounts[0];
     emailEdit.setText(account.name);
   }
 
   protected class UpdateCredentialsDelegate implements RequestDelegate<LoginResponse> {
     public final String email;
     public final String password;
     public final String serverURI;
     public final byte[] quickStretchedPW;
@@ -118,17 +126,16 @@ public class FxAccountUpdateCredentialsA
       // XXX wasteful, should only do this once.
       fxAccount.setQuickStretchedPW(quickStretchedPW);
 
       // For great debugging.
       if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
         fxAccount.dump();
       }
 
-      Toast.makeText(getApplicationContext(), "Got success updating account credential.", Toast.LENGTH_LONG).show();
       redirectToActivity(FxAccountStatusActivity.class);
     }
   }
 
   public void updateCredentials(String email, String password) {
     String serverURI = FxAccountConstants.DEFAULT_IDP_ENDPOINT;
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient20 client = new FxAccountClient20(serverURI, executor);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/activities/FxAccountVerifiedAccountActivity.java
@@ -0,0 +1,60 @@
+/* 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.fxa.activities;
+
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.background.common.log.Logger;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Activity which displays "Account verified" success screen.
+ */
+public class FxAccountVerifiedAccountActivity extends Activity {
+  protected static final String LOG_TAG = FxAccountVerifiedAccountActivity.class.getSimpleName();
+
+  protected TextView emailText;
+
+  /**
+   * Helper to find view or error if it is missing.
+   *
+   * @param id of view to find.
+   * @param description to print in error.
+   * @return non-null <code>View</code> instance.
+   */
+  public View ensureFindViewById(View v, int id, String description) {
+    View view;
+    if (v != null) {
+      view = v.findViewById(id);
+    } else {
+      view = findViewById(id);
+    }
+    if (view == null) {
+      String message = "Could not find view " + description + ".";
+      Logger.error(LOG_TAG, message);
+      throw new RuntimeException(message);
+    }
+    return view;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void onCreate(Bundle icicle) {
+    Logger.debug(LOG_TAG, "onCreate(" + icicle + ")");
+
+    super.onCreate(icicle);
+    setContentView(R.layout.fxaccount_account_verified);
+
+    emailText = (TextView) ensureFindViewById(null, R.id.email, "email text");
+    if (getIntent() != null && getIntent().getExtras() != null) {
+      emailText.setText(getIntent().getStringExtra("email"));
+    }
+  }
+}
--- a/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java
+++ b/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java
@@ -140,9 +140,19 @@ public class FxAccountAuthenticator exte
    * Return Firefox Accounts.
    *
    * @param context Android context.
    * @return Firefox Account objects.
    */
   public static Account[] getFirefoxAccounts(final Context context) {
     return AccountManager.get(context).getAccountsByType(FxAccountConstants.ACCOUNT_TYPE);
   }
+
+  /**
+   * Return true if at least one Firefox Account exists.
+   *
+   * @param context Android context.
+   * @return true if at least one Firefox Account exists.
+   */
+  public static boolean firefoxAccountsExist(final Context context) {
+    return getFirefoxAccounts(context).length > 0;
+  }
 }
--- a/mobile/android/base/fxa/authenticator/FxAccountLoginPolicy.java
+++ b/mobile/android/base/fxa/authenticator/FxAccountLoginPolicy.java
@@ -10,16 +10,17 @@ import java.util.concurrent.Executor;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.StatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.TwoKeys;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
+import org.mozilla.gecko.background.fxa.SkewHandler;
 import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.JSONWebTokenUtils;
 import org.mozilla.gecko.browserid.VerifyingPublicKey;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginException.FxAccountLoginAccountNotVerifiedException;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginException.FxAccountLoginBadPasswordException;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.Utils;
@@ -52,16 +53,18 @@ public class FxAccountLoginPolicy {
     return assertionDurationInMilliseconds;
   }
 
   protected FxAccountClient makeFxAccountClient() {
     String serverURI = fxAccount.getServerURI();
     return new FxAccountClient20(serverURI, executor);
   }
 
+  private SkewHandler skewHandler;
+
   /**
    * Check if this certificate is not worth generating an assertion from: for
    * example, because it is not well-formed, or it is already expired.
    *
    * @param certificate
    *          to check.
    * @return if it is definitely not worth generating an assertion from this
    *         certificate.
@@ -78,16 +81,20 @@ public class FxAccountLoginPolicy {
    *          to check.
    * @return if assertion is definitely not worth presenting to the token
    *         server.
    */
   protected boolean isInvalidAssertion(String assertion) {
     return false;
   }
 
+  protected long now() {
+    return System.currentTimeMillis();
+  }
+
   public enum AccountState {
     Invalid,
     NeedsSessionToken,
     NeedsVerification,
     NeedsKeys,
     NeedsCertificate,
     NeedsAssertion,
     Valid,
@@ -162,16 +169,21 @@ public class FxAccountLoginPolicy {
     }
     stages.addFirst(new EnsureSessionTokenStage());
     if (state == AccountState.NeedsSessionToken) {
       return stages;
     }
     return stages;
   }
 
+  public void login(final String audience, final FxAccountLoginDelegate delegate, final SkewHandler skewHandler) {
+    this.skewHandler = skewHandler;
+    this.login(audience, delegate);
+  }
+
   /**
    * Do as much of a Firefox Account login dance as possible.
    * <p>
    * To avoid deeply nested callbacks, we maintain a simple queue of stages to
    * execute in sequence.
    *
    * @param audience to generate assertion for.
    * @param delegate providing callbacks to invoke.
@@ -270,16 +282,20 @@ public class FxAccountLoginPolicy {
       delegate.client.loginAndGetKeys(emailUTF8, quickStretchedPW, new RequestDelegate<FxAccountClient20.LoginResponse>() {
         @Override
         public void handleError(Exception e) {
           delegate.handleError(new FxAccountLoginException(e));
         }
 
         @Override
         public void handleFailure(int status, HttpResponse response) {
+          if (skewHandler != null) {
+            skewHandler.updateSkew(response, now());
+          }
+
           if (status != 401) {
             delegate.handleError(new FxAccountLoginException(new HTTPFailureException(new SyncStorageResponse(response))));
             return;
           }
           // We just got denied for a sessionToken. That's a problem with
           // our email or password. Only thing to do is mark the account
           // invalid and ask for user intervention.
           fxAccount.setInvalid();
@@ -315,16 +331,20 @@ public class FxAccountLoginPolicy {
       delegate.client.status(sessionToken, new RequestDelegate<StatusResponse>() {
         @Override
         public void handleError(Exception e) {
           delegate.handleError(new FxAccountLoginException(e));
         }
 
         @Override
         public void handleFailure(int status, HttpResponse response) {
+          if (skewHandler != null) {
+            skewHandler.updateSkew(response, now());
+          }
+
           if (status != 401) {
             delegate.handleError(new FxAccountLoginException(new HTTPFailureException(new SyncStorageResponse(response))));
             return;
           }
           // We just got denied due to our sessionToken.  Invalidate it.
           fxAccount.setSessionToken(null);
           delegate.handleError(new FxAccountLoginBadPasswordException("Auth server rejected session token while fetching status."));
         }
@@ -374,16 +394,20 @@ public class FxAccountLoginPolicy {
       delegate.client.loginAndGetKeys(emailUTF8, quickStretchedPW, new RequestDelegate<FxAccountClient20.LoginResponse>() {
         @Override
         public void handleError(Exception e) {
           delegate.handleError(new FxAccountLoginException(e));
         }
 
         @Override
         public void handleFailure(int status, HttpResponse response) {
+          if (skewHandler != null) {
+            skewHandler.updateSkew(response, now());
+          }
+
           if (status != 401) {
             delegate.handleError(new FxAccountLoginException(new HTTPFailureException(new SyncStorageResponse(response))));
             return;
           }
           // We just got denied for a keyFetchToken. That's a problem with
           // our email or password. Only thing to do is mark the account
           // invalid and ask for user intervention.
           fxAccount.setInvalid();
@@ -421,16 +445,20 @@ public class FxAccountLoginPolicy {
       delegate.client.keys(keyFetchToken, new RequestDelegate<FxAccountClient10.TwoKeys>() {
         @Override
         public void handleError(Exception e) {
           delegate.handleError(new FxAccountLoginException(e));
         }
 
         @Override
         public void handleFailure(int status, HttpResponse response) {
+          if (skewHandler != null) {
+            skewHandler.updateSkew(response, now());
+          }
+
           if (status != 401) {
             delegate.handleError(new FxAccountLoginException(new HTTPFailureException(new SyncStorageResponse(response))));
             return;
           }
           delegate.handleError(new FxAccountLoginBadPasswordException("Auth server rejected key token while fetching keys."));
         }
 
         @Override
@@ -468,16 +496,20 @@ public class FxAccountLoginPolicy {
       delegate.client.sign(sessionToken, publicKey.toJSONObject(), getCertificateDurationInMilliseconds(), new RequestDelegate<String>() {
         @Override
         public void handleError(Exception e) {
           delegate.handleError(new FxAccountLoginException(e));
         }
 
         @Override
         public void handleFailure(int status, HttpResponse response) {
+          if (skewHandler != null) {
+            skewHandler.updateSkew(response, now());
+          }
+
           if (status != 401) {
             delegate.handleError(new FxAccountLoginException(new HTTPFailureException(new SyncStorageResponse(response))));
             return;
           }
           // Our sessionToken was just rejected; we should get a new
           // sessionToken. TODO: Make sure the exception below is fine
           // enough grained.
           // Since this is the place we'll see the majority of lifecylcle
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.fxa.sync;
 
 import java.net.URI;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
+import org.mozilla.gecko.background.fxa.SkewHandler;
 import org.mozilla.gecko.browserid.verifier.BrowserIDRemoteVerifierClient;
 import org.mozilla.gecko.browserid.verifier.BrowserIDVerifierDelegate;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginDelegate;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginException;
 import org.mozilla.gecko.fxa.authenticator.FxAccountLoginPolicy;
@@ -183,18 +184,28 @@ public class FxAccountSyncAdapter extend
             @Override
             public void handleSuccess(final TokenServerToken token) {
               FxAccountConstants.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
               sharedPrefs.edit().putLong("tokenFailures", 0).commit();
 
               FxAccountGlobalSession globalSession = null;
               try {
                 ClientsDataDelegate clientsDataDelegate = new SharedPreferencesClientsDataDelegate(sharedPrefs);
-                final KeyBundle syncKeyBundle = FxAccountUtils.generateSyncKeyBundle(fxAccount.getKb()); // TODO Document this choice for deriving from kB.
-                AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false);
+
+                // TODO Document this choice for deriving from kB.
+                final KeyBundle syncKeyBundle = FxAccountUtils.generateSyncKeyBundle(fxAccount.getKb());
+
+                // We compute skew over time using SkewHandler. This yields an unchanging
+                // skew adjustment that the HawkAuthHeaderProvider uses to adjust its
+                // timestamps. Eventually we might want this to adapt within the scope of a
+                // global session.
+                final SkewHandler tokenServerSkewHandler = SkewHandler.getSkewHandlerFromEndpointString(token.endpoint);
+                final long tokenServerSkew = tokenServerSkewHandler.getSkewInSeconds();
+                AuthHeaderProvider authHeaderProvider = new HawkAuthHeaderProvider(token.id, token.key.getBytes("UTF-8"), false, tokenServerSkew);
+
                 globalSession = new FxAccountGlobalSession(token.endpoint, token.uid, authHeaderProvider, FxAccountConstants.PREFS_PATH, syncKeyBundle, callback, getContext(), extras, clientsDataDelegate);
                 globalSession.start();
               } catch (Exception e) {
                 callback.handleError(globalSession, e);
                 return;
               }
             }
 
--- a/mobile/android/base/home/HomeConfig.java
+++ b/mobile/android/base/home/HomeConfig.java
@@ -1,20 +1,25 @@
 /* -*- 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.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 
+import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
 final class HomeConfig {
     /**
      * Used to determine what type of HomeFragment subclass to use when creating
      * a given panel. With the exception of DYNAMIC, all of these types correspond
      * to a default set of built-in panels. The DYNAMIC panel type is used by
@@ -80,98 +85,399 @@ final class HomeConfig {
             }
         };
     }
 
     public static class PanelConfig implements Parcelable {
         private final PanelType mType;
         private final String mTitle;
         private final String mId;
+        private final LayoutType mLayoutType;
+        private final List<ViewConfig> mViews;
         private final EnumSet<Flags> mFlags;
 
+        private static final String JSON_KEY_TYPE = "type";
+        private static final String JSON_KEY_TITLE = "title";
+        private static final String JSON_KEY_ID = "id";
+        private static final String JSON_KEY_LAYOUT = "layout";
+        private static final String JSON_KEY_VIEWS = "views";
+        private static final String JSON_KEY_DEFAULT = "default";
+
+        private static final int IS_DEFAULT = 1;
+
         public enum Flags {
             DEFAULT_PANEL
         }
 
+        public PanelConfig(JSONObject json) throws JSONException, IllegalArgumentException {
+            mType = PanelType.fromId(json.getString(JSON_KEY_TYPE));
+            mTitle = json.getString(JSON_KEY_TITLE);
+            mId = json.getString(JSON_KEY_ID);
+
+            final String layoutTypeId = json.optString(JSON_KEY_LAYOUT, null);
+            if (layoutTypeId != null) {
+                mLayoutType = LayoutType.fromId(layoutTypeId);
+            } else {
+                mLayoutType = null;
+            }
+
+            final JSONArray jsonViews = json.optJSONArray(JSON_KEY_VIEWS);
+            if (jsonViews != null) {
+                mViews = new ArrayList<ViewConfig>();
+
+                final int viewCount = jsonViews.length();
+                for (int i = 0; i < viewCount; i++) {
+                    final JSONObject jsonViewConfig = (JSONObject) jsonViews.get(i);
+                    final ViewConfig viewConfig = new ViewConfig(jsonViewConfig);
+                    mViews.add(viewConfig);
+                }
+            } else {
+                mViews = null;
+            }
+
+            mFlags = EnumSet.noneOf(Flags.class);
+
+            final boolean isDefault = (json.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT);
+            if (isDefault) {
+                mFlags.add(Flags.DEFAULT_PANEL);
+            }
+
+            validate();
+        }
+
         @SuppressWarnings("unchecked")
         public PanelConfig(Parcel in) {
             mType = (PanelType) in.readParcelable(getClass().getClassLoader());
             mTitle = in.readString();
             mId = in.readString();
+            mLayoutType = (LayoutType) in.readParcelable(getClass().getClassLoader());
+
+            mViews = new ArrayList<ViewConfig>();
+            in.readTypedList(mViews, ViewConfig.CREATOR);
+
             mFlags = (EnumSet<Flags>) in.readSerializable();
+
+            validate();
         }
 
         public PanelConfig(PanelType type, String title, String id) {
             this(type, title, id, EnumSet.noneOf(Flags.class));
         }
 
         public PanelConfig(PanelType type, String title, String id, EnumSet<Flags> flags) {
-            if (type == null) {
+            this(type, title, id, null, null, flags);
+        }
+
+        public PanelConfig(PanelType type, String title, String id, LayoutType layoutType,
+                List<ViewConfig> views, EnumSet<Flags> flags) {
+            mType = type;
+            mTitle = title;
+            mId = id;
+            mFlags = flags;
+            mLayoutType = layoutType;
+            mViews = views;
+
+            validate();
+        }
+
+        private void validate() {
+            if (mType == null) {
                 throw new IllegalArgumentException("Can't create PanelConfig with null type");
             }
-            mType = type;
 
-            if (title == null) {
-                throw new IllegalArgumentException("Can't create PanelConfig with null title");
+            if (TextUtils.isEmpty(mTitle)) {
+                throw new IllegalArgumentException("Can't create PanelConfig with empty title");
             }
-            mTitle = title;
+
+            if (TextUtils.isEmpty(mId)) {
+                throw new IllegalArgumentException("Can't create PanelConfig with empty id");
+            }
 
-            if (id == null) {
-                throw new IllegalArgumentException("Can't create PanelConfig with null id");
+            if (mLayoutType == null && mType == PanelType.DYNAMIC) {
+                throw new IllegalArgumentException("Can't create a dynamic PanelConfig with null layout type");
             }
-            mId = id;
 
-            if (flags == null) {
+            if ((mViews == null || mViews.size() == 0) && mType == PanelType.DYNAMIC) {
+                throw new IllegalArgumentException("Can't create a dynamic PanelConfig with no views");
+            }
+
+            if (mFlags == null) {
                 throw new IllegalArgumentException("Can't create PanelConfig with null flags");
             }
-            mFlags = flags;
         }
 
         public PanelType getType() {
             return mType;
         }
 
         public String getTitle() {
             return mTitle;
         }
 
         public String getId() {
             return mId;
         }
 
+        public LayoutType getLayoutType() {
+            return mLayoutType;
+        }
+
+        public int getViewCount() {
+            return (mViews != null ? mViews.size() : 0);
+        }
+
+        public ViewConfig getViewAt(int index) {
+            return (mViews != null ? mViews.get(index) : null);
+        }
+
         public boolean isDefault() {
             return mFlags.contains(Flags.DEFAULT_PANEL);
         }
 
+        public JSONObject toJSON() throws JSONException {
+            final JSONObject json = new JSONObject();
+
+            json.put(JSON_KEY_TYPE, mType.toString());
+            json.put(JSON_KEY_TITLE, mTitle);
+            json.put(JSON_KEY_ID, mId);
+
+            if (mLayoutType != null) {
+                json.put(JSON_KEY_LAYOUT, mLayoutType.toString());
+            }
+
+            if (mViews != null) {
+                final JSONArray jsonViews = new JSONArray();
+
+                final int viewCount = mViews.size();
+                for (int i = 0; i < viewCount; i++) {
+                    final ViewConfig viewConfig = mViews.get(i);
+                    final JSONObject jsonViewConfig = viewConfig.toJSON();
+                    jsonViews.put(jsonViewConfig);
+                }
+
+                json.put(JSON_KEY_VIEWS, jsonViews);
+            }
+
+            if (mFlags.contains(Flags.DEFAULT_PANEL)) {
+                json.put(JSON_KEY_DEFAULT, IS_DEFAULT);
+            }
+
+            return json;
+        }
+
         @Override
         public int describeContents() {
             return 0;
         }
 
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeParcelable(mType, 0);
             dest.writeString(mTitle);
             dest.writeString(mId);
+            dest.writeParcelable(mLayoutType, 0);
+            dest.writeTypedList(mViews);
             dest.writeSerializable(mFlags);
         }
 
         public static final Creator<PanelConfig> CREATOR = new Creator<PanelConfig>() {
             @Override
             public PanelConfig createFromParcel(final Parcel in) {
                 return new PanelConfig(in);
             }
 
             @Override
             public PanelConfig[] newArray(final int size) {
                 return new PanelConfig[size];
             }
         };
     }
 
+    public static enum LayoutType implements Parcelable {
+        FRAME("frame");
+
+        private final String mId;
+
+        LayoutType(String id) {
+            mId = id;
+        }
+
+        public static LayoutType fromId(String id) {
+            if (id == null) {
+                throw new IllegalArgumentException("Could not convert null String to LayoutType");
+            }
+
+            for (LayoutType layoutType : LayoutType.values()) {
+                if (TextUtils.equals(layoutType.mId, id.toLowerCase())) {
+                    return layoutType;
+                }
+            }
+
+            throw new IllegalArgumentException("Could not convert String id to LayoutType");
+        }
+
+        @Override
+        public String toString() {
+            return mId;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(ordinal());
+        }
+
+        public static final Creator<LayoutType> CREATOR = new Creator<LayoutType>() {
+            @Override
+            public LayoutType createFromParcel(final Parcel source) {
+                return LayoutType.values()[source.readInt()];
+            }
+
+            @Override
+            public LayoutType[] newArray(final int size) {
+                return new LayoutType[size];
+            }
+        };
+    }
+
+    public static enum ViewType implements Parcelable {
+        LIST("list");
+
+        private final String mId;
+
+        ViewType(String id) {
+            mId = id;
+        }
+
+        public static ViewType fromId(String id) {
+            if (id == null) {
+                throw new IllegalArgumentException("Could not convert null String to ViewType");
+            }
+
+            for (ViewType viewType : ViewType.values()) {
+                if (TextUtils.equals(viewType.mId, id.toLowerCase())) {
+                    return viewType;
+                }
+            }
+
+            throw new IllegalArgumentException("Could not convert String id to ViewType");
+        }
+
+        @Override
+        public String toString() {
+            return mId;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(ordinal());
+        }
+
+        public static final Creator<ViewType> CREATOR = new Creator<ViewType>() {
+            @Override
+            public ViewType createFromParcel(final Parcel source) {
+                return ViewType.values()[source.readInt()];
+            }
+
+            @Override
+            public ViewType[] newArray(final int size) {
+                return new ViewType[size];
+            }
+        };
+    }
+
+    public static class ViewConfig implements Parcelable {
+        private final ViewType mType;
+        private final String mDatasetId;
+
+        private static final String JSON_KEY_TYPE = "type";
+        private static final String JSON_KEY_DATASET = "dataset";
+
+        public ViewConfig(JSONObject json) throws JSONException, IllegalArgumentException {
+            mType = ViewType.fromId(json.getString(JSON_KEY_TYPE));
+            mDatasetId = json.getString(JSON_KEY_DATASET);
+
+            validate();
+        }
+
+        @SuppressWarnings("unchecked")
+        public ViewConfig(Parcel in) {
+            mType = (ViewType) in.readParcelable(getClass().getClassLoader());
+            mDatasetId = in.readString();
+
+            validate();
+        }
+
+        public ViewConfig(ViewType type, String datasetId) {
+            mType = type;
+            mDatasetId = datasetId;
+
+            validate();
+        }
+
+        private void validate() {
+            if (mType == null) {
+                throw new IllegalArgumentException("Can't create ViewConfig with null type");
+            }
+
+            if (TextUtils.isEmpty(mDatasetId)) {
+                throw new IllegalArgumentException("Can't create ViewConfig with empty dataset ID");
+            }
+        }
+
+        public ViewType getType() {
+            return mType;
+        }
+
+        public String getDatasetId() {
+            return mDatasetId;
+        }
+
+        public JSONObject toJSON() throws JSONException {
+            final JSONObject json = new JSONObject();
+
+            json.put(JSON_KEY_TYPE, mType.toString());
+            json.put(JSON_KEY_DATASET, mDatasetId);
+
+            return json;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeParcelable(mType, 0);
+            dest.writeString(mDatasetId);
+        }
+
+        public static final Creator<ViewConfig> CREATOR = new Creator<ViewConfig>() {
+            @Override
+            public ViewConfig createFromParcel(final Parcel in) {
+                return new ViewConfig(in);
+            }
+
+            @Override
+            public ViewConfig[] newArray(final int size) {
+                return new ViewConfig[size];
+            }
+        };
+    }
+
     public interface OnChangeListener {
         public void onChange();
     }
 
     public interface HomeConfigBackend {
         public List<PanelConfig> load();
         public void save(List<PanelConfig> entries);
         public void setOnChangeListener(OnChangeListener listener);
--- a/mobile/android/base/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/home/HomeConfigPrefsBackend.java
@@ -29,23 +29,16 @@ import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 
 class HomeConfigPrefsBackend implements HomeConfigBackend {
     private static final String LOGTAG = "GeckoHomeConfigBackend";
 
     private static final String PREFS_KEY = "home_panels";
 
-    private static final String JSON_KEY_TYPE = "type";
-    private static final String JSON_KEY_TITLE = "title";
-    private static final String JSON_KEY_ID = "id";
-    private static final String JSON_KEY_DEFAULT = "default";
-
-    private static final int IS_DEFAULT = 1;
-
     // 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 final Context mContext;
     private PrefsListener mPrefsListener;
@@ -106,81 +99,50 @@ class HomeConfigPrefsBackend implements 
         }
 
         final ArrayList<PanelConfig> panelConfigs = new ArrayList<PanelConfig>();
 
         final int count = jsonPanelConfigs.length();
         for (int i = 0; i < count; i++) {
             try {
                 final JSONObject jsonPanelConfig = jsonPanelConfigs.getJSONObject(i);
-
-                final PanelConfig panelConfig = loadPanelConfigFromJSON(jsonPanelConfig);
+                final PanelConfig panelConfig = new PanelConfig(jsonPanelConfig);
                 panelConfigs.add(panelConfig);
             } catch (Exception e) {
                 Log.e(LOGTAG, "Exception loading PanelConfig from JSON", e);
             }
         }
 
         return panelConfigs;
     }
 
-    private PanelConfig loadPanelConfigFromJSON(JSONObject jsonPanelConfig)
-            throws JSONException, IllegalArgumentException {
-        final PanelType type = PanelType.fromId(jsonPanelConfig.getString(JSON_KEY_TYPE));
-        final String title = jsonPanelConfig.getString(JSON_KEY_TITLE);
-        final String id = jsonPanelConfig.getString(JSON_KEY_ID);
-
-        final EnumSet<PanelConfig.Flags> flags = EnumSet.noneOf(PanelConfig.Flags.class);
-        final boolean isDefault = (jsonPanelConfig.optInt(JSON_KEY_DEFAULT, -1) == IS_DEFAULT);
-        if (isDefault) {
-            flags.add(PanelConfig.Flags.DEFAULT_PANEL);
-        }
-
-        return new PanelConfig(type, title, id, flags);
-    }
-
     @Override
     public List<PanelConfig> load() {
         final SharedPreferences prefs = getSharedPreferences();
         final String jsonString = prefs.getString(PREFS_KEY, null);
 
         final List<PanelConfig> panelConfigs;
         if (TextUtils.isEmpty(jsonString)) {
             panelConfigs = loadDefaultConfig();
         } else {
             panelConfigs = loadConfigFromString(jsonString);
         }
 
         return Collections.unmodifiableList(panelConfigs);
     }
 
-    private JSONObject convertPanelConfigToJSON(PanelConfig PanelConfig) throws JSONException {
-        final JSONObject jsonPanelConfig = new JSONObject();
-
-        jsonPanelConfig.put(JSON_KEY_TYPE, PanelConfig.getType().toString());
-        jsonPanelConfig.put(JSON_KEY_TITLE, PanelConfig.getTitle());
-        jsonPanelConfig.put(JSON_KEY_ID, PanelConfig.getId());
-
-        if (PanelConfig.isDefault()) {
-            jsonPanelConfig.put(JSON_KEY_DEFAULT, IS_DEFAULT);
-        }
-
-        return jsonPanelConfig;
-    }
-
     @Override
     public void save(List<PanelConfig> panelConfigs) {
         final JSONArray jsonPanelConfigs = new JSONArray();
 
         final int count = panelConfigs.size();
         for (int i = 0; i < count; i++) {
             try {
-                final PanelConfig PanelConfig = panelConfigs.get(i);
-
-                final JSONObject jsonPanelConfig = convertPanelConfigToJSON(PanelConfig);
+                final PanelConfig panelConfig = panelConfigs.get(i);
+                final JSONObject jsonPanelConfig = panelConfig.toJSON();
                 jsonPanelConfigs.put(jsonPanelConfig);
             } catch (Exception e) {
                 Log.e(LOGTAG, "Exception converting PanelConfig to JSON", e);
             }
         }
 
         final SharedPreferences prefs = getSharedPreferences();
         final SharedPreferences.Editor editor = prefs.edit();
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -154,8 +154,36 @@
 <!ENTITY fxaccount.status.syncing 'Firefox is syncing...'>
 <!ENTITY fxaccount.status.sync 'Sync' >
 <!ENTITY fxaccount.status.bookmarks 'Bookmarks'>
 <!ENTITY fxaccount.status.history 'History'>
 <!ENTITY fxaccount.status.passwords 'Passwords'>
 <!ENTITY fxaccount.status.tabs 'Open tabs'>
 <!ENTITY fxaccount.update.credentials 'Update password'>
 <!ENTITY fxaccount.update.credentials.button.label 'Re-connect'>
+<!ENTITY fxaccount.account.create.not.allowed 'Cannot create account'>
+<!ENTITY fxaccount.account.create.not.allowed.description 'You must meet certain age requirements to create a Firefox Account.'>
+
+<!-- Note to translators:
+
+These strings represent ranges of birth years, used to determine if a
+potential user is old enough to meet legal requirements. A user born
+in 1984 should understand that they should pick the translation of
+"1980s".
+
+Any years that are *not* old enough to create an account must be
+appear as individual numbers, so that they can be converted to a year
+and compared to the current year.  Year ranges (any entry that cannot
+be converted to a number) are *always* considered old enough to create
+an account.  -->
+
+<!ENTITY fxaccount.create.account.age.1960s '1960s or earlier'>
+<!ENTITY fxaccount.create.account.age.1970s '1970s'>
+<!ENTITY fxaccount.create.account.age.1980s '1980s'>
+<!ENTITY fxaccount.create.account.age.1990s '1990s'>
+<!ENTITY fxaccount.create.account.age.2000 '2000'>
+<!ENTITY fxaccount.create.account.age.2001 '2001'>
+<!ENTITY fxaccount.create.account.age.2002 '2002'>
+<!ENTITY fxaccount.create.account.age.2003 '2003'>
+<!ENTITY fxaccount.create.account.age.2004 '2004'>
+<!ENTITY fxaccount.create.account.age.2005 '2005'>
+<!ENTITY fxaccount.create.account.age.2006 '2006'>
+<!ENTITY fxaccount.create.account.age.2007 '2007'>
--- a/mobile/android/base/resources/drawable/fxaccount_linkitem_textcolor.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_linkitem_textcolor.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:state_enabled="false" android:color="@color/fxaccount_link_textColor_inactive" />
   <item android:state_pressed="true" android:color="@color/fxaccount_link_textColor_pressed" />
   <item android:color="@color/fxaccount_link_textColor" />
 </selector>
--- a/mobile/android/base/resources/drawable/fxaccount_password_active.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_active.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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
-      android:color="@android:color/transparent" />
+      android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderActive" />
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="@dimen/fxaccount_corner_radius"
       android:topRightRadius="0dp"
       android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
--- a/mobile/android/base/resources/drawable/fxaccount_password_button_active.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_button_active.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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
-      android:color="@android:color/transparent" />
+      android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderActive" />
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="0dp"
       android:topRightRadius="@dimen/fxaccount_corner_radius"
       android:bottomLeftRadius="0dp"
--- a/mobile/android/base/resources/drawable/fxaccount_password_button_inactive.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_button_inactive.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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
-      android:color="@android:color/transparent" />
+      android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderInactive" />
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="0dp"
       android:topRightRadius="@dimen/fxaccount_corner_radius"
       android:bottomLeftRadius="0dp"
--- a/mobile/android/base/resources/drawable/fxaccount_password_inactive.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_password_inactive.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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
-      android:color="@android:color/transparent" />
+      android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderInactive" />
   <corners
       android:radius="@dimen/fxaccount_corner_radius"
       android:topLeftRadius="@dimen/fxaccount_corner_radius"
       android:topRightRadius="0dp"
       android:bottomLeftRadius="@dimen/fxaccount_corner_radius"
--- a/mobile/android/base/resources/drawable/fxaccount_textfield_active.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_textfield_active.xml
@@ -1,15 +1,15 @@
 <?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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
   <solid
-      android:color="@android:color/transparent" />
+      android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderActive" />
   <corners
       android:radius="@dimen/fxaccount_corner_radius" />
 </shape>
--- a/mobile/android/base/resources/drawable/fxaccount_textfield_inactive.xml
+++ b/mobile/android/base/resources/drawable/fxaccount_textfield_inactive.xml
@@ -1,13 +1,15 @@
 <?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/. -->
 
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
+  <solid
+      android:color="@android:color/white" />
   <stroke
       android:width="@dimen/fxaccount_stroke_width"
       android:color="@color/fxaccount_input_borderInactive" />
   <corners
       android:radius="@dimen/fxaccount_corner_radius" />
 </shape>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/fxaccount_account_verified.xml
@@ -0,0 +1,55 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent"
+    android:fillViewport="true" >
+
+    <LinearLayout style="@style/FxAccountMiddle" >
+
+        <TextView
+            style="@style/FxAccountHeaderItem"
+            android:text="@string/firefox_accounts" >
+        </TextView>
+
+        <TextView
+            style="@style/FxAccountSubHeaderItem"
+            android:text="@string/fxaccount_account_verified" >
+        </TextView>
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginBottom="45dp"
+            android:contentDescription="@string/fxaccount_checkbox_contentDescription"
+            android:src="@drawable/fxaccount_checkbox" >
+        </ImageView>
+
+        <TextView
+            style="@style/FxAccountTextItem"
+            android:layout_marginBottom="15dp"
+            android:text="@string/fxaccount_account_verified_description"
+            android:textSize="18sp" >
+        </TextView>
+
+        <TextView
+            android:id="@+id/email"
+            style="@style/FxAccountTextItem"
+            android:layout_marginBottom="45dp"
+            android:textSize="20sp"
+            android:textStyle="bold" >
+        </TextView>
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <ImageView
+            style="@style/FxAccountIcon"
+            android:contentDescription="@string/fxaccount_icon_contentDescription" />
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/fxaccount_confirm_account.xml
@@ -0,0 +1,64 @@
+<?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="fill_parent"
+    android:layout_height="fill_parent"
+    android:fillViewport="true" >
+
+    <LinearLayout style="@style/FxAccountMiddle" >
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <TextView
+            style="@style/FxAccountHeaderItem"
+            android:text="@string/firefox_accounts" >
+        </TextView>
+
+        <TextView
+            style="@style/FxAccountSubHeaderItem"
+            android:text="@string/fxaccount_confirm_your_account" >
+        </TextView>
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginBottom="45dp"
+            android:background="@android:color/transparent"
+            android:contentDescription="@string/fxaccount_mail_contentDescription"
+            android:src="@drawable/fxaccount_mail" >
+
+        </ImageView>
+
+        <TextView
+            style="@style/FxAccountTextItem"
+            android:layout_marginBottom="15dp"
+            android:text="@string/fxaccount_verification_link_awaits_at"
+            android:textSize="18sp" >
+        </TextView>
+
+        <TextView
+            android:id="@+id/email"
+            style="@style/FxAccountTextItem"
+            android:layout_marginBottom="45dp"
+            android:textSize="20sp"
+            android:textStyle="bold" >
+        </TextView>
+
+        <TextView
+            android:id="@+id/resend_confirmation_email_link"
+            style="@style/FxAccountLinkItem"
+            android:text="@string/fxaccount_verification_link_not_showing_up_offer_resend" />
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <ImageView
+            style="@style/FxAccountIcon"
+            android:contentDescription="@string/fxaccount_icon_contentDescription" />
+    </LinearLayout>
+
+</ScrollView>
--- a/mobile/android/base/resources/layout/fxaccount_create_account.xml
+++ b/mobile/android/base/resources/layout/fxaccount_create_account.xml
@@ -9,16 +9,18 @@
     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/firefox_accounts" />
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_create_account" />
 
@@ -71,9 +73,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_icon_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
\ No newline at end of file
+</ScrollView>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/fxaccount_create_account_not_allowed.xml
@@ -0,0 +1,47 @@
+<?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="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" />
+
+        <TextView
+            style="@style/FxAccountHeaderItem"
+            android:text="@string/firefox_accounts" >
+        </TextView>
+
+        <TextView
+            style="@style/FxAccountSubHeaderItem"
+            android:text="@string/fxaccount_account_create_not_allowed" >
+        </TextView>
+
+        <TextView
+            style="@style/FxAccountTextItem"
+            android:layout_marginBottom="45dp"
+            android:layout_marginTop="45dp"
+            android:text="@string/fxaccount_account_create_not_allowed_description" >
+        </TextView>
+
+        <TextView
+            android:id="@+id/learn_more_link"
+            style="@style/FxAccountLinkifiedItem"
+            android:text="@string/fxaccount_account_create_not_allowed_learn_more" />
+
+        <LinearLayout style="@style/FxAccountSpacer" />
+
+        <ImageView
+            style="@style/FxAccountIcon"
+            android:contentDescription="@string/fxaccount_icon_contentDescription" />
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
--- a/mobile/android/base/resources/layout/fxaccount_create_success.xml
+++ b/mobile/android/base/resources/layout/fxaccount_create_success.xml
@@ -8,16 +8,18 @@
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     android:fillViewport="true" >
 
     <LinearLayout
         android:id="@+id/create_success_view"
         style="@style/FxAccountMiddle" >
 
+        <LinearLayout style="@style/FxAccountSpacer" />
+
         <TextView
             style="@style/FxAccountHeaderItem"
             android:text="@string/firefox_accounts" >
         </TextView>
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_confirmation_email_sent" >
--- a/mobile/android/base/resources/layout/fxaccount_get_started.xml
+++ b/mobile/android/base/resources/layout/fxaccount_get_started.xml
@@ -8,16 +8,18 @@
     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" />
+
         <TextView
             style="@style/FxAccountHeaderItem"
             android:text="@string/firefox_accounts" >
         </TextView>
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_get_started" >
--- a/mobile/android/base/resources/layout/fxaccount_sign_in.xml
+++ b/mobile/android/base/resources/layout/fxaccount_sign_in.xml
@@ -9,16 +9,18 @@
     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/firefox_accounts" />
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_sign_in" />
 
@@ -57,9 +59,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_icon_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
\ No newline at end of file
+</ScrollView>
--- a/mobile/android/base/resources/layout/fxaccount_status.xml
+++ b/mobile/android/base/resources/layout/fxaccount_status.xml
@@ -151,9 +151,9 @@
             <Button
                 style="@style/FxAccountButton"
                 android:layout_marginBottom="10dp"
                 android:onClick="onClickForgetPassword"
                 android:text="Forget password" />
         </LinearLayout>
     </LinearLayout>
 
-</ScrollView>
\ No newline at end of file
+</ScrollView>
--- a/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
+++ b/mobile/android/base/resources/layout/fxaccount_update_credentials.xml
@@ -9,16 +9,18 @@
     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/firefox_accounts" />
 
         <TextView
             style="@style/FxAccountSubHeaderItem"
             android:text="@string/fxaccount_update_credentials" />
 
@@ -38,9 +40,9 @@
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
             android:contentDescription="@string/fxaccount_icon_contentDescription" />
     </LinearLayout>
 
-</ScrollView>
\ No newline at end of file
+</ScrollView>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/values-large-v11/fxaccount_styles.xml
@@ -0,0 +1,23 @@
+<?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 xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="FxAccountMiddle">
+        <item name="android:orientation">vertical</item>
+        <item name="android:layout_width">500dp</item>
+        <item name="android:minWidth">500dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:layout_weight">1</item>
+        <item name="android:layout_gravity">center</item>
+        <item name="android:paddingTop">25dp</item>
+        <item name="android:paddingLeft">12dp</item>
+        <item name="android:paddingRight">12dp</item>
+        <item name="android:paddingBottom">15dp</item>
+    </style>
+
+</resources>
\ No newline at end of file
--- a/mobile/android/base/resources/values/fxaccount_colors.xml
+++ b/mobile/android/base/resources/values/fxaccount_colors.xml
@@ -17,11 +17,12 @@
   <color name="fxaccount_button_background_active">#e66000</color>
   <color name="fxaccount_button_background_hit">#fd9500</color>
   <color name="fxaccount_button_background_loading">#424f59</color>
   <color name="fxaccount_button_background_inactive">#c0c9d0</color>
   <color name="fxaccount_input_textColor">#424f59</color>
   <color name="fxaccount_input_textColorHint">#c0c9d0</color>
   <color name="fxaccount_link_textColor">#0096dd</color>
   <color name="fxaccount_link_textColor_pressed">#00767d</color>
+  <color name="fxaccount_link_textColor_inactive">#c0c9d0</color>
   <color name="fxaccount_input_borderActive">#6a7b86</color>
   <color name="fxaccount_input_borderInactive">#c0c9d0</color>
 </resources>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/values/fxaccount_strings.xml
@@ -0,0 +1,21 @@
+<?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>
+    <string-array name="fxaccount_create_account_ages_array">
+        <item>@string/fxaccount_create_account_age_1960s</item>
+        <item>@string/fxaccount_create_account_age_1970s</item>
+        <item>@string/fxaccount_create_account_age_1980s</item>
+        <item>@string/fxaccount_create_account_age_1990s</item>
+        <item>@string/fxaccount_create_account_age_2000</item>
+        <item>@string/fxaccount_create_account_age_2001</item>
+        <item>@string/fxaccount_create_account_age_2002</item>
+        <item>@string/fxaccount_create_account_age_2003</item>
+        <item>@string/fxaccount_create_account_age_2004</item>
+        <item>@string/fxaccount_create_account_age_2005</item>
+        <item>@string/fxaccount_create_account_age_2006</item>
+        <item>@string/fxaccount_create_account_age_2007</item>
+    </string-array>
+</resources>
--- a/mobile/android/base/resources/values/fxaccount_styles.xml
+++ b/mobile/android/base/resources/values/fxaccount_styles.xml
@@ -5,16 +5,17 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/.
 -->
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="FxAccountTheme" parent="@style/Gecko" />
 
     <style name="FxAccountMiddle">
+        <item name="android:background">@android:color/white</item>
         <item name="android:orientation">vertical</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">25dp</item>
         <item name="android:paddingLeft">12dp</item>
         <item name="android:paddingRight">12dp</item>
         <item name="android:paddingBottom">15dp</item>
--- a/mobile/android/base/sync/net/BaseResource.java
+++ b/mobile/android/base/sync/net/BaseResource.java
@@ -64,17 +64,17 @@ public class BaseResource implements Res
   private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
 
   private boolean retryOnFailedRequest = true;
 
   public static boolean rewriteLocalhost = true;
 
   private static final String LOG_TAG = "BaseResource";
 
-  protected URI uri;
+  protected final URI uri;
   protected BasicHttpContext context;
   protected DefaultHttpClient client;
   public    ResourceDelegate delegate;
   protected HttpRequestBase request;
   public String charset = "utf-8";
 
   protected static WeakReference<HttpResponseObserver> httpResponseObserver = null;
 
@@ -96,16 +96,17 @@ public class BaseResource implements Res
     }
     if (rewrite && "localhost".equals(uri.getHost())) {
       // Rewrite localhost URIs to refer to the special Android emulator loopback passthrough interface.
       Logger.debug(LOG_TAG, "Rewriting " + uri + " to point to " + ANDROID_LOOPBACK_IP + ".");
       try {
         this.uri = new URI(uri.getScheme(), uri.getUserInfo(), ANDROID_LOOPBACK_IP, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment());
       } catch (URISyntaxException e) {
         Logger.error(LOG_TAG, "Got error rewriting URI for Android emulator.", e);
+        throw new IllegalArgumentException("Invalid URI", e);
       }
     } else {
       this.uri = uri;
     }
   }
 
   public static synchronized HttpResponseObserver getHttpResponseObserver() {
     if (httpResponseObserver == null) {
@@ -116,20 +117,31 @@ public class BaseResource implements Res
 
   public static synchronized void setHttpResponseObserver(HttpResponseObserver newHttpResponseObserver) {
     if (httpResponseObserver != null) {
       httpResponseObserver.clear();
     }
     httpResponseObserver = new WeakReference<HttpResponseObserver>(newHttpResponseObserver);
   }
 
+  @Override
   public URI getURI() {
     return this.uri;
   }
 
+  @Override
+  public String getURIString() {
+    return this.uri.toString();
+  }
+
+  @Override
+  public String getHostname() {
+    return this.getURI().getHost();
+  }
+
   /**
    * This shuts up HttpClient, which will otherwise debug log about there
    * being no auth cache in the context.
    */
   private static void addAuthCacheToContext(HttpUriRequest request, HttpContext context) {
     AuthCache authCache = new BasicAuthCache();                // Not thread safe.
     context.setAttribute(ClientContext.AUTH_CACHE, authCache);
   }
--- a/mobile/android/base/sync/net/BrowserIDAuthHeaderProvider.java
+++ b/mobile/android/base/sync/net/BrowserIDAuthHeaderProvider.java
@@ -7,17 +7,17 @@ package org.mozilla.gecko.sync.net;
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 import ch.boye.httpclientandroidlib.message.BasicHeader;
 import ch.boye.httpclientandroidlib.protocol.BasicHttpContext;
 
 /**
  * An <code>AuthHeaderProvider</code> that returns an Authorization header for
- * Browser-ID assertions in the format expected by a Mozilla Services Token
+ * BrowserID assertions in the format expected by a Mozilla Services Token
  * Server.
  * <p>
  * See <a href="http://docs.services.mozilla.com/token/apis.html">http://docs.services.mozilla.com/token/apis.html</a>.
  */
 public class BrowserIDAuthHeaderProvider implements AuthHeaderProvider {
   protected final String assertion;
 
   public BrowserIDAuthHeaderProvider(String assertion) {
--- a/mobile/android/base/sync/net/HawkAuthHeaderProvider.java
+++ b/mobile/android/base/sync/net/HawkAuthHeaderProvider.java
@@ -43,47 +43,74 @@ public class HawkAuthHeaderProvider impl
   public static final int HAWK_HEADER_VERSION = 1;
 
   protected static final int NONCE_LENGTH_IN_BYTES = 8;
   protected static final String HMAC_SHA256_ALGORITHM = "hmacSHA256";
 
   protected final String id;
   protected final byte[] key;
   protected final boolean includePayloadHash;
+  protected final long skewSeconds;
 
   /**
    * Create a Hawk Authorization header provider.
    * <p>
    * Hawk specifies no mechanism by which a client receives an
    * identifier-and-key pair from the server.
    *
    * @param id
    *          to name requests with.
    * @param key
    *          to sign request with.
    *
    * @param includePayloadHash
    *          true if message integrity hash should be included in signed
    *          request header. See <a href="https://github.com/hueniverse/hawk#payload-validation">https://github.com/hueniverse/hawk#payload-validation</a>.
+   *
+   * @param skewSeconds
+   *          a number of seconds by which to skew the current time when
+   *          computing a header.
    */
-  public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash) {
+  public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash, long skewSeconds) {
     if (id == null) {
       throw new IllegalArgumentException("id must not be null");
     }
     if (key == null) {
       throw new IllegalArgumentException("key must not be null");
     }
     this.id = id;
     this.key = key;
     this.includePayloadHash = includePayloadHash;
+    this.skewSeconds = skewSeconds;
+  }
+
+  public HawkAuthHeaderProvider(String id, byte[] key, boolean includePayloadHash) {
+    this(id, key, includePayloadHash, 0L);
+  }
+
+
+  /**
+   * @return the current time in milliseconds.
+   */
+  @SuppressWarnings("static-method")
+  protected long now() {
+    return System.currentTimeMillis();
+  }
+
+  /**
+   * @return the current time in seconds, adjusted for skew. This should
+   *         approximate the server's timestamp.
+   */
+  protected long getTimestampSeconds() {
+    return (now() / 1000) + skewSeconds;
   }
 
   @Override
   public Header getAuthHeader(HttpRequestBase request, BasicHttpContext context, DefaultHttpClient client) throws GeneralSecurityException {
-    long timestamp = System.currentTimeMillis() / 1000;
+    long timestamp = getTimestampSeconds();
     String nonce = Base64.encodeBase64String(Utils.generateRandomBytes(NONCE_LENGTH_IN_BYTES));
     String extra = "";
 
     try {
       return getAuthHeader(request, context, client, timestamp, nonce, extra, this.includePayloadHash);
     } catch (Exception e) {
       // We lie a little and make every exception a GeneralSecurityException.
       throw new GeneralSecurityException(e);
--- a/mobile/android/base/sync/net/Resource.java
+++ b/mobile/android/base/sync/net/Resource.java
@@ -1,14 +1,19 @@
 /* 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.sync.net;
 
+import java.net.URI;
+
 import ch.boye.httpclientandroidlib.HttpEntity;
 
 public interface Resource {
+  public abstract URI getURI();
+  public abstract String getURIString();
+  public abstract String getHostname();
   public abstract void get();
   public abstract void delete();
   public abstract void post(HttpEntity body);
   public abstract void put(HttpEntity body);
 }
--- a/mobile/android/base/sync/net/SyncStorageRequest.java
+++ b/mobile/android/base/sync/net/SyncStorageRequest.java
@@ -73,16 +73,31 @@ public class SyncStorageRequest implemen
    * @param uri
    */
   public SyncStorageRequest(URI uri) {
     this.resource = new BaseResource(uri);
     this.resourceDelegate = this.makeResourceDelegate(this);
     this.resource.delegate = this.resourceDelegate;
   }
 
+  @Override
+  public URI getURI() {
+    return this.resource.getURI();
+  }
+
+  @Override
+  public String getURIString() {
+    return this.resource.getURIString();
+  }
+
+  @Override
+  public String getHostname() {
+    return this.resource.getHostname();
+  }
+
   /**
    * A ResourceDelegate that mediates between Resource-level notifications and the SyncStorageRequest.
    */
   public class SyncStorageResourceDelegate extends BaseResourceDelegate {
     private static final String LOG_TAG = "SSResourceDelegate";
     protected SyncStorageRequest request;
 
     SyncStorageResourceDelegate(SyncStorageRequest request) {
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -104,16 +104,21 @@ public class BrowserToolbar extends Geck
     public interface OnStartEditingListener {
         public void onStartEditing();
     }
 
     public interface OnStopEditingListener {
         public void onStopEditing();
     }
 
+    private enum UIMode {
+        EDIT,
+        DISPLAY
+    }
+
     enum ForwardButtonAnimation {
         SHOW,
         HIDE
     }
 
     private ToolbarDisplayLayout mUrlDisplayLayout;
     private ToolbarEditLayout mUrlEditLayout;
     private View mUrlBarEntry;
@@ -136,17 +141,17 @@ public class BrowserToolbar extends Geck
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
     private OnStartEditingListener mStartEditingListener;
     private OnStopEditingListener mStopEditingListener;
 
     final private BrowserApp mActivity;
     private boolean mHasSoftMenuButton;
 
-    private boolean mIsEditing;
+    private UIMode mUIMode;
     private boolean mAnimatingEntry;
 
     private int mUrlBarViewOffset;
     private int mDefaultForwardMargin;
 
     private static final Interpolator sButtonsInterpolator = new AccelerateInterpolator();
 
     private static final int FORWARD_ANIMATION_DURATION = 450;
@@ -215,17 +220,17 @@ public class BrowserToolbar extends Geck
             mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
             mFocusOrder.addAll(Arrays.asList(mActionItemBar, mMenu));
         } else {
             mFocusOrder.add(this);
             mFocusOrder.addAll(mUrlDisplayLayout.getFocusOrder());
             mFocusOrder.addAll(Arrays.asList(mTabs, mMenu));
         }
 
-        setIsEditing(false);
+        setUIMode(UIMode.DISPLAY);
     }
 
     @Override
     public void onAttachedToWindow() {
         super.onAttachedToWindow();
 
         setOnClickListener(new Button.OnClickListener() {
             @Override
@@ -360,16 +365,21 @@ public class BrowserToolbar extends Geck
         }
     }
 
     public void refresh() {
         mUrlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onBackPressed() {
+        if (isEditing()) {
+            stopEditing();
+            return true;
+        }
+
         return mUrlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onKey(int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return false;
         }
 
@@ -532,17 +542,17 @@ public class BrowserToolbar extends Geck
     }
 
     private void updateProgressVisibility() {
         final Tab selectedTab = Tabs.getInstance().getSelectedTab();
         updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
     }
 
     private void updateProgressVisibility(Tab selectedTab, int progress) {
-        if (!mIsEditing && selectedTab.getState() == Tab.STATE_LOADING) {
+        if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
             mProgressBar.setProgress(progress);
             mProgressBar.setVisibility(View.VISIBLE);
         } else {
             mProgressBar.setVisibility(View.GONE);
         }
     }
 
     public boolean isVisible() {
@@ -563,21 +573,21 @@ public class BrowserToolbar extends Geck
         return getWidth() - mUrlBarEntry.getRight();
     }
 
     private int getUrlBarCurveTranslation() {
         return getWidth() - mTabs.getLeft();
     }
 
     private boolean canDoBack(Tab tab) {
-        return (tab.canDoBack() && !mIsEditing);
+        return (tab.canDoBack() && !isEditing());
     }
 
     private boolean canDoForward(Tab tab) {
-        return (tab.canDoForward() && !mIsEditing);
+        return (tab.canDoForward() && !isEditing());
     }
 
     private void addTab() {
         mActivity.addTab();
     }
 
     private void toggleTabs() {
         if (mActivity.areTabsShown()) {
@@ -843,17 +853,17 @@ public class BrowserToolbar extends Geck
      */
     private void updateChildrenForEditing() {
         // This is for the tablet UI only
         if (!HardwareUtils.isTablet()) {
             return;
         }
 
         // Disable toolbar elemens while in editing mode
-        final boolean enabled = !mIsEditing;
+        final boolean enabled = !isEditing();
 
         // This alpha value has to be in sync with the one used
         // in setButtonEnabled().
         final float alpha = (enabled ? 1.0f : 0.24f);
 
         mTabs.setEnabled(enabled);
         ViewHelper.setAlpha(mTabsCounter, alpha);
         mMenu.setEnabled(enabled);
@@ -869,44 +879,44 @@ public class BrowserToolbar extends Geck
         if (tab != null) {
             setButtonEnabled(mBack, canDoBack(tab));
             setButtonEnabled(mForward, canDoForward(tab));
 
             // Once the editing mode is finished, we have to ensure that the
             // forward button slides away if necessary. This is because we might
             // have only disabled it (without hiding it) when the toolbar entered
             // editing mode.
-            if (!mIsEditing) {
+            if (!isEditing()) {
                 animateForwardButton(canDoForward(tab) ?
                                      ForwardButtonAnimation.SHOW : ForwardButtonAnimation.HIDE);
             }
         }
     }
 
-    private void setIsEditing(boolean isEditing) {
-        mIsEditing = isEditing;
-        mUrlEditLayout.setEnabled(isEditing);
+    private void setUIMode(final UIMode uiMode) {
+        mUIMode = uiMode;
+        mUrlEditLayout.setEnabled(uiMode == UIMode.EDIT);
     }
 
     /**
      * Returns whether or not the URL bar is in editing mode (url bar is expanded, hiding the new
      * tab button). Note that selection state is independent of editing mode.
      */
     public boolean isEditing() {
-        return mIsEditing;
+        return (mUIMode == UIMode.EDIT);
     }
 
     public void startEditing(String url, PropertyAnimator animator) {
         if (isEditing()) {
             return;
         }
 
         mUrlEditLayout.setText(url != null ? url : "");
 
-        setIsEditing(true);
+        setUIMode(UIMode.EDIT);
         updateChildrenForEditing();
 
         updateProgressVisibility();
 
         if (mStartEditingListener != null) {
             mStartEditingListener.onStartEditing();
         }
 
@@ -1013,17 +1023,17 @@ public class BrowserToolbar extends Geck
         return url;
     }
 
     private String stopEditing() {
         final String url = mUrlEditLayout.getText();
         if (!isEditing()) {
             return url;
         }
-        setIsEditing(false);
+        setUIMode(UIMode.DISPLAY);
 
         updateChildrenForEditing();
 
         if (mStopEditingListener != null) {
             mStopEditingListener.onStopEditing();
         }
 
         updateProgressVisibility();
--- a/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
+++ b/mobile/android/services/manifests/FxAccountAndroidManifest_activities.xml.in
@@ -33,25 +33,39 @@
             android:icon="@drawable/fxaccount_icon"
             android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountActivity"
             android:windowSoftInputMode="adjustResize">
         </activity>
 
         <activity
             android:theme="@style/FxAccountTheme"
             android:icon="@drawable/fxaccount_icon"
-            android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateSuccessActivity"
+            android:name="org.mozilla.gecko.fxa.activities.FxAccountConfirmAccountActivity"
             android:windowSoftInputMode="adjustResize">
         </activity>
 
         <activity
             android:theme="@style/FxAccountTheme"
             android:icon="@drawable/fxaccount_icon"
             android:name="org.mozilla.gecko.fxa.activities.FxAccountSignInActivity"
             android:windowSoftInputMode="adjustResize">
         </activity>
 
         <activity
             android:theme="@style/FxAccountTheme"
             android:icon="@drawable/fxaccount_icon"
+            android:name="org.mozilla.gecko.fxa.activities.FxAccountVerifiedAccountActivity"
+            android:windowSoftInputMode="adjustResize">
+        </activity>
+
+        <activity
+            android:theme="@style/FxAccountTheme"
+            android:icon="@drawable/fxaccount_icon"
             android:name="org.mozilla.gecko.fxa.activities.FxAccountUpdateCredentialsActivity"
             android:windowSoftInputMode="adjustResize">
         </activity>
+
+        <activity
+            android:theme="@style/FxAccountTheme"
+            android:icon="@drawable/fxaccount_icon"
+            android:name="org.mozilla.gecko.fxa.activities.FxAccountCreateAccountNotAllowedActivity"
+            android:windowSoftInputMode="adjustResize">
+        </activity>
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -147,8 +147,37 @@
   <string name="fxaccount_status_syncing">&fxaccount.status.syncing;</string>
   <string name="fxaccount_status_sync">&fxaccount.status.sync;</string>
   <string name="fxaccount_status_bookmarks">&fxaccount.status.bookmarks;</string>
   <string name="fxaccount_status_history">&fxaccount.status.history;</string>
   <string name="fxaccount_status_passwords">&fxaccount.status.passwords;</string>
   <string name="fxaccount_status_tabs">&fxaccount.status.tabs;</string>
   <string name="fxaccount_update_credentials">&fxaccount.update.credentials;</string>
   <string name="fxaccount_update_credentials_button_label">&fxaccount.update.credentials.button.label;</string>
+  <string name="fxaccount_account_create_not_allowed">&fxaccount.account.create.not.allowed;</string>
+  <string name="fxaccount_account_create_not_allowed_description">&fxaccount.account.create.not.allowed.description;</string>
+  <string name="fxaccount_create_account_age_1960s">&fxaccount.create.account.age.1960s;</string>
+  <string name="fxaccount_create_account_age_1970s">&fxaccount.create.account.age.1970s;</string>
+  <string name="fxaccount_create_account_age_1980s">&fxaccount.create.account.age.1980s;</string>
+  <string name="fxaccount_create_account_age_1990s">&fxaccount.create.account.age.1990s;</string>
+  <string name="fxaccount_create_account_age_2000">&fxaccount.create.account.age.2000;</string>
+  <string name="fxaccount_create_account_age_2001">&fxaccount.create.account.age.2001;</string>
+  <string name="fxaccount_create_account_age_2002">&fxaccount.create.account.age.2002;</string>
+  <string name="fxaccount_create_account_age_2003">&fxaccount.create.account.age.2003;</string>
+  <string name="fxaccount_create_account_age_2004">&fxaccount.create.account.age.2004;</string>
+  <string name="fxaccount_create_account_age_2005">&fxaccount.create.account.age.2005;</string>
+  <string name="fxaccount_create_account_age_2006">&fxaccount.create.account.age.2006;</string>
+  <string name="fxaccount_create_account_age_2007">&fxaccount.create.account.age.2007;</string>
+  <!-- Temporary Firefox Accounts strings. -->
+  <!-- We haven't yet decided how to linkify, so to save translation
+       time we're holding this back. -->
+  <string name="fxaccount_account_create_not_allowed_learn_more">&lt;a href="http://www.ftc.gov/news-events/media-resources/protecting-consumer-privacy/kids-privacy-coppa"&gt;Learn more&lt;/a&gt;</string>
+  <string name="fxaccount_sign_in_success">Account verified</string>
+  <string name="fxaccount_sign_in_success_description">You are now ready to use Sync!</string>
+  <string name="fxaccount_checkbox_contentDescription">Firefox Accounts checkbox graphic</string>
+  <string name="fxaccount_account_verified">Account verified</string>
+  <string name="fxaccount_account_verified_description">You are now ready to use Sync!</string>
+  <string name="fxaccount_confirm_your_account">Confirm your account</string>
+  <string name="fxaccount_verification_link_awaits_at">A verification link awaits at:</string>
+  <string name="fxaccount_verification_link_not_showing_up_offer_resend">Email not showing up? Send again</string>
+  <!-- TODO: add email address to toast? -->
+  <string name="fxaccount_confirm_verification_link_sent">Sent fresh verification link</string>
+  <string name="fxaccount_confirm_verification_link_not_sent">Couldn\&apos;t send a fresh verification link</string>
--- a/toolkit/devtools/touch-events.js
+++ b/toolkit/devtools/touch-events.js
@@ -132,19 +132,23 @@ function TouchEventHandler (window) {
 
           window.removeEventListener('click', this, true, false);
 
           if (this.cancelClick)
             return;
 
           ignoreEvents = true;
           content.setTimeout(function dispatchMouseEvents(self) {
-            self.fireMouseEvent('mousedown', evt);
-            self.fireMouseEvent('mousemove', evt);
-            self.fireMouseEvent('mouseup', evt);
+            try {
+              self.fireMouseEvent('mousedown', evt);
+              self.fireMouseEvent('mousemove', evt);
+              self.fireMouseEvent('mouseup', evt);
+            } catch(e) {
+              Cu.reportError('Exception in touch event helper: ' + e);
+            }
             ignoreEvents = false;
          }, 0, this);
 
           return;
       }
 
       let target = eventTarget || this.target;
       if (target && type) {