Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 28 Mar 2015 11:56:12 -0700
changeset 266679 87e10bac6fd1332ac4a62eae3f9ef673a5dab7f0
parent 266678 99415fbccf8375d8f7b0e4f85a6b040f83adb969 (current diff)
parent 266629 b08b0a413b329ec04a9450221825496367b888fd (diff)
child 266680 73fbc0f8d061d433fd1795a19876b7039f84c3f2
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to m-i CLOSED TREE
dom/plugins/ipc/COMMessageFilter.cpp
dom/plugins/ipc/COMMessageFilter.h
dom/plugins/ipc/PluginModuleChild.cpp
mobile/android/base/reading/ReadingListConstants.java
toolkit/modules/moz.build
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <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="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
   <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="527d1c939ee57deb7192166e56e2a3fffa8cb087"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="50d1ca4ab8add54523b7bc692860d57e8ee4c0d1"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="fb3845864573857677f9b500040a8f011eaf5078"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="354496e8eddd28c743d8e02c02eeab02958367e6"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="b37c91354272b7413a0dc058b7445e677921d39e"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="a227c92e0170bcf2296a63386956946b0dd78ca7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="884626610186b6dbea52cec5194b1c4bcfe1cb98"/>
   <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="29f9b82faa1af9730f52e933dca848546cbea84c"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="93f9ba577f68d772093987c2f1c0a4ae293e1802"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="527d1c939ee57deb7192166e56e2a3fffa8cb087"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="ef937d1aca7c4cf89ecb5cc43ae8c21c2000a9db">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <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="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "0e7c8ade48129b3e03c5de8ae0452fd1f756535c", 
+        "git_revision": "67ad91f3f660b1f16b354ee4c5159ddc5a74d149", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "97a01eae94361363300254b54e53e2ac0f0b9d38", 
+    "revision": "fb33cb000bc06f3d8a8bc1e91cd31c3914936ea5", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52775e03a2d8532429dff579cb2cd56718e488c3">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0e7c8ade48129b3e03c5de8ae0452fd1f756535c"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="67ad91f3f660b1f16b354ee4c5159ddc5a74d149"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="2aa4a75c63cd6e93870a8bddbba45f863cbfd9a3"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ed2cf97a6c37a4bbd0bbbbffe06ec7136d8c79ff"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="f5de61a5d8fdaa2db3d4e17e0c4212ec4d54a365"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="47503b5ec80f00630548023efb5ea4f830e1527d"/>
   <!-- Stock Android things -->
   <project groups="pdk,linux" name="platform/prebuilts/clang/linux-x86/host/3.5" path="prebuilts/clang/linux-x86/host/3.5" revision="50d1ca4ab8add54523b7bc692860d57e8ee4c0d1"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" path="prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.8" revision="fb3845864573857677f9b500040a8f011eaf5078"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.8" revision="354496e8eddd28c743d8e02c02eeab02958367e6"/>
   <project groups="pdk,linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.8" revision="b37c91354272b7413a0dc058b7445e677921d39e"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.6" revision="a227c92e0170bcf2296a63386956946b0dd78ca7"/>
   <project groups="pdk,linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.11-4.8" revision="884626610186b6dbea52cec5194b1c4bcfe1cb98"/>
   <project groups="pdk,linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" path="prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.8" revision="29f9b82faa1af9730f52e933dca848546cbea84c"/>
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -2978,9 +2978,10 @@
                       <device>0x9806</device>
                       <device>0x9807</device>
                   </devices>
             <feature>DIRECT3D_9_LAYERS</feature>      <featureStatus>BLOCKED_DEVICE</featureStatus>    </gfxBlacklistEntry>
     <gfxBlacklistEntry  blockID="g511">      <os>WINNT 5.1</os>      <vendor>0x8086</vendor>            <feature>DIRECT3D_9_LAYERS, WEBGL_ANGLE</feature>      <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>      <driverVersion>6.14.10.5218</driverVersion>      <driverVersionComparator>LESS_THAN</driverVersionComparator>    </gfxBlacklistEntry>
     </gfxItems>
 
 
+
 </blocklist>
\ No newline at end of file
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1422,18 +1422,43 @@ pref("devtools.debugger.tracer", false);
 // The default Debugger UI settings
 pref("devtools.debugger.ui.panes-sources-width", 200);
 pref("devtools.debugger.ui.panes-instruments-width", 300);
 pref("devtools.debugger.ui.panes-visible-on-startup", false);
 pref("devtools.debugger.ui.variables-sorting-enabled", true);
 pref("devtools.debugger.ui.variables-only-enum-visible", false);
 pref("devtools.debugger.ui.variables-searchbox-visible", false);
 
-// Enable the Performance tools
-pref("devtools.performance.enabled", true);
+// Enable the Profiler
+pref("devtools.profiler.enabled", true);
+
+// Timeline panel settings
+#ifdef NIGHTLY_BUILD
+pref("devtools.timeline.enabled", true);
+#else
+pref("devtools.timeline.enabled", false);
+#endif
+
+// TODO remove `devtools.timeline.hiddenMarkers.` branches when performance
+// tool lands (bug 1075567)
+pref("devtools.timeline.hiddenMarkers", "[]");
+
+// Enable perftools via build command
+#ifdef MOZ_DEVTOOLS_PERFTOOLS
+  pref("devtools.performance_dev.enabled", true);
+#else
+  pref("devtools.performance_dev.enabled", false);
+#endif
+
+// The default Profiler UI settings
+// TODO remove `devtools.profiler.ui.` branches when performance
+// tool lands (bug 1075567)
+pref("devtools.profiler.ui.flatten-tree-recursion", true);
+pref("devtools.profiler.ui.show-platform-data", false);
+pref("devtools.profiler.ui.show-idle-blocks", true);
 
 // The default Performance UI settings
 pref("devtools.performance.memory.sample-probability", "0.05");
 pref("devtools.performance.memory.max-log-length", 2147483647); // Math.pow(2,31) - 1
 pref("devtools.performance.timeline.hidden-markers", "[]");
 pref("devtools.performance.ui.invert-call-tree", true);
 pref("devtools.performance.ui.invert-flame-graph", false);
 pref("devtools.performance.ui.flatten-tree-recursion", true);
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -174,27 +174,20 @@ const gXPInstallObserver = {
             this.acceptInstallation = null;
             break;
           case "shown":
             let addonList = document.getElementById("addon-install-confirmation-content");
             while (addonList.firstChild)
               addonList.firstChild.remove();
 
             for (let install of installInfo.installs) {
-              let container = document.createElement("hbox");
               let name = document.createElement("label");
-              let author = document.createElement("label");
               name.setAttribute("value", install.addon.name);
-              author.setAttribute("value", !install.addon.creator ? "" :
-                gNavigatorBundle.getFormattedString("addonConfirmInstall.author", [install.addon.creator]));
               name.setAttribute("class", "addon-install-confirmation-name");
-              author.setAttribute("class", "addon-install-confirmation-author");
-              container.appendChild(name);
-              container.appendChild(author);
-              addonList.appendChild(container);
+              addonList.appendChild(name);
             }
 
             this.acceptInstallation = () => {
               for (let install of installInfo.installs)
                 install.install();
               installInfo = null;
 
               Services.telemetry
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -154,17 +154,17 @@ input[type=button] {
 #newtab-grid[page-disabled] {
   pointer-events: none;
 }
 
 /* CELLS */
 .newtab-cell {
   display: -moz-box;
   height: 180px;
-  margin: 20px 10px 85px;
+  margin: 20px 10px 68px;
   width: 290px;
 }
 
 /* SITES */
 .newtab-site {
   position: relative;
   -moz-box-flex: 1;
   transition: 100ms ease-out;
@@ -221,17 +221,17 @@ input[type=button] {
   display: none;
   margin-left: auto;
   margin-right: auto;
   left: 0;
   top: 215px;
 }
 
 .newtab-suggested-bounds {
-  max-height: 51px; /* 51 / 17 = 3 lines maximum */
+  max-height: 34px; /* 34 / 17 = 2 lines maximum */
 }
 
 .newtab-title {
   left: 0;
   padding-top: 14px;
 }
 
 .newtab-sponsored {
--- a/browser/base/content/popup-notifications.inc
+++ b/browser/base/content/popup-notifications.inc
@@ -64,16 +64,20 @@
 
 #ifdef E10S_TESTING_ONLY
     <popupnotification id="enable-e10s-notification" hidden="true">
       <popupnotificationcontent orient="vertical"/>
     </popupnotification>
 #endif
 
     <popupnotification id="addon-progress-notification" hidden="true">
+      <popupnotificationcontent orient="vertical">
+        <progressmeter id="addon-progress-notification-progressmeter"/>
+        <label id="addon-progress-notification-progresstext" crop="end"/>
+      </popupnotificationcontent>
       <button id="addon-progress-cancel"
               oncommand="this.parentNode.cancel();"/>
       <button id="addon-progress-accept" disabled="true"/>
     </popupnotification>
 
     <popupnotification id="addon-install-confirmation-notification" hidden="true">
       <popupnotificationcontent id="addon-install-confirmation-content" orient="vertical"/>
       <button id="addon-install-confirmation-cancel"
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -55,19 +55,19 @@ function runTests() {
   is(title, "title");
 
   is(getData(1), null, "history link pushed out by directory link");
 
   // Test with a pinned link
   setPinnedLinks("-1");
   yield addNewTabPageTab();
   ({type, enhanced, title} = getData(0));
-  is(type, "enhanced", "pinned history link is enhanced");
-  isnot(enhanced, "", "pinned history link has enhanced image");
-  is(title, "title");
+  is(type, "history", "pinned history link is not enhanced");
+  is(enhanced, "", "pinned history link doesn't have enhanced image");
+  is(title, "site#-1");
 
   is(getData(1), null, "directory link pushed out by pinned history link");
 
   // Test pinned link with enhanced = false
   yield addNewTabPageTab();
   yield customizeNewTabPage("classic");
   ({type, enhanced, title} = getData(0));
   isnot(type, "enhanced", "history link is not enhanced");
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -291,17 +291,18 @@ function fillHistory(aLinks, aCallback =
  */
 function setPinnedLinks(aLinks) {
   let links = aLinks;
 
   if (typeof links == "string") {
     links = aLinks.split(/\s*,\s*/).map(function (id) {
       if (id)
         return {url: "http://example" + (id != "-1" ? id : "") + ".com/",
-                title: "site#" + id};
+                title: "site#" + id,
+                type: "history"};
     });
   }
 
   let string = Cc["@mozilla.org/supports-string;1"]
                  .createInstance(Ci.nsISupportsString);
   string.data = JSON.stringify(links);
   Services.prefs.setComplexValue("browser.newtabpage.pinned",
                                  Ci.nsISupportsString, string);
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1556,52 +1556,16 @@
           ]]>
         </body>
       </method>
 
     </implementation>
   </binding>
 
   <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
-    <content align="start">
-      <xul:image class="popup-notification-icon"
-                 xbl:inherits="popupid,src=icon"/>
-      <xul:vbox flex="1">
-        <xul:label class="popup-notification-originHost header"
-                   xbl:inherits="value=originhost"
-                   crop="end"/>
-        <xul:description class="popup-notification-description"
-                         xbl:inherits="xbl:text=label,popupid"/>
-        <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
-        <xul:label anonid="progresstext" class="popup-progress-label" flex="1" crop="end"/>
-        <xul:spacer flex="1"/>
-        <xul:hbox class="popup-notification-button-container"
-                  pack="end" align="center">
-          <children includes="button"/>
-          <xul:button anonid="button"
-                      class="popup-notification-menubutton"
-                      type="menu-button"
-                      xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
-            <xul:menupopup anonid="menupopup"
-                           xbl:inherits="oncommand=menucommand">
-              <children/>
-              <xul:menuitem class="menuitem-iconic popup-notification-closeitem close-icon"
-                            label="&closeNotificationItem.label;"
-                            xbl:inherits="oncommand=closeitemcommand"/>
-            </xul:menupopup>
-          </xul:button>
-        </xul:hbox>
-      </xul:vbox>
-      <xul:vbox pack="start">
-        <xul:toolbarbutton anonid="closebutton"
-                           class="messageCloseButton close-icon popup-notification-closebutton tabbable"
-                           xbl:inherits="oncommand=closebuttoncommand"
-                           tooltiptext="&closeNotification.tooltip;"/>
-      </xul:vbox>
-    </content>
     <implementation>
       <constructor><![CDATA[
         if (!this.notification)
           return;
 
         this.notification.options.installs.forEach(function(aInstall) {
           aInstall.addListener(this);
         }, this);
@@ -1615,20 +1579,20 @@
         this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
       ]]></constructor>
 
       <destructor><![CDATA[
         this.destroy();
       ]]></destructor>
 
       <field name="progressmeter" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "progressmeter");
+        document.getElementById("addon-progress-notification-progressmeter"); 
       </field>
       <field name="progresstext" readonly="true">
-        document.getAnonymousElementByAttribute(this, "anonid", "progresstext");
+        document.getElementById("addon-progress-notification-progresstext");
       </field>
       <field name="DownloadUtils" readonly="true">
         let utils = {};
         Components.utils.import("resource://gre/modules/DownloadUtils.jsm", utils);
         utils.DownloadUtils;
       </field>
 
       <method name="destroy">
@@ -1643,21 +1607,21 @@
         ]]></body>
       </method>
 
       <method name="setProgress">
         <parameter name="aProgress"/>
         <parameter name="aMaxProgress"/>
         <body><![CDATA[
           if (aMaxProgress == -1) {
-            this.progressmeter.mode = "undetermined";
+            this.progressmeter.setAttribute("mode", "undetermined");
           }
           else {
-            this.progressmeter.mode = "determined";
-            this.progressmeter.value = (aProgress * 100) / aMaxProgress;
+            this.progressmeter.setAttribute("mode", "determined");
+            this.progressmeter.setAttribute("value", (aProgress * 100) / aMaxProgress);
           }
 
           let now = Date.now();
 
           if (!this.notification.lastUpdate) {
             this.notification.lastUpdate = now;
             this.notification.lastProgress = aProgress;
             return;
@@ -1675,17 +1639,18 @@
             speed = speed * 0.9 + this.notification.speed * 0.1;
 
           this.notification.lastUpdate = now;
           this.notification.lastProgress = aProgress;
           this.notification.speed = speed;
 
           let status = null;
           [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
-          this.progresstext.value = this.progresstext.tooltipText = status;
+          this.progresstext.setAttribute("value", status);
+          this.progresstext.setAttribute("tooltiptext", status);
         ]]></body>
       </method>
 
       <method name="cancel">
         <body><![CDATA[
           let installs = this.notification.options.installs;
           installs.forEach(function(aInstall) {
             try {
@@ -1717,19 +1682,20 @@
               maxProgress += aInstall.maxProgress;
             if (aInstall.state < AddonManager.STATE_DOWNLOADED)
               downloadingCount++;
           });
 
           if (downloadingCount == 0) {
             this.destroy();
             if (Preferences.get("xpinstall.customConfirmationUI", false)) {
-              this.progressmeter.mode = "undetermined";
-              this.progresstext.value = this.progresstext.tooltipText =
-                gNavigatorBundle.getString("addonDownloadVerifying");
+              this.progressmeter.setAttribute("mode", "undetermined");
+              let status = gNavigatorBundle.getString("addonDownloadVerifying");
+              this.progresstext.setAttribute("value", status);
+              this.progresstext.setAttribute("tooltiptext", status);
             } else {
               PopupNotifications.remove(this.notification);
             }
           }
           else {
             this.setProgress(progress, maxProgress);
           }
         ]]></body>
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -1339,18 +1339,18 @@ PlacesController.prototype = {
             if (insertionIndex != PlacesUtils.bookmarks.DEFAULT_INDEX)
               insertionIndex++;
           }
         });
       }
     }
     else {
       let transactions = [];
+      let insertionIndex = ip.index;
       for (let i = 0; i < items.length; ++i) {
-        let insertionIndex = ip.index + i;
         if (ip.isTag) {
           // Pasting into a tag container means tagging the item, regardless of
           // the requested action.
           let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
                                                    [ip.itemId]);
           transactions.push(tagTxn);
           continue;
         }
--- a/browser/components/places/tests/browser/browser_416459_cut.js
+++ b/browser/components/places/tests/browser/browser_416459_cut.js
@@ -1,59 +1,83 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const TEST_URL = "http://example.com/";
 
 add_task(function* () {
+  yield PlacesUtils.bookmarks.eraseEverything();
   let organizer = yield promiseLibrary();
+
+  registerCleanupFunction(function* () {
+    yield promiseLibraryClosed(organizer);
+    yield PlacesUtils.bookmarks.eraseEverything();
+  });
+
   let PlacesOrganizer = organizer.PlacesOrganizer;
   let ContentTree = organizer.ContentTree;
 
   // Sanity checks.
   ok(PlacesUtils, "PlacesUtils in scope");
   ok(PlacesUIUtils, "PlacesUIUtils in scope");
   ok(PlacesOrganizer, "PlacesOrganizer in scope");
   ok(ContentTree, "ContentTree is in scope");
 
-  let bm = yield PlacesUtils.bookmarks.insert({
+  // Test with multiple entries to ensure they retain their order.
+  let bookmarks = [];
+  bookmarks.push(yield PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+    url: TEST_URL,
+    title: "0"
+  }));
+  bookmarks.push(yield PlacesUtils.bookmarks.insert({
     parentGuid: PlacesUtils.bookmarks.toolbarGuid,
     type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
-    url: TEST_URL
-  });
+    url: TEST_URL,
+    title: "1"
+  }));
+  bookmarks.push(yield PlacesUtils.bookmarks.insert({
+    parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+    type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
+    url: TEST_URL,
+    title: "2"
+  }));
 
-  yield selectBookmarkIn(organizer, bm, "BookmarksToolbar");
+  yield selectBookmarksIn(organizer, bookmarks, "BookmarksToolbar");
 
   yield promiseClipboard(() => {
     info("Cutting selection");
     ContentTree.view.controller.cut();
   }, PlacesUtils.TYPE_X_MOZ_PLACE);
 
   info("Selecting UnfiledBookmarks in the left pane");
   PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
   info("Pasting clipboard");
   ContentTree.view.controller.paste();
 
-  yield selectBookmarkIn(organizer, bm, "UnfiledBookmarks");
-
-  yield promiseLibraryClosed(organizer);
-  yield PlacesUtils.bookmarks.eraseEverything();
+  yield selectBookmarksIn(organizer, bookmarks, "UnfiledBookmarks");
 });
 
-let selectBookmarkIn = Task.async(function* (organizer, bm, aLeftPaneQuery) {
+let selectBookmarksIn = Task.async(function* (organizer, bookmarks, aLeftPaneQuery) {
   let PlacesOrganizer = organizer.PlacesOrganizer;
   let ContentTree = organizer.ContentTree;
-
   info("Selecting " + aLeftPaneQuery + " in the left pane");
   PlacesOrganizer.selectLeftPaneQuery(aLeftPaneQuery);
-  let rootId = PlacesUtils.getConcreteItemId(PlacesOrganizer._places.selectedNode);
 
-  bm = yield PlacesUtils.bookmarks.fetch(bm.guid);
-  is((yield PlacesUtils.promiseItemId(bm.parentGuid)), rootId,
-     "Bookmark has the right parent");
+  let ids = [];
+  for (let {guid} of bookmarks) {
+    let bookmark = yield PlacesUtils.bookmarks.fetch(guid);
+    is (bookmark.parentGuid, PlacesOrganizer._places.selectedNode.targetFolderGuid,
+        "Bookmark has the right parent");
+    ids.push(yield PlacesUtils.promiseItemId(bookmark.guid));
+  }
 
-  info("Selecting the bookmark in the right pane");
-  ContentTree.view.selectItems([yield PlacesUtils.promiseItemId(bm.guid)]);
-  let bookmarkNode = ContentTree.view.selectedNode;
-  is(bookmarkNode.uri, TEST_URL, "Found the expected bookmark");
+  info("Selecting the bookmarks in the right pane");
+  ContentTree.view.selectItems(ids);
+
+  for (let node of ContentTree.view.selectedNodes) {
+    is(node.bookmarkIndex, node.title,
+       "Found the expected bookmark in the expected position");
+  }
 });
--- a/browser/components/readinglist/Sync.jsm
+++ b/browser/components/readinglist/Sync.jsm
@@ -183,47 +183,45 @@ SyncImpl.prototype = {
       method: "POST",
       path: "/batch",
       body: {
         defaults: {
           method: "PATCH",
         },
         requests: requests,
       },
-      headers: {},
     };
-    if (this._serverLastModifiedHeader) {
-      request.headers["If-Unmodified-Since"] = this._serverLastModifiedHeader;
-    }
-
     let batchResponse = yield this._sendRequest(request);
     if (batchResponse.status != 200) {
       this._handleUnexpectedResponse("uploading changes", batchResponse);
       return;
     }
 
     // Update local items based on the response.
     for (let response of batchResponse.body.responses) {
       if (response.status == 404) {
         // item deleted
         yield this._deleteItemForGUID(response.body.id);
         continue;
       }
-      if (response.status == 412 || response.status == 409) {
-        // 412 Precondition failed: The item was modified since the last sync.
-        // 409 Conflict: A change violated a uniqueness constraint.
-        // In either case, mark the item as having material changes, and
-        // reconcile and upload it in the material-changes phase.
+      if (response.status == 409) {
+        // "Conflict": A change violated a uniqueness constraint.  Mark the item
+        // as having material changes, and reconcile and upload it in the
+        // material-changes phase.
         // TODO
         continue;
       }
       if (response.status != 200) {
         this._handleUnexpectedResponse("uploading a change", response);
         continue;
       }
+      // Don't assume the local record and the server record aren't materially
+      // different.  Reconcile the differences.
+      // TODO
+
       let item = yield this._itemForGUID(response.body.id);
       yield this._updateItemWithServerRecord(item, response.body);
     }
   }),
 
   /**
    * Phase 1 part 2
    *
@@ -250,22 +248,17 @@ SyncImpl.prototype = {
       path: "/batch",
       body: {
         defaults: {
           method: "POST",
           path: "/articles",
         },
         requests: requests,
       },
-      headers: {},
     };
-    if (this._serverLastModifiedHeader) {
-      request.headers["If-Unmodified-Since"] = this._serverLastModifiedHeader;
-    }
-
     let batchResponse = yield this._sendRequest(request);
     if (batchResponse.status != 200) {
       this._handleUnexpectedResponse("uploading new items", batchResponse);
       return;
     }
 
     // Update local items based on the response.
     for (let response of batchResponse.body.responses) {
@@ -318,37 +311,25 @@ SyncImpl.prototype = {
       method: "POST",
       path: "/batch",
       body: {
         defaults: {
           method: "DELETE",
         },
         requests: requests,
       },
-      headers: {},
     };
-    if (this._serverLastModifiedHeader) {
-      request.headers["If-Unmodified-Since"] = this._serverLastModifiedHeader;
-    }
-
     let batchResponse = yield this._sendRequest(request);
     if (batchResponse.status != 200) {
       this._handleUnexpectedResponse("uploading deleted items", batchResponse);
       return;
     }
 
     // Delete local items based on the response.
     for (let response of batchResponse.body.responses) {
-      if (response.status == 412) {
-        // "Precondition failed": The item was modified since the last sync.
-        // Mark the item as having material changes, and reconcile and upload it
-        // in the material-changes phase.
-        // TODO
-        continue;
-      }
       // A 404 means the item was already deleted on the server, which is OK.
       // We still need to make sure it's deleted locally, though.
       if (response.status != 200 && response.status != 404) {
         this._handleUnexpectedResponse("uploading a deleted item", response);
         continue;
       }
       yield this._deleteItemForGUID(response.body.id);
     }
@@ -365,28 +346,18 @@ SyncImpl.prototype = {
     // Get modified items from the server.
     let path = "/articles";
     if (this._serverLastModifiedHeader) {
       path += "?_since=" + this._serverLastModifiedHeader;
     }
     let request = {
       method: "GET",
       path: path,
-      headers: {},
     };
-    if (this._serverLastModifiedHeader) {
-      request.headers["If-Modified-Since"] = this._serverLastModifiedHeader;
-    }
-
     let response = yield this._sendRequest(request);
-    if (response.status == 304) {
-      // not modified
-      log.debug("No server changes");
-      return;
-    }
     if (response.status != 200) {
       this._handleUnexpectedResponse("downloading modified items", response);
       return;
     }
 
     // Update local items based on the response.
     for (let serverRecord of response.body.items) {
       let localItem = yield this._itemForGUID(serverRecord.id);
@@ -412,16 +383,23 @@ SyncImpl.prototype = {
       let localRecord = localRecordFromServerRecord(serverRecord);
       try {
         yield this.list.addItem(localRecord);
       } catch (ex) {
         log.warn("Failed to add a new item from server record ${serverRecord}: ${ex}",
                  {serverRecord, ex});
       }
     }
+
+    // Now that changes have been successfully applied, advance the server
+    // last-modified timestamp so that next time we fetch items starting from
+    // the current point.  Response header names are lowercase.
+    if (response.headers && "last-modified" in response.headers) {
+      this._serverLastModifiedHeader = response.headers["last-modified"];
+    }
   }),
 
   /**
    * Phase 3 (material changes)
    *
    * Uploads not-new items with material changes.
    */
   _uploadMaterialChanges: Task.async(function* () {
@@ -501,20 +479,16 @@ SyncImpl.prototype = {
    * @param req The request object: { method, path, body, headers }.
    * @return Promise<response> Resolved with the server's response object:
    *         { status, body, headers }.
    */
   _sendRequest: Task.async(function* (req) {
     log.debug("Sending request", req);
     let response = yield this._client.request(req);
     log.debug("Received response", response);
-    // Response header names are lowercase.
-    if (response.headers && "last-modified" in response.headers) {
-      this._serverLastModifiedHeader = response.headers["last-modified"];
-    }
     return response;
   }),
 
   _handleUnexpectedResponse(contextMsgFragment, response) {
     log.warn(`Unexpected response ${contextMsgFragment}`, response);
   },
 
   // TODO: Wipe this pref when user logs out.
--- a/browser/devtools/debugger/test/browser_dbg_addon-panels.js
+++ b/browser/devtools/debugger/test/browser_dbg_addon-panels.js
@@ -5,17 +5,17 @@
 // display in the toolbox
 
 const ADDON_URL = EXAMPLE_URL + "addon3.xpi";
 
 let gAddon, gClient, gThreadClient, gDebugger, gSources;
 let PREFS = [
   "devtools.canvasdebugger.enabled",
   "devtools.shadereditor.enabled",
-  "devtools.performance.enabled",
+  "devtools.profiler.enabled",
   "devtools.netmonitor.enabled"
 ];
 function test() {
   Task.spawn(function () {
     let addon = yield addAddon(ADDON_URL);
     let addonDebugger = yield initAddonDebugger(ADDON_URL);
 
     // Store and enable all optional dev tools panels
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -1213,18 +1213,18 @@ let gDevToolsBrowser = {
     }
   },
 
   /**
    * Connects to the SPS profiler when the developer tools are open. This is
    * necessary because of the WebConsole's `profile` and `profileEnd` methods.
    */
   _connectToProfiler: function DT_connectToProfiler(event, toolbox) {
-    let SharedPerformanceUtils = devtools.require("devtools/performance/front");
-    let connection = SharedPerformanceUtils.getPerformanceActorsConnection(toolbox.target);
+    let SharedProfilerUtils = devtools.require("devtools/profiler/shared");
+    let connection = SharedProfilerUtils.getProfilerConnection(toolbox);
     connection.open();
   },
 
   /**
    * Remove the menuitem for a tool to all open browser windows.
    *
    * @param {string} toolId
    *        id of the tool to remove
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -79,17 +79,17 @@
           <checkbox label="&options.stylesheetAutocompletion.label;"
                     tooltiptext="&options.stylesheetAutocompletion.tooltip;"
                     data-pref="devtools.styleeditor.autocompletion-enabled"/>
         </vbox>
         <label>&options.profiler.label;</label>
         <vbox id="profiler-options" class="options-groupbox">
           <checkbox label="&options.showPlatformData.label;"
                     tooltiptext="&options.showPlatformData.tooltip;"
-                    data-pref="devtools.performance.ui.show-platform-data"/>
+                    data-pref="devtools.profiler.ui.show-platform-data"/>
         </vbox>
       </vbox>
 
       <vbox id="sourceeditor-box" class="options-vertical-pane" flex="1">
         <label>&options.sourceeditor.label;</label>
         <vbox id="sourceeditor-options" class="options-groupbox">
           <checkbox id="devtools-sourceeditor-detectindentation"
                     label="&options.sourceeditor.detectindentation.label;"
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -46,17 +46,17 @@ let connect = Task.async(function*() {
       });
     }
   });
 });
 
 // Certain options should be toggled since we can assume chrome debugging here
 function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
-  Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
+  Services.prefs.setBoolPref("devtools.profiler.ui.show-platform-data", true);
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
   Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
   Services.prefs.setBoolPref("devtools.command-button-frames.enabled", true);
 }
 
 window.addEventListener("load", function() {
   let cmdClose = document.getElementById("toolbox-cmd-close");
   cmdClose.addEventListener("command", onCloseCommand);
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -84,29 +84,35 @@ browser.jar:
     content/browser/devtools/webaudioeditor/includes.js                (webaudioeditor/includes.js)
     content/browser/devtools/webaudioeditor/models.js                  (webaudioeditor/models.js)
     content/browser/devtools/webaudioeditor/controller.js              (webaudioeditor/controller.js)
     content/browser/devtools/webaudioeditor/views/utils.js             (webaudioeditor/views/utils.js)
     content/browser/devtools/webaudioeditor/views/context.js           (webaudioeditor/views/context.js)
     content/browser/devtools/webaudioeditor/views/inspector.js         (webaudioeditor/views/inspector.js)
     content/browser/devtools/webaudioeditor/views/properties.js        (webaudioeditor/views/properties.js)
     content/browser/devtools/webaudioeditor/views/automation.js        (webaudioeditor/views/automation.js)
+    content/browser/devtools/profiler.xul                              (profiler/profiler.xul)
+    content/browser/devtools/profiler.js                               (profiler/profiler.js)
+    content/browser/devtools/ui-recordings.js                          (profiler/ui-recordings.js)
+    content/browser/devtools/ui-profile.js                             (profiler/ui-profile.js)
+#ifdef MOZ_DEVTOOLS_PERFTOOLS
     content/browser/devtools/performance.xul                           (performance/performance.xul)
     content/browser/devtools/performance/performance-controller.js     (performance/performance-controller.js)
     content/browser/devtools/performance/performance-view.js           (performance/performance-view.js)
     content/browser/devtools/performance/views/overview.js             (performance/views/overview.js)
     content/browser/devtools/performance/views/toolbar.js              (performance/views/toolbar.js)
     content/browser/devtools/performance/views/details.js              (performance/views/details.js)
     content/browser/devtools/performance/views/details-subview.js      (performance/views/details-abstract-subview.js)
     content/browser/devtools/performance/views/details-waterfall.js    (performance/views/details-waterfall.js)
     content/browser/devtools/performance/views/details-js-call-tree.js      (performance/views/details-js-call-tree.js)
     content/browser/devtools/performance/views/details-js-flamegraph.js     (performance/views/details-js-flamegraph.js)
     content/browser/devtools/performance/views/details-memory-call-tree.js  (performance/views/details-memory-call-tree.js)
     content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
     content/browser/devtools/performance/views/recordings.js           (performance/views/recordings.js)
+#endif
     content/browser/devtools/responsivedesign/resize-commands.js       (responsivedesign/resize-commands.js)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
     content/browser/devtools/commandline/commands-index.js             (commandline/commands-index.js)
     content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
     content/browser/devtools/framework/toolbox-options.js              (framework/toolbox-options.js)
@@ -137,8 +143,10 @@ browser.jar:
     content/browser/devtools/graphs-frame.xhtml                        (shared/widgets/graphs-frame.xhtml)
     content/browser/devtools/spectrum-frame.xhtml                      (shared/widgets/spectrum-frame.xhtml)
     content/browser/devtools/spectrum.css                              (shared/widgets/spectrum.css)
     content/browser/devtools/cubic-bezier-frame.xhtml                  (shared/widgets/cubic-bezier-frame.xhtml)
     content/browser/devtools/cubic-bezier.css                          (shared/widgets/cubic-bezier.css)
     content/browser/devtools/eyedropper.xul                            (eyedropper/eyedropper.xul)
     content/browser/devtools/eyedropper/crosshairs.css                 (eyedropper/crosshairs.css)
     content/browser/devtools/eyedropper/nocursor.css                   (eyedropper/nocursor.css)
+    content/browser/devtools/timeline/timeline.xul                     (timeline/timeline.xul)
+    content/browser/devtools/timeline/timeline.js                      (timeline/timeline.js)
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -25,44 +25,48 @@ let events = require("sdk/system/events"
 loader.lazyGetter(this, "OptionsPanel", () => require("devtools/framework/toolbox-options").OptionsPanel);
 loader.lazyGetter(this, "InspectorPanel", () => require("devtools/inspector/inspector-panel").InspectorPanel);
 loader.lazyGetter(this, "WebConsolePanel", () => require("devtools/webconsole/panel").WebConsolePanel);
 loader.lazyGetter(this, "DebuggerPanel", () => require("devtools/debugger/panel").DebuggerPanel);
 loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/styleeditor/styleeditor-panel").StyleEditorPanel);
 loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/shadereditor/panel").ShaderEditorPanel);
 loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/canvasdebugger/panel").CanvasDebuggerPanel);
 loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/webaudioeditor/panel").WebAudioEditorPanel);
+loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel").ProfilerPanel);
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/performance/panel").PerformancePanel);
+loader.lazyGetter(this, "TimelinePanel", () => require("devtools/timeline/panel").TimelinePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/scratchpad/scratchpad-panel").ScratchpadPanel);
 
 // Strings
 const toolboxProps = "chrome://browser/locale/devtools/toolbox.properties";
 const inspectorProps = "chrome://browser/locale/devtools/inspector.properties";
 const webConsoleProps = "chrome://browser/locale/devtools/webconsole.properties";
 const debuggerProps = "chrome://browser/locale/devtools/debugger.properties";
 const styleEditorProps = "chrome://browser/locale/devtools/styleeditor.properties";
 const shaderEditorProps = "chrome://browser/locale/devtools/shadereditor.properties";
 const canvasDebuggerProps = "chrome://browser/locale/devtools/canvasdebugger.properties";
 const webAudioEditorProps = "chrome://browser/locale/devtools/webaudioeditor.properties";
 const profilerProps = "chrome://browser/locale/devtools/profiler.properties";
+const timelineProps = "chrome://browser/locale/devtools/timeline.properties";
 const netMonitorProps = "chrome://browser/locale/devtools/netmonitor.properties";
 const storageProps = "chrome://browser/locale/devtools/storage.properties";
 const scratchpadProps = "chrome://browser/locale/devtools/scratchpad.properties";
 
 loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
 loader.lazyGetter(this, "profilerStrings",() => Services.strings.createBundle(profilerProps));
 loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
 loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
 loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
 loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBundle(shaderEditorProps));
 loader.lazyGetter(this, "canvasDebuggerStrings", () => Services.strings.createBundle(canvasDebuggerProps));
 loader.lazyGetter(this, "webAudioEditorStrings", () => Services.strings.createBundle(webAudioEditorProps));
 loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps));
+loader.lazyGetter(this, "timelineStrings", () => Services.strings.createBundle(timelineProps));
 loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
 loader.lazyGetter(this, "storageStrings", () => Services.strings.createBundle(storageProps));
 loader.lazyGetter(this, "scratchpadStrings", () => Services.strings.createBundle(scratchpadProps));
 
 let Tools = {};
 exports.Tools = Tools;
 
 // Definitions
@@ -240,40 +244,87 @@ Tools.canvasDebugger = {
     return target.hasActor("canvas") && !target.chrome;
   },
 
   build: function (iframeWindow, toolbox) {
     return new CanvasDebuggerPanel(iframeWindow, toolbox);
   }
 };
 
+Tools.jsprofiler = {
+  id: "jsprofiler",
+  accesskey: l10n("profiler.accesskey", profilerStrings),
+  key: l10n("profiler.commandkey2", profilerStrings),
+  ordinal: 7,
+  modifiers: "shift",
+  visibilityswitch: "devtools.profiler.enabled",
+  icon: "chrome://browser/skin/devtools/tool-profiler.svg",
+  invertIconForLightTheme: true,
+  url: "chrome://browser/content/devtools/profiler.xul",
+  label: l10n("profiler.label2", profilerStrings),
+  panelLabel: l10n("profiler.panelLabel2", profilerStrings),
+  tooltip: l10n("profiler.tooltip2", profilerStrings),
+  inMenu: true,
+
+  isTargetSupported: function (target) {
+    // Hide the profiler when debugging devices pre bug 1046394,
+    // that don't expose profiler actor in content processes.
+    return target.hasActor("profiler");
+  },
+
+  build: function (frame, target) {
+    return new ProfilerPanel(frame, target);
+  }
+};
+
 Tools.performance = {
   id: "performance",
-  ordinal: 7,
+  ordinal: 19,
   icon: "chrome://browser/skin/devtools/tool-profiler.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/performance.xul",
-  visibilityswitch: "devtools.performance.enabled",
-  label: l10n("profiler.label2", profilerStrings),
-  panelLabel: l10n("profiler.panelLabel2", profilerStrings),
+  // TODO bug 1082695 audit the Performance tools labels
+  label: "Performance++", //l10n("profiler.label2", profilerStrings),
+  panelLabel: "Performance++", //l10n("profiler.panelLabel2", profilerStrings),
   tooltip: l10n("profiler.tooltip2", profilerStrings),
   accesskey: l10n("profiler.accesskey", profilerStrings),
   key: l10n("profiler.commandkey2", profilerStrings),
   modifiers: "shift",
   inMenu: true,
 
   isTargetSupported: function (target) {
     return target.hasActor("profiler");
   },
 
   build: function (frame, target) {
     return new PerformancePanel(frame, target);
   }
 };
 
+Tools.timeline = {
+  id: "timeline",
+  ordinal: 8,
+  visibilityswitch: "devtools.timeline.enabled",
+  icon: "chrome://browser/skin/devtools/tool-network.svg",
+  invertIconForLightTheme: true,
+  url: "chrome://browser/content/devtools/timeline/timeline.xul",
+  label: l10n("timeline.label", timelineStrings),
+  panelLabel: l10n("timeline.panelLabel", timelineStrings),
+  tooltip: l10n("timeline.tooltip", timelineStrings),
+
+  isTargetSupported: function(target) {
+    return target.hasActor("timeline");
+  },
+
+  build: function (iframeWindow, toolbox) {
+    let panel = new TimelinePanel(iframeWindow, toolbox);
+    return panel.open();
+  }
+};
+
 Tools.netMonitor = {
   id: "netmonitor",
   accesskey: l10n("netmonitor.accesskey", netMonitorStrings),
   key: l10n("netmonitor.commandkey", netMonitorStrings),
   ordinal: 9,
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   visibilityswitch: "devtools.netmonitor.enabled",
   icon: "chrome://browser/skin/devtools/tool-network.svg",
@@ -366,22 +417,33 @@ let defaultTools = [
   Tools.options,
   Tools.webConsole,
   Tools.inspector,
   Tools.jsdebugger,
   Tools.styleEditor,
   Tools.shaderEditor,
   Tools.canvasDebugger,
   Tools.webAudioEditor,
-  Tools.performance,
+  Tools.jsprofiler,
+  Tools.timeline,
   Tools.netMonitor,
   Tools.storage,
   Tools.scratchpad
 ];
 
+// Only enable in-development performance tools if `--enable-devtools-perf`
+// used in build, turning on `devtools.performance_dev.enabled`.
+// Add to normal `defaultTools` when ready for normal release,
+// pull out MOZ_DEVTOOLS_PERFTOOLS setting in `./configure.in`, and
+// leave config on in `./browser/app/profile/firefox.js`, and always
+// build in `./browser/devtools/moz.build`.
+if (Services.prefs.getBoolPref("devtools.performance_dev.enabled")) {
+  defaultTools.push(Tools.performance);
+}
+
 exports.defaultTools = defaultTools;
 
 for (let definition of defaultTools) {
   gDevTools.registerTool(definition);
 }
 
 Tools.darkTheme = {
   id: "dark",
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -12,32 +12,36 @@ DIRS += [
     'debugger',
     'eyedropper',
     'fontinspector',
     'framework',
     'inspector',
     'layoutview',
     'markupview',
     'netmonitor',
-    'performance',
+    'profiler',
     'projecteditor',
     'responsivedesign',
     'scratchpad',
     'shadereditor',
     'shared',
     'sourceeditor',
     'storage',
     'styleeditor',
     'styleinspector',
     'tilt',
+    'timeline',
     'webaudioeditor',
     'webconsole',
     'webide',
 ]
 
+if CONFIG['MOZ_DEVTOOLS_PERFTOOLS']:
+  DIRS += ['performance']
+
 EXTRA_COMPONENTS += [
     'devtools-clhandler.js',
     'devtools-clhandler.manifest',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES.devtools += [
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -43,23 +43,27 @@ gDevTools.testing = true;
 let DEFAULT_PREFS = [
   "devtools.debugger.log",
   "devtools.performance.ui.invert-call-tree",
   "devtools.performance.ui.flatten-tree-recursion",
   "devtools.performance.ui.show-platform-data",
   "devtools.performance.ui.show-idle-blocks",
   "devtools.performance.ui.enable-memory",
   "devtools.performance.ui.enable-framerate",
+
+  // remove after bug 1075567 is resolved.
+  "devtools.performance_dev.enabled"
 ].reduce((prefs, pref) => {
   prefs[pref] = Services.prefs.getBoolPref(pref);
   return prefs;
 }, {});
 
-// Enable the new performance panel for all tests.
-Services.prefs.setBoolPref("devtools.performance.enabled", true);
+// Enable the new performance panel for all tests. Remove this after
+// bug 1075567 is resolved.
+Services.prefs.setBoolPref("devtools.performance_dev.enabled", true);
 // Enable logging for all the tests. Both the debugger server and frontend will
 // be affected by this pref.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 /**
  * Call manually in tests that use frame script utils after initializing
  * the tool. Must be called after initializing so we can detect
  * whether or not `content` is a CPOW or not. Call after init but before navigating
--- a/browser/devtools/profiler/moz.build
+++ b/browser/devtools/profiler/moz.build
@@ -2,8 +2,10 @@
 # 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/.
 
 EXTRA_JS_MODULES.devtools.profiler += [
     'panel.js',
     'utils/shared.js'
 ]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/shared/telemetry.js
+++ b/browser/devtools/shared/telemetry.js
@@ -135,17 +135,17 @@ Telemetry.prototype = {
       userHistogram: "DEVTOOLS_WEBAUDIOEDITOR_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_WEBAUDIOEDITOR_TIME_ACTIVE_SECONDS"
     },
     canvasdebugger: {
       histogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_CANVASDEBUGGER_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS"
     },
-    performance: {
+    jsprofiler: {
       histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS"
     },
     netmonitor: {
       histogram: "DEVTOOLS_NETMONITOR_OPENED_BOOLEAN",
       userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS"
--- a/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
+++ b/browser/devtools/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
@@ -6,14 +6,14 @@ const TEST_URI = "data:text/html;charset
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(function*() {
   yield promiseTab(TEST_URI);
   let Telemetry = loadTelemetryAndRecordLogs();
 
-  yield openAndCloseToolbox(2, TOOL_DELAY, "performance");
+  yield openAndCloseToolbox(2, TOOL_DELAY, "jsprofiler");
   checkTelemetryResults(Telemetry);
 
   stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -110,44 +110,47 @@ StyleEditorUI.prototype = {
     return this.selectedEditor ?
            this.selectedEditor.styleSheet.styleSheetIndex : -1;
   },
 
   /**
    * Initiates the style editor ui creation, the inspector front to get
    * reference to the walker and the selector highlighter if available
    */
-  initialize: function() {
-    return Task.spawn(function*() {
-      let toolbox = gDevTools.getToolbox(this._target);
-      yield toolbox.initInspector();
-      this._walker = toolbox.walker;
+  initialize: Task.async(function* () {
+    yield this.initializeHighlighter();
+
+    this.createUI();
+
+    let styleSheets = yield this._debuggee.getStyleSheets();
+    yield this._resetStyleSheetList(styleSheets);
+
+    this._target.on("will-navigate", this._clear);
+    this._target.on("navigate", this._onNewDocument);
+  }),
 
-      let hUtils = toolbox.highlighterUtils;
-      if (hUtils.supportsCustomHighlighters()) {
-        try {
-          this._highlighter =
-            yield hUtils.getHighlighterByType(SELECTOR_HIGHLIGHTER_TYPE);
-        } catch (e) {
-          // The selectorHighlighter can't always be instantiated, for example
-          // it doesn't work with XUL windows (until bug 1094959 gets fixed);
-          // or the selectorHighlighter doesn't exist on the backend.
-          console.warn("The selectorHighlighter couldn't be instantiated, " +
-            "elements matching hovered selectors will not be highlighted");
-        }
+  initializeHighlighter: Task.async(function* () {
+    let toolbox = gDevTools.getToolbox(this._target);
+    yield toolbox.initInspector();
+    this._walker = toolbox.walker;
+
+    let hUtils = toolbox.highlighterUtils;
+    if (hUtils.supportsCustomHighlighters()) {
+      try {
+        this._highlighter =
+          yield hUtils.getHighlighterByType(SELECTOR_HIGHLIGHTER_TYPE);
+      } catch (e) {
+        // The selectorHighlighter can't always be instantiated, for example
+        // it doesn't work with XUL windows (until bug 1094959 gets fixed);
+        // or the selectorHighlighter doesn't exist on the backend.
+        console.warn("The selectorHighlighter couldn't be instantiated, " +
+          "elements matching hovered selectors will not be highlighted");
       }
-    }.bind(this)).then(() => {
-      this.createUI();
-      this._debuggee.getStyleSheets().then((styleSheets) => {
-        this._resetStyleSheetList(styleSheets); 
-        this._target.on("will-navigate", this._clear);
-        this._target.on("navigate", this._onNewDocument);
-      }, Cu.reportError);
-    });
-  },
+    }
+  }),
 
   /**
    * Build the initial UI and wire buttons with event handlers.
    */
   createUI: function() {
     let viewRoot = this._root.parentNode.querySelector(".splitview-root");
 
     this._view = new SplitView(viewRoot);
@@ -202,37 +205,37 @@ StyleEditorUI.prototype = {
    *
    * @param {string} event
    *        Event name
    * @param {StyleSheet} styleSheet
    *        StyleSheet object for new sheet
    */
   _onNewDocument: function() {
     this._debuggee.getStyleSheets().then((styleSheets) => {
-      this._resetStyleSheetList(styleSheets);
-    }, Cu.reportError);
+      return this._resetStyleSheetList(styleSheets);
+    }).then(null, Cu.reportError);
   },
 
   /**
    * Add editors for all the given stylesheets to the UI.
    *
    * @param  {array} styleSheets
    *         Array of StyleSheetFront
    */
-  _resetStyleSheetList: function(styleSheets) {
+  _resetStyleSheetList: Task.async(function* (styleSheets) {
     this._clear();
 
     for (let sheet of styleSheets) {
-      this._addStyleSheet(sheet);
+      yield this._addStyleSheet(sheet);
     }
 
     this._root.classList.remove("loading");
 
     this.emit("stylesheets-reset");
-  },
+  }),
 
   /**
    * Remove all editors and add loading indicator.
    */
   _clear: function() {
     // remember selected sheet and line number for next load
     if (this.selectedEditor && this.selectedEditor.sourceEditor) {
       let href = this.selectedEditor.styleSheet.href;
@@ -263,48 +266,50 @@ StyleEditorUI.prototype = {
 
   /**
    * Add an editor for this stylesheet. Add editors for its original sources
    * instead (e.g. Sass sources), if applicable.
    *
    * @param  {StyleSheetFront} styleSheet
    *         Style sheet to add to style editor
    */
-  _addStyleSheet: function(styleSheet) {
-    let editor = this._addStyleSheetEditor(styleSheet);
+  _addStyleSheet: Task.async(function* (styleSheet) {
+    let editor = yield this._addStyleSheetEditor(styleSheet);
 
     if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
       return;
     }
 
-    styleSheet.getOriginalSources().then((sources) => {
-      if (sources && sources.length) {
-        this._removeStyleSheetEditor(editor);
-        sources.forEach((source) => {
-          // set so the first sheet will be selected, even if it's a source
-          source.styleSheetIndex = styleSheet.styleSheetIndex;
-          source.relatedStyleSheet = styleSheet;
+    let sources = yield styleSheet.getOriginalSources();
+    if (sources && sources.length) {
+      this._removeStyleSheetEditor(editor);
 
-          this._addStyleSheetEditor(source);
-        });
+      for (let source of sources) {
+        // set so the first sheet will be selected, even if it's a source
+        source.styleSheetIndex = styleSheet.styleSheetIndex;
+        source.relatedStyleSheet = styleSheet;
+
+        yield this._addStyleSheetEditor(source);
       }
-    }, Cu.reportError);
-  },
+    }
+  }),
 
   /**
    * Add a new editor to the UI for a source.
    *
    * @param {StyleSheet}  styleSheet
    *        Object representing stylesheet
    * @param {nsIfile}  file
    *         Optional file object that sheet was imported from
    * @param {Boolean} isNew
    *         Optional if stylesheet is a new sheet created by user
+   * @return {Promise} that is resolved with the created StyleSheetEditor when
+   *                   the editor is fully initialized or rejected on error.
    */
-  _addStyleSheetEditor: function(styleSheet, file, isNew) {
+  _addStyleSheetEditor: Task.async(function* (styleSheet, file, isNew) {
     // recall location of saved file for this sheet after page reload
     let identifier = this.getStyleSheetIdentifier(styleSheet);
     let savedFile = this.savedLocations[identifier];
     if (savedFile && !file) {
       file = savedFile;
     }
 
     let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew,
@@ -313,20 +318,21 @@ StyleEditorUI.prototype = {
     editor.on("property-change", this._summaryChange.bind(this, editor));
     editor.on("media-rules-changed", this._updateMediaList.bind(this, editor));
     editor.on("linked-css-file", this._summaryChange.bind(this, editor));
     editor.on("linked-css-file-error", this._summaryChange.bind(this, editor));
     editor.on("error", this._onError);
 
     this.editors.push(editor);
 
-    editor.fetchSource(this._sourceLoaded.bind(this, editor))
-          .then(null, Cu.reportError);
+    yield editor.fetchSource();
+    this._sourceLoaded(editor);
+
     return editor;
-  },
+  }),
 
   /**
    * Import a style sheet from file and asynchronously create a
    * new stylesheet on the debuggee for it.
    *
    * @param {mixed} file
    *        Optional nsIFile or filename string.
    *        If not set a file picker will be shown.
--- a/browser/devtools/styleeditor/styleeditor-panel.js
+++ b/browser/devtools/styleeditor/styleeditor-panel.js
@@ -38,49 +38,41 @@ exports.StyleEditorPanel = StyleEditorPa
 StyleEditorPanel.prototype = {
   get target() this._toolbox.target,
 
   get panelWindow() this._panelWin,
 
   /**
    * open is effectively an asynchronous constructor
    */
-  open: function() {
-    let deferred = promise.defer();
-
-    let targetPromise;
+  open: Task.async(function* () {
     // We always interact with the target as if it were remote
     if (!this.target.isRemote) {
-      targetPromise = this.target.makeRemote();
-    } else {
-      targetPromise = promise.resolve(this.target);
+      yield this.target.makeRemote();
     }
 
-    targetPromise.then(() => {
-      this.target.on("close", this.destroy);
+    this.target.on("close", this.destroy);
+
+    if (this.target.form.styleSheetsActor) {
+      this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
+    }
+    else {
+      /* We're talking to a pre-Firefox 29 server-side */
+      this._debuggee = StyleEditorFront(this.target.client, this.target.form);
+    }
 
-      if (this.target.form.styleSheetsActor) {
-        this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
-      }
-      else {
-        /* We're talking to a pre-Firefox 29 server-side */
-        this._debuggee = StyleEditorFront(this.target.client, this.target.form);
-      }
-      this.UI = new StyleEditorUI(this._debuggee, this.target, this._panelDoc);
-      this.UI.initialize().then(() => {
-        this.UI.on("error", this._showError);
+    // Initialize the UI
+    this.UI = new StyleEditorUI(this._debuggee, this.target, this._panelDoc);
+    yield this.UI.initialize();
 
-        this.isReady = true;
+    this.UI.on("error", this._showError);
+    this.isReady = true;
 
-        deferred.resolve(this);
-      });
-    }, console.error);
-
-    return deferred.promise;
-  },
+    return this;
+  }),
 
   /**
    * Show an error message from the style editor in the toolbox
    * notification box.
    *
    * @param  {string} event
    *         Type of event
    * @param  {string} data
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -1,91 +1,44 @@
+"use strict";
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-///////////////////
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: summary is undefined");
+// Checks that style editor contains correct stylesheets after initialization.
 
 const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
-
-let gUI;
-
-function test()
-{
-  waitForExplicitFinish();
-
-  addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, testEditorAdded);
-
-  content.location = TESTCASE_URI;
-}
-
-let gEditorAddedCount = 0;
-function testEditorAdded(aEditor)
-{
-  if (aEditor.styleSheet.styleSheetIndex == 0) {
-    gEditorAddedCount++;
-    gUI.editors[0].getSourceEditor().then(testFirstStyleSheetEditor);
+const EXPECTED_SHEETS = [
+  {
+    sheetIndex: 0,
+    name: /^simple.css$/,
+    rules: 1,
+    active: true
+  }, {
+    sheetIndex: 1,
+    name: /^<.*>$/,
+    rules: 3,
+    active: false
   }
-  if (aEditor.styleSheet.styleSheetIndex == 1) {
-    gEditorAddedCount++;
-    testSecondStyleSheetEditor(aEditor);
-  }
+];
 
-  if (gEditorAddedCount == 2) {
-    gUI = null;
-    finish();
-  }
-}
+add_task(function* () {
+  let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
 
-function testFirstStyleSheetEditor(aEditor)
-{
-  // Note: the html <link> contains charset="UTF-8".
-  ok(aEditor._state.text.indexOf("\u263a") >= 0,
-     "stylesheet is unicode-aware.");
+  is(ui.editors.length, 2, "The UI contains two style sheets.");
+  checkSheet(ui.editors[0], EXPECTED_SHEETS[0]);
+  checkSheet(ui.editors[1], EXPECTED_SHEETS[1]);
+});
 
-  //testing TESTCASE's simple.css stylesheet
-  is(aEditor.styleSheet.styleSheetIndex, 0,
-     "first stylesheet is at index 0");
+function checkSheet(editor, expected) {
+  is(editor.styleSheet.styleSheetIndex, expected.sheetIndex,
+    "Style sheet has correct index.");
 
-  is(aEditor, gUI.editors[0],
-     "first stylesheet corresponds to StyleEditorChrome.editors[0]");
-
-  let summary = aEditor.summary;
-
+  let summary = editor.summary;
   let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
-  is(name, "simple.css",
-     "first stylesheet's name is `simple.css`");
+  ok(expected.name.test(name), "The name '" + name + "' is correct.");
 
   let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
-  is(parseInt(ruleCount), 1,
-     "first stylesheet UI shows rule count as 1");
-
-  ok(summary.classList.contains("splitview-active"),
-     "first stylesheet UI is focused/active");
-}
-
-function testSecondStyleSheetEditor(aEditor)
-{
-  //testing TESTCASE's inline stylesheet
-  is(aEditor.styleSheet.styleSheetIndex, 1,
-     "second stylesheet is at index 1");
+  is(parseInt(ruleCount), expected.rules, "the rule count is correct");
 
-  is(aEditor, gUI.editors[1],
-     "second stylesheet corresponds to StyleEditorChrome.editors[1]");
-
-  let summary = aEditor.summary;
-
-  let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
-  ok(/^<.*>$/.test(name),
-     "second stylesheet's name is surrounded by `<>`");
-
-  let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
-  is(parseInt(ruleCount), 3,
-     "second stylesheet UI shows rule count as 3");
-
-  ok(!summary.classList.contains("splitview-active"),
-     "second stylesheet UI is NOT focused/active");
+  is(summary.classList.contains("splitview-active"), expected.active,
+    "The active status for this sheet is correct.");
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
@@ -10,24 +10,24 @@ const LABELS = ["screen and (max-width: 
                 "screen and (min-width: 1200px)"];
 const LINE_NOS = [5, 8];
 
 waitForExplicitFinish();
 
 add_task(function*() {
   Services.prefs.setBoolPref(MAP_PREF, true);
 
-  let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+  let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
 
-  yield listenForMediaChange(UI);
+  yield listenForMediaChange(ui);
 
-  is(UI.editors.length, 1, "correct number of editors");
+  is(ui.editors.length, 1, "correct number of editors");
 
   // Test editor with @media rules
-  let mediaEditor = UI.editors[0];
+  let mediaEditor = ui.editors[0];
   yield openEditor(mediaEditor);
   testMediaEditor(mediaEditor);
 
   Services.prefs.clearUserPref(MAP_PREF);
 });
 
 function testMediaEditor(editor) {
   let sidebar = editor.details.querySelector(".stylesheet-sidebar");
--- a/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
@@ -1,52 +1,73 @@
 /* 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/. */
 
 // This test makes sure that the style editor does not store any
 // content CSS files in the permanent cache when opened from PB mode.
 
-function test() {
-  waitForExplicitFinish();
-  let gUI;
-  let testURI = 'http://' + TEST_HOST + '/browser/browser/devtools/styleeditor/test/test_private.html';
+const TEST_URL = 'http://' + TEST_HOST + '/browser/browser/devtools/styleeditor/test/test_private.html';
+const {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
+const cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+                .getService(Ci.nsICacheStorageService);
 
+add_task(function* () {
   info("Opening a new private window");
   let win = OpenBrowserWindow({private: true});
-  win.addEventListener("load", function onLoad() {
-    win.removeEventListener("load", onLoad, false);
-    executeSoon(startTest);
-  }, false);
+  yield waitForDelayedStartupFinished(win);
+
+  info("Clearing the browser cache");
+  cache.clear();
 
-  function startTest() {
-    win.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
-      win.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+  let { ui } = yield openStyleEditorForURL(TEST_URL, win);
+
+  is(ui.editors.length, 1, "The style editor contains one sheet.");
+  let editor = ui.editors[0];
 
-      info("Clearing the browser cache");
-      cache.clear();
+  yield editor.getSourceEditor();
+  yield checkDiskCacheFor(TEST_HOST);
+  win.close();
 
-      info("Opening the style editor in the private window");
-      openStyleEditorInWindow(win, function(panel) {
-        gUI = panel.UI;
-        gUI.on("editor-added", onEditorAdded);
-      });
-    }, true);
+});
+
+function checkDiskCacheFor(host)
+{
+  let foundPrivateData = false;
+  let deferred = promise.defer();
 
-    info("Loading the test URL in the new private window");
-    win.content.location = testURI;
-  }
-
-  function onEditorAdded(aEvent, aEditor) {
-    info("The style editor is ready")
-    aEditor.getSourceEditor().then(checkCache);
-  }
+  Visitor.prototype = {
+    onCacheStorageInfo: function(num, consumption)
+    {
+      info("disk storage contains " + num + " entries");
+    },
+    onCacheEntryInfo: function(uri)
+    {
+      var urispec = uri.asciiSpec;
+      info(urispec);
+      foundPrivateData |= urispec.contains(host);
+    },
+    onCacheEntryVisitCompleted: function()
+    {
+      is(foundPrivateData, false, "web content present in disk cache");
+      deferred.resolve();
+    }
+  };
+  function Visitor() {}
 
-  function checkCache() {
-    checkDiskCacheFor(TEST_HOST, function() {
-      gUI.off("editor-added", onEditorAdded);
-      win.close();
-      win = null;
-      gUI = null;
-      finish();
-    });
-  }
+  var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+  storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
+
+  return deferred.promise;
 }
+
+function waitForDelayedStartupFinished(aWindow)
+{
+  let deferred = promise.defer();
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    if (aWindow == aSubject) {
+      Services.obs.removeObserver(observer, aTopic);
+      deferred.resolve();
+    }
+  }, "browser-delayed-startup-finished", false);
+
+  return deferred.promise;
+}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sourcemap_large.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sourcemap_large.js
@@ -5,27 +5,27 @@
 // Covers the case from Bug 1128747, where loading a sourcemapped
 // file prevents the correct editor from being selected on load,
 // and causes a second iframe to be appended when the user clicks
 // editor in the list.
 
 const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps-large.html";
 
 add_task(function*() {
-  let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+  let { ui } = yield openStyleEditorForURL(TESTCASE_URI);
 
-  yield openEditor(UI.editors[0]);
-  let iframes = UI.selectedEditor.details.querySelectorAll("iframe");
+  yield openEditor(ui.editors[0]);
+  let iframes = ui.selectedEditor.details.querySelectorAll("iframe");
 
   is (iframes.length, 1, "There is only one editor iframe");
-  ok (UI.selectedEditor.summary.classList.contains("splitview-active"),
+  ok (ui.selectedEditor.summary.classList.contains("splitview-active"),
     "The editor is selected");
 });
 
 function openEditor(editor) {
   getLinkFor(editor).click();
 
   return editor.getSourceEditor();
 }
 
 function getLinkFor(editor) {
   return editor.summary.querySelector(".stylesheet-name");
-}
\ No newline at end of file
+}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js
@@ -27,28 +27,40 @@ let FileUtils = tempScope.FileUtils;
 let NetUtil = tempScope.NetUtil;
 
 function test()
 {
   waitForExplicitFinish();
 
   Services.prefs.setBoolPref(TRANSITIONS_PREF, false);
 
-  Task.spawn(function() {
+  Task.spawn(function*() {
     // copy all our files over so we don't screw them up for other tests
     let HTMLFile = yield copy(TESTCASE_URI_HTML, ["sourcemaps.html"]);
     let CSSFile = yield copy(TESTCASE_URI_CSS, ["sourcemap-css", "sourcemaps.css"]);
     yield copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]);
     yield copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]);
     yield copy(TESTCASE_URI_REG_CSS, ["simple.css"]);
 
     let uri = Services.io.newFileURI(HTMLFile);
     let testcaseURI = uri.resolve("");
 
-    let editor = yield openEditor(testcaseURI);
+    let { ui } = yield openStyleEditorForURL(testcaseURI);
+
+    let editor = ui.editors[1];
+    if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) {
+      editor = ui.editors[2];
+    }
+
+    is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor");
+
+    let link = getLinkFor(editor);
+    link.click();
+
+    yield editor.getSourceEditor();
 
     let element = content.document.querySelector("div");
     let style = content.getComputedStyle(element, null);
 
     is(style.color, "rgb(255, 0, 102)", "div is red before saving file");
 
     editor.styleSheet.relatedStyleSheet.once("style-applied", function() {
       is(style.color, "rgb(0, 0, 255)", "div is blue after saving file");
@@ -64,41 +76,16 @@ function test()
     // We can't run Sass or another compiler, so we fake it by just
     // directly changing the CSS file.
     yield editCSSFile(CSSFile);
 
     info("wrote to CSS file");
   })
 }
 
-function openEditor(testcaseURI) {
-  let deferred = promise.defer();
-
-  addTabAndOpenStyleEditors(3, panel => {
-    let UI = panel.UI;
-
-    // wait for 5 editors - 1 for first style sheet, 2 for the
-    // generated style sheets, and 2 for original source after it
-    // loads and replaces the generated style sheets.
-    let editor = UI.editors[1];
-    if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) {
-      editor = UI.editors[2];
-    }
-    is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor");
-
-    let link = getLinkFor(editor);
-    link.click();
-
-    editor.getSourceEditor().then(deferred.resolve);
-  });
-  content.location = testcaseURI;
-
-  return deferred.promise;
-}
-
 function editSCSS(editor) {
   let deferred = promise.defer();
 
   let pos = {line: 0, ch: 0};
   editor.sourceEditor.replaceText(CSS_TEXT, pos, pos);
 
   editor.saveToFile(null, function (file) {
     ok(file, "Scss file should be saved");
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
@@ -64,38 +64,38 @@ const contents = {
 }
 
 const cssNames = ["sourcemaps.css", "contained.css", "test-stylus.css"];
 const origNames = ["sourcemaps.scss", "contained.scss", "test-stylus.styl"];
 
 waitForExplicitFinish();
 
 add_task(function*() {
-  let {UI} = yield addTabAndOpenStyleEditors(7, null, TESTCASE_URI);
+  let {ui} = yield openStyleEditorForURL(TESTCASE_URI);
 
-  is(UI.editors.length, 4,
+  is(ui.editors.length, 4,
     "correct number of editors with source maps enabled");
 
   // Test first plain css editor
-  testFirstEditor(UI.editors[0]);
+  testFirstEditor(ui.editors[0]);
 
   // Test Scss editors
-  yield testEditor(UI.editors[1], origNames);
-  yield testEditor(UI.editors[2], origNames);
-  yield testEditor(UI.editors[3], origNames);
+  yield testEditor(ui.editors[1], origNames);
+  yield testEditor(ui.editors[2], origNames);
+  yield testEditor(ui.editors[3], origNames);
 
   // Test disabling original sources
-  yield togglePref(UI);
+  yield togglePref(ui);
 
-  is(UI.editors.length, 4, "correct number of editors after pref toggled");
+  is(ui.editors.length, 4, "correct number of editors after pref toggled");
 
   // Test CSS editors
-  yield testEditor(UI.editors[1], cssNames);
-  yield testEditor(UI.editors[2], cssNames);
-  yield testEditor(UI.editors[3], cssNames);
+  yield testEditor(ui.editors[1], cssNames);
+  yield testEditor(ui.editors[2], cssNames);
+  yield testEditor(ui.editors[3], cssNames);
 
   Services.prefs.clearUserPref(PREF);
 });
 
 function testFirstEditor(editor) {
   let name = getStylesheetNameFor(editor);
   is(name, "simple.css", "First style sheet display name is correct");
 }
@@ -111,26 +111,17 @@ function testEditor(editor, possibleName
 
     is(text, expectedText, name + " editor contains expected text");
   });
 }
 
 /* Helpers */
 
 function togglePref(UI) {
-  let deferred = promise.defer();
-  let count = 0;
-
-  UI.on("editor-added", (event, editor) => {
-    if (++count == 3) {
-      deferred.resolve();
-    }
-  })
-  let editorsPromise = deferred.promise;
-
+  let editorsPromise = UI.once("stylesheets-reset");
   let selectedPromise = UI.once("editor-selected");
 
   Services.prefs.setBoolPref(PREF, false);
 
   return promise.all([editorsPromise, selectedPromise]);
 }
 
 function openEditor(editor) {
--- a/browser/devtools/styleeditor/test/browser_styleeditor_xul.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_xul.js
@@ -9,12 +9,11 @@ waitForExplicitFinish();
 const TEST_URL = TEST_BASE + "doc_xulpage.xul";
 
 add_task(function*() {
   let tab = yield addTab(TEST_URL);
   let target = TargetFactory.forTab(tab);
 
   let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
   let panel = toolbox.getCurrentPanel();
-  yield panel.UI.once("editor-added");
 
   ok(panel, "The style-editor panel did initialize correctly for the XUL window");
 });
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -3,50 +3,50 @@
 
 const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/styleeditor/test/";
 const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleeditor/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleeditor/test/";
 const TEST_HOST = 'mochi.test:8888';
 
 let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 let TargetFactory = devtools.TargetFactory;
-let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
 let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
 let gPanelWindow;
-let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
-              .getService(Ci.nsICacheStorageService);
 
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
 /**
  * Add a new test tab in the browser and load the given url.
  * @param {String} url The url to be loaded in the new tab
+ * @param {Window} win The window to add the tab to (default: current window).
  * @return a promise that resolves to the tab object when the url is loaded
  */
-function addTab(url) {
+function addTab(url, win) {
   info("Adding a new tab with URL: '" + url + "'");
   let def = promise.defer();
 
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+  let targetWindow = win || window;
+  let targetBrowser = targetWindow.gBrowser;
+
+  let tab = targetBrowser.selectedTab = targetBrowser.addTab(url);
+  targetBrowser.selectedBrowser.addEventListener("load", function onload() {
+    targetBrowser.selectedBrowser.removeEventListener("load", onload, true);
     info("URL '" + url + "' loading complete");
     def.resolve(tab);
   }, true);
-  content.location = url;
 
   return def.promise;
 }
 
 /**
  * Navigate the currently selected tab to a new URL and wait for it to load.
  * @param {String} url The url to be loaded in the current tab.
  * @return a promise that resolves when the page has fully loaded.
@@ -67,16 +67,30 @@ function* cleanup()
   while (gBrowser.tabs.length > 1) {
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     yield gDevTools.closeToolbox(target);
 
     gBrowser.removeCurrentTab();
   }
 }
 
+/**
+ * Creates a new tab in specified window navigates it to the given URL and
+ * opens style editor in it.
+ */
+let openStyleEditorForURL = Task.async(function* (url, win) {
+  let tab = yield addTab(url, win);
+  let target = TargetFactory.forTab(tab);
+  let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
+  let panel = toolbox.getPanel("styleeditor");
+  let ui = panel.UI;
+
+  return { tab, toolbox, panel, ui };
+});
+
 function addTabAndOpenStyleEditors(count, callback, uri) {
   let deferred = promise.defer();
   let currentCount = 0;
   let panel;
   addTabAndCheckOnStyleEditorAdded(p => panel = p, function (editor) {
     currentCount++;
     info(currentCount + " of " + count + " editors opened: "
          + editor.styleSheet.href);
@@ -117,36 +131,9 @@ function openStyleEditorInWindow(win, ca
     let panel = toolbox.getCurrentPanel();
     gPanelWindow = panel._panelWin;
 
     panel.UI._alwaysDisableAnimations = true;
     callback(panel);
   });
 }
 
-function checkDiskCacheFor(host, done)
-{
-  let foundPrivateData = false;
-
-  Visitor.prototype = {
-    onCacheStorageInfo: function(num, consumption)
-    {
-      info("disk storage contains " + num + " entries");
-    },
-    onCacheEntryInfo: function(uri)
-    {
-      var urispec = uri.asciiSpec;
-      info(urispec);
-      foundPrivateData |= urispec.contains(host);
-    },
-    onCacheEntryVisitCompleted: function()
-    {
-      is(foundPrivateData, false, "web content present in disk cache");
-      done();
-    }
-  };
-  function Visitor() {}
-
-  var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
-  storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
-}
-
 registerCleanupFunction(cleanup);
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -911,27 +911,43 @@ function getComputedViewLinkByIndex(view
  * to be ready
  * @return a promise that resolves to the editor when the stylesheet editor is
  * ready
  */
 function waitForStyleEditor(toolbox, href) {
   let def = promise.defer();
 
   info("Waiting for the toolbox to switch to the styleeditor");
-  toolbox.once("styleeditor-ready").then(() => {
+  toolbox.once("styleeditor-selected").then(() => {
     let panel = toolbox.getCurrentPanel();
     ok(panel && panel.UI, "Styleeditor panel switched to front");
 
-    panel.UI.on("editor-selected", function onEditorSelected(event, editor) {
+    // A helper that resolves the promise once it receives an editor that
+    // matches the expected href. Returns false if the editor was not correct.
+    let gotEditor = (event, editor) => {
       let currentHref = editor.styleSheet.href;
       if (!href || (href && currentHref.endsWith(href))) {
         info("Stylesheet editor selected");
-        panel.UI.off("editor-selected", onEditorSelected);
+        panel.UI.off("editor-selected", gotEditor);
+
         editor.getSourceEditor().then(editor => {
           info("Stylesheet editor fully loaded");
           def.resolve(editor);
         });
+
+        return true;
       }
-    });
+
+      info("The editor was incorrect. Waiting for editor-selected event.");
+      return false;
+    };
+
+    // The expected editor may already be selected. Check the if the currently
+    // selected editor is the expected one and if not wait for an
+    // editor-selected event.
+    if (!gotEditor("styleeditor-selected", panel.UI.selectedEditor)) {
+      // The expected editor is not selected (yet). Wait for it.
+      panel.UI.on("editor-selected", gotEditor);
+    }
   });
 
   return def.promise;
 }
--- a/browser/devtools/timeline/moz.build
+++ b/browser/devtools/timeline/moz.build
@@ -1,8 +1,10 @@
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_JS_MODULES.devtools.timeline += [
     'panel.js',
 ]
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -43,23 +43,17 @@ function testViewSource()
              error2Msg.querySelector(".message-location")];
     ok(nodes[0], ".message-location node for the first error");
     ok(nodes[1], ".message-location node for the second error");
 
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
     toolbox.once("styleeditor-selected", (event, panel) => {
       StyleEditorUI = panel.UI;
-
-      let count = 0;
-      StyleEditorUI.on("editor-added", function() {
-        if (++count == 2) {
-          deferred.resolve(panel);
-        }
-      });
+      deferred.resolve(panel);
     });
 
     EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
   });
 
   return deferred.promise;
 }
 
--- a/browser/devtools/webconsole/test/browser_webconsole_split.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_split.js
@@ -48,17 +48,17 @@ function test()
   function checkAllTools()
   {
     info("About to check split console with each panel individually.");
 
     Task.spawn(function() {
       yield openAndCheckPanel("jsdebugger");
       yield openAndCheckPanel("inspector");
       yield openAndCheckPanel("styleeditor");
-      yield openAndCheckPanel("performance");
+      yield openAndCheckPanel("jsprofiler");
       yield openAndCheckPanel("netmonitor");
 
       yield checkWebconsolePanelOpened();
       testBottomHost();
     });
   }
 
   function getCurrentUIState()
--- a/browser/extensions/pdfjs/LICENSE
+++ b/browser/extensions/pdfjs/LICENSE
@@ -170,9 +170,8 @@
       License. However, in accepting such obligations, You may act only
       on Your own behalf and on Your sole responsibility, not on behalf
       of any other Contributor, and only if You agree to indemnify,
       defend, and hold each Contributor harmless for any liability
       incurred by, or claims asserted against, such Contributor by reason
       of your accepting any such warranty or additional liability.
 
    END OF TERMS AND CONDITIONS
-
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.0.1149
-
+Current extension version is: 1.1.24
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -261,19 +261,19 @@ let PdfJs = {
     this.updateRegistration();
     if (Services.appinfo.processType ===
         Services.appinfo.PROCESS_TYPE_DEFAULT) {
       let jsm = 'resource://pdf.js/PdfjsChromeUtils.jsm';
       let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
       PdfjsChromeUtils.notifyChildOfSettingsChange();
     }
   },
-  
+
   /**
-   * pdf.js is only enabled if it is both selected as the pdf viewer and if the 
+   * pdf.js is only enabled if it is both selected as the pdf viewer and if the
    * global switch enabling it is true.
    * @return {boolean} Wether or not it's enabled.
    */
   get enabled() {
     var disabled = getBoolPref(PREF_DISABLED, true);
     if (disabled) {
       return false;
     }
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -58,26 +58,19 @@ XPCOMUtils.defineLazyServiceGetter(Svc, 
 
 function getContainingBrowser(domWindow) {
   return domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                   .getInterface(Ci.nsIWebNavigation)
                   .QueryInterface(Ci.nsIDocShell)
                   .chromeEventHandler;
 }
 
-function getChromeWindow(domWindow) {
-  if (PdfjsContentUtils.isRemote) {
-    return PdfjsContentUtils.getChromeWindow(domWindow);
-  }
-  return getContainingBrowser(domWindow).ownerDocument.defaultView;
-}
-
 function getFindBar(domWindow) {
   if (PdfjsContentUtils.isRemote) {
-    return PdfjsContentUtils.getFindBar(domWindow);
+    throw new Error('FindBar is not accessible from the content process.');
   }
   var browser = getContainingBrowser(domWindow);
   try {
     var tabbrowser = browser.getTabBrowser();
     var tab;
     tab = tabbrowser.getTabForBrowser(browser);
     return tabbrowser.getFindBar(tab);
   } catch (e) {
@@ -158,16 +151,31 @@ function getLocalizedString(strings, id,
   return id;
 }
 
 function makeContentReadable(obj, window) {
   /* jshint -W027 */
   return Cu.cloneInto(obj, window);
 }
 
+function createNewChannel(uri, node, principal) {
+  return NetUtil.newChannel2(uri,
+                             null,
+                             null,
+                             node, // aLoadingNode
+                             principal, // aLoadingPrincipal
+                             null, // aTriggeringPrincipal
+                             Ci.nsILoadInfo.SEC_NORMAL,
+                             Ci.nsIContentPolicy.TYPE_OTHER);
+}
+
+function asyncFetchChannel(channel, callback) {
+  return NetUtil.asyncFetch2(channel, callback);
+}
+
 // PDF data storage
 function PdfDataListener(length) {
   this.length = length; // less than 0, if length is unknown
   this.buffer = null;
   this.loaded = 0;
 }
 
 PdfDataListener.prototype = {
@@ -236,38 +244,39 @@ function ChromeActions(domWindow, conten
 
 ChromeActions.prototype = {
   isInPrivateBrowsing: function() {
     return PrivateBrowsingUtils.isContentWindowPrivate(this.domWindow);
   },
   download: function(data, sendResponse) {
     var self = this;
     var originalUrl = data.originalUrl;
+    var blobUrl = data.blobUrl || originalUrl;
     // The data may not be downloaded so we need just retry getting the pdf with
     // the original url.
-    var originalUri = NetUtil.newURI(data.originalUrl);
+    var originalUri = NetUtil.newURI(originalUrl);
     var filename = data.filename;
     if (typeof filename !== 'string' ||
         (!/\.pdf$/i.test(filename) && !data.isAttachment)) {
       filename = 'document.pdf';
     }
-    var blobUri = data.blobUrl ? NetUtil.newURI(data.blobUrl) : originalUri;
+    var blobUri = NetUtil.newURI(blobUrl);
     var extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
              getService(Ci.nsIExternalHelperAppService);
     var frontWindow = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                          getService(Ci.nsIWindowWatcher).activeWindow;
 
     var docIsPrivate = this.isInPrivateBrowsing();
-    var netChannel = NetUtil.newChannel(blobUri);
+    var netChannel = createNewChannel(blobUri, frontWindow.document, null);
     if ('nsIPrivateBrowsingChannel' in Ci &&
         netChannel instanceof Ci.nsIPrivateBrowsingChannel) {
       netChannel.setPrivate(docIsPrivate);
     }
-    NetUtil.asyncFetch(netChannel, function(aInputStream, aResult) {
+    asyncFetchChannel(netChannel, function(aInputStream, aResult) {
       if (!Components.isSuccessCode(aResult)) {
         if (sendResponse) {
           sendResponse(true);
         }
         return;
       }
       // Create a nsIInputStreamChannel so we can set the url on the channel
       // so the filename will be correct.
@@ -334,17 +343,23 @@ ChromeActions.prototype = {
       return 'null';
     }
   },
   supportsIntegratedFind: function() {
     // Integrated find is only supported when we're not in a frame
     if (this.domWindow.frameElement !== null) {
       return false;
     }
-    // ... and when the new find events code exists.
+
+    // ... and we are in a child process
+    if (PdfjsContentUtils.isRemote) {
+      return true;
+    }
+
+    // ... or when the new find events code exists.
     var findBar = getFindBar(this.domWindow);
     return findBar && ('updateControlState' in findBar);
   },
   supportsDocumentFonts: function() {
     var prefBrowser = getIntPref('browser.display.use_document_fonts', 1);
     var prefGfx = getBoolPref('gfx.downloadable_fonts.enabled', true);
     return (!!prefBrowser && prefGfx);
   },
@@ -436,17 +451,25 @@ ChromeActions.prototype = {
     // Verify what we're sending to the findbar.
     var result = data.result;
     var findPrevious = data.findPrevious;
     var findPreviousType = typeof findPrevious;
     if ((typeof result !== 'number' || result < 0 || result > 3) ||
         (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
       return;
     }
-    getFindBar(this.domWindow).updateControlState(result, findPrevious);
+
+    var winmm = this.domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIDocShell)
+                              .sameTypeRootTreeItem
+                              .QueryInterface(Ci.nsIDocShell)
+                              .QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIContentFrameMessageManager);
+
+    winmm.sendAsyncMessage('PDFJS:Parent:updateControlState', data);
   },
   setPreferences: function(prefs, sendResponse) {
     var defaultBranch = Services.prefs.getDefaultBranch(PREF_PREFIX + '.');
     var numberOfPrefs = 0;
     var prefValue, prefName;
     for (var key in prefs) {
       if (++numberOfPrefs > MAX_NUMBER_OF_PREFS) {
         log('setPreferences - Exceeded the maximum number of preferences ' +
@@ -741,68 +764,54 @@ RequestListener.prototype.receive = func
       };
     }
     actions[action].call(this.actions, data, response);
   }
 };
 
 // Forwards events from the eventElement to the contentWindow only if the
 // content window matches the currently selected browser window.
-function FindEventManager(eventElement, contentWindow, chromeWindow) {
-  this.types = ['find',
-                'findagain',
-                'findhighlightallchange',
-                'findcasesensitivitychange'];
-  this.chromeWindow = chromeWindow;
+function FindEventManager(contentWindow) {
   this.contentWindow = contentWindow;
-  this.eventElement = eventElement;
+  this.winmm = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIDocShell)
+                            .sameTypeRootTreeItem
+                            .QueryInterface(Ci.nsIDocShell)
+                            .QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIContentFrameMessageManager);
 }
 
 FindEventManager.prototype.bind = function() {
   var unload = function(e) {
     this.unbind();
     this.contentWindow.removeEventListener(e.type, unload);
   }.bind(this);
   this.contentWindow.addEventListener('unload', unload);
 
-  for (var i = 0; i < this.types.length; i++) {
-    var type = this.types[i];
-    this.eventElement.addEventListener(type, this, true);
-  }
+  // We cannot directly attach listeners to for the find events
+  // since the FindBar is in the parent process. Instead we're
+  // asking the PdfjsChromeUtils to do it for us and forward
+  // all the find events to us.
+  this.winmm.sendAsyncMessage('PDFJS:Parent:addEventListener');
+  this.winmm.addMessageListener('PDFJS:Child:handleEvent', this);
 };
 
-FindEventManager.prototype.handleEvent = function(e) {
-  var chromeWindow = this.chromeWindow;
+FindEventManager.prototype.receiveMessage = function(msg) {
+  var detail = msg.data.detail;
+  var type = msg.data.type;
   var contentWindow = this.contentWindow;
-  // Only forward the events if they are for our dom window.
-  if (chromeWindow.gBrowser.selectedBrowser.contentWindow === contentWindow) {
-    var detail = {
-      query: e.detail.query,
-      caseSensitive: e.detail.caseSensitive,
-      highlightAll: e.detail.highlightAll,
-      findPrevious: e.detail.findPrevious
-    };
-    detail = makeContentReadable(detail, contentWindow);
-    var forward = contentWindow.document.createEvent('CustomEvent');
-    forward.initCustomEvent(e.type, true, true, detail);
-    // Due to restrictions with cpow use, we can't dispatch
-    // dom events with an urgent message on the stack. So bounce
-    // this off the main thread to make it async. 
-    Services.tm.mainThread.dispatch(function () {
-      contentWindow.dispatchEvent(forward);
-    }, Ci.nsIThread.DISPATCH_NORMAL);
-    e.preventDefault();
-  }
+
+  detail = makeContentReadable(detail, contentWindow);
+  var forward = contentWindow.document.createEvent('CustomEvent');
+  forward.initCustomEvent(type, true, true, detail);
+  contentWindow.dispatchEvent(forward);
 };
 
 FindEventManager.prototype.unbind = function() {
-  for (var i = 0; i < this.types.length; i++) {
-    var type = this.types[i];
-    this.eventElement.removeEventListener(type, this, true);
-  }
+  this.winmm.sendAsyncMessage('PDFJS:Parent:removeEventListener');
 };
 
 function PdfStreamConverter() {
 }
 
 PdfStreamConverter.prototype = {
 
   // properties required for XPCOM registration:
@@ -917,19 +926,18 @@ PdfStreamConverter.prototype = {
 
     // Creating storage for PDF data
     var contentLength = aRequest.contentLength;
     this.dataListener = new PdfDataListener(contentLength);
     this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
                         .createInstance(Ci.nsIBinaryInputStream);
 
     // Create a new channel that is viewer loaded as a resource.
-    var ioService = Services.io;
-    var channel = ioService.newChannel(
-                    PDF_VIEWER_WEB_PAGE, null, null);
+    var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+    var channel = createNewChannel(PDF_VIEWER_WEB_PAGE, null, systemPrincipal);
 
     var listener = this.listener;
     var dataListener = this.dataListener;
     // Proxy all the request observer calls, when it gets to onStopRequest
     // we can get the dom window.  We also intentionally pass on the original
     // request(aRequest) below so we don't overwrite the original channel and
     // trigger an assertion.
     var proxy = {
@@ -952,21 +960,17 @@ PdfStreamConverter.prototype = {
           actions = new StandardChromeActions(
             domWindow, contentDispositionFilename, dataListener);
         }
         var requestListener = new RequestListener(actions);
         domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
           requestListener.receive(event);
         }, false, true);
         if (actions.supportsIntegratedFind()) {
-          var chromeWindow = getChromeWindow(domWindow);
-          var findBar = getFindBar(domWindow);
-          var findEventManager = new FindEventManager(findBar,
-                                                      domWindow,
-                                                      chromeWindow);
+          var findEventManager = new FindEventManager(domWindow);
           findEventManager.bind();
         }
         listener.onStopRequest(aRequest, context, statusCode);
 
         if (domWindow.frameElement) {
           var isObjectEmbed = domWindow.frameElement.tagName !== 'IFRAME' ||
             domWindow.frameElement.className === 'previewPluginContentFrame';
           PdfJsTelemetry.onEmbed(isObjectEmbed);
@@ -977,17 +981,17 @@ PdfStreamConverter.prototype = {
     // Keep the URL the same so the browser sees it as the same.
     channel.originalURI = aRequest.URI;
     channel.loadGroup = aRequest.loadGroup;
 
     // We can use resource principal when data is fetched by the chrome
     // e.g. useful for NoScript
     var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
                           .getService(Ci.nsIScriptSecurityManager);
-    var uri = ioService.newURI(PDF_VIEWER_WEB_PAGE, null, null);
+    var uri = NetUtil.newURI(PDF_VIEWER_WEB_PAGE, null, null);
     // FF16 and below had getCodebasePrincipal, it was replaced by
     // getNoAppCodebasePrincipal (bug 758258).
     var resourcePrincipal = 'getNoAppCodebasePrincipal' in securityManager ?
                             securityManager.getNoAppCodebasePrincipal(uri) :
                             securityManager.getCodebasePrincipal(uri);
     aRequest.owner = resourcePrincipal;
     channel.asyncOpen(proxy, aContext);
   },
--- a/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsChromeUtils.jsm
@@ -61,53 +61,58 @@ let PdfjsChromeUtils = {
   _ppmm: null,
   _mmg: null,
 
   /*
    * Public API
    */
 
   init: function () {
+    this._browsers = new Set();
     if (!this._ppmm) {
       // global parent process message manager (PPMM)
       this._ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1'].
         getService(Ci.nsIMessageBroadcaster);
       this._ppmm.addMessageListener('PDFJS:Parent:clearUserPref', this);
       this._ppmm.addMessageListener('PDFJS:Parent:setIntPref', this);
       this._ppmm.addMessageListener('PDFJS:Parent:setBoolPref', this);
       this._ppmm.addMessageListener('PDFJS:Parent:setCharPref', this);
       this._ppmm.addMessageListener('PDFJS:Parent:setStringPref', this);
       this._ppmm.addMessageListener('PDFJS:Parent:isDefaultHandlerApp', this);
 
       // global dom message manager (MMg)
       this._mmg = Cc['@mozilla.org/globalmessagemanager;1'].
         getService(Ci.nsIMessageListenerManager);
-      this._mmg.addMessageListener('PDFJS:Parent:getChromeWindow', this);
-      this._mmg.addMessageListener('PDFJS:Parent:getFindBar', this);
       this._mmg.addMessageListener('PDFJS:Parent:displayWarning', this);
 
+      this._mmg.addMessageListener('PDFJS:Parent:addEventListener', this);
+      this._mmg.addMessageListener('PDFJS:Parent:removeEventListener', this);
+      this._mmg.addMessageListener('PDFJS:Parent:updateControlState', this);
+
       // observer to handle shutdown
       Services.obs.addObserver(this, 'quit-application', false);
     }
   },
 
   uninit: function () {
     if (this._ppmm) {
       this._ppmm.removeMessageListener('PDFJS:Parent:clearUserPref', this);
       this._ppmm.removeMessageListener('PDFJS:Parent:setIntPref', this);
       this._ppmm.removeMessageListener('PDFJS:Parent:setBoolPref', this);
       this._ppmm.removeMessageListener('PDFJS:Parent:setCharPref', this);
       this._ppmm.removeMessageListener('PDFJS:Parent:setStringPref', this);
       this._ppmm.removeMessageListener('PDFJS:Parent:isDefaultHandlerApp',
                                        this);
 
-      this._mmg.removeMessageListener('PDFJS:Parent:getChromeWindow', this);
-      this._mmg.removeMessageListener('PDFJS:Parent:getFindBar', this);
       this._mmg.removeMessageListener('PDFJS:Parent:displayWarning', this);
 
+      this._mmg.removeMessageListener('PDFJS:Parent:addEventListener', this);
+      this._mmg.removeMessageListener('PDFJS:Parent:removeEventListener', this);
+      this._mmg.removeMessageListener('PDFJS:Parent:updateControlState', this);
+
       Services.obs.removeObserver(this, 'quit-application', false);
 
       this._mmg = null;
       this._ppmm = null;
     }
   },
 
   /*
@@ -156,46 +161,105 @@ let PdfjsChromeUtils = {
         this._setStringPref(aMsg.data.name, aMsg.data.value);
         break;
       case 'PDFJS:Parent:isDefaultHandlerApp':
         return this.isDefaultHandlerApp();
       case 'PDFJS:Parent:displayWarning':
         this._displayWarning(aMsg);
         break;
 
-      // CPOW getters
-      case 'PDFJS:Parent:getChromeWindow':
-        return this._getChromeWindow(aMsg);
-      case 'PDFJS:Parent:getFindBar':
-        return this._getFindBar(aMsg);
+
+      case 'PDFJS:Parent:updateControlState':
+        return this._updateControlState(aMsg);
+      case 'PDFJS:Parent:addEventListener':
+        return this._addEventListener(aMsg);
+      case 'PDFJS:Parent:removeEventListener':
+        return this._removeEventListener(aMsg);
     }
   },
 
   /*
    * Internal
    */
 
-  _getChromeWindow: function (aMsg) {
-    // See the child module, our return result here can't be the element
-    // since return results don't get auto CPOW'd.
+  _findbarFromMessage: function(aMsg) {
     let browser = aMsg.target;
-    let wrapper = new PdfjsWindowWrapper(browser);
-    let suitcase = aMsg.objects.suitcase;
-    suitcase.setChromeWindow(wrapper);
-    return true;
+    let tabbrowser = browser.getTabBrowser();
+    let tab = tabbrowser.getTabForBrowser(browser);
+    return tabbrowser.getFindBar(tab);
+  },
+
+  _updateControlState: function (aMsg) {
+    let data = aMsg.data;
+    this._findbarFromMessage(aMsg)
+        .updateControlState(data.result, data.findPrevious);
+  },
+
+  handleEvent: function(aEvent) {
+    // We cannot just forward the message as a CPOW without setting up
+    // __exposedProps__ on it. Instead, let's just create a structured
+    // cloneable version of the event for performance and for the ease of usage.
+    let type = aEvent.type;
+    let detail = {
+      query: aEvent.detail.query,
+      caseSensitive: aEvent.detail.caseSensitive,
+      highlightAll: aEvent.detail.highlightAll,
+      findPrevious: aEvent.detail.findPrevious
+    };
+
+    let chromeWindow = aEvent.target.ownerDocument.defaultView;
+    let browser = chromeWindow.gBrowser.selectedBrowser;
+    if (this._browsers.has(browser)) {
+      // Only forward the events if the selected browser is a registered
+      // browser.
+      let mm = browser.messageManager;
+      mm.sendAsyncMessage('PDFJS:Child:handleEvent',
+                          { type: type, detail: detail });
+      aEvent.preventDefault();
+    }
   },
 
-  _getFindBar: function (aMsg) {
-    // We send this over via the window's message manager, so target should
-    // be the dom window.
+  _types: ['find',
+           'findagain',
+           'findhighlightallchange',
+           'findcasesensitivitychange'],
+
+  _addEventListener: function (aMsg) {
     let browser = aMsg.target;
-    let wrapper = new PdfjsFindbarWrapper(browser);
-    let suitcase = aMsg.objects.suitcase;
-    suitcase.setFindBar(wrapper);
-    return true;
+    if (this._browsers.has(browser)) {
+      throw new Error('FindEventManager was bound 2nd time ' +
+                      'without unbinding it first.');
+    }
+
+    // Since this jsm is global, we need to store all the browsers
+    // we have to forward the messages for.
+    this._browsers.add(browser);
+
+    // And we need to start listening to find events.
+    for (var i = 0; i < this._types.length; i++) {
+      var type = this._types[i];
+      this._findbarFromMessage(aMsg)
+          .addEventListener(type, this, true);
+    }
+  },
+
+  _removeEventListener: function (aMsg) {
+    let browser = aMsg.target;
+    if (!this._browsers.has(browser)) {
+      throw new Error('FindEventManager was unbound without binding it first.');
+    }
+
+    this._browsers.delete(browser);
+
+    // No reason to listen to find events any longer.
+    for (var i = 0; i < this._types.length; i++) {
+      var type = this._types[i];
+      this._findbarFromMessage(aMsg)
+          .removeEventListener(type, this, true);
+    }
   },
 
   _ensurePreferenceAllowed: function (aPrefName) {
     let unPrefixedName = aPrefName.split(PREF_PREFIX + '.');
     if (unPrefixedName[0] !== '' ||
         this._allowedPrefNames.indexOf(unPrefixedName[1]) === -1) {
       let msg = '"' + aPrefName + '" ' +
                 'can\'t be accessed from content. See PdfjsChromeUtils.';
@@ -278,58 +342,9 @@ let PdfjsChromeUtils = {
       if (responseSent) {
         return;
       }
       cpowCallback(false);
     });
   }
 };
 
-/*
- * CPOW security features require chrome objects declare exposed
- * properties via __exposedProps__. We don't want to expose things
- * directly on the findbar, so we wrap the findbar in a smaller
- * object here that supports the features pdf.js needs.
- */
-function PdfjsFindbarWrapper(aBrowser) {
-  let tabbrowser = aBrowser.getTabBrowser();
-  let tab;
-  tab = tabbrowser.getTabForBrowser(aBrowser);
-  this._findbar = tabbrowser.getFindBar(tab);
-}
 
-PdfjsFindbarWrapper.prototype = {
-  __exposedProps__: {
-    addEventListener: 'r',
-    removeEventListener: 'r',
-    updateControlState: 'r',
-  },
-  _findbar: null,
-
-  updateControlState: function (aResult, aFindPrevious) {
-    this._findbar.updateControlState(aResult, aFindPrevious);
-  },
-
-  addEventListener: function (aType, aListener, aUseCapture, aWantsUntrusted) {
-    this._findbar.addEventListener(aType, aListener, aUseCapture,
-                                   aWantsUntrusted);
-  },
-
-  removeEventListener: function (aType, aListener, aUseCapture) {
-    this._findbar.removeEventListener(aType, aListener, aUseCapture);
-  }
-};
-
-function PdfjsWindowWrapper(aBrowser) {
-  this._window = aBrowser.ownerDocument.defaultView;
-}
-
-PdfjsWindowWrapper.prototype = {
-  __exposedProps__: {
-    valueOf: 'r',
-  },
-  _window: null,
-
-  valueOf: function () {
-    return this._window.valueOf();
-  }
-};
-
--- a/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
+++ b/browser/extensions/pdfjs/content/PdfjsContentUtils.jsm
@@ -144,61 +144,11 @@ let PdfjsContentUtils = {
         if (Services.appinfo.processType ===
             Services.appinfo.PROCESS_TYPE_CONTENT) {
           let jsm = 'resource://pdf.js/PdfJs.jsm';
           let pdfjs = Components.utils.import(jsm, {}).PdfJs;
           pdfjs.updateRegistration();
         }
         break;
     }
-  },
-
-  /*
-   * CPOWs
-   */
-
-  getChromeWindow: function (aWindow) {
-    let winmm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIDocShell)
-                        .sameTypeRootTreeItem
-                        .QueryInterface(Ci.nsIDocShell)
-                        .QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIContentFrameMessageManager);
-    // Sync calls don't support cpow wrapping of returned results, so we
-    // send over a small container for the object we want.
-    let suitcase = {
-      _window: null,
-      setChromeWindow: function (aObj) {
-        this._window = aObj;
-      }
-    };
-    if (!winmm.sendSyncMessage('PDFJS:Parent:getChromeWindow', {},
-                               { suitcase: suitcase })[0]) {
-      Cu.reportError('A request for a CPOW wrapped chrome window ' +
-                     'failed for unknown reasons.');
-      return null;
-    }
-    return suitcase._window;
-  },
-
-  getFindBar: function (aWindow) {
-    let winmm = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIDocShell)
-                        .sameTypeRootTreeItem
-                        .QueryInterface(Ci.nsIDocShell)
-                        .QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIContentFrameMessageManager);
-    let suitcase = {
-      _findbar: null,
-      setFindBar: function (aObj) {
-        this._findbar = aObj;
-      }
-    };
-    if (!winmm.sendSyncMessage('PDFJS:Parent:getFindBar', {},
-                               { suitcase: suitcase })[0]) {
-      Cu.reportError('A request for a CPOW wrapped findbar ' +
-                     'failed for unknown reasons.');
-      return null;
-    }
-    return suitcase._findbar;
   }
 };
 
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.1149';
-PDFJS.build = 'bc7a110';
+PDFJS.version = '1.1.24';
+PDFJS.build = 'f6a8110';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -944,20 +944,16 @@ function isInt(v) {
 function isNum(v) {
   return typeof v === 'number';
 }
 
 function isString(v) {
   return typeof v === 'string';
 }
 
-function isNull(v) {
-  return v === null;
-}
-
 function isName(v) {
   return v instanceof Name;
 }
 
 function isCmd(v, cmd) {
   return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
 }
 
@@ -1319,17 +1315,17 @@ PDFJS.maxImageSize = (PDFJS.maxImageSize
 PDFJS.cMapUrl = (PDFJS.cMapUrl === undefined ? null : PDFJS.cMapUrl);
 
 /**
  * Specifies if CMaps are binary packed.
  * @var {boolean}
  */
 PDFJS.cMapPacked = PDFJS.cMapPacked === undefined ? false : PDFJS.cMapPacked;
 
-/*
+/**
  * By default fonts are converted to OpenType fonts and loaded via font face
  * rules. If disabled, the font will be rendered using a built in font renderer
  * that constructs the glyphs with primitive path commands.
  * @var {boolean}
  */
 PDFJS.disableFontFace = (PDFJS.disableFontFace === undefined ?
                          false : PDFJS.disableFontFace);
 
@@ -1410,16 +1406,24 @@ PDFJS.disableCreateObjectURL = (PDFJS.di
 /**
  * Disables WebGL usage.
  * @var {boolean}
  */
 PDFJS.disableWebGL = (PDFJS.disableWebGL === undefined ?
                       true : PDFJS.disableWebGL);
 
 /**
+ * Disables fullscreen support, and by extension Presentation Mode,
+ * in browsers which support the fullscreen API.
+ * @var {boolean}
+ */
+PDFJS.disableFullscreen = (PDFJS.disableFullscreen === undefined ?
+                           false : PDFJS.disableFullscreen);
+
+/**
  * Enables CSS only zooming.
  * @var {boolean}
  */
 PDFJS.useOnlyCssZoom = (PDFJS.useOnlyCssZoom === undefined ?
                         false : PDFJS.useOnlyCssZoom);
 
 /**
  * Controls the logging level.
@@ -1428,24 +1432,33 @@ PDFJS.useOnlyCssZoom = (PDFJS.useOnlyCss
  * - warnings [default]
  * - infos
  * @var {number}
  */
 PDFJS.verbosity = (PDFJS.verbosity === undefined ?
                    PDFJS.VERBOSITY_LEVELS.warnings : PDFJS.verbosity);
 
 /**
- * The maximum supported canvas size in total pixels e.g. width * height. 
+ * The maximum supported canvas size in total pixels e.g. width * height.
  * The default value is 4096 * 4096. Use -1 for no limit.
  * @var {number}
  */
 PDFJS.maxCanvasPixels = (PDFJS.maxCanvasPixels === undefined ?
                          16777216 : PDFJS.maxCanvasPixels);
 
 /**
+ * Opens external links in a new window if enabled. The default behavior opens
+ * external links in the PDF.js window.
+ * @var {boolean}
+ */
+PDFJS.openExternalLinksInNewWindow = (
+  PDFJS.openExternalLinksInNewWindow === undefined ?
+    false : PDFJS.openExternalLinksInNewWindow);
+
+/**
  * Document initialization / loading parameters object.
  *
  * @typedef {Object} DocumentInitParameters
  * @property {string}     url   - The URL of the PDF.
  * @property {TypedArray|Array|string} data - Binary PDF data. Use typed arrays
  *   (Uint8Array) to improve the memory usage. If PDF data is BASE64-encoded,
  *   use atob() to convert it to a binary string first.
  * @property {Object}     httpHeaders - Basic authentication headers.
@@ -1874,17 +1887,17 @@ var PDFDocumentProxy = (function PDFDocu
  *                    (default value is 'display').
  * @property {Object} imageLayer - (optional) An object that has beginLayout,
  *                    endLayout and appendImage functions.
  * @property {function} continueCallback - (deprecated) A function that will be
  *                      called each time the rendering is paused.  To continue
  *                      rendering call the function that is the first argument
  *                      to the callback.
  */
- 
+
 /**
  * PDF page operator list.
  *
  * @typedef {Object} PDFOperatorList
  * @property {Array} fnArray - Array containing the operator functions.
  * @property {Array} argsArray - Array containing the arguments of the
  *                               functions.
  */
@@ -5113,17 +5126,16 @@ var CanvasGraphics = (function CanvasGra
   for (var op in OPS) {
     CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
   }
 
   return CanvasGraphics;
 })();
 
 
-
 var WebGLUtils = (function WebGLUtilsClosure() {
   function loadShader(gl, code, shaderType) {
     var shader = gl.createShader(shaderType);
     gl.shaderSource(shader, code);
     gl.compileShader(shader);
     var compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
     if (!compiled) {
       var errorMsg = gl.getShaderInfoLog(shader);
@@ -6230,16 +6242,19 @@ var AnnotationUtils = (function Annotati
   }
 
   function getHtmlElementForLinkAnnotation(item) {
     var container = initContainer(item, true);
     container.className = 'annotLink';
 
     var link = document.createElement('a');
     link.href = link.title = item.url || '';
+    if (item.url && PDFJS.openExternalLinksInNewWindow) {
+      link.target = '_blank';
+    }
 
     container.appendChild(link);
 
     return container;
   }
 
   function getHtmlElement(data, objs) {
     switch (data.annotationType) {
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -17,18 +17,18 @@
 /*jshint globalstrict: false */
 /* globals PDFJS */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '1.0.1149';
-PDFJS.build = 'bc7a110';
+PDFJS.version = '1.1.24';
+PDFJS.build = 'f6a8110';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -944,20 +944,16 @@ function isInt(v) {
 function isNum(v) {
   return typeof v === 'number';
 }
 
 function isString(v) {
   return typeof v === 'string';
 }
 
-function isNull(v) {
-  return v === null;
-}
-
 function isName(v) {
   return v instanceof Name;
 }
 
 function isCmd(v, cmd) {
   return v instanceof Cmd && (cmd === undefined || v.cmd === cmd);
 }
 
@@ -1837,17 +1833,16 @@ var ChunkedStreamManager = (function Chu
       return chunk;
     }
   };
 
   return ChunkedStreamManager;
 })();
 
 
-
 // The maximum number of bytes fetched per range request
 var RANGE_CHUNK_SIZE = 65536;
 
 // TODO(mack): Make use of PDFJS.Util.inherit() when it becomes available
 var BasePdfManager = (function BasePdfManagerClosure() {
   function BasePdfManager() {
     throw new Error('Cannot initialize BaseManagerManager');
   }
@@ -2043,17 +2038,16 @@ var NetworkPdfManager = (function Networ
       function NetworkPdfManager_terminate() {
     this.streamManager.networkManager.abortAllRequests();
   };
 
   return NetworkPdfManager;
 })();
 
 
-
 var Page = (function PageClosure() {
 
   var LETTER_SIZE_MEDIABOX = [0, 0, 612, 792];
 
   function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) {
     this.pdfManager = pdfManager;
     this.pageIndex = pageIndex;
     this.pageDict = pageDict;
@@ -2553,17 +2547,16 @@ var PDFDocument = (function PDFDocumentC
       return this.catalog.cleanup();
     }
   };
 
   return PDFDocument;
 })();
 
 
-
 var Name = (function NameClosure() {
   function Name(name) {
     this.name = name;
   }
 
   Name.prototype = {};
 
   var nameCache = {};
@@ -3086,17 +3079,17 @@ var Catalog = (function CatalogClosure()
       var openactionDict = this.catDict.get('OpenAction');
       if (isDict(openactionDict)) {
         var objType = openactionDict.get('Type');
         var actionType = openactionDict.get('S');
         var action = openactionDict.get('N');
         var isPrintAction = (isName(objType) && objType.name === 'Action' &&
                             isName(actionType) && actionType.name === 'Named' &&
                             isName(action) && action.name === 'Print');
-        
+
         if (isPrintAction) {
           javaScript.push('print(true);');
         }
       }
 
       return shadow(this, 'javaScript', javaScript);
     },
 
@@ -3942,17 +3935,17 @@ var NameTree = (function NameTreeClosure
       // Perform a binary search to quickly find the entry that
       // contains the named destination we are looking for.
       while (kidsOrNames.has('Kids')) {
         loopCount++;
         if (loopCount > MAX_NAMES_LEVELS) {
           warn('Search depth limit for named destionations has been reached.');
           return null;
         }
-        
+
         var kids = kidsOrNames.get('Kids');
         if (!isArray(kids)) {
           return null;
         }
 
         l = 0;
         r = kids.length - 1;
         while (l <= r) {
@@ -3997,20 +3990,20 @@ var NameTree = (function NameTreeClosure
       }
       return null;
     }
   };
   return NameTree;
 })();
 
 /**
- * "A PDF file can refer to the contents of another file by using a File 
+ * "A PDF file can refer to the contents of another file by using a File
  * Specification (PDF 1.1)", see the spec (7.11) for more details.
  * NOTE: Only embedded files are supported (as part of the attachments support)
- * TODO: support the 'URL' file system (with caching if !/V), portable 
+ * TODO: support the 'URL' file system (with caching if !/V), portable
  * collections attributes and related files (/RF)
  */
 var FileSpec = (function FileSpecClosure() {
   function FileSpec(root, xref) {
     if (!root || !isDict(root)) {
       return;
     }
     this.xref = xref;
@@ -4330,17 +4323,16 @@ var ExpertSubsetCharset = [
   'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior',
   'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior',
   'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior',
   'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior',
   'periodinferior', 'commainferior'
 ];
 
 
-
 var DEFAULT_ICON_SIZE = 22; // px
 var SUPPORTED_TYPES = ['Link', 'Text', 'Widget'];
 
 var Annotation = (function AnnotationClosure() {
   // 12.5.5: Algorithm: Appearance streams
   function getTransformMatrix(rect, bbox, matrix) {
     var bounds = Util.getAxialAlignedBoundingBox(bbox, matrix);
     var minX = bounds[0];
@@ -6224,20 +6216,20 @@ var ColorSpace = (function ColorSpaceClo
           return 'DeviceGrayCS';
         case 'DeviceRGB':
         case 'RGB':
           return 'DeviceRgbCS';
         case 'DeviceCMYK':
         case 'CMYK':
           return 'DeviceCmykCS';
         case 'CalGray':
-          params = cs[1].getAll();
+          params = xref.fetchIfRef(cs[1]).getAll();
           return ['CalGrayCS', params];
         case 'CalRGB':
-          params = cs[1].getAll();
+          params = xref.fetchIfRef(cs[1]).getAll();
           return ['CalRGBCS', params];
         case 'ICCBased':
           var stream = xref.fetchIfRef(cs[1]);
           var dict = stream.dict;
           numComps = dict.get('N');
           if (numComps === 1) {
             return 'DeviceGrayCS';
           } else if (numComps === 3) {
@@ -10868,17 +10860,17 @@ var PartialEvaluator = (function Partial
 
       stateManager = (stateManager || new StateManager(new TextState()));
 
       var textContent = {
         items: [],
         styles: Object.create(null)
       };
       var bidiTexts = textContent.items;
-      var SPACE_FACTOR = 0.35;
+      var SPACE_FACTOR = 0.3;
       var MULTI_SPACE_FACTOR = 1.5;
 
       var self = this;
       var xref = this.xref;
 
       resources = (xref.fetchIfRef(resources) || Dict.empty);
 
       // The xobj is parsed iff it's needed, e.g. if there is a `DO` cmd.
@@ -11307,18 +11299,21 @@ var PartialEvaluator = (function Partial
       properties.baseEncodingName = baseEncodingName;
       properties.dict = dict;
     },
 
     readToUnicode: function PartialEvaluator_readToUnicode(toUnicode) {
       var cmap, cmapObj = toUnicode;
       if (isName(cmapObj)) {
         cmap = CMapFactory.create(cmapObj,
-          { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null).getMap();
-        return new ToUnicodeMap(cmap);
+          { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null);
+        if (cmap instanceof IdentityCMap) {
+          return new IdentityToUnicodeMap(0, 0xFFFF);
+        }
+        return new ToUnicodeMap(cmap.getMap());
       } else if (isStream(cmapObj)) {
         cmap = CMapFactory.create(cmapObj,
           { url: PDFJS.cMapUrl, packed: PDFJS.cMapPacked }, null).getMap();
         // Convert UTF-16BE
         // NOTE: cmap can be a sparse array, so use forEach instead of for(;;)
         // to iterate over all keys.
         cmap.forEach(function(token, i) {
           var str = [];
@@ -15763,17 +15758,17 @@ var ToUnicodeMap = (function ToUnicodeMa
 var IdentityToUnicodeMap = (function IdentityToUnicodeMapClosure() {
   function IdentityToUnicodeMap(firstChar, lastChar) {
     this.firstChar = firstChar;
     this.lastChar = lastChar;
   }
 
   IdentityToUnicodeMap.prototype = {
     get length() {
-      error('should not access .length');
+      return (this.lastChar + 1) - this.firstChar;
     },
 
     forEach: function (callback) {
       for (var i = this.firstChar, ii = this.lastChar; i <= ii; i++) {
         callback(i, i);
       }
     },
 
@@ -16108,18 +16103,16 @@ var Font = (function FontClosure() {
         subtype = 'TrueType';
       } else {
         type = 'Type1';
       }
     }
     if (subtype === 'CIDFontType0C' && type !== 'CIDFontType0') {
       type = 'CIDFontType0';
     }
-    // XXX: Temporarily change the type for open type so we trigger a warning.
-    // This should be removed when we add support for open type.
     if (subtype === 'OpenType') {
       type = 'OpenType';
     }
 
     var data;
     switch (type) {
       case 'MMType1':
         info('MMType1 font (' + name + '), falling back to Type1.');
@@ -16236,16 +16229,18 @@ var Font = (function FontClosure() {
       // font was symbolic and there is only an identity unicode map since the
       // characters probably aren't in the correct position (fixes an issue
       // with firefox and thuluthfont).
       if ((usedFontCharCodes[fontCharCode] !== undefined ||
            fontCharCode <= 0x1f || // Control chars
            fontCharCode === 0x7F || // Control char
            fontCharCode === 0xAD || // Soft hyphen
            fontCharCode === 0xA0 || // Non breaking space
+           fontCharCode === 0x0E33 || // Thai character SARA AM 
+           fontCharCode === 0x25CC || // Dotted circle (combining mark)
            (fontCharCode >= 0x80 && fontCharCode <= 0x9F) || // Control chars
            // Prevent drawing characters in the specials unicode block.
            (fontCharCode >= 0xFFF0 && fontCharCode <= 0xFFFF) ||
            (isSymbolic && isIdentityUnicode)) &&
           nextAvailableFontCharCode <= PRIVATE_USE_OFFSET_END) { // Room left.
         // Loop to try and find a free spot in the private use area.
         do {
           fontCharCode = nextAvailableFontCharCode++;
@@ -17516,17 +17511,18 @@ var Font = (function FontClosure() {
           continue; // skipping empty tables
         }
         tables[table.tag] = table;
       }
 
       var isTrueType = !tables['CFF '];
       if (!isTrueType) {
         // OpenType font
-        if (!tables.head || !tables.hhea || !tables.maxp || !tables.post) {
+        if (header.version === 'OTTO' ||
+            !tables.head || !tables.hhea || !tables.maxp || !tables.post) {
           // no major tables: throwing everything at CFFFont
           cffFile = new Stream(tables['CFF '].data);
           cff = new CFFFont(cffFile, properties);
 
           return this.convert(name, cff, properties);
         }
 
         delete tables.glyf;
@@ -21485,17 +21481,16 @@ var FontRendererFactory = (function Font
       } else {
         return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
       }
     }
   };
 })();
 
 
-
 var GlyphsUnicode = {
   A: 0x0041,
   AE: 0x00C6,
   AEacute: 0x01FC,
   AEmacron: 0x01E2,
   AEsmall: 0xF7E6,
   Aacute: 0x00C1,
   Aacutesmall: 0xF7E1,
@@ -29492,17 +29487,16 @@ var Metrics = {
     'a188': 873,
     'a189': 927,
     'a190': 970,
     'a191': 918
   }
 };
 
 
-
 var EOF = {};
 
 function isEOF(v) {
   return (v === EOF);
 }
 
 var MAX_LENGTH_TO_CACHE = 1000;
 
@@ -33650,19 +33644,19 @@ if (typeof window === 'undefined') {
 
   var handler = new MessageHandler('worker_processor', this);
   WorkerMessageHandler.setup(handler);
 }
 
 
 /* This class implements the QM Coder decoding as defined in
  *   JPEG 2000 Part I Final Committee Draft Version 1.0
- *   Annex C.3 Arithmetic decoding procedure 
+ *   Annex C.3 Arithmetic decoding procedure
  * available at http://www.jpeg.org/public/fcd15444-1.pdf
- * 
+ *
  * The arithmetic decoder is used in conjunction with context models to decode
  * JPEG2000 and JBIG2 streams.
  */
 var ArithmeticDecoder = (function ArithmeticDecoderClosure() {
   // Table C-2
   var QeTable = [
     {qe: 0x5601, nmps: 1, nlps: 1, switchFlag: 1},
     {qe: 0x3401, nmps: 2, nlps: 6, switchFlag: 0},
@@ -34454,19 +34448,19 @@ var JpegImage = (function jpegImage() {
                                               3 * appData[12] * appData[13])
                 };
               }
             }
             // TODO APP1 - Exif
             if (fileMarker === 0xFFEE) {
               if (appData[0] === 0x41 && appData[1] === 0x64 &&
                   appData[2] === 0x6F && appData[3] === 0x62 &&
-                  appData[4] === 0x65 && appData[5] === 0) { // 'Adobe\x00'
+                  appData[4] === 0x65) { // 'Adobe'
                 adobe = {
-                  version: appData[6],
+                  version: (appData[5] << 8) | appData[6],
                   flags0: (appData[7] << 8) | appData[8],
                   flags1: (appData[9] << 8) | appData[10],
                   transformCode: appData[11]
                 };
               }
             }
             break;
 
@@ -34577,16 +34571,23 @@ var JpegImage = (function jpegImage() {
             var spectralEnd = data[offset++];
             var successiveApproximation = data[offset++];
             var processed = decodeScan(data, offset,
               frame, components, resetInterval,
               spectralStart, spectralEnd,
               successiveApproximation >> 4, successiveApproximation & 15);
             offset += processed;
             break;
+
+          case 0xFFFF: // Fill bytes
+            if (data[offset] !== 0xFF) { // Avoid skipping a valid marker.
+              offset--;
+            }
+            break;
+
           default:
             if (data[offset - 3] === 0xFF &&
                 data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE) {
               // could be incorrect encoding -- last 0xFF byte of the previous
               // block was eaten by the encoder
               offset -= 3;
               break;
             }
@@ -37011,17 +37012,16 @@ var JpxImage = (function JpxImageClosure
 
     return ReversibleTransform;
   })();
 
   return JpxImage;
 })();
 
 
-
 var Jbig2Image = (function Jbig2ImageClosure() {
   // Utility data structures
   function ContextCache() {}
 
   ContextCache.prototype = {
     getContexts: function(id) {
       if (id in this) {
         return this[id];
@@ -38488,17 +38488,16 @@ var bidi = PDFJS.bidi = (function bidiCl
       }
     }
     return createBidiText(result, isLTR);
   }
 
   return bidi;
 })();
 
-
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 
 /* Copyright 2014 Opera Software ASA
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
--- a/browser/extensions/pdfjs/content/network.js
+++ b/browser/extensions/pdfjs/content/network.js
@@ -264,9 +264,8 @@ var NetworkManager = (function NetworkMa
       delete this.pendingRequests[xhrId];
       xhr.abort();
     }
   };
 
   return NetworkManager;
 })();
 
-
--- a/browser/extensions/pdfjs/content/web/l10n.js
+++ b/browser/extensions/pdfjs/content/web/l10n.js
@@ -119,19 +119,18 @@
     },
 
     // get the direction (ltr|rtl) of the current language
     getDirection: function() {
       // http://www.w3.org/International/questions/qa-scripts
       // Arabic, Hebrew, Farsi, Pashto, Urdu
       var rtlList = ['ar', 'he', 'fa', 'ps', 'ur'];
 
-      // use the short language code for "full" codes like 'ar-sa' (issue 5440) 
+      // use the short language code for "full" codes like 'ar-sa' (issue 5440)
       var shortCode = gLanguage.split('-')[0];
 
       return (rtlList.indexOf(shortCode) >= 0) ? 'rtl' : 'ltr';
     },
 
     // translate an element or document fragment
     translate: translateFragment
   };
 })(this);
-
--- a/browser/extensions/pdfjs/content/web/viewer.css
+++ b/browser/extensions/pdfjs/content/web/viewer.css
@@ -71,16 +71,21 @@
   position: relative;
   overflow: visible;
   border: 9px solid transparent;
   background-clip: content-box;
   border-image: url(images/shadow.png) 9 9 repeat;
   background-color: white;
 }
 
+.pdfViewer.removePageBorders .page {
+  margin: 0px auto 10px auto;
+  border: none;
+}
+
 .pdfViewer .page canvas {
   margin: 0;
   display: block;
 }
 
 .pdfViewer .page .loadingIcon {
   position: absolute;
   display: block;
@@ -1746,20 +1751,16 @@ html[dir='rtl'] #documentPropertiesOverl
     display: block;
   }
   #printContainer canvas {
     position: relative;
     top: 0;
     left: 0;
     display: block;
   }
-  #printContainer div {
-    page-break-after: always;
-    page-break-inside: avoid;
-  }
 }
 
 .visibleLargeView,
 .visibleMediumView,
 .visibleSmallView {
   display: none;
 }
 
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -128,17 +128,17 @@ http://sourceforge.net/adobe/cmap/wiki/L
               <span data-l10n-id="page_rotate_ccw_label">Rotate Counterclockwise</span>
             </button>
 
             <div class="horizontalToolbarSeparator"></div>
 
             <button id="toggleHandTool" class="secondaryToolbarButton handTool" title="Enable hand tool" tabindex="60" data-l10n-id="hand_tool_enable">
               <span data-l10n-id="hand_tool_enable_label">Enable hand tool</span>
             </button>
-            
+
             <div class="horizontalToolbarSeparator"></div>
 
             <button id="documentProperties" class="secondaryToolbarButton documentProperties" title="Document Properties…" tabindex="61" data-l10n-id="document_properties">
               <span data-l10n-id="document_properties_label">Document Properties…</span>
             </button>
           </div>
         </div>  <!-- secondaryToolbar -->
 
@@ -183,20 +183,20 @@ http://sourceforge.net/adobe/cmap/wiki/L
                   <span data-l10n-id="download_label">Download</span>
                 </button>
                 <!-- <div class="toolbarButtonSpacer"></div> -->
                 <a href="#" id="viewBookmark" class="toolbarButton bookmark hiddenSmallView" title="Current view (copy or open in new window)" tabindex="35" data-l10n-id="bookmark">
                   <span data-l10n-id="bookmark_label">Current View</span>
                 </a>
 
                 <div class="verticalToolbarSeparator hiddenSmallView"></div>
-                
+
                 <button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="36" data-l10n-id="tools">
                   <span data-l10n-id="tools_label">Tools</span>
-                </button> 
+                </button>
               </div>
               <div class="outerCenter">
                 <div class="innerCenter" id="toolbarViewerMiddle">
                   <div class="splitToolbarButton">
                     <button id="zoomOut" class="toolbarButton zoomOut" title="Zoom Out" tabindex="21" data-l10n-id="zoom_out">
                       <span data-l10n-id="zoom_out_label">Zoom Out</span>
                     </button>
                     <div class="splitToolbarButtonSeparator"></div>
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -184,69 +184,113 @@ function watchScroll(viewAreaElement, ca
       return;
     }
     // schedule an invocation of scroll for next animation frame.
     rAF = window.requestAnimationFrame(function viewAreaElementScrolled() {
       rAF = null;
 
       var currentY = viewAreaElement.scrollTop;
       var lastY = state.lastY;
-      if (currentY > lastY) {
-        state.down = true;
-      } else if (currentY < lastY) {
-        state.down = false;
+      if (currentY !== lastY) {
+        state.down = currentY > lastY;
       }
       state.lastY = currentY;
-      // else do nothing and use previous value
       callback(state);
     });
   };
 
   var state = {
     down: true,
     lastY: viewAreaElement.scrollTop,
     _eventHandler: debounceScroll
   };
 
   var rAF = null;
   viewAreaElement.addEventListener('scroll', debounceScroll, true);
   return state;
 }
 
 /**
+ * Use binary search to find the index of the first item in a given array which
+ * passes a given condition. The items are expected to be sorted in the sense
+ * that if the condition is true for one item in the array, then it is also true
+ * for all following items.
+ *
+ * @returns {Number} Index of the first array element to pass the test,
+ *                   or |items.length| if no such element exists.
+ */
+function binarySearchFirstItem(items, condition) {
+  var minIndex = 0;
+  var maxIndex = items.length - 1;
+
+  if (items.length === 0 || !condition(items[maxIndex])) {
+    return items.length;
+  }
+  if (condition(items[minIndex])) {
+    return minIndex;
+  }
+
+  while (minIndex < maxIndex) {
+    var currentIndex = (minIndex + maxIndex) >> 1;
+    var currentItem = items[currentIndex];
+    if (condition(currentItem)) {
+      maxIndex = currentIndex;
+    } else {
+      minIndex = currentIndex + 1;
+    }
+  }
+  return minIndex; /* === maxIndex */
+}
+
+/**
  * Generic helper to find out what elements are visible within a scroll pane.
  */
 function getVisibleElements(scrollEl, views, sortByVisibility) {
   var top = scrollEl.scrollTop, bottom = top + scrollEl.clientHeight;
   var left = scrollEl.scrollLeft, right = left + scrollEl.clientWidth;
 
-  var visible = [], view;
+  function isElementBottomBelowViewTop(view) {
+    var element = view.div;
+    var elementBottom =
+      element.offsetTop + element.clientTop + element.clientHeight;
+    return elementBottom > top;
+  }
+
+  var visible = [], view, element;
   var currentHeight, viewHeight, hiddenHeight, percentHeight;
   var currentWidth, viewWidth;
-  for (var i = 0, ii = views.length; i < ii; ++i) {
+  var firstVisibleElementInd = (views.length === 0) ? 0 :
+    binarySearchFirstItem(views, isElementBottomBelowViewTop);
+
+  for (var i = firstVisibleElementInd, ii = views.length; i < ii; i++) {
     view = views[i];
-    currentHeight = view.el.offsetTop + view.el.clientTop;
-    viewHeight = view.el.clientHeight;
-    if ((currentHeight + viewHeight) < top) {
-      continue;
-    }
+    element = view.div;
+    currentHeight = element.offsetTop + element.clientTop;
+    viewHeight = element.clientHeight;
+
     if (currentHeight > bottom) {
       break;
     }
-    currentWidth = view.el.offsetLeft + view.el.clientLeft;
-    viewWidth = view.el.clientWidth;
-    if ((currentWidth + viewWidth) < left || currentWidth > right) {
+
+    currentWidth = element.offsetLeft + element.clientLeft;
+    viewWidth = element.clientWidth;
+    if (currentWidth + viewWidth < left || currentWidth > right) {
       continue;
     }
     hiddenHeight = Math.max(0, top - currentHeight) +
       Math.max(0, currentHeight + viewHeight - bottom);
     percentHeight = ((viewHeight - hiddenHeight) * 100 / viewHeight) | 0;
 
-    visible.push({ id: view.id, x: currentWidth, y: currentHeight,
-      view: view, percent: percentHeight });
+    visible.push({
+      id: view.id,
+      x: currentWidth,
+      y: currentHeight,
+      view: view,
+      percent: percentHeight
+    });
   }
 
   var first = visible[0];
   var last = visible[visible.length - 1];
 
   if (sortByVisibility) {
     visible.sort(function(a, b) {
       var pc = a.percent - b.percent;
@@ -610,17 +654,17 @@ var DownloadManager = (function Download
         originalUrl: url,
         filename: filename
       });
     },
 
     downloadData: function DownloadManager_downloadData(data, filename,
                                                         contentType) {
       var blobUrl = PDFJS.createObjectURL(data, contentType);
-      
+
       FirefoxCom.request('download', {
         blobUrl: blobUrl,
         originalUrl: blobUrl,
         filename: filename,
         isAttachment: true
       });
     },
 
@@ -897,17 +941,16 @@ var PDFFindBar = (function PDFFindBarClo
         this.open();
       }
     }
   };
   return PDFFindBar;
 })();
 
 
-
 var FindStates = {
   FIND_FOUND: 0,
   FIND_NOTFOUND: 1,
   FIND_WRAPPED: 2,
   FIND_PENDING: 3
 };
 
 var FIND_SCROLL_OFFSET_TOP = -50;
@@ -946,17 +989,18 @@ var PDFFindController = (function PDFFin
       '\u201A': '\'', // Single low-9 quotation mark
       '\u201B': '\'', // Single high-reversed-9 quotation mark
       '\u201C': '"', // Left double quotation mark
       '\u201D': '"', // Right double quotation mark
       '\u201E': '"', // Double low-9 quotation mark
       '\u201F': '"', // Double high-reversed-9 quotation mark
       '\u00BC': '1/4', // Vulgar fraction one quarter
       '\u00BD': '1/2', // Vulgar fraction one half
-      '\u00BE': '3/4' // Vulgar fraction three quarters
+      '\u00BE': '3/4', // Vulgar fraction three quarters
+      '\u00A0': ' ' // No-break space
     };
     this.findBar = options.findBar || null;
 
     // Compile the regular expression for text normalization once
     var replace = Object.keys(this.charactersToNormalize).join('');
     this.normalizationRegex = new RegExp('[' + replace + ']', 'g');
 
     var events = [
@@ -1182,17 +1226,17 @@ var PDFFindController = (function PDFFin
       } else {
         // No matches, so attempt to search the next page.
         this.advanceOffsetPage(previous);
         if (offset.wrapped) {
           offset.matchIdx = null;
           if (this.pagesToSearch < 0) {
             // No point in wrapping again, there were no matches.
             this.updateMatch(false);
-            // while matches were not found, searching for a page 
+            // while matches were not found, searching for a page
             // with matches should nevertheless halt.
             return true;
           }
         }
         // Matches were not found (and searching is not done).
         return false;
       }
     },
@@ -1235,39 +1279,39 @@ var PDFFindController = (function PDFFin
 
     advanceOffsetPage: function PDFFindController_advanceOffsetPage(previous) {
       var offset = this.offset;
       var numPages = this.extractTextPromises.length;
       offset.pageIdx = (previous ? offset.pageIdx - 1 : offset.pageIdx + 1);
       offset.matchIdx = null;
 
       this.pagesToSearch--;
-      
+
       if (offset.pageIdx >= numPages || offset.pageIdx < 0) {
         offset.pageIdx = (previous ? numPages - 1 : 0);
         offset.wrapped = true;
       }
     },
 
     updateMatch: function PDFFindController_updateMatch(found) {
       var state = FindStates.FIND_NOTFOUND;
       var wrapped = this.offset.wrapped;
       this.offset.wrapped = false;
-    
+
       if (found) {
         var previousPage = this.selected.pageIdx;
         this.selected.pageIdx = this.offset.pageIdx;
         this.selected.matchIdx = this.offset.matchIdx;
         state = (wrapped ? FindStates.FIND_WRAPPED : FindStates.FIND_FOUND);
         // Update the currently selected page to wipe out any selected matches.
         if (previousPage !== -1 && previousPage !== this.selected.pageIdx) {
           this.updatePage(previousPage);
         }
       }
-    
+
       this.updateUIState(state, this.state.findPrevious);
       if (this.selected.pageIdx !== -1) {
         this.updatePage(this.selected.pageIdx);
       }
     },
 
     updateUIState: function PDFFindController_updateUIState(state, previous) {
       if (this.integratedFind) {
@@ -1281,17 +1325,16 @@ var PDFFindController = (function PDFFin
       }
       this.findBar.updateUIState(state, previous);
     }
   };
   return PDFFindController;
 })();
 
 
-
 var PDFHistory = {
   initialized: false,
   initialDestination: null,
 
   /**
    * @param {string} fingerprint
    * @param {IPDFLinkService} linkService
    */
@@ -1858,18 +1901,18 @@ var PresentationMode = {
     }
     this._setSwitchInProgress();
     this._notifyStateChange();
 
     if (this.container.requestFullscreen) {
       this.container.requestFullscreen();
     } else if (this.container.mozRequestFullScreen) {
       this.container.mozRequestFullScreen();
-    } else if (this.container.webkitRequestFullScreen) {
-      this.container.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
+    } else if (this.container.webkitRequestFullscreen) {
+      this.container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
     } else if (this.container.msRequestFullscreen) {
       this.container.msRequestFullscreen();
     } else {
       return false;
     }
 
     this.args = {
       page: PDFViewerApplication.page,
@@ -2612,17 +2655,17 @@ var DocumentProperties = {
   },
 
   close: function documentPropertiesClose() {
     OverlayManager.close(this.overlayName);
   },
 
   parseDate: function documentPropertiesParseDate(inputDate) {
     // This is implemented according to the PDF specification (see
-    // http://www.gnupdf.org/Date for an overview), but note that 
+    // http://www.gnupdf.org/Date for an overview), but note that
     // Adobe Reader doesn't handle changing the date to universal time
     // and doesn't use the user's time zone (they're effectively ignoring
     // the HH' and mm' parts of the date string).
     var dateToParse = inputDate;
     if (dateToParse === undefined) {
       return '';
     }
 
@@ -2894,17 +2937,16 @@ var PDFPageView = (function PDFPageViewC
 
     this.annotationLayer = null;
 
     var div = document.createElement('div');
     div.id = 'pageContainer' + this.id;
     div.className = 'page';
     div.style.width = Math.floor(this.viewport.width) + 'px';
     div.style.height = Math.floor(this.viewport.height) + 'px';
-    this.el = div; // TODO replace 'el' property usage
     this.div = div;
 
     container.appendChild(div);
   }
 
   PDFPageView.prototype = {
     setPdfPage: function PDFPageView_setPdfPage(pdfPage) {
       this.pdfPage = pdfPage;
@@ -3919,16 +3961,18 @@ DefaultAnnotationsLayerFactory.prototype
 
 /**
  * @typedef {Object} PDFViewerOptions
  * @property {HTMLDivElement} container - The container for the viewer element.
  * @property {HTMLDivElement} viewer - (optional) The viewer element.
  * @property {IPDFLinkService} linkService - The navigation/linking service.
  * @property {PDFRenderingQueue} renderingQueue - (optional) The rendering
  *   queue object.
+ * @property {boolean} removePageBorders - (optional) Removes the border shadow
+ *   around the pages. The default is false.
  */
 
 /**
  * Simple viewer control to display PDF content/pages.
  * @class
  * @implements {IRenderableView}
  */
 var PDFViewer = (function pdfViewer() {
@@ -3955,30 +3999,35 @@ var PDFViewer = (function pdfViewer() {
   /**
    * @constructs PDFViewer
    * @param {PDFViewerOptions} options
    */
   function PDFViewer(options) {
     this.container = options.container;
     this.viewer = options.viewer || options.container.firstElementChild;
     this.linkService = options.linkService || new SimpleLinkService(this);
+    this.removePageBorders = options.removePageBorders || false;
 
     this.defaultRenderingQueue = !options.renderingQueue;
     if (this.defaultRenderingQueue) {
       // Custom rendering queue is not specified, using default one
       this.renderingQueue = new PDFRenderingQueue();
       this.renderingQueue.setViewer(this);
     } else {
       this.renderingQueue = options.renderingQueue;
     }
 
     this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
     this.updateInProgress = false;
     this.presentationModeState = PresentationModeState.UNKNOWN;
     this._resetView();
+
+    if (this.removePageBorders) {
+      this.viewer.classList.add('removePageBorders');
+    }
   }
 
   PDFViewer.prototype = /** @lends PDFViewer.prototype */{
     get pagesCount() {
       return this.pages.length;
     },
 
     getPageView: function (index) {
@@ -4181,16 +4230,20 @@ var PDFViewer = (function pdfViewer() {
 
         var event = document.createEvent('CustomEvent');
         event.initCustomEvent('pagesinit', true, true, null);
         self.container.dispatchEvent(event);
 
         if (this.defaultRenderingQueue) {
           this.update();
         }
+
+        if (this.findController) {
+          this.findController.resolveFirstPage();
+        }
       }.bind(this));
     },
 
     _resetView: function () {
       this.pages = [];
       this._currentPageNumber = 1;
       this._currentScale = UNKNOWN_SCALE;
       this._currentScaleValue = null;
@@ -4210,22 +4263,37 @@ var PDFViewer = (function pdfViewer() {
         return;
       }
       this.update();
       for (var i = 0, ii = this.pages.length; i < ii; i++) {
         this.pages[i].updatePosition();
       }
     },
 
+    _setScaleDispatchEvent: function pdfViewer_setScaleDispatchEvent(
+        newScale, newValue, preset) {
+      var event = document.createEvent('UIEvents');
+      event.initUIEvent('scalechange', true, true, window, 0);
+      event.scale = newScale;
+      if (preset) {
+        event.presetValue = newValue;
+      }
+      this.container.dispatchEvent(event);
+    },
+
     _setScaleUpdatePages: function pdfViewer_setScaleUpdatePages(
         newScale, newValue, noScroll, preset) {
       this._currentScaleValue = newValue;
       if (newScale === this._currentScale) {
+        if (preset) {
+          this._setScaleDispatchEvent(newScale, newValue, true);
+        }
         return;
       }
+
       for (var i = 0, ii = this.pages.length; i < ii; i++) {
         this.pages[i].update(newScale);
       }
       this._currentScale = newScale;
 
       if (!noScroll) {
         var page = this._currentPageNumber, dest;
         var inPresentationMode =
@@ -4235,23 +4303,17 @@ var PDFViewer = (function pdfViewer() {
             !IGNORE_CURRENT_POSITION_ON_ZOOM) {
           page = this.location.pageNumber;
           dest = [null, { name: 'XYZ' }, this.location.left,
             this.location.top, null];
         }
         this.scrollPageIntoView(page, dest);
       }
 
-      var event = document.createEvent('UIEvents');
-      event.initUIEvent('scalechange', true, true, window, 0);
-      event.scale = newScale;
-      if (preset) {
-        event.presetValue = newValue;
-      }
-      this.container.dispatchEvent(event);
+      this._setScaleDispatchEvent(newScale, newValue, preset);
     },
 
     _setScale: function pdfViewer_setScale(value, noScroll) {
       if (value === 'custom') {
         return;
       }
       var scale = parseFloat(value);
 
@@ -4259,18 +4321,20 @@ var PDFViewer = (function pdfViewer() {
         this._setScaleUpdatePages(scale, value, noScroll, false);
       } else {
         var currentPage = this.pages[this._currentPageNumber - 1];
         if (!currentPage) {
           return;
         }
         var inPresentationMode =
           this.presentationModeState === PresentationModeState.FULLSCREEN;
-        var hPadding = inPresentationMode ? 0 : SCROLLBAR_PADDING;
-        var vPadding = inPresentationMode ? 0 : VERTICAL_PADDING;
+        var hPadding = (inPresentationMode || this.removePageBorders) ?
+          0 : SCROLLBAR_PADDING;
+        var vPadding = (inPresentationMode || this.removePageBorders) ?
+          0 : VERTICAL_PADDING;
         var pageWidthScale = (this.container.clientWidth - hPadding) /
                              currentPage.width * currentPage.scale;
         var pageHeightScale = (this.container.clientHeight - vPadding) /
                               currentPage.height * currentPage.scale;
         switch (value) {
           case 'page-actual':
             scale = 1;
             break;
@@ -4363,19 +4427,22 @@ var PDFViewer = (function pdfViewer() {
           scale = 'page-height';
           break;
         case 'FitR':
           x = dest[2];
           y = dest[3];
           width = dest[4] - x;
           height = dest[5] - y;
           var viewerContainer = this.container;
-          widthScale = (viewerContainer.clientWidth - SCROLLBAR_PADDING) /
+          var hPadding = this.removePageBorders ? 0 : SCROLLBAR_PADDING;
+          var vPadding = this.removePageBorders ? 0 : VERTICAL_PADDING;
+
+          widthScale = (viewerContainer.clientWidth - hPadding) /
             width / CSS_UNITS;
-          heightScale = (viewerContainer.clientHeight - SCROLLBAR_PADDING) /
+          heightScale = (viewerContainer.clientHeight - vPadding) /
             height / CSS_UNITS;
           scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
           break;
         default:
           return;
       }
 
       if (scale && scale !== this.currentScale) {
@@ -4722,17 +4789,16 @@ var PDFThumbnailView = (function PDFThum
     anchor.onclick = function stopNavigation() {
       linkService.page = id;
       return false;
     };
 
     var div = document.createElement('div');
     div.id = 'thumbnailContainer' + id;
     div.className = 'thumbnail';
-    this.el = div; // TODO: replace 'el' property usage.
     this.div = div;
 
     if (id === 1) {
       // Highlight the thumbnail of the first page when no page number is
       // specified (or exists in cache) when the document is loaded.
       div.classList.add('selected');
     }
 
@@ -4990,17 +5056,19 @@ var PDFThumbnailViewer = (function PDFTh
 
     scrollThumbnailIntoView:
         function PDFThumbnailViewer_scrollThumbnailIntoView(page) {
       var selected = document.querySelector('.thumbnail.selected');
       if (selected) {
         selected.classList.remove('selected');
       }
       var thumbnail = document.getElementById('thumbnailContainer' + page);
-      thumbnail.classList.add('selected');
+      if (thumbnail) {
+        thumbnail.classList.add('selected');
+      }
       var visibleThumbs = this._getVisibleThumbs();
       var numVisibleThumbs = visibleThumbs.views.length;
 
       // If the thumbnail isn't currently visible, scroll it into view.
       if (numVisibleThumbs > 0) {
         var first = visibleThumbs.first.id;
         // Account for only one thumbnail being visible.
         var last = (numVisibleThumbs > 1 ? visibleThumbs.last.id : first);
@@ -5150,31 +5218,44 @@ var PDFOutlineView = (function PDFOutlin
       while (container.firstChild) {
         container.removeChild(container.firstChild);
       }
     },
 
     /**
      * @private
      */
+    _dispatchEvent: function PDFOutlineView_dispatchEvent(outlineCount) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('outlineloaded', true, true, {
+        outlineCount: outlineCount
+      });
+      this.container.dispatchEvent(event);
+    },
+
+    /**
+     * @private
+     */
     _bindLink: function PDFOutlineView_bindLink(element, item) {
       var linkService = this.linkService;
       element.href = linkService.getDestinationHash(item.dest);
       element.onclick = function goToDestination(e) {
         linkService.navigateTo(item.dest);
         return false;
       };
     },
 
     render: function PDFOutlineView_render() {
       var outline = this.outline;
+      var outlineCount = 0;
 
       this.reset();
 
       if (!outline) {
+        this._dispatchEvent(outlineCount);
         return;
       }
 
       var queue = [{ parent: this.container, items: this.outline }];
       while (queue.length > 0) {
         var levelData = queue.shift();
         for (var i = 0, len = levelData.items.length; i < len; i++) {
           var item = levelData.items[i];
@@ -5188,18 +5269,21 @@ var PDFOutlineView = (function PDFOutlin
           if (item.items.length > 0) {
             var itemsDiv = document.createElement('div');
             itemsDiv.className = 'outlineItems';
             div.appendChild(itemsDiv);
             queue.push({ parent: itemsDiv, items: item.items });
           }
 
           levelData.parent.appendChild(div);
+          outlineCount++;
         }
       }
+
+      this._dispatchEvent(outlineCount);
     }
   };
 
   return PDFOutlineView;
 })();
 
 
 /**
@@ -5229,46 +5313,63 @@ var PDFAttachmentView = (function PDFAtt
       while (container.firstChild) {
         container.removeChild(container.firstChild);
       }
     },
 
     /**
      * @private
      */
+    _dispatchEvent: function PDFAttachmentView_dispatchEvent(attachmentsCount) {
+      var event = document.createEvent('CustomEvent');
+      event.initCustomEvent('attachmentsloaded', true, true, {
+        attachmentsCount: attachmentsCount
+      });
+      this.container.dispatchEvent(event);
+    },
+
+    /**
+     * @private
+     */
     _bindLink: function PDFAttachmentView_bindLink(button, content, filename) {
       button.onclick = function downloadFile(e) {
         this.downloadManager.downloadData(content, filename, '');
         return false;
       }.bind(this);
     },
 
     render: function PDFAttachmentView_render() {
       var attachments = this.attachments;
+      var attachmentsCount = 0;
 
       this.reset();
 
       if (!attachments) {
+        this._dispatchEvent(attachmentsCount);
         return;
       }
 
       var names = Object.keys(attachments).sort(function(a, b) {
         return a.toLowerCase().localeCompare(b.toLowerCase());
       });
-      for (var i = 0, len = names.length; i < len; i++) {
+      attachmentsCount = names.length;
+
+      for (var i = 0; i < attachmentsCount; i++) {
         var item = attachments[names[i]];
         var filename = getFileName(item.filename);
         var div = document.createElement('div');
         div.className = 'attachmentsItem';
         var button = document.createElement('button');
         this._bindLink(button, item.content, filename);
         button.textContent = filename;
         div.appendChild(button);
         this.container.appendChild(div);
       }
+
+      this._dispatchEvent(attachmentsCount);
     }
   };
 
   return PDFAttachmentView;
 })();
 
 
 var PDFViewerApplication = {
@@ -5287,16 +5388,17 @@ var PDFViewerApplication = {
   pageRotation: 0,
   updateScaleControls: true,
   isInitialViewSet: false,
   animationStartedPromise: null,
   mouseScrollTimeStamp: 0,
   mouseScrollDelta: 0,
   preferenceSidebarViewOnLoad: SidebarView.NONE,
   preferencePdfBugEnabled: false,
+  preferenceShowPreviousViewOnLoad: true,
   isViewerEmbedded: (window.parent !== window),
   url: '',
 
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
     var pdfRenderingQueue = new PDFRenderingQueue();
     pdfRenderingQueue.onIdle = this.cleanup.bind(this);
     this.pdfRenderingQueue = pdfRenderingQueue;
@@ -5405,16 +5507,22 @@ var PDFViewerApplication = {
         PDFJS.disableWebGL = !value;
       }),
       Preferences.get('sidebarViewOnLoad').then(function resolved(value) {
         self.preferenceSidebarViewOnLoad = value;
       }),
       Preferences.get('pdfBugEnabled').then(function resolved(value) {
         self.preferencePdfBugEnabled = value;
       }),
+      Preferences.get('showPreviousViewOnLoad').then(function resolved(value) {
+        self.preferenceShowPreviousViewOnLoad = value;
+        if (!value && window.history.state) {
+          window.history.replaceState(null, '');
+        }
+      }),
       Preferences.get('disableTextLayer').then(function resolved(value) {
         if (PDFJS.disableTextLayer === true) {
           return;
         }
         PDFJS.disableTextLayer = value;
       }),
       Preferences.get('disableRange').then(function resolved(value) {
         if (PDFJS.disableRange === true) {
@@ -5429,16 +5537,17 @@ var PDFViewerApplication = {
         if (PDFJS.disableFontFace === true) {
           return;
         }
         PDFJS.disableFontFace = value;
       }),
       Preferences.get('useOnlyCssZoom').then(function resolved(value) {
         PDFJS.useOnlyCssZoom = value;
       })
+
       // TODO move more preferences and other async stuff here
     ]).catch(function (reason) { });
 
     return initializedPromise.then(function () {
       PDFViewerApplication.initialized = true;
     });
   },
 
@@ -5491,16 +5600,19 @@ var PDFViewerApplication = {
                   doc.webkitRequestFullScreen || doc.msRequestFullscreen;
 
     if (document.fullscreenEnabled === false ||
         document.mozFullScreenEnabled === false ||
         document.webkitFullscreenEnabled === false ||
         document.msFullscreenEnabled === false) {
       support = false;
     }
+    if (support && PDFJS.disableFullscreen === true) {
+      support = false;
+    }
 
     return PDFJS.shadow(this, 'supportsFullscreen', support);
   },
 
   get supportsIntegratedFind() {
     var support = false;
     support = FirefoxCom.requestSync('supportsIntegratedFind');
 
@@ -5595,16 +5707,20 @@ var PDFViewerApplication = {
     } catch (e) {
       // decodeURIComponent may throw URIError,
       // fall back to using the unprocessed url in that case
       this.setTitle(url);
     }
   },
 
   setTitle: function pdfViewSetTitle(title) {
+    if (this.isViewerEmbedded) {
+      // Embedded PDF viewers should not be changing their parent page's title.
+      return;
+    }
     document.title = title;
   },
 
   close: function pdfViewClose() {
     var errorWrapper = document.getElementById('errorWrapper');
     errorWrapper.setAttribute('hidden', 'true');
 
     if (!this.pdfDocument) {
@@ -5979,42 +6095,36 @@ var PDFViewerApplication = {
       downloadedPromise.then(function () {
         var event = document.createEvent('CustomEvent');
         event.initCustomEvent('documentload', true, true, {});
         window.dispatchEvent(event);
       });
 
       self.loadingBar.setWidth(document.getElementById('viewer'));
 
-      self.findController.resolveFirstPage();
-
       if (!PDFJS.disableHistory && !self.isViewerEmbedded) {
         // The browsing history is only enabled when the viewer is standalone,
         // i.e. not when it is embedded in a web page.
         PDFHistory.initialize(self.documentFingerprint, self);
       }
     });
 
     // Fetch the necessary preference values.
-    var showPreviousViewOnLoad;
-    var showPreviousViewOnLoadPromise =
-      Preferences.get('showPreviousViewOnLoad').then(function (prefValue) {
-        showPreviousViewOnLoad = prefValue;
-      });
     var defaultZoomValue;
     var defaultZoomValuePromise =
       Preferences.get('defaultZoomValue').then(function (prefValue) {
         defaultZoomValue = prefValue;
       });
 
     var storePromise = store.initializedPromise;
-    Promise.all([firstPagePromise, storePromise, showPreviousViewOnLoadPromise,
-                 defaultZoomValuePromise]).then(function resolved() {
+    Promise.all([firstPagePromise, storePromise, defaultZoomValuePromise]).then(
+        function resolved() {
       var storedHash = null;
-      if (showPreviousViewOnLoad && store.get('exists', false)) {
+      if (PDFViewerApplication.preferenceShowPreviousViewOnLoad &&
+          store.get('exists', false)) {
         var pageNum = store.get('page', '1');
         var zoom = defaultZoomValue ||
                    store.get('zoom', self.pdfViewer.currentScale);
         var left = store.get('scrollLeft', '0');
         var top = store.get('scrollTop', '0');
 
         storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
                      left + ',' + top;
@@ -6210,20 +6320,16 @@ var PDFViewerApplication = {
     this.pdfRenderingQueue.renderHighestPriority();
   },
 
   setHash: function pdfViewSetHash(hash) {
     if (!this.isInitialViewSet) {
       this.initialBookmark = hash;
       return;
     }
-
-    var validFitZoomValues = ['Fit','FitB','FitH','FitBH',
-      'FitV','FitBV','FitR'];
-
     if (!hash) {
       return;
     }
 
     if (hash.indexOf('=') >= 0) {
       var params = this.parseQueryString(hash);
       // borrowing syntax from "Parameters for Opening PDF Files"
       if ('nameddest' in params) {
@@ -6231,33 +6337,49 @@ var PDFViewerApplication = {
         this.navigateTo(params.nameddest);
         return;
       }
       var pageNumber, dest;
       if ('page' in params) {
         pageNumber = (params.page | 0) || 1;
       }
       if ('zoom' in params) {
+        // Build the destination array.
         var zoomArgs = params.zoom.split(','); // scale,left,top
-        // building destination array
-
-        // If the zoom value, it has to get divided by 100. If it is a string,
-        // it should stay as it is.
         var zoomArg = zoomArgs[0];
         var zoomArgNumber = parseFloat(zoomArg);
-        var destName = 'XYZ';
-        if (zoomArgNumber) {
-          zoomArg = zoomArgNumber / 100;
-        } else if (validFitZoomValues.indexOf(zoomArg) >= 0) {
-          destName = zoomArg;
+
+        if (zoomArg.indexOf('Fit') === -1) {
+          // If the zoomArg is a number, it has to get divided by 100. If it's
+          // a string, it should stay as it is.
+          dest = [null, { name: 'XYZ' },
+                  zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+                  zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+                  (zoomArgNumber ? zoomArgNumber / 100 : zoomArg)];
+        } else {
+          if (zoomArg === 'Fit' || zoomArg === 'FitB') {
+            dest = [null, { name: zoomArg }];
+          } else if ((zoomArg === 'FitH' || zoomArg === 'FitBH') ||
+                     (zoomArg === 'FitV' || zoomArg === 'FitBV')) {
+            dest = [null, { name: zoomArg },
+                    zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null];
+          } else if (zoomArg === 'FitR') {
+            if (zoomArgs.length !== 5) {
+              console.error('pdfViewSetHash: ' +
+                            'Not enough parameters for \'FitR\'.');
+            } else {
+              dest = [null, { name: zoomArg },
+                      (zoomArgs[1] | 0), (zoomArgs[2] | 0),
+                      (zoomArgs[3] | 0), (zoomArgs[4] | 0)];
+            }
+          } else {
+            console.error('pdfViewSetHash: \'' + zoomArg +
+                          '\' is not a valid zoom value.');
+          }
         }
-        dest = [null, { name: destName },
-                zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
-                zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
-                zoomArg];
       }
       if (dest) {
         this.pdfViewer.scrollPageIntoView(pageNumber || this.page, dest);
       } else if (pageNumber) {
         this.page = pageNumber; // simple page
       }
       if ('pagemode' in params) {
         if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks' ||
@@ -6271,16 +6393,33 @@ var PDFViewerApplication = {
     } else if (/^\d+$/.test(hash)) { // page number
       this.page = hash;
     } else { // named destination
       PDFHistory.updateNextHashParam(unescape(hash));
       this.navigateTo(unescape(hash));
     }
   },
 
+  refreshThumbnailViewer: function pdfViewRefreshThumbnailViewer() {
+    var pdfViewer = this.pdfViewer;
+    var thumbnailViewer = this.pdfThumbnailViewer;
+
+    // set thumbnail images of rendered pages
+    var pagesCount = pdfViewer.pagesCount;
+    for (var pageIndex = 0; pageIndex < pagesCount; pageIndex++) {
+      var pageView = pdfViewer.getPageView(pageIndex);
+      if (pageView && pageView.renderingState === RenderingStates.FINISHED) {
+        var thumbnailView = thumbnailViewer.getThumbnail(pageIndex);
+        thumbnailView.setImage(pageView);
+      }
+    }
+
+    thumbnailViewer.scrollThumbnailIntoView(this.page);
+  },
+
   switchSidebarView: function pdfViewSwitchSidebarView(view, openSidebar) {
     if (openSidebar && !this.sidebarOpen) {
       document.getElementById('sidebarToggle').click();
     }
     var thumbsView = document.getElementById('thumbnailView');
     var outlineView = document.getElementById('outlineView');
     var attachmentsView = document.getElementById('attachmentsView');
 
@@ -6595,16 +6734,19 @@ function webViewerInitialized() {
 
   document.getElementById('sidebarToggle').addEventListener('click',
     function() {
       this.classList.toggle('toggled');
       outerContainer.classList.add('sidebarMoving');
       outerContainer.classList.toggle('sidebarOpen');
       PDFViewerApplication.sidebarOpen =
         outerContainer.classList.contains('sidebarOpen');
+      if (PDFViewerApplication.sidebarOpen) {
+        PDFViewerApplication.refreshThumbnailViewer();
+      }
       PDFViewerApplication.forceRendering();
     });
 
   document.getElementById('viewThumbnail').addEventListener('click',
     function() {
       PDFViewerApplication.switchSidebarView('thumbs');
     });
 
@@ -6679,19 +6821,22 @@ function webViewerInitialized() {
 }
 
 document.addEventListener('DOMContentLoaded', webViewerLoad, true);
 
 document.addEventListener('pagerendered', function (e) {
   var pageNumber = e.detail.pageNumber;
   var pageIndex = pageNumber - 1;
   var pageView = PDFViewerApplication.pdfViewer.getPageView(pageIndex);
-  var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
-                      getThumbnail(pageIndex);
-  thumbnailView.setImage(pageView);
+
+  if (PDFViewerApplication.sidebarOpen) {
+    var thumbnailView = PDFViewerApplication.pdfThumbnailViewer.
+                        getThumbnail(pageIndex);
+    thumbnailView.setImage(pageView);
+  }
 
   if (PDFJS.pdfBug && Stats.enabled && pageView.stats) {
     Stats.add(pageNumber, pageView.stats);
   }
 
   if (pageView.error) {
     PDFViewerApplication.error(mozL10n.get('rendering_error', null,
       'An error occurred while rendering the page.'), pageView.error);
@@ -6780,19 +6925,20 @@ window.addEventListener('updateviewarea'
     pageNumberInput.classList.remove(PAGE_NUMBER_LOADING_INDICATOR);
   } else {
     pageNumberInput.classList.add(PAGE_NUMBER_LOADING_INDICATOR);
   }
 }, true);
 
 window.addEventListener('resize', function webViewerResize(evt) {
   if (PDFViewerApplication.initialized &&
-      (document.getElementById('pageWidthOption').selected ||
+      (document.getElementById('pageAutoOption').selected ||
+       /* Note: the scale is constant for |pageActualOption|. */
        document.getElementById('pageFitOption').selected ||
-       document.getElementById('pageAutoOption').selected)) {
+       document.getElementById('pageWidthOption').selected)) {
     var selectedScale = document.getElementById('scaleSelect').value;
     PDFViewerApplication.setScale(selectedScale, false);
   }
   updateViewarea();
 
   // Set the 'max-height' CSS property of the secondary toolbar.
   SecondaryToolbar.setMaxHeight(document.getElementById('viewerContainer'));
 });
@@ -6848,19 +6994,20 @@ window.addEventListener('localized', fun
 window.addEventListener('scalechange', function scalechange(evt) {
   document.getElementById('zoomOut').disabled = (evt.scale === MIN_SCALE);
   document.getElementById('zoomIn').disabled = (evt.scale === MAX_SCALE);
 
   var customScaleOption = document.getElementById('customScaleOption');
   customScaleOption.selected = false;
 
   if (!PDFViewerApplication.updateScaleControls &&
-      (document.getElementById('pageWidthOption').selected ||
+      (document.getElementById('pageAutoOption').selected ||
+       document.getElementById('pageActualOption').selected ||
        document.getElementById('pageFitOption').selected ||
-       document.getElementById('pageAutoOption').selected)) {
+       document.getElementById('pageWidthOption').selected)) {
     updateViewarea();
     return;
   }
 
   if (evt.presetValue) {
     selectScaleOption(evt.presetValue);
     updateViewarea();
     return;
@@ -6875,17 +7022,19 @@ window.addEventListener('scalechange', f
   }
   updateViewarea();
 }, true);
 
 window.addEventListener('pagechange', function pagechange(evt) {
   var page = evt.pageNumber;
   if (evt.previousPageNumber !== page) {
     document.getElementById('pageNumber').value = page;
-    PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
+    if (PDFViewerApplication.sidebarOpen) {
+      PDFViewerApplication.pdfThumbnailViewer.scrollThumbnailIntoView(page);
+    }
   }
   var numPages = PDFViewerApplication.pagesCount;
 
   document.getElementById('previous').disabled = (page <= 1);
   document.getElementById('next').disabled = (page >= numPages);
 
   document.getElementById('firstPage').disabled = (page <= 1);
   document.getElementById('lastPage').disabled = (page >= numPages);
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -43,19 +43,16 @@ addonInstall.acceptButton.label=Install
 addonInstall.acceptButton.accesskey=I
 
 # LOCALIZATION NOTE (addonConfirmInstallMessage):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 is brandShortName
 # #2 is the number of add-ons being installed
 addonConfirmInstall.message=This site would like to install an add-on in #1:;This site would like to install #2 add-ons in #1:
-# LOCALIZATION NOTE (addonConfirmInstall.author):
-# %S is the add-on author's name
-addonConfirmInstall.author=by %S
 
 addonwatch.slow=%1$S might be making %2$S run slowly
 addonwatch.disable.label=Disable %S
 addonwatch.disable.accesskey=D
 addonwatch.ignoreSession.label=Ignore for now
 addonwatch.ignoreSession.accesskey=I
 addonwatch.ignorePerm.label=Ignore permanently
 addonwatch.ignorePerm.accesskey=p
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -405,17 +405,18 @@ let DirectoryLinksProvider = {
     return this._fetchAndCacheLinksIfNecessary();
   },
 
   /**
    * Get the enhanced link object for a link (whether history or directory)
    */
   getEnhancedLink: function DirectoryLinksProvider_getEnhancedLink(link) {
     // Use the provided link if it's already enhanced
-    return link.enhancedImageURI && link ||
+    return link.type == "history" ? null :
+           link.enhancedImageURI && link ? link :
            this._enhancedLinks.get(NewTabUtils.extractSite(link.url));
   },
 
   /**
    * Check if a url's scheme is in a Set of allowed schemes
    */
   isURLAllowed: function DirectoryLinksProvider_isURLAllowed(url, allowed) {
     // Assume no url is an allowed url
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1154,20 +1154,16 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 .popup-notification-description[popupid="addon-progress"],
 .popup-notification-description[popupid="addon-install-confirmation"] {
   width: 27em;
   max-width: 27em;
 }
 
-.popup-progress-meter {
-  margin-top: .5em;
-}
-
 .addon-install-confirmation-name {
   font-weight: bold;
 }
 
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4160,27 +4160,23 @@ notification[value="loop-sharing-notific
 }
 
 .popup-notification-description[popupid="addon-progress"],
 .popup-notification-description[popupid="addon-install-confirmation"] {
   width: 27em;
   max-width: 27em;
 }
 
-.popup-progress-label,
-.popup-progress-meter {
+#addon-progress-notification-progresstext,
+#addon-progress-notification-progressmeter {
+  /* override default label margin to match description margin */
   -moz-margin-start: 0;
   -moz-margin-end: 0;
 }
 
-.popup-progress-meter,
-#addon-install-confirmation-content {
-  margin-top: 1em;
-}
-
 .addon-install-confirmation-name {
   font-weight: bold;
   -moz-margin-start: 0 !important; /* override default label margin to match description margin */
 }
 
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2168,21 +2168,16 @@ toolbarbutton.bookmark-item[dragover="tr
 }
 
 .popup-notification-description[popupid="addon-progress"],
 .popup-notification-description[popupid="addon-install-confirmation"] {
   width: 27em;
   max-width: 27em;
 }
 
-.popup-progress-meter,
-#addon-install-confirmation-content {
-  margin-top: 1em;
-}
-
 .addon-install-confirmation-name {
   font-weight: bold;
 }
 
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
--- a/build/mozconfig.common
+++ b/build/mozconfig.common
@@ -12,11 +12,8 @@
 
 mk_add_options AUTOCLOBBER=1
 
 ac_add_options --enable-crashreporter
 
 ac_add_options --enable-release
 
 . "$topsrcdir/build/mozconfig.automation"
-
-# Temporarily enable building the new Performance++ tool.
-ac_add_options --enable-devtools-perf
--- a/configure.in
+++ b/configure.in
@@ -7665,16 +7665,29 @@ fi
 
 if test "$MOZ_CHROME_FILE_FORMAT" != "jar" &&
     test "$MOZ_CHROME_FILE_FORMAT" != "flat" &&
     test "$MOZ_CHROME_FILE_FORMAT" != "omni"; then
     AC_MSG_ERROR([--enable-chrome-format must be set to either jar, flat, or omni])
 fi
 
 dnl =========================================================
+dnl Enable support for revamped devtools Performance Tools
+dnl =========================================================
+
+MOZ_ARG_ENABLE_BOOL(devtools-perf,
+[  --enable-devtools-perf Set compile flags necessary for compiling devtools perftools],
+MOZ_DEVTOOLS_PERFTOOLS=1,
+MOZ_DEVTOOLS_PERFTOOLS= )
+if test -n "$MOZ_DEVTOOLS_PERFTOOLS"; then
+  AC_DEFINE(MOZ_DEVTOOLS_PERFTOOLS)
+fi
+AC_SUBST(MOZ_DEVTOOLS_PERFTOOLS)
+
+dnl =========================================================
 dnl Omnijar packaging (bug 552121)
 dnl =========================================================
 dnl Omnijar packaging is compatible with flat packaging.
 dnl In unpackaged builds, omnijar looks for files as if
 dnl things were flat packaged. After packaging, all files
 dnl are loaded from a single jar. MOZ_CHROME_FILE_FORMAT
 dnl is set to flat since putting files into jars is only
 dnl done during packaging with omnijar.
deleted file mode 100644
--- a/dom/plugins/ipc/COMMessageFilter.cpp
+++ /dev/null
@@ -1,84 +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 "base/basictypes.h"
-
-#include "COMMessageFilter.h"
-#include "base/message_loop.h"
-#include "mozilla/plugins/PluginModuleChild.h"
-
-#include <stdio.h>
-
-namespace mozilla {
-namespace plugins {
-
-HRESULT
-COMMessageFilter::QueryInterface(REFIID riid, void** ppv)
-{
-  if (riid == IID_IUnknown || riid == IID_IMessageFilter) {
-    *ppv = static_cast<IMessageFilter*>(this);
-    AddRef();
-    return S_OK;
-  }
-  return E_NOINTERFACE;
-}
-
-DWORD COMMessageFilter::AddRef()
-{
-  ++mRefCnt;
-  return mRefCnt;
-}
-
-DWORD COMMessageFilter::Release()
-{
-  DWORD r = --mRefCnt;
-  if (0 == r)
-    delete this;
-  return r;
-}
-
-DWORD 
-COMMessageFilter::HandleInComingCall(DWORD dwCallType,
-				     HTASK htaskCaller,
-				     DWORD dwTickCount,
-				     LPINTERFACEINFO lpInterfaceInfo)
-{
-  if (mPreviousFilter)
-    return mPreviousFilter->HandleInComingCall(dwCallType, htaskCaller,
-					       dwTickCount, lpInterfaceInfo);
-  return SERVERCALL_ISHANDLED;
-}
-
-DWORD
-COMMessageFilter::RetryRejectedCall(HTASK htaskCallee,
-				    DWORD dwTickCount,
-				    DWORD dwRejectType)
-{
-  if (mPreviousFilter)
-    return mPreviousFilter->RetryRejectedCall(htaskCallee, dwTickCount,
-					      dwRejectType);
-  return -1;
-}
-
-DWORD
-COMMessageFilter::MessagePending(HTASK htaskCallee,
-				 DWORD dwTickCount,
-				 DWORD dwPendingType)
-{
-  mPlugin->FlushPendingInterruptQueue();
-  if (mPreviousFilter)
-    return mPreviousFilter->MessagePending(htaskCallee, dwTickCount,
-					   dwPendingType);
-  return PENDINGMSG_WAITNOPROCESS;
-}
-
-void
-COMMessageFilter::Initialize(PluginModuleChild* module)
-{
-  nsRefPtr<COMMessageFilter> f = new COMMessageFilter(module);
-  ::CoRegisterMessageFilter(f, getter_AddRefs(f->mPreviousFilter));
-}
-
-} // namespace plugins
-} // namespace mozilla
deleted file mode 100644
--- a/dom/plugins/ipc/COMMessageFilter.h
+++ /dev/null
@@ -1,50 +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_plugins_COMMessageFilter_h
-#define mozilla_plugins_COMMessageFilter_h
-
-#include <objidl.h>
-#include "nsISupportsImpl.h"
-#include "nsAutoPtr.h"
-
-namespace mozilla {
-namespace plugins {
-
-class PluginModuleChild;
-
-class COMMessageFilter final : public IMessageFilter
-{
-public:
-  static void Initialize(PluginModuleChild* plugin);
-
-  COMMessageFilter(PluginModuleChild* plugin)
-    : mPlugin(plugin)
-  { }
-
-  HRESULT WINAPI QueryInterface(REFIID riid, void** ppv);
-  DWORD WINAPI AddRef();
-  DWORD WINAPI Release();
-
-  DWORD WINAPI HandleInComingCall(DWORD dwCallType,
-                                  HTASK htaskCaller,
-                                  DWORD dwTickCount,
-                                  LPINTERFACEINFO lpInterfaceInfo);
-  DWORD WINAPI RetryRejectedCall(HTASK htaskCallee,
-                                 DWORD dwTickCount,
-                                 DWORD dwRejectType);
-  DWORD WINAPI MessagePending(HTASK htaskCallee,
-                              DWORD dwTickCount,
-                              DWORD dwPendingType);
-
-private:
-  nsAutoRefCnt mRefCnt;
-  PluginModuleChild* mPlugin;
-  nsRefPtr<IMessageFilter> mPreviousFilter;
-};
-
-} // namespace plugins
-} // namespace mozilla
-
-#endif // COMMessageFilter_h
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -37,17 +37,16 @@
 #include "mozilla/plugins/StreamNotifyChild.h"
 #include "mozilla/plugins/BrowserStreamChild.h"
 #include "mozilla/plugins/PluginStreamChild.h"
 #include "mozilla/dom/CrashReporterChild.h"
 
 #include "nsNPAPIPlugin.h"
 
 #ifdef XP_WIN
-#include "COMMessageFilter.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/widget/AudioSession.h"
 #endif
 
 #ifdef MOZ_WIDGET_COCOA
 #include "PluginInterposeOSX.h"
 #include "PluginUtilsOSX.h"
 #endif
@@ -259,20 +258,16 @@ PluginModuleChild::RecvDisableFlashProte
 }
 
 bool
 PluginModuleChild::InitForChrome(const std::string& aPluginFilename,
                                  base::ProcessHandle aParentProcessHandle,
                                  MessageLoop* aIOLoop,
                                  IPC::Channel* aChannel)
 {
-#ifdef XP_WIN
-    COMMessageFilter::Initialize(this);
-#endif
-
     NS_ASSERTION(aChannel, "need a channel");
 
     if (!InitGraphics())
         return false;
 
     mPluginFilename = aPluginFilename.c_str();
     nsCOMPtr<nsIFile> localFile;
     NS_NewLocalFile(NS_ConvertUTF8toUTF16(mPluginFilename),
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -828,16 +828,17 @@ sync_java_files = [
     'background/healthreport/upload/AndroidSubmissionClient.java',
     'background/healthreport/upload/HealthReportUploadService.java',
     'background/healthreport/upload/ObsoleteDocumentTracker.java',
     'background/healthreport/upload/SubmissionClient.java',
     'background/healthreport/upload/SubmissionPolicy.java',
     'background/nativecode/NativeCrypto.java',
     'background/preferences/PreferenceFragment.java',
     'background/preferences/PreferenceManagerCompat.java',
+    'background/ReadingListConstants.java',
     'browserid/ASNUtils.java',
     'browserid/BrowserIDKeyPair.java',
     'browserid/DSACryptoImplementation.java',
     'browserid/JSONWebTokenUtils.java',
     'browserid/MockMyIDTokenFactory.java',
     'browserid/RSACryptoImplementation.java',
     'browserid/SigningPrivateKey.java',
     'browserid/verifier/AbstractBrowserIDRemoteVerifierClient.java',
@@ -863,16 +864,17 @@ sync_java_files = [
     'fxa/activities/FxAccountUpdateCredentialsActivity.java',
     'fxa/activities/FxAccountVerifiedAccountActivity.java',
     'fxa/authenticator/AccountPickler.java',
     'fxa/authenticator/AndroidFxAccount.java',
     'fxa/authenticator/FxAccountAuthenticator.java',
     'fxa/authenticator/FxAccountAuthenticatorService.java',
     'fxa/authenticator/FxAccountLoginDelegate.java',
     'fxa/authenticator/FxAccountLoginException.java',
+    'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java',
     'fxa/FirefoxAccounts.java',
     'fxa/FxAccountConstants.java',
     'fxa/login/BaseRequestDelegate.java',
     'fxa/login/Cohabiting.java',
     'fxa/login/Doghouse.java',
     'fxa/login/Engaged.java',
     'fxa/login/FxAccountLoginStateMachine.java',
     'fxa/login/FxAccountLoginTransition.java',
@@ -1160,22 +1162,23 @@ sync_java_files = [
     'tokenserver/TokenServerException.java',
     'tokenserver/TokenServerToken.java',
 ]
 reading_list_service_java_files = [
     'reading/ClientMetadata.java',
     'reading/ClientReadingListRecord.java',
     'reading/FetchSpec.java',
     'reading/LocalReadingListStorage.java',
+    'reading/ReadingListBackoffObserver.java',
     'reading/ReadingListChangeAccumulator.java',
     'reading/ReadingListClient.java',
     'reading/ReadingListClientContentValuesFactory.java',
     'reading/ReadingListClientRecordFactory.java',
-    'reading/ReadingListConstants.java',
     'reading/ReadingListDeleteDelegate.java',
+    'reading/ReadingListInvalidAuthenticationException.java',
     'reading/ReadingListRecord.java',
     'reading/ReadingListRecordDelegate.java',
     'reading/ReadingListRecordResponse.java',
     'reading/ReadingListRecordUploadDelegate.java',
     'reading/ReadingListResponse.java',
     'reading/ReadingListStorage.java',
     'reading/ReadingListStorageResponse.java',
     'reading/ReadingListSyncAdapter.java',
rename from mobile/android/base/reading/ReadingListConstants.java
rename to mobile/android/base/background/ReadingListConstants.java
--- a/mobile/android/base/reading/ReadingListConstants.java
+++ b/mobile/android/base/background/ReadingListConstants.java
@@ -1,18 +1,23 @@
 /* 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.reading;
+package org.mozilla.gecko.background;
 
 import org.mozilla.gecko.AppConstants;
 
+/**
+ * This is in 'background' not 'reading' so that it's still usable even when the
+ * Reading List feature is build-time disabled.
+ */
 public class ReadingListConstants {
   public static final String GLOBAL_LOG_TAG = "FxReadingList";
   public static final String USER_AGENT = "Firefox-Android-FxReader/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_DISPLAYNAME + ")";
   public static final String DEFAULT_DEV_ENDPOINT = "https://readinglist.dev.mozaws.net/v1/";
   public static final String DEFAULT_PROD_ENDPOINT = "https://readinglist.services.mozilla.com/v1/";
 
-  public static final String OAUTH_ENDPOINT_PROD = "https://oauth.accounts.firefox.com/v1";
+  public static final String OAUTH_SCOPE_READINGLIST = "readinglist";
+  public static final String AUTH_TOKEN_TYPE = "oauth::" + OAUTH_SCOPE_READINGLIST;
 
   public static boolean DEBUG = false;
 }
--- a/mobile/android/base/background/fxa/FxAccountUtils.java
+++ b/mobile/android/base/background/fxa/FxAccountUtils.java
@@ -9,26 +9,24 @@ import java.math.BigInteger;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
-import org.mozilla.gecko.R.string;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.crypto.PBKDF2;
 
 import android.content.Context;
-import android.os.Build;
 
 public class FxAccountUtils {
   private static final String LOG_TAG = FxAccountUtils.class.getSimpleName();
 
   public static final int SALT_LENGTH_BYTES = 32;
   public static final int SALT_LENGTH_HEX = 2 * SALT_LENGTH_BYTES;
 
   public static final int HASH_LENGTH_BYTES = 16;
--- a/mobile/android/base/background/fxa/oauth/FxAccountAbstractClientException.java
+++ b/mobile/android/base/background/fxa/oauth/FxAccountAbstractClientException.java
@@ -4,16 +4,17 @@
 
 package org.mozilla.gecko.background.fxa.oauth;
 
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 
 import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.HttpStatus;
 
 /**
  * From <a href="https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md">https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md</a>.
  */
 public class FxAccountAbstractClientException extends Exception {
   private static final long serialVersionUID = 1953459541558266597L;
 
   public FxAccountAbstractClientException(String detailMessage) {
@@ -46,16 +47,20 @@ public class FxAccountAbstractClientExce
       this.message = message;
       this.body = body;
     }
 
     @Override
     public String toString() {
       return "<FxAccountAbstractClientRemoteException " + this.httpStatusCode + " [" + this.apiErrorNumber + "]: " + this.message + ">";
     }
+
+    public boolean isInvalidAuthentication() {
+      return this.httpStatusCode == HttpStatus.SC_UNAUTHORIZED;
+    }
   }
 
   public static class FxAccountAbstractClientMalformedResponseException extends FxAccountAbstractClientRemoteException {
     private static final long serialVersionUID = 1209313149952001098L;
 
     public FxAccountAbstractClientMalformedResponseException(HttpResponse response) {
       super(response, 0, FxAccountOAuthRemoteError.UNKNOWN_ERROR, "Response malformed", "Response malformed", new ExtendedJSONObject());
     }
--- a/mobile/android/base/fxa/FxAccountConstants.java
+++ b/mobile/android/base/fxa/FxAccountConstants.java
@@ -5,21 +5,26 @@
 package org.mozilla.gecko.fxa;
 
 import org.mozilla.gecko.AppConstants;
 
 public class FxAccountConstants {
   public static final String GLOBAL_LOG_TAG = "FxAccounts";
   public static final String ACCOUNT_TYPE = AppConstants.MOZ_ANDROID_SHARED_FXACCOUNT_TYPE;
 
+  // Must be a client ID allocated with "canGrant" privileges!
+  public static final String OAUTH_CLIENT_ID_FENNEC = "3332a18d142636cb";
+
   public static final String DEFAULT_AUTH_SERVER_ENDPOINT = "https://api.accounts.firefox.com/v1";
   public static final String DEFAULT_TOKEN_SERVER_ENDPOINT = "https://token.services.mozilla.com/1.0/sync/1.5";
+  public static final String DEFAULT_OAUTH_SERVER_ENDPOINT = "https://oauth.accounts.firefox.com/v1";
 
-  public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://api-accounts.stage.mozaws.net/v1";
-  public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://token.stage.mozaws.net/1.0/sync/1.5";
+  public static final String STAGE_AUTH_SERVER_ENDPOINT = "https://stable.dev.lcip.org/auth/v1";
+  public static final String STAGE_TOKEN_SERVER_ENDPOINT = "https://stable.dev.lcip.org/syncserver/token/1.0/sync/1.5";
+  public static final String STAGE_OAUTH_SERVER_ENDPOINT = "https://oauth-stable.dev.lcip.org/v1";
 
   // You must be at least 13 years old, on the day of creation, to create a Firefox Account.
   public static final int MINIMUM_AGE_TO_CREATE_AN_ACCOUNT = 13;
 
   // 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;
 
   public static final String USER_AGENT = "Firefox-Android-FxAccounts/" + AppConstants.MOZ_APP_VERSION + " (" + AppConstants.MOZ_APP_DISPLAYNAME + ")";
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -1,16 +1,24 @@
 /* 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.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.R;
+import org.mozilla.gecko.background.ReadingListConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.preferences.PreferenceFragment;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Married;
@@ -18,60 +26,56 @@ import org.mozilla.gecko.fxa.login.State
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.fxa.tasks.FxAccountCodeResender;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.Handler;
 import android.preference.CheckBoxPreference;
 import android.preference.EditTextPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceScreen;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+import android.widget.Toast;
 
 
 /**
  * A fragment that displays the status of an AndroidFxAccount.
  * <p>
  * The owning activity is responsible for providing an AndroidFxAccount at
  * appropriate times.
  */
 public class FxAccountStatusFragment
     extends PreferenceFragment
     implements OnPreferenceClickListener, OnPreferenceChangeListener {
   private static final String LOG_TAG = FxAccountStatusFragment.class.getSimpleName();
 
-    /**
-     * If a device claims to have synced before this date, we will assume it has never synced.
-     */
-    private static final Date EARLIEST_VALID_SYNCED_DATE;
-    static {
-        final Calendar c = GregorianCalendar.getInstance();
-        c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
-        EARLIEST_VALID_SYNCED_DATE = c.getTime();
-    }
+  /**
+   * If a device claims to have synced before this date, we will assume it has never synced.
+   */
+  private static final Date EARLIEST_VALID_SYNCED_DATE;
+  static {
+    final Calendar c = GregorianCalendar.getInstance();
+    c.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
+    EARLIEST_VALID_SYNCED_DATE = c.getTime();
+  }
+
   // When a checkbox is toggled, wait 5 seconds (for other checkbox actions)
   // before trying to sync. Should we kill off the fragment before the sync
   // request happens, that's okay: the runnable will run if the UI thread is
   // still around to service it, and since we're not updating any UI, we'll just
   // schedule the sync as usual. See also comment below about garbage
   // collection.
   private static final long DELAY_IN_MILLISECONDS_BEFORE_REQUESTING_SYNC = 5 * 1000;
   private static final long LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS = 60 * 1000;
@@ -79,16 +83,25 @@ public class FxAccountStatusFragment
   // By default, the auth/account server preference is only shown when the
   // account is configured to use a custom server. In debug mode, this is set.
   private static boolean ALWAYS_SHOW_AUTH_SERVER = false;
 
   // By default, the Sync server preference is only shown when the account is
   // configured to use a custom Sync server. In debug mode, this is set.
   private static boolean ALWAYS_SHOW_SYNC_SERVER = false;
 
+  // If the user clicks the email field this many times, the debug / personal
+  // information logging setting will toggle. The setting is not permanent: it
+  // lasts until this process is killed. We don't want to dump PII to the log
+  // for a long time!
+  private final int NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG =
+      // !defined(MOZILLA_OFFICIAL) || defined(NIGHTLY_BUILD) || defined(MOZ_DEBUG)
+      (!AppConstants.MOZILLA_OFFICIAL || AppConstants.NIGHTLY_BUILD || AppConstants.DEBUG_BUILD) ? 5 : -1 /* infinite */;
+  private int debugClickCount = 0;
+
   protected PreferenceCategory accountCategory;
   protected Preference emailPreference;
   protected Preference authServerPreference;
 
   protected Preference needsPasswordPreference;
   protected Preference needsUpgradePreference;
   protected Preference needsVerificationPreference;
   protected Preference needsMasterSyncAutomaticallyEnabledPreference;
@@ -172,16 +185,18 @@ public class FxAccountStatusFragment
     if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
       removeDebugButtons();
     } else {
       connectDebugButtons();
       ALWAYS_SHOW_AUTH_SERVER = true;
       ALWAYS_SHOW_SYNC_SERVER = true;
     }
 
+    emailPreference.setOnPreferenceClickListener(this);
+
     needsPasswordPreference.setOnPreferenceClickListener(this);
     needsVerificationPreference.setOnPreferenceClickListener(this);
     needsFinishMigratingPreference.setOnPreferenceClickListener(this);
 
     bookmarksPreference.setOnPreferenceClickListener(this);
     historyPreference.setOnPreferenceClickListener(this);
     tabsPreference.setOnPreferenceClickListener(this);
     passwordsPreference.setOnPreferenceClickListener(this);
@@ -209,16 +224,27 @@ public class FxAccountStatusFragment
    */
   @Override
   public void onResume() {
     super.onResume();
   }
 
   @Override
   public boolean onPreferenceClick(Preference preference) {
+    if (preference == emailPreference) {
+      debugClickCount += 1;
+      if (NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG > 0 && debugClickCount >= NUMBER_OF_CLICKS_TO_TOGGLE_DEBUG) {
+        debugClickCount = 0;
+        FxAccountUtils.LOG_PERSONAL_INFORMATION = !FxAccountUtils.LOG_PERSONAL_INFORMATION;
+        Toast.makeText(getActivity(), "Toggled logging Firefox Account personal information!", Toast.LENGTH_LONG).show();
+        hardRefresh(); // Display or hide debug options.
+      }
+      return true;
+    }
+
     if (preference == needsPasswordPreference) {
       Intent intent = new Intent(getActivity(), FxAccountUpdateCredentialsActivity.class);
       final Bundle extras = getExtrasForAccount();
       if (extras != null) {
         intent.putExtras(extras);
       }
       // Per http://stackoverflow.com/a/8992365, this triggers a known bug with
       // the soft keyboard not being shown for the started activity. Why, Android, why?
@@ -759,42 +785,71 @@ public class FxAccountStatusFragment
         Logger.info(LOG_TAG, "Refreshing.");
         refresh();
       } else if ("debug_dump".equals(key)) {
         fxAccount.dump();
       } else if ("debug_force_sync".equals(key)) {
         Logger.info(LOG_TAG, "Force syncing.");
         fxAccount.requestSync(FirefoxAccounts.FORCE);
         // No sense refreshing, since the sync will complete in the future.
+      } else if ("debug_forget_reading_list_oauth_token".equals(key)) {
+        final Account account = fxAccount.getAndroidAccount();
+        final AccountManager accountManager = AccountManager.get(getActivity());
+        final String authToken = accountManager.peekAuthToken(account, ReadingListConstants.AUTH_TOKEN_TYPE);
+        if (authToken != null) {
+          Logger.info(LOG_TAG, "Forgetting reading list oauth token: " + authToken);
+          accountManager.invalidateAuthToken(account.type, authToken);
+        } else {
+          Logger.warn(LOG_TAG, "No reading list oauth token to forget!");
+        }
       } else if ("debug_forget_certificate".equals(key)) {
         State state = fxAccount.getState();
         try {
           Married married = (Married) state;
           Logger.info(LOG_TAG, "Moving to Cohabiting state: Forgetting certificate.");
           fxAccount.setState(married.makeCohabitingState());
           refresh();
         } catch (ClassCastException e) {
           Logger.info(LOG_TAG, "Not in Married state; can't forget certificate.");
           // Ignore.
         }
+      } else if ("debug_invalidate_certificate".equals(key)) {
+        State state = fxAccount.getState();
+        try {
+          Married married = (Married) state;
+          Logger.info(LOG_TAG, "Invalidating certificate.");
+          fxAccount.setState(married.makeCohabitingState().withCertificate("INVALID CERTIFICATE"));
+          refresh();
+        } catch (ClassCastException e) {
+          Logger.info(LOG_TAG, "Not in Married state; can't invalidate certificate.");
+          // Ignore.
+        }
       } else if ("debug_require_password".equals(key)) {
         Logger.info(LOG_TAG, "Moving to Separated state: Forgetting password.");
         State state = fxAccount.getState();
         fxAccount.setState(state.makeSeparatedState());
         refresh();
       } else if ("debug_require_upgrade".equals(key)) {
         Logger.info(LOG_TAG, "Moving to Doghouse state: Requiring upgrade.");
         State state = fxAccount.getState();
         fxAccount.setState(state.makeDoghouseState());
         refresh();
       } else if ("debug_migrated_from_sync11".equals(key)) {
         Logger.info(LOG_TAG, "Moving to MigratedFromSync11 state: Requiring password.");
         State state = fxAccount.getState();
         fxAccount.setState(state.makeMigratedFromSync11State(null));
         refresh();
+      } else if ("debug_make_account_stage".equals(key)) {
+        Logger.info(LOG_TAG, "Moving Account endpoints, in place, to stage.  Deleting Sync and RL prefs and requiring password.");
+        fxAccount.unsafeTransitionToStageEndpoints();
+        refresh();
+      } else if ("debug_make_account_default".equals(key)) {
+        Logger.info(LOG_TAG, "Moving Account endpoints, in place, to default (production).  Deleting Sync and RL prefs and requiring password.");
+        fxAccount.unsafeTransitionToDefaultEndpoints();
+        refresh();
       } else {
         return false;
       }
       return true;
     }
   }
 
   /**
@@ -802,30 +857,22 @@ public class FxAccountStatusFragment
    * listener to each of them.
    */
   protected void connectDebugButtons() {
     // Separate listener to really separate debug logic from main code paths.
     final OnPreferenceClickListener listener = new DebugPreferenceClickListener();
 
     // We don't want to use Android resource strings for debug UI, so we just
     // use the keys throughout.
-    final Preference debugCategory = ensureFindPreference("debug_category");
+    final PreferenceCategory debugCategory = (PreferenceCategory) ensureFindPreference("debug_category");
     debugCategory.setTitle(debugCategory.getKey());
 
-    String[] debugKeys = new String[] {
-        "debug_refresh",
-        "debug_dump",
-        "debug_force_sync",
-        "debug_forget_certificate",
-        "debug_require_password",
-        "debug_require_upgrade",
-        "debug_migrated_from_sync11" };
-    for (String debugKey : debugKeys) {
-      final Preference button = ensureFindPreference(debugKey);
-      button.setTitle(debugKey); // Not very friendly, but this is for debugging only!
+    for (int i = 0; i < debugCategory.getPreferenceCount(); i++) {
+      final Preference button = debugCategory.getPreference(i);
+      button.setTitle(button.getKey()); // Not very friendly, but this is for debugging only!
       button.setOnPreferenceClickListener(listener);
     }
   }
 
   @Override
   public boolean onPreferenceChange(Preference preference, Object newValue) {
     if (preference == deviceNamePreference) {
       String newClientName = (String) newValue;
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -8,16 +8,17 @@ import java.io.UnsupportedEncodingExcept
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Semaphore;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
@@ -30,16 +31,17 @@ import org.mozilla.gecko.sync.setup.Cons
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
+import android.util.Log;
 
 /**
  * A Firefox Account that stores its details and state as user data attached to
  * an Android Account instance.
  * <p>
  * Account user data is accessible only to the Android App(s) that own the
  * Account type. Account user data is not removed when the App's private data is
  * cleared.
@@ -615,9 +617,93 @@ public class AndroidFxAccount {
     final long neverSynced = -1L;
     try {
       return getSyncPrefs().getLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, neverSynced);
     } catch (Exception e) {
       Logger.warn(LOG_TAG, "Got exception getting last synced time; ignoring.", e);
       return neverSynced;
     }
   }
+
+  // Debug only!  This is dangerous!
+  public void unsafeTransitionToDefaultEndpoints() {
+    unsafeTransitionToStageEndpoints(
+        FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT,
+        FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT);
+    }
+
+  // Debug only!  This is dangerous!
+  public void unsafeTransitionToStageEndpoints() {
+    unsafeTransitionToStageEndpoints(
+        FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT,
+        FxAccountConstants.STAGE_TOKEN_SERVER_ENDPOINT);
+  }
+
+  protected void unsafeTransitionToStageEndpoints(String authServerEndpoint, String tokenServerEndpoint) {
+    try {
+      getReadingListPrefs().edit().clear().commit();
+    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
+      // Ignore.
+    }
+    try {
+      getSyncPrefs().edit().clear().commit();
+    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
+      // Ignore.
+    }
+    State state = getState();
+    setState(state.makeSeparatedState());
+    accountManager.setUserData(account, ACCOUNT_KEY_IDP_SERVER, authServerEndpoint);
+    accountManager.setUserData(account, ACCOUNT_KEY_TOKEN_SERVER, tokenServerEndpoint);
+    ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 1);
+  }
+
+  /**
+   * Take the lock to own updating any Firefox Account's internal state.
+   *
+   * We use a <code>Semaphore</code> rather than a <code>ReentrantLock</code>
+   * because the callback that needs to release the lock may not be invoked on
+   * the thread that initially acquired the lock. Be aware!
+   */
+  protected static final Semaphore sLock = new Semaphore(1, true /* fair */);
+
+  // Which consumer took the lock?
+  // Synchronized by this.
+  protected String lockTag = null;
+
+  // Are we locked?  (It's not easy to determine who took the lock dynamically,
+  // so we maintain this flag internally.)
+  // Synchronized by this.
+  protected boolean locked = false;
+
+  // Block until we can take the shared state lock.
+  public synchronized void acquireSharedAccountStateLock(final String tag) throws InterruptedException {
+    final long id = Thread.currentThread().getId();
+    this.lockTag = tag;
+    Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id acquiring lock: " + lockTag + ", " + id + " ...");
+    sLock.acquire();
+    locked = true;
+    Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id acquiring lock: " + lockTag + ", " + id + " ... ACQUIRED");
+  }
+
+  // If we hold the shared state lock, release it.  Otherwise, ignore the request.
+  public synchronized void releaseSharedAccountStateLock() {
+    final long id = Thread.currentThread().getId();
+    Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ...");
+    if (locked) {
+      sLock.release();
+      locked = false;
+      Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ... RELEASED");
+    } else {
+      Log.d(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ... NOT LOCKED");
+    }
+  }
+
+  @Override
+  protected synchronized void finalize() {
+    if (locked) {
+      // Should never happen, but...
+      sLock.release();
+      locked = false;
+      final long id = Thread.currentThread().getId();
+      Log.e(Logger.DEFAULT_LOG_TAG, "Thread with tag and thread id releasing lock: " + lockTag + ", " + id + " ... RELEASED DURING FINALIZE");
+    }
+  }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/authenticator/FxADefaultLoginStateMachineDelegate.java
@@ -0,0 +1,84 @@
+/* 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.authenticator;
+
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.browserid.BrowserIDKeyPair;
+import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
+import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
+import org.mozilla.gecko.fxa.login.Married;
+import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.fxa.login.State.StateLabel;
+import org.mozilla.gecko.fxa.login.StateFactory;
+import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
+
+import android.content.Context;
+
+public abstract class FxADefaultLoginStateMachineDelegate implements LoginStateMachineDelegate {
+  protected final static String LOG_TAG = LoginStateMachineDelegate.class.getSimpleName();
+
+  protected final Context context;
+  protected final AndroidFxAccount fxAccount;
+  protected final Executor executor;
+  protected final FxAccountClient client;
+
+  public FxADefaultLoginStateMachineDelegate(Context context, AndroidFxAccount fxAccount) {
+    this.context = context;
+    this.fxAccount = fxAccount;
+    this.executor = Executors.newSingleThreadExecutor();
+    this.client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
+  }
+
+  abstract public void handleNotMarried(State notMarried);
+  abstract public void handleMarried(Married married);
+
+  @Override
+  public FxAccountClient getClient() {
+    return client;
+  }
+
+  @Override
+  public long getCertificateDurationInMilliseconds() {
+    return 12 * 60 * 60 * 1000;
+  }
+
+  @Override
+  public long getAssertionDurationInMilliseconds() {
+    return 15 * 60 * 1000;
+  }
+
+  @Override
+  public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+    return StateFactory.generateKeyPair();
+  }
+
+  @Override
+  public void handleTransition(Transition transition, State state) {
+    Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
+  }
+
+  @Override
+  public void handleFinal(State state) {
+    Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
+    fxAccount.setState(state);
+    // Update any notifications displayed.
+    final FxAccountNotificationManager notificationManager = new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID);
+    notificationManager.update(context, fxAccount);
+
+    if (state.getStateLabel() != StateLabel.Married) {
+      handleNotMarried(state);
+      return;
+    } else {
+      handleMarried((Married) state);
+    }
+  }
+}
--- a/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java
+++ b/mobile/android/base/fxa/authenticator/FxAccountAuthenticator.java
@@ -1,29 +1,52 @@
 /* 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.authenticator;
 
+import java.security.NoSuchAlgorithmException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.background.fxa.FxAccountUtils;
+import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient.RequestDelegate;
+import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
+import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
+import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10.AuthorizationResponse;
+import org.mozilla.gecko.browserid.BrowserIDKeyPair;
+import org.mozilla.gecko.browserid.JSONWebTokenUtils;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.activities.FxAccountGetStartedActivity;
+import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
+import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
+import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
+import org.mozilla.gecko.fxa.login.Married;
+import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.fxa.login.State.StateLabel;
+import org.mozilla.gecko.fxa.login.StateFactory;
+import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
 
 import android.accounts.AbstractAccountAuthenticator;
 import android.accounts.Account;
 import android.accounts.AccountAuthenticatorResponse;
 import android.accounts.AccountManager;
 import android.accounts.NetworkErrorException;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 
 public class FxAccountAuthenticator extends AbstractAccountAuthenticator {
   public static final String LOG_TAG = FxAccountAuthenticator.class.getSimpleName();
+  public static final int UNKNOWN_ERROR_CODE = 999;
 
   protected final Context context;
   protected final AccountManager accountManager;
 
   public FxAccountAuthenticator(Context context) {
     super(context);
     this.context = context;
     this.accountManager = AccountManager.get(context);
@@ -63,25 +86,224 @@ public class FxAccountAuthenticator exte
 
   @Override
   public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
     Logger.debug(LOG_TAG, "editProperties");
 
     return null;
   }
 
+  protected static class Responder {
+    final AccountAuthenticatorResponse response;
+    final AndroidFxAccount fxAccount;
+
+    public Responder(AccountAuthenticatorResponse response, AndroidFxAccount fxAccount) {
+      this.response = response;
+      this.fxAccount = fxAccount;
+    }
+
+    public void fail(Exception e) {
+      Logger.warn(LOG_TAG, "Responding with error!", e);
+      fxAccount.releaseSharedAccountStateLock();
+      final Bundle result = new Bundle();
+      result.putInt(AccountManager.KEY_ERROR_CODE, UNKNOWN_ERROR_CODE);
+      result.putString(AccountManager.KEY_ERROR_MESSAGE, e.toString());
+      response.onResult(result);
+    }
+
+    public void succeed(String authToken) {
+      Logger.info(LOG_TAG, "Responding with success!");
+      fxAccount.releaseSharedAccountStateLock();
+      final Bundle result = new Bundle();
+      result.putString(AccountManager.KEY_ACCOUNT_NAME, fxAccount.account.name);
+      result.putString(AccountManager.KEY_ACCOUNT_TYPE, fxAccount.account.type);
+      result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
+      response.onResult(result);
+    }
+  }
+
+  public abstract static class FxADefaultLoginStateMachineDelegate implements LoginStateMachineDelegate {
+    protected final Context context;
+    protected final AndroidFxAccount fxAccount;
+    protected final Executor executor;
+    protected final FxAccountClient client;
+
+    public FxADefaultLoginStateMachineDelegate(Context context, AndroidFxAccount fxAccount) {
+      this.context = context;
+      this.fxAccount = fxAccount;
+      this.executor = Executors.newSingleThreadExecutor();
+      this.client = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
+    }
+
+    @Override
+    public FxAccountClient getClient() {
+      return client;
+    }
+
+    @Override
+    public long getCertificateDurationInMilliseconds() {
+      return 12 * 60 * 60 * 1000;
+    }
+
+    @Override
+    public long getAssertionDurationInMilliseconds() {
+      return 15 * 60 * 1000;
+    }
+
+    @Override
+    public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
+      return StateFactory.generateKeyPair();
+    }
+
+    @Override
+    public void handleTransition(Transition transition, State state) {
+      Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
+    }
+
+    abstract public void handleNotMarried(State notMarried);
+    abstract public void handleMarried(Married married);
+
+    @Override
+    public void handleFinal(State state) {
+      Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
+      fxAccount.setState(state);
+      // Update any notifications displayed.
+      final FxAccountNotificationManager notificationManager = new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID);
+      notificationManager.update(context, fxAccount);
+
+      if (state.getStateLabel() != StateLabel.Married) {
+        handleNotMarried(state);
+        return;
+      } else {
+        handleMarried((Married) state);
+      }
+    }
+  }
+
+  protected void getOAuthToken(final AccountAuthenticatorResponse response, final AndroidFxAccount fxAccount, final String scope) throws NetworkErrorException {
+    Logger.info(LOG_TAG, "Fetching oauth token with scope: " + scope);
+
+    final Responder responder = new Responder(response, fxAccount);
+
+    // Allow testing against stage.
+    final boolean usingStageAuthServer = FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(fxAccount.getAccountServerURI());
+    final String oauthServerUri;
+    if (usingStageAuthServer) {
+      oauthServerUri = FxAccountConstants.STAGE_OAUTH_SERVER_ENDPOINT;
+    } else {
+      oauthServerUri = FxAccountConstants.DEFAULT_OAUTH_SERVER_ENDPOINT;
+    }
+
+    final String audience;
+    try {
+      audience = FxAccountUtils.getAudienceForURL(oauthServerUri); // The assertion gets traded in for an oauth bearer token.
+    } catch (Exception e) {
+      Logger.warn(LOG_TAG, "Got exception fetching oauth token.", e);
+      responder.fail(e);
+      return;
+    }
+
+    final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
+
+    stateMachine.advance(fxAccount.getState(), StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
+      @Override
+      public void handleNotMarried(State state) {
+        final String message = "Cannot fetch oauth token from state: " + state.getStateLabel();
+        Logger.warn(LOG_TAG, message);
+        responder.fail(new RuntimeException(message));
+      }
+
+      @Override
+      public void handleMarried(final Married married) {
+        final String assertion;
+        try {
+          assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
+          if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
+            JSONWebTokenUtils.dumpAssertion(assertion);
+          }
+        } catch (Exception e) {
+          Logger.warn(LOG_TAG, "Got exception fetching oauth token.", e);
+          responder.fail(e);
+          return;
+        }
+
+        final FxAccountOAuthClient10 oauthClient = new FxAccountOAuthClient10(oauthServerUri, executor);
+        Logger.debug(LOG_TAG, "OAuth fetch for scope: " + scope);
+        oauthClient.authorization(FxAccountConstants.OAUTH_CLIENT_ID_FENNEC, assertion, null, scope, new RequestDelegate<FxAccountOAuthClient10.AuthorizationResponse>() {
+          @Override
+          public void handleSuccess(AuthorizationResponse result) {
+            Logger.debug(LOG_TAG, "OAuth success.");
+            FxAccountUtils.pii(LOG_TAG, "Fetched oauth token: " + result.access_token);
+            responder.succeed(result.access_token);
+          }
+
+          @Override
+          public void handleFailure(FxAccountAbstractClientRemoteException e) {
+            Logger.error(LOG_TAG, "OAuth failure.", e);
+            if (e.isInvalidAuthentication()) {
+              // We were married, generated an assertion, and our assertion was rejected by the
+              // oauth client. If it's a 401, we probably have a stale certificate.  If instead of
+              // a stale certificate we have bad credentials, the state machine will fail to sign
+              // our public key and drive us back to Separated.
+              fxAccount.setState(married.makeCohabitingState());
+            }
+            responder.fail(e);
+          }
+
+          @Override
+          public void handleError(Exception e) {
+            Logger.error(LOG_TAG, "OAuth error.", e);
+            responder.fail(e);
+          }
+        });
+      }
+    });
+  }
+
   @Override
   public Bundle getAuthToken(final AccountAuthenticatorResponse response,
       final Account account, final String authTokenType, final Bundle options)
           throws NetworkErrorException {
-    Logger.debug(LOG_TAG, "getAuthToken");
+    Logger.debug(LOG_TAG, "getAuthToken: " + authTokenType);
+
+    // If we have a cached authToken, hand it over.
+    final String cachedAuthToken = AccountManager.get(context).peekAuthToken(account, authTokenType);
+    if (cachedAuthToken != null && !cachedAuthToken.isEmpty()) {
+      Logger.info(LOG_TAG, "Return cached token.");
+      final Bundle result = new Bundle();
+      result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
+      result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
+      result.putString(AccountManager.KEY_AUTHTOKEN, cachedAuthToken);
+      return result;
+    }
 
-    Logger.warn(LOG_TAG, "Returning null bundle for getAuthToken.");
+    // If we're asked for an oauth::scope token, try to generate one.
+    final String oauthPrefix = "oauth::";
+    if (authTokenType != null && authTokenType.startsWith(oauthPrefix)) {
+      final String scope = authTokenType.substring(oauthPrefix.length());
+      final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+      try {
+        fxAccount.acquireSharedAccountStateLock(LOG_TAG);
+      } catch (InterruptedException e) {
+        Logger.warn(LOG_TAG, "Could not acquire account state lock; return error bundle.");
+        final Bundle bundle = new Bundle();
+        bundle.putInt(AccountManager.KEY_ERROR_CODE, 1);
+        bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Could not acquire account state lock.");
+        return bundle;
+      }
+      getOAuthToken(response, fxAccount, scope);
+      return null;
+    }
 
-    return null;
+    // Otherwise, fail.
+    Logger.warn(LOG_TAG, "Returning error bundle for getAuthToken with unknown token type.");
+    final Bundle bundle = new Bundle();
+    bundle.putInt(AccountManager.KEY_ERROR_CODE, 2);
+    bundle.putString(AccountManager.KEY_ERROR_MESSAGE, "Unknown token type: " + authTokenType);
+    return bundle;
   }
 
   @Override
   public String getAuthTokenLabel(String authTokenType) {
     Logger.debug(LOG_TAG, "getAuthTokenLabel");
 
     return null;
   }
--- a/mobile/android/base/fxa/login/Cohabiting.java
+++ b/mobile/android/base/fxa/login/Cohabiting.java
@@ -13,16 +13,20 @@ import org.mozilla.gecko.sync.ExtendedJS
 
 public class Cohabiting extends TokensAndKeysState {
   private static final String LOG_TAG = Cohabiting.class.getSimpleName();
 
   public Cohabiting(String email, String uid, byte[] sessionToken, byte[] kA, byte[] kB, BrowserIDKeyPair keyPair) {
     super(StateLabel.Cohabiting, email, uid, sessionToken, kA, kB, keyPair);
   }
 
+  public Married withCertificate(String certificate) {
+    return new Married(email, uid, sessionToken, kA, kB, keyPair, certificate);
+  }
+
   @Override
   public void execute(final ExecuteDelegate delegate) {
     delegate.getClient().sign(sessionToken, keyPair.getPublic().toJSONObject(), delegate.getCertificateDurationInMilliseconds(),
         new BaseRequestDelegate<String>(this, delegate) {
       @Override
       public void handleSuccess(String certificate) {
         if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
           try {
@@ -34,13 +38,13 @@ public class Cohabiting extends TokensAn
               FxAccountUtils.pii(LOG_TAG, "Signature: " + c.getString("signature"));
             } else {
               FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
             }
           } catch (Exception e) {
             FxAccountUtils.pii(LOG_TAG, "Could not parse certificate!");
           }
         }
-        delegate.handleTransition(new LogMessage("sign succeeded"), new Married(email, uid, sessionToken, kA, kB, keyPair, certificate));
+        delegate.handleTransition(new LogMessage("sign succeeded"), withCertificate(certificate));
       }
     });
   }
 }
--- a/mobile/android/base/fxa/login/Married.java
+++ b/mobile/android/base/fxa/login/Married.java
@@ -107,12 +107,12 @@ public class Married extends TokensAndKe
 
   public String getClientState() {
     if (FxAccountUtils.LOG_PERSONAL_INFORMATION) {
       FxAccountUtils.pii(LOG_TAG, "Client state: " + this.clientState);
     }
     return this.clientState;
   }
 
-  public State makeCohabitingState() {
+  public Cohabiting makeCohabitingState() {
     return new Cohabiting(email, uid, sessionToken, kA, kB, keyPair);
   }
 }
--- a/mobile/android/base/fxa/receivers/FxAccountDeletedService.java
+++ b/mobile/android/base/fxa/receivers/FxAccountDeletedService.java
@@ -63,16 +63,20 @@ public class FxAccountDeletedService ext
     deletePickle(context);
 
     // Delete client database and non-local tabs.
     Logger.info(LOG_TAG, "Deleting the entire clients database and non-local tabs");
     FennecTabsRepository.deleteNonLocalClientsAndTabs(context);
 
     // Remove any displayed notifications.
     new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID).clear(context);
+
+    // Bug 1147275: Delete cached oauth tokens. There's no way to query all
+    // oauth tokens from Android, so this is tricky to do comprehensively. We
+    // can query, individually, for specific oauth tokens to delete, however.
   }
 
   public static void deletePickle(final Context context) {
     try {
       AccountPickler.deletePickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
     } catch (Exception e) {
       // This should never happen, but we really don't want to die in a background thread.
       Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
--- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
+++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
@@ -60,17 +60,17 @@ public class FxAccountNotificationManage
    * Reflect new Firefox Account state to the notification manager: show or hide
    * notifications reflecting the state of a Firefox Account.
    *
    * @param context
    *          Android context.
    * @param fxAccount
    *          Firefox Account to reflect to the notification manager.
    */
-  protected void update(Context context, AndroidFxAccount fxAccount) {
+  public void update(Context context, AndroidFxAccount fxAccount) {
     final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
 
     final State state = fxAccount.getState();
     final Action action = state.getNeededAction();
     if (action == Action.None) {
       Logger.info(LOG_TAG, "State " + state.getStateLabel() + " needs no action; cancelling any existing notification.");
       notificationManager.cancel(notificationId);
       return;
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -1,43 +1,39 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.fxa.sync;
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.security.NoSuchAlgorithmException;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.SkewHandler;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
 import org.mozilla.gecko.browserid.JSONWebTokenUtils;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.authenticator.FxADefaultLoginStateMachineDelegate;
 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate.Result;
 import org.mozilla.gecko.sync.BackoffHandler;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.PrefsBackoffHandler;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
@@ -115,18 +111,18 @@ public class FxAccountSyncAdapter extend
 
     @Override
     public void rejectSync() {
       super.rejectSync();
     }
 
     protected final Collection<String> stageNamesToSync;
 
-    public SyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) {
-      super(latch, syncResult, fxAccount);
+    public SyncDelegate(BlockingQueue<Result> latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) {
+      super(latch, syncResult);
       this.stageNamesToSync = Collections.unmodifiableCollection(stageNamesToSync);
     }
 
     public Collection<String> getStageNamesToSync() {
       return this.stageNamesToSync;
     }
   }
 
@@ -235,28 +231,30 @@ public class FxAccountSyncAdapter extend
   protected void syncWithAssertion(final String audience,
                                    final String assertion,
                                    final URI tokenServerEndpointURI,
                                    final BackoffHandler tokenBackoffHandler,
                                    final SharedPreferences sharedPrefs,
                                    final KeyBundle syncKeyBundle,
                                    final String clientState,
                                    final SessionCallback callback,
-                                   final Bundle extras) {
+                                   final Bundle extras,
+                                   final AndroidFxAccount fxAccount) {
     final TokenServerClientDelegate delegate = new TokenServerClientDelegate() {
       private boolean didReceiveBackoff = false;
 
       @Override
       public String getUserAgent() {
         return FxAccountConstants.USER_AGENT;
       }
 
       @Override
       public void handleSuccess(final TokenServerToken token) {
         FxAccountUtils.pii(LOG_TAG, "Got token! uid is " + token.uid + " and endpoint is " + token.endpoint + ".");
+        fxAccount.releaseSharedAccountStateLock();
 
         if (!didReceiveBackoff) {
           // We must be OK to touch this token server.
           tokenBackoffHandler.setEarliestNextRequest(0L);
         }
 
         final URI storageServerURI;
         try {
@@ -324,22 +322,34 @@ public class FxAccountSyncAdapter extend
         } catch (Exception e) {
           callback.handleError(globalSession, e);
           return;
         }
       }
 
       @Override
       public void handleFailure(TokenServerException e) {
-        handleError(e);
+        Logger.error(LOG_TAG, "Failed to get token.", e);
+        try {
+          // We should only get here *after* we're locked into the married state.
+          State state = fxAccount.getState();
+          if (state.getStateLabel() == StateLabel.Married) {
+            Married married = (Married) state;
+            fxAccount.setState(married.makeCohabitingState());
+          }
+        } finally {
+          fxAccount.releaseSharedAccountStateLock();
+        }
+        callback.handleError(null, e);
       }
 
       @Override
       public void handleError(Exception e) {
         Logger.error(LOG_TAG, "Failed to get token.", e);
+        fxAccount.releaseSharedAccountStateLock();
         callback.handleError(null, e);
       }
 
       @Override
       public void handleBackoff(int backoffSeconds) {
         // This is the token server telling us to back off.
         Logger.info(LOG_TAG, "Token server requesting backoff of " + backoffSeconds + "s. Backoff handler: " + tokenBackoffHandler);
         didReceiveBackoff = true;
@@ -403,32 +413,24 @@ public class FxAccountSyncAdapter extend
           AccountPickler.pickle(fxAccount, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
         } catch (Exception e) {
           // Should never happen, but we really don't want to die in a background thread.
           Logger.warn(LOG_TAG, "Got exception pickling current account details; ignoring.", e);
         }
       }
     });
 
-    final CountDownLatch latch = new CountDownLatch(1);
+    final BlockingQueue<Result> latch = new LinkedBlockingQueue<>(1);
 
     Collection<String> knownStageNames = SyncConfiguration.validEngineNames();
     Collection<String> stageNamesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
 
     final SyncDelegate syncDelegate = new SyncDelegate(latch, syncResult, fxAccount, stageNamesToSync);
 
     try {
-      final State state;
-      try {
-        state = fxAccount.getState();
-      } catch (Exception e) {
-        syncDelegate.handleError(e);
-        return;
-      }
-
       // This will be the same chunk of SharedPreferences that we pass through to GlobalSession/SyncConfiguration.
       final SharedPreferences sharedPrefs = fxAccount.getSyncPrefs();
 
       final BackoffHandler backgroundBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "background");
       final BackoffHandler rateLimitBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "rate");
 
       // If this sync was triggered by user action, this will be true.
       final boolean isImmediate = (extras != null) &&
@@ -451,67 +453,57 @@ public class FxAccountSyncAdapter extend
       }
 
       final SchedulePolicy schedulePolicy = new FxAccountSchedulePolicy(context, fxAccount);
 
       // Set a small scheduled 'backoff' to rate-limit the next sync,
       // and extend the background delay even further into the future.
       schedulePolicy.configureBackoffMillisBeforeSyncing(rateLimitBackoffHandler, backgroundBackoffHandler);
 
-      final String authServerEndpoint = fxAccount.getAccountServerURI();
       final String tokenServerEndpoint = fxAccount.getTokenServerURI();
       final URI tokenServerEndpointURI = new URI(tokenServerEndpoint);
       final String audience = FxAccountUtils.getAudienceForURL(tokenServerEndpoint);
 
-      // TODO: why doesn't the loginPolicy extract the audience from the account?
-      final FxAccountClient client = new FxAccountClient20(authServerEndpoint, executor);
-      final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
-      stateMachine.advance(state, StateLabel.Married, new LoginStateMachineDelegate() {
-        @Override
-        public FxAccountClient getClient() {
-          return client;
-        }
-
-        @Override
-        public long getCertificateDurationInMilliseconds() {
-          return 12 * 60 * 60 * 1000;
-        }
+      try {
+        // The clock starts... now!
+        fxAccount.acquireSharedAccountStateLock(FxAccountSyncAdapter.LOG_TAG);
+      } catch (InterruptedException e) {
+        // OK, skip this sync.
+        syncDelegate.handleError(e);
+        return;
+      }
 
-        @Override
-        public long getAssertionDurationInMilliseconds() {
-          return 15 * 60 * 1000;
-        }
+      final State state;
+      try {
+        state = fxAccount.getState();
+      } catch (Exception e) {
+        fxAccount.releaseSharedAccountStateLock();
+        syncDelegate.handleError(e);
+        return;
+      }
 
+      final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
+      stateMachine.advance(state, StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
         @Override
-        public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
-          return StateFactory.generateKeyPair();
-        }
-
-        @Override
-        public void handleTransition(Transition transition, State state) {
-          Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
+        public void handleNotMarried(State notMarried) {
+          Logger.info(LOG_TAG, "handleNotMarried: in " + notMarried.getStateLabel());
+          schedulePolicy.onHandleFinal(notMarried.getNeededAction());
+          syncDelegate.handleCannotSync(notMarried);
         }
 
         private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
           return shouldPerformSync(tokenBackoffHandler, "token", extras);
         }
 
         @Override
-        public void handleFinal(State state) {
-          Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
-          fxAccount.setState(state);
-          schedulePolicy.onHandleFinal(state.getNeededAction());
-          notificationManager.update(context, fxAccount);
+        public void handleMarried(Married married) {
+          schedulePolicy.onHandleFinal(married.getNeededAction());
+          Logger.info(LOG_TAG, "handleMarried: in " + married.getStateLabel());
+
           try {
-            if (state.getStateLabel() != StateLabel.Married) {
-              syncDelegate.handleCannotSync(state);
-              return;
-            }
-
-            final Married married = (Married) state;
             final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
 
             /*
              * At this point we're in the correct state to sync, and we're ready to fetch
              * a token and do some work.
              *
              * But first we need to do two things:
              * 1. Check to see whether we're in a backoff situation for the token server.
@@ -534,26 +526,28 @@ public class FxAccountSyncAdapter extend
               Logger.info(LOG_TAG, "Not syncing (token server).");
               syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
               return;
             }
 
             final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
             final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
             final String clientState = married.getClientState();
-            syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras);
+            syncWithAssertion(audience, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, syncKeyBundle, clientState, sessionCallback, extras, fxAccount);
           } catch (Exception e) {
             syncDelegate.handleError(e);
             return;
           }
         }
       });
 
-      latch.await();
+      latch.take();
     } catch (Exception e) {
       Logger.error(LOG_TAG, "Got error syncing.", e);
       syncDelegate.handleError(e);
+    } finally {
+      fxAccount.releaseSharedAccountStateLock();
     }
 
     Logger.info(LOG_TAG, "Syncing done.");
     lastSyncRealtimeMillis = SystemClock.elapsedRealtime();
   }
 }
--- a/mobile/android/base/fxa/sync/FxAccountSyncDelegate.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncDelegate.java
@@ -1,42 +1,40 @@
 /* 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.sync;
 
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.BlockingQueue;
 
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.tokenserver.TokenServerException;
 
 import android.content.SyncResult;
 
 public class FxAccountSyncDelegate {
-  protected final CountDownLatch latch;
+  public enum Result {
+    Success,
+    Error,
+    Postponed,
+    Rejected,
+  }
+
+  protected final BlockingQueue<Result> latch;
   protected final SyncResult syncResult;
-  protected final AndroidFxAccount fxAccount;
 
-  public FxAccountSyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount) {
+  public FxAccountSyncDelegate(BlockingQueue<Result> latch, SyncResult syncResult) {
     if (latch == null) {
       throw new IllegalArgumentException("latch must not be null");
     }
     if (syncResult == null) {
       throw new IllegalArgumentException("syncResult must not be null");
     }
-    if (fxAccount == null) {
-      throw new IllegalArgumentException("fxAccount must not be null");
-    }
     this.latch = latch;
     this.syncResult = syncResult;
-    this.fxAccount = fxAccount;
   }
 
   /**
    * No error!  Say that we made progress.
    */
   protected void setSyncResultSuccess() {
     syncResult.stats.numUpdates += 1;
   }
@@ -55,32 +53,22 @@ public class FxAccountSyncDelegate {
    * progress, until the user intervenes.
    */
   protected void setSyncResultHardError() {
     syncResult.stats.numAuthExceptions += 1;
   }
 
   public void handleSuccess() {
     setSyncResultSuccess();
-    latch.countDown();
+    latch.offer(Result.Success);
   }
 
   public void handleError(Exception e) {
     setSyncResultSoftError();
-    // This is awful, but we need to propagate bad assertions back up the
-    // chain somehow, and this will do for now.
-    if (e instanceof TokenServerException) {
-      // We should only get here *after* we're locked into the married state.
-      State state = fxAccount.getState();
-      if (state.getStateLabel() == StateLabel.Married) {
-        Married married = (Married) state;
-        fxAccount.setState(married.makeCohabitingState());
-      }
-    }
-    latch.countDown();
+    latch.offer(Result.Error);
   }
 
   /**
    * When the login machine terminates, we might not be in the
    * <code>Married</code> state, and therefore we can't sync. This method
    * messages as much to the user.
    * <p>
    * To avoid stopping us syncing altogether, we set a soft error rather than
@@ -89,34 +77,34 @@ public class FxAccountSyncDelegate {
    * initiated activity mark the Android account as ready to sync again. This
    * is tricky, though, so we play it safe for now.
    *
    * @param finalState
    *          that login machine ended in.
    */
   public void handleCannotSync(State finalState) {
     setSyncResultSoftError();
-    latch.countDown();
+    latch.offer(Result.Error);
   }
 
   public void postponeSync(long millis) {
     if (millis > 0) {
       // delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
       // So we don't bother doing this. Instead, we rely on the periodic sync
       // we schedule, and the backoff handler for the rest.
       /*
       Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
       syncResult.delayUntil = millis / 1000;
        */
     }
     setSyncResultSoftError();
-    latch.countDown();
+    latch.offer(Result.Postponed);
   }
 
   /**
    * Simply don't sync, without setting any error flags.
    * This is the appropriate behavior when a routine backoff has not yet
    * been met.
    */
   public void rejectSync() {
-    latch.countDown();
+    latch.offer(Result.Rejected);
   }
-}
\ No newline at end of file
+}
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -367,16 +367,25 @@ size. -->
 <!ENTITY contextmenu_edit_bookmark "Edit">
 <!ENTITY contextmenu_subscribe "Subscribe to Page">
 <!ENTITY contextmenu_site_settings "Edit Site Settings">
 <!ENTITY contextmenu_top_sites_edit "Edit">
 <!ENTITY contextmenu_top_sites_pin "Pin Site">
 <!ENTITY contextmenu_top_sites_unpin "Unpin Site">
 <!ENTITY contextmenu_add_search_engine "Add a Search Engine">
 
+<!-- Localization note (doorhanger_login_no_username): This string is used in the save-login doorhanger
+     where normally a username would be displayed. In this case, no username was found, and this placeholder
+     contains brackets to indicate this is not actually a username, but rather a placeholder -->
+<!ENTITY doorhanger_login_no_username "[No username]">
+<!ENTITY doorhanger_login_edit_title "Edit login">
+<!ENTITY doorhanger_login_edit_username_hint "Username">
+<!ENTITY doorhanger_login_edit_password_hint "Password">
+<!ENTITY doorhanger_login_edit_toggle "Show password">
+
 <!ENTITY pref_titlebar_mode "Title bar">
 <!ENTITY pref_titlebar_mode_title "Show page title">
 <!ENTITY pref_titlebar_mode_url "Show page address">
 
 <!-- Localization note (pref_scroll_title_bar2): Label for setting that controls
      whether or not the dynamic toolbar is enabled. -->
 <!ENTITY pref_scroll_title_bar2 "Full-screen browsing">
 <!ENTITY pref_scroll_title_bar_summary "Hide the &brandShortName; title bar when scrolling down a page">
@@ -425,16 +434,17 @@ size. -->
 
 <!ENTITY button_ok "OK">
 <!ENTITY button_cancel "Cancel">
 <!ENTITY button_yes "Yes">
 <!ENTITY button_no "No">
 <!ENTITY button_clear_data "Clear data">
 <!ENTITY button_set "Set">
 <!ENTITY button_clear "Clear">
+<!ENTITY button_remember "Remember">
 
 <!ENTITY home_top_sites_title "Top Sites">
 <!-- Localization note (home_top_sites_add): This string is used as placeholder
      text underneath empty thumbnails in the Top Sites page on about:home. -->
 <!ENTITY home_top_sites_add "Add a site">
 
 <!-- Localization note (home_title): This string should be kept in sync
      with the page title defined in aboutHome.dtd -->
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/reading/ReadingListBackoffObserver.java
@@ -0,0 +1,54 @@
+/* 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.reading;
+
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.sync.net.HttpResponseObserver;
+import org.mozilla.gecko.sync.net.MozResponse;
+
+import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
+
+public class ReadingListBackoffObserver implements HttpResponseObserver {
+  protected final String host;
+  protected final AtomicLong largestBackoffObservedInSeconds = new AtomicLong(-1);
+
+  public ReadingListBackoffObserver(String host) {
+    this.host = host;
+    Utils.throwIfNull(host);
+  }
+
+  @Override
+  public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
+    // Ignore non-Reading List storage requests.
+    if (!host.equals(request.getURI().getHost())) {
+      return;
+    }
+
+    final MozResponse res = new MozResponse(response);
+    long backoffInSeconds = -1;
+    try {
+      backoffInSeconds = Math.max(res.backoffInSeconds(), res.retryAfterInSeconds());
+    } catch (NumberFormatException e) {
+      // Ignore.
+    }
+
+    if (backoffInSeconds <= 0) {
+      return;
+    }
+
+    while (true) {
+      long existingBackoff = largestBackoffObservedInSeconds.get();
+      if (existingBackoff >= backoffInSeconds) {
+        return;
+      }
+      if (largestBackoffObservedInSeconds.compareAndSet(existingBackoff, backoffInSeconds)) {
+        return;
+      }
+    }
+  }
+}
--- a/mobile/android/base/reading/ReadingListClient.java
+++ b/mobile/android/base/reading/ReadingListClient.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.reading;
 
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.util.Queue;
 import java.util.concurrent.Executor;
 
+import org.mozilla.gecko.background.ReadingListConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.reading.ReadingListResponse.ResponseFactory;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.MozResponse;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/reading/ReadingListInvalidAuthenticationException.java
@@ -0,0 +1,18 @@
+/* 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.reading;
+
+import org.mozilla.gecko.sync.net.MozResponse;
+
+public class ReadingListInvalidAuthenticationException extends Exception {
+  private static final long serialVersionUID = 7112459541558266597L;
+
+  public final MozResponse response;
+
+  public ReadingListInvalidAuthenticationException(MozResponse response) {
+    super();
+    this.response = response;
+  }
+}
--- a/mobile/android/base/reading/ReadingListSyncAdapter.java
+++ b/mobile/android/base/reading/ReadingListSyncAdapter.java
@@ -1,87 +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.reading;
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.security.NoSuchAlgorithmException;
 import java.util.Collection;
-import java.util.concurrent.CountDownLatch;
+import java.util.EnumSet;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import org.mozilla.gecko.background.ReadingListConstants;
 import org.mozilla.gecko.background.common.PrefsBranch;
 import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient.RequestDelegate;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
-import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10.AuthorizationResponse;
-import org.mozilla.gecko.browserid.BrowserIDKeyPair;
-import org.mozilla.gecko.browserid.JSONWebTokenUtils;
+import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
+import org.mozilla.gecko.fxa.FirefoxAccounts;
+import org.mozilla.gecko.fxa.FirefoxAccounts.SyncHint;
+import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
-import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine.LoginStateMachineDelegate;
-import org.mozilla.gecko.fxa.login.FxAccountLoginTransition.Transition;
-import org.mozilla.gecko.fxa.login.Married;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.StateFactory;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate.Result;
+import org.mozilla.gecko.sync.BackoffHandler;
+import org.mozilla.gecko.sync.PrefsBackoffHandler;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
+import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BearerAuthHeaderProvider;
 
 import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.content.AbstractThreadedSyncAdapter;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SyncResult;
 import android.os.Bundle;
 
 public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
   public static final String PREF_LOCAL_NAME = "device.localname";
-  public static final String OAUTH_CLIENT_ID_FENNEC = "3332a18d142636cb";
-  public static final String OAUTH_SCOPE_READINGLIST = "readinglist";
 
   private static final String LOG_TAG = ReadingListSyncAdapter.class.getSimpleName();
   private static final long TIMEOUT_SECONDS = 60;
   protected final ExecutorService executor;
 
+  // Don't sync again if we successfully synced within this duration.
+  private static final int AFTER_SUCCESS_SYNC_DELAY_SECONDS = 5 * 60; // 5 minutes.
+  // Don't sync again if we unsuccessfully synced within this duration.
+  private static final int AFTER_ERROR_SYNC_DELAY_SECONDS = 15 * 60; // 15 minutes.
+
   public ReadingListSyncAdapter(Context context, boolean autoInitialize) {
     super(context, autoInitialize);
     this.executor = Executors.newSingleThreadExecutor();
   }
 
-
-  static final class SyncAdapterSynchronizerDelegate implements ReadingListSynchronizerDelegate {
+  protected static abstract class SyncAdapterSynchronizerDelegate implements ReadingListSynchronizerDelegate {
     private final FxAccountSyncDelegate syncDelegate;
     private final ContentProviderClient cpc;
     private final SyncResult result;
 
     SyncAdapterSynchronizerDelegate(FxAccountSyncDelegate syncDelegate,
                                     ContentProviderClient cpc,
                                     SyncResult result) {
       this.syncDelegate = syncDelegate;
       this.cpc = cpc;
       this.result = result;
     }
 
+    abstract public void onInvalidAuthentication();
+
     @Override
     public void onUnableToSync(Exception e) {
       Logger.warn(LOG_TAG, "Unable to sync.", e);
+      if (e instanceof ReadingListInvalidAuthenticationException) {
+        onInvalidAuthentication();
+      }
       cpc.release();
       syncDelegate.handleError(e);
     }
 
     @Override
     public void onDeletionsUploadComplete() {
       Logger.debug(LOG_TAG, "Step: onDeletionsUploadComplete");
       this.result.stats.numEntries += 1;   // TODO: Bug 1140809.
@@ -116,185 +119,197 @@ public class ReadingListSyncAdapter exte
     @Override
     public void onComplete() {
       Logger.info(LOG_TAG, "Reading list synchronization complete.");
       cpc.release();
       syncDelegate.handleSuccess();
     }
   }
 
+  private void syncWithAuthorization(final Context context,
+                                     final URI endpoint,
+                                     final SyncResult syncResult,
+                                     final FxAccountSyncDelegate syncDelegate,
+                                     final String authToken,
+                                     final SharedPreferences sharedPrefs,
+                                     final Bundle extras) {
+    final AuthHeaderProvider auth = new BearerAuthHeaderProvider(authToken);
+
+    final PrefsBranch branch = new PrefsBranch(sharedPrefs, "readinglist.");
+    final ReadingListClient remote = new ReadingListClient(endpoint, auth);
+    final ContentProviderClient cpc = getContentProviderClient(context); // Released by the inner SyncAdapterSynchronizerDelegate.
+
+    final LocalReadingListStorage local = new LocalReadingListStorage(cpc);
+    String localName = branch.getString(PREF_LOCAL_NAME, null);
+    if (localName == null) {
+      localName = FxAccountUtils.defaultClientName(context);
+    }
+
+    // Make sure DB rows don't refer to placeholder values.
+    local.updateLocalNames(localName);
+
+    final ReadingListSynchronizer synchronizer = new ReadingListSynchronizer(branch, remote, local);
+
+    synchronizer.syncAll(new SyncAdapterSynchronizerDelegate(syncDelegate, cpc, syncResult) {
+      @Override
+      public void onInvalidAuthentication() {
+        // The reading list server rejected our oauth token! Invalidate it. Next
+        // time through, we'll request a new one, which will drive the login
+        // state machine, produce a new assertion, and eventually a fresh token.
+        Logger.info(LOG_TAG, "Invalidating oauth token after 401!");
+        AccountManager.get(context).invalidateAuthToken(FxAccountConstants.ACCOUNT_TYPE, authToken);
+      }
+    });
+    // TODO: backoffs, and everything else handled by a SessionCallback.
+  }
+
   @Override
   public void onPerformSync(final Account account, final Bundle extras, final String authority, final ContentProviderClient provider, final SyncResult syncResult) {
     Logger.setThreadLogTag(ReadingListConstants.GLOBAL_LOG_TAG);
     Logger.resetLogging();
 
+    final EnumSet<SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
+    FirefoxAccounts.logSyncHints(syncHints);
+
     final Context context = getContext();
     final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
 
-    // If this sync was triggered by user action, this will be true.
-    final boolean isImmediate = (extras != null) &&
-        (extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false) ||
-            extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
+    // Don't sync Reading List if we're in a non-default configuration, but allow testing against stage.
+    final String accountServerURI = fxAccount.getAccountServerURI();
+    final boolean usingDefaultAuthServer = FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(accountServerURI);
+    final boolean usingStageAuthServer = FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(accountServerURI);
+    if (!usingDefaultAuthServer && !usingStageAuthServer) {
+      Logger.error(LOG_TAG, "Skipping Reading List sync because Firefox Account is not using prod or stage auth server.");
+      // Stop syncing the Reading List entirely.
+      ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 0);
+      return;
+    }
+    final String tokenServerURI = fxAccount.getTokenServerURI();
+    final boolean usingDefaultSyncServer = FxAccountConstants.DEFAULT_TOKEN_SERVER_ENDPOINT.equals(tokenServerURI);
+    final boolean usingStageSyncServer = FxAccountConstants.STAGE_TOKEN_SERVER_ENDPOINT.equals(tokenServerURI);
+    if (!usingDefaultSyncServer && !usingStageSyncServer) {
+      Logger.error(LOG_TAG, "Skipping Reading List sync because Sync is not using the prod or stage Sync (token) server.");
+      Logger.debug(LOG_TAG, "If the user has chosen to not store Sync data with Mozilla, we shouldn't store Reading List data with Mozilla .");
+      // Stop syncing the Reading List entirely.
+      ContentResolver.setIsSyncable(account, BrowserContract.READING_LIST_AUTHORITY, 0);
+      return;
+    }
+
+    Result result = Result.Error;
+    final BlockingQueue<Result> latch = new LinkedBlockingQueue<Result>(1);
+    final FxAccountSyncDelegate syncDelegate = new FxAccountSyncDelegate(latch, syncResult);
 
-    final CountDownLatch latch = new CountDownLatch(1);
-    final FxAccountSyncDelegate syncDelegate = new FxAccountSyncDelegate(latch, syncResult, fxAccount);
+    // Allow testing against stage.
+    final String endpointString;
+    if (usingStageAuthServer) {
+      endpointString = ReadingListConstants.DEFAULT_DEV_ENDPOINT;
+    } else {
+      endpointString = ReadingListConstants.DEFAULT_PROD_ENDPOINT;
+    }
+
+    Logger.info(LOG_TAG, "Syncing reading list against endpoint: " + endpointString);
+    final URI endpointURI;
     try {
-      final State state;
-      try {
-        state = fxAccount.getState();
-      } catch (Exception e) {
-        Logger.error(LOG_TAG, "Unable to sync.", e);
+      endpointURI = new URI(endpointString);
+    } catch (URISyntaxException e) {
+      // Should never happen.
+      Logger.error(LOG_TAG, "Unexpected malformed URI for reading list service: " + endpointString);
+      syncDelegate.handleError(e);
+      return;
+    }
+
+    final AccountManager accountManager = AccountManager.get(context);
+    // If we have an auth failure that requires user intervention, FxA will show system
+    // notifications prompting the user to re-connect as it advances the internal account state.
+    // true causes the auth token fetch to return null on failure immediately, rather than doing
+    // Mysterious Internal Work to try to get the token.
+    final boolean notifyAuthFailure = true;
+    try {
+      final SharedPreferences sharedPrefs = fxAccount.getReadingListPrefs();
+
+      final BackoffHandler storageBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "storage");
+      final long storageBackoffDelayMilliseconds = storageBackoffHandler.delayMilliseconds();
+      if (!syncHints.contains(SyncHint.SCHEDULE_NOW) && !syncHints.contains(SyncHint.IGNORE_REMOTE_SERVER_BACKOFF) && storageBackoffDelayMilliseconds > 0) {
+        Logger.warn(LOG_TAG, "Not syncing: storage requested additional backoff: " + storageBackoffDelayMilliseconds + " milliseconds.");
+        syncDelegate.rejectSync();
         return;
       }
 
-      final String oauthServerUri = ReadingListConstants.OAUTH_ENDPOINT_PROD;
-      final String authServerEndpoint = fxAccount.getAccountServerURI();
-      final String audience = FxAccountUtils.getAudienceForURL(oauthServerUri); // The assertion gets traded in for an oauth bearer token.
-
-      final SharedPreferences sharedPrefs = fxAccount.getReadingListPrefs();
-      final FxAccountClient client = new FxAccountClient20(authServerEndpoint, executor);
-      final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
-
-      stateMachine.advance(state, StateLabel.Married, new LoginStateMachineDelegate() {
-        @Override
-        public FxAccountClient getClient() {
-          return client;
-        }
-
-        @Override
-        public long getCertificateDurationInMilliseconds() {
-          return 12 * 60 * 60 * 1000;
-        }
-
-        @Override
-        public long getAssertionDurationInMilliseconds() {
-          return 15 * 60 * 1000;
-        }
-
-        @Override
-        public BrowserIDKeyPair generateKeyPair() throws NoSuchAlgorithmException {
-          return StateFactory.generateKeyPair();
-        }
+      final BackoffHandler rateLimitBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "rate");
+      final long rateLimitBackoffDelayMilliseconds = rateLimitBackoffHandler.delayMilliseconds();
+      if (!syncHints.contains(SyncHint.SCHEDULE_NOW) && !syncHints.contains(SyncHint.IGNORE_LOCAL_RATE_LIMIT) && rateLimitBackoffDelayMilliseconds > 0) {
+        Logger.warn(LOG_TAG, "Not syncing: local rate limiting for another: " + rateLimitBackoffDelayMilliseconds + " milliseconds.");
+        syncDelegate.rejectSync();
+        return;
+      }
 
-        @Override
-        public void handleTransition(Transition transition, State state) {
-          Logger.info(LOG_TAG, "handleTransition: " + transition + " to " + state.getStateLabel());
-        }
-
-        @Override
-        public void handleFinal(State state) {
-          Logger.info(LOG_TAG, "handleFinal: in " + state.getStateLabel());
-          fxAccount.setState(state);
-
-          // TODO: scheduling, notifications.
-          try {
-            if (state.getStateLabel() != StateLabel.Married) {
-              syncDelegate.handleCannotSync(state);
-              return;
-            }
-
-            final Married married = (Married) state;
-            final String assertion = married.generateAssertion(audience, JSONWebTokenUtils.DEFAULT_ASSERTION_ISSUER);
-            JSONWebTokenUtils.dumpAssertion(assertion);
-
-            final String clientID = OAUTH_CLIENT_ID_FENNEC;
-            final String scope = OAUTH_SCOPE_READINGLIST;
-            syncWithAssertion(clientID, scope, assertion, sharedPrefs, extras);
-          } catch (Exception e) {
-            syncDelegate.handleError(e);
-            return;
-          }
-        }
+      final String authToken = accountManager.blockingGetAuthToken(account, ReadingListConstants.AUTH_TOKEN_TYPE, notifyAuthFailure);
+      if (authToken == null) {
+        throw new RuntimeException("Couldn't get oauth token!  Aborting sync.");
+      }
 
-        private void syncWithAssertion(final String client_id, final String scope, final String assertion,
-                                       final SharedPreferences sharedPrefs, final Bundle extras) {
-          final FxAccountOAuthClient10 oauthClient = new FxAccountOAuthClient10(oauthServerUri, executor);
-          Logger.debug(LOG_TAG, "OAuth fetch.");
-          oauthClient.authorization(client_id, assertion, null, scope, new RequestDelegate<FxAccountOAuthClient10.AuthorizationResponse>() {
-            @Override
-            public void handleSuccess(AuthorizationResponse result) {
-              Logger.debug(LOG_TAG, "OAuth success.");
-              syncWithAuthorization(result, sharedPrefs, extras);
-            }
-
-            @Override
-            public void handleFailure(FxAccountAbstractClientRemoteException e) {
-              Logger.error(LOG_TAG, "OAuth failure.", e);
-              syncDelegate.handleError(e);
-            }
-
-            @Override
-            public void handleError(Exception e) {
-              Logger.error(LOG_TAG, "OAuth error.", e);
-              syncDelegate.handleError(e);
-            }
-          });
+      final ReadingListBackoffObserver observer = new ReadingListBackoffObserver(endpointURI.getHost());
+      BaseResource.addHttpResponseObserver(observer);
+      try {
+        syncWithAuthorization(context, endpointURI, syncResult, syncDelegate, authToken, sharedPrefs, extras);
+        result = latch.poll(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+      } finally {
+        BaseResource.removeHttpResponseObserver(observer);
+        long backoffInSeconds = observer.largestBackoffObservedInSeconds.get();
+        if (backoffInSeconds > 0) {
+          Logger.warn(LOG_TAG, "Observed " + backoffInSeconds + "-second backoff request.");
+          storageBackoffHandler.extendEarliestNextRequest(System.currentTimeMillis() + 1000 * backoffInSeconds);
         }
-
-        private void syncWithAuthorization(AuthorizationResponse authResponse,
-                                           SharedPreferences sharedPrefs,
-                                           Bundle extras) {
-          final AuthHeaderProvider auth = new BearerAuthHeaderProvider(authResponse.access_token);
+      }
 
-          final String endpointString = ReadingListConstants.DEFAULT_DEV_ENDPOINT;
-          final URI endpoint;
-          Logger.info(LOG_TAG, "XXX Syncing to " + endpointString);
-          try {
-            endpoint = new URI(endpointString);
-          } catch (URISyntaxException e) {
-            // Should never happen.
-            Logger.error(LOG_TAG, "Unexpected malformed URI for reading list service: " + endpointString);
-            syncDelegate.handleError(e);
-            return;
-          }
-
-          final PrefsBranch branch = new PrefsBranch(sharedPrefs, "readinglist.");
-          final ReadingListClient remote = new ReadingListClient(endpoint, auth);
-          final ContentProviderClient cpc = getContentProviderClient(context);     // TODO: make sure I'm always released!
+      switch (result) {
+      case Success:
+        requestPeriodicSync(account, ReadingListSyncAdapter.AFTER_SUCCESS_SYNC_DELAY_SECONDS);
+        break;
+      case Error:
+        requestPeriodicSync(account, ReadingListSyncAdapter.AFTER_ERROR_SYNC_DELAY_SECONDS);
+        break;
+      case Postponed:
+        break;
+      case Rejected:
+        break;
+      }
 
-          final LocalReadingListStorage local = new LocalReadingListStorage(cpc);
-          String localName = branch.getString(PREF_LOCAL_NAME, null);
-          if (localName == null) {
-            localName = FxAccountUtils.defaultClientName(context);
-          }
-
-          // Make sure DB rows don't refer to placeholder values.
-          local.updateLocalNames(localName);
-
-          final ReadingListSynchronizer synchronizer = new ReadingListSynchronizer(branch, remote, local);
-
-          synchronizer.syncAll(new SyncAdapterSynchronizerDelegate(syncDelegate, cpc, syncResult));
-          // TODO: backoffs, and everything else handled by a SessionCallback.
-        }
-      });
-
-      latch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
       Logger.info(LOG_TAG, "Reading list sync done.");
-
     } catch (Exception e) {
+      // We can get lots of exceptions here; handle them uniformly.
       Logger.error(LOG_TAG, "Got error syncing.", e);
       syncDelegate.handleError(e);
     }
+
     /*
      * TODO:
      * * Account error notifications. How do we avoid these overlapping with Sync?
      * * Pickling. How do we avoid pickling twice if you use both Sync and RL?
      */
 
     /*
      * TODO:
      * * Auth.
      * * Server URI lookup.
      * * Syncing.
      * * Error handling.
-     * * Backoff and retry-after.
-     * * Sync scheduling.
      * * Forcing syncs/interactive use.
      */
   }
 
-
   private ContentProviderClient getContentProviderClient(Context context) {
     final ContentResolver contentResolver = context.getContentResolver();
     final ContentProviderClient client = contentResolver.acquireContentProviderClient(ReadingListItems.CONTENT_URI);
     return client;
   }
+
+  /**
+   * Updates the existing system periodic sync interval to the specified duration.
+   *
+   * @param intervalSeconds the requested period, which Android will vary by up to 4%.
+   */
+  protected void requestPeriodicSync(final Account account, final long intervalSeconds) {
+    final String authority = BrowserContract.AUTHORITY;
+    Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + ".");
+    ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds);
+  }
 }
--- a/mobile/android/base/reading/ReadingListSynchronizer.java
+++ b/mobile/android/base/reading/ReadingListSynchronizer.java
@@ -769,17 +769,21 @@ public class ReadingListSynchronizer {
         delegate.fail(error);
       }
 
       @Override
       public void onFailure(MozResponse response) {
         final int statusCode = response.getStatusCode();
         Logger.error(LOG_TAG, "Download failed. since = " + since + ". Response: " + statusCode);
         response.logResponseBody(LOG_TAG);
-        delegate.fail();
+        if (response.isInvalidAuthentication()) {
+          delegate.fail(new ReadingListInvalidAuthenticationException(response));
+        } else {
+          delegate.fail();
+        }
       }
 
       @Override
       public void onComplete(ReadingListResponse response) {
         long lastModified = response.getLastModified();
         Logger.info(LOG_TAG, "Server last modified: " + lastModified);
         try {
           postDownload.finish();
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -121,15 +121,19 @@
                 android:targetPackage="@string/browser_intent_package" />
         </Preference>
     </PreferenceCategory>
     <PreferenceCategory
         android:key="debug_category" >
         <Preference android:key="debug_refresh" />
         <Preference android:key="debug_dump" />
         <Preference android:key="debug_force_sync" />
+        <Preference android:key="debug_invalidate_certificate" />
+        <Preference android:key="debug_forget_reading_list_oauth_token" />
         <Preference android:key="debug_forget_certificate" />
         <Preference android:key="debug_require_password" />
         <Preference android:key="debug_require_upgrade" />
         <Preference android:key="debug_migrated_from_sync11" />
-    </PreferenceCategory>
+        <Preference android:key="debug_make_account_stage" />
+        <Preference android:key="debug_make_account_default" />
+	</PreferenceCategory>
 
 </PreferenceScreen>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -334,16 +334,22 @@
   <string name="contextmenu_edit_bookmark">&contextmenu_edit_bookmark;</string>
   <string name="contextmenu_subscribe">&contextmenu_subscribe;</string>
   <string name="contextmenu_site_settings">&contextmenu_site_settings;</string>
   <string name="contextmenu_top_sites_edit">&contextmenu_top_sites_edit;</string>
   <string name="contextmenu_top_sites_pin">&contextmenu_top_sites_pin;</string>
   <string name="contextmenu_top_sites_unpin">&contextmenu_top_sites_unpin;</string>
   <string name="contextmenu_add_search_engine">&contextmenu_add_search_engine;</string>
 
+  <string name="doorhanger_login_no_username">&doorhanger_login_no_username;</string>
+  <string name="doorhanger_login_edit_title">&doorhanger_login_edit_title;</string>
+  <string name="doorhanger_login_edit_username_hint">&doorhanger_login_edit_username_hint;</string>
+  <string name="doorhanger_login_edit_password_hint">&doorhanger_login_edit_password_hint;</string>
+  <string name="doorhanger_login_edit_toggle">&doorhanger_login_edit_toggle;</string>
+
   <string name="pref_titlebar_mode">&pref_titlebar_mode;</string>
   <string name="pref_titlebar_mode_title">&pref_titlebar_mode_title;</string>
   <string name="pref_titlebar_mode_url">&pref_titlebar_mode_url;</string>
 
   <string name="pref_scroll_title_bar2">&pref_scroll_title_bar2;</string>
   <string name="pref_scroll_title_bar_summary">&pref_scroll_title_bar_summary;</string>
 
   <string name="page_removed">&page_removed;</string>
@@ -361,16 +367,17 @@
 
   <string name="button_ok">&button_ok;</string>
   <string name="button_cancel">&button_cancel;</string>
   <string name="button_clear_data">&button_clear_data;</string>
   <string name="button_set">&button_set;</string>
   <string name="button_clear">&button_clear;</string>
   <string name="button_yes">&button_yes;</string>
   <string name="button_no">&button_no;</string>
+  <string name="button_remember">&button_remember;</string>
 
   <string name="home_title">&home_title;</string>
   <string name="home_top_sites_title">&home_top_sites_title;</string>
   <string name="home_top_sites_add">&home_top_sites_add;</string>
   <string name="home_history_title">&home_history_title;</string>
   <string name="home_clear_history_button">&home_clear_history_button;</string>
   <string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
   <string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
--- a/mobile/android/base/sync/GlobalSession.java
+++ b/mobile/android/base/sync/GlobalSession.java
@@ -54,16 +54,17 @@ import org.mozilla.gecko.sync.stage.Glob
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 import org.mozilla.gecko.sync.stage.NoSuchStageException;
 import org.mozilla.gecko.sync.stage.PasswordsServerSyncStage;
 import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
 import org.mozilla.gecko.sync.stage.UploadMetaGlobalStage;
 
 import android.content.Context;
 import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
 
 public class GlobalSession implements HttpResponseObserver {
   private static final String LOG_TAG = "GlobalSession";
 
   public static final long STORAGE_VERSION = 5;
 
   public SyncConfiguration config = null;
 
@@ -1119,34 +1120,42 @@ public class GlobalSession implements Ht
    */
   protected final AtomicLong largestBackoffObserved = new AtomicLong(-1);
 
   /**
    * Reset any observed backoff and start observing HTTP responses for backoff
    * requests.
    */
   protected void installAsHttpResponseObserver() {
-    Logger.debug(LOG_TAG, "Installing " + this + " as BaseResource HttpResponseObserver.");
-    BaseResource.setHttpResponseObserver(this);
+    Logger.debug(LOG_TAG, "Adding " + this + " as a BaseResource HttpResponseObserver.");
+    BaseResource.addHttpResponseObserver(this);
     largestBackoffObserved.set(-1);
   }
 
   /**
    * Stop observing HttpResponses for backoff requests.
    */
   protected void uninstallAsHttpResponseObserver() {
-    Logger.debug(LOG_TAG, "Uninstalling " + this + " as BaseResource HttpResponseObserver.");
-    BaseResource.setHttpResponseObserver(null);
+    Logger.debug(LOG_TAG, "Removing " + this + " as a BaseResource HttpResponseObserver.");
+    BaseResource.removeHttpResponseObserver(this);
   }
 
   /**
    * Observe all HTTP response for backoff requests on all status codes, not just errors.
    */
   @Override
-  public void observeHttpResponse(HttpResponse response) {
+  public void observeHttpResponse(HttpUriRequest request, HttpResponse response) {
+    // Ignore non-Sync storage requests.
+    final URI clusterURL = config.getClusterURL();
+    if (clusterURL != null && !clusterURL.getHost().equals(request.getURI().getHost())) {
+      // It's possible to see requests without a clusterURL (in particular,
+      // during testing); allow some extra backoffs in this case.
+      return;
+    }
+
     long responseBackoff = (new SyncResponse(response)).totalBackoffInMilliseconds(); // TODO: don't allocate object?
     if (responseBackoff <= 0) {
       return;
     }
 
     Logger.debug(LOG_TAG, "Observed " + responseBackoff + " millisecond backoff request.");
     while (true) {
       long existingBackoff = largestBackoffObserved.get();
--- a/mobile/android/base/sync/net/BaseResource.java
+++ b/mobile/android/base/sync/net/BaseResource.java
@@ -9,16 +9,17 @@ import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.lang.ref.WeakReference;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.security.GeneralSecurityException;
 import java.security.KeyManagementException;
 import java.security.NoSuchAlgorithmException;
 import java.security.SecureRandom;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 import javax.net.ssl.SSLContext;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 
@@ -53,16 +54,17 @@ import ch.boye.httpclientandroidlib.prot
 import ch.boye.httpclientandroidlib.util.EntityUtils;
 
 /**
  * Provide simple HTTP access to a Sync server or similar.
  * Implements Basic Auth by asking its delegate for credentials.
  * Communicates with a ResourceDelegate to asynchronously return responses and errors.
  * Exposes simple get/post/put/delete methods.
  */
+@SuppressWarnings("deprecation")
 public class BaseResource implements Resource {
   private static final String ANDROID_LOOPBACK_IP = "10.0.2.2";
 
   private static final int MAX_TOTAL_CONNECTIONS     = 20;
   private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
 
   private boolean retryOnFailedRequest = true;
 
@@ -72,17 +74,23 @@ public class BaseResource implements Res
 
   protected final URI uri;
   protected BasicHttpContext context;
   protected DefaultHttpClient client;
   public    ResourceDelegate delegate;
   protected HttpRequestBase request;
   public final String charset = "utf-8";
 
-  protected static WeakReference<HttpResponseObserver> httpResponseObserver = null;
+  /**
+   * We have very few writes (observers tend to be installed around sync
+   * sessions) and many iterations (every HTTP request iterates observers), so
+   * CopyOnWriteArrayList is a reasonable choice.
+   */
+  protected static final CopyOnWriteArrayList<WeakReference<HttpResponseObserver>>
+    httpResponseObservers = new CopyOnWriteArrayList<>();
 
   public BaseResource(String uri) throws URISyntaxException {
     this(uri, rewriteLocalhost);
   }
 
   public BaseResource(URI uri) {
     this(uri, rewriteLocalhost);
   }
@@ -104,28 +112,43 @@ public class BaseResource implements Res
         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) {
-      return null;
+  public static void addHttpResponseObserver(HttpResponseObserver newHttpResponseObserver) {
+    if (newHttpResponseObserver == null) {
+      return;
     }
-    return httpResponseObserver.get();
+    httpResponseObservers.add(new WeakReference<HttpResponseObserver>(newHttpResponseObserver));
   }
 
-  public static synchronized void setHttpResponseObserver(HttpResponseObserver newHttpResponseObserver) {
-    if (httpResponseObserver != null) {
-      httpResponseObserver.clear();
+  public static boolean isHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
+    for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
+      HttpResponseObserver innerHttpResponseObserver = weakReference.get();
+      if (innerHttpResponseObserver == httpResponseObserver) {
+        return true;
+      }
     }
-    httpResponseObserver = new WeakReference<HttpResponseObserver>(newHttpResponseObserver);
+    return false;
+  }
+
+  public static boolean removeHttpResponseObserver(HttpResponseObserver httpResponseObserver) {
+    for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
+      HttpResponseObserver innerHttpResponseObserver = weakReference.get();
+      if (innerHttpResponseObserver == httpResponseObserver) {
+        // It's safe to mutate the observers while iterating.
+        httpResponseObservers.remove(weakReference);
+        return true;
+      }
+    }
+    return false;
   }
 
   @Override
   public URI getURI() {
     return this.uri;
   }
 
   @Override
@@ -269,19 +292,21 @@ public class BaseResource implements Res
         delegate.handleHttpIOException(ex);
       } else {
         retryRequest();
       }
       return;
     }
 
     // Don't retry if the observer or delegate throws!
-    HttpResponseObserver observer = getHttpResponseObserver();
-    if (observer != null) {
-      observer.observeHttpResponse(response);
+    for (WeakReference<HttpResponseObserver> weakReference : httpResponseObservers) {
+      HttpResponseObserver observer = weakReference.get();
+      if (observer != null) {
+        observer.observeHttpResponse(request, response);
+      }
     }
     delegate.handleHttpResponse(response);
   }
 
   private void retryRequest() {
     // Only retry once.
     retryOnFailedRequest = false;
     Logger.debug(LOG_TAG, "Retrying request...");
--- a/mobile/android/base/sync/net/HttpResponseObserver.java
+++ b/mobile/android/base/sync/net/HttpResponseObserver.java
@@ -1,17 +1,20 @@
 /* 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 ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.client.methods.HttpUriRequest;
 
 public interface HttpResponseObserver {
   /**
    * Observe an HTTP response.
+   * @param request
+   *          The <code>HttpUriRequest<code> that elicited the response.
    *
    * @param response
    *          The <code>HttpResponse</code> to observe.
    */
-  public void observeHttpResponse(HttpResponse response);
+  public void observeHttpResponse(HttpUriRequest request, HttpResponse response);
 }
--- a/mobile/android/base/sync/net/MozResponse.java
+++ b/mobile/android/base/sync/net/MozResponse.java
@@ -14,16 +14,17 @@ import java.util.Scanner;
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpResponse;
+import ch.boye.httpclientandroidlib.HttpStatus;
 import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
 import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
 
 public class MozResponse {
   private static final String LOG_TAG = "MozResponse";
 
   private static final String HEADER_RETRY_AFTER = "retry-after";
 
@@ -37,16 +38,20 @@ public class MozResponse {
   public int getStatusCode() {
     return this.response.getStatusLine().getStatusCode();
   }
 
   public boolean wasSuccessful() {
     return this.getStatusCode() == 200;
   }
 
+  public boolean isInvalidAuthentication() {
+    return this.getStatusCode() == HttpStatus.SC_UNAUTHORIZED;
+  }
+
   /**
    * Fetch the content type of the HTTP response body.
    *
    * @return a <code>Header</code> instance, or <code>null</code> if there was
    *         no body or no valid Content-Type.
    */
   public Header getContentType() {
     HttpEntity entity = this.response.getEntity();
@@ -167,21 +172,21 @@ public class MozResponse {
       return (int)((then - now) / 1000);     // Convert milliseconds to seconds.
     } catch (DateParseException e) {
       Logger.warn(LOG_TAG, "Retry-After header neither integer nor date: " + retryAfter);
       return -1;
     }
   }
 
   /**
-   * @return A number of seconds, or -1 if the 'X-Backoff' header was not
+   * @return A number of seconds, or -1 if the 'Backoff' header was not
    *         present.
    */
   public int backoffInSeconds() throws NumberFormatException {
-    return this.getIntegerHeader("x-backoff");
+    return this.getIntegerHeader("backoff");
   }
 
   public void logResponseBody(final String logTag) {
     if (!Logger.LOG_PERSONAL_INFORMATION) {
       return;
     }
     try {
       Logger.pii(logTag, "Response body: " + body());
--- a/mobile/android/base/sync/net/SyncResponse.java
+++ b/mobile/android/base/sync/net/SyncResponse.java
@@ -17,16 +17,24 @@ public class SyncResponse extends MozRes
    * @return A number of seconds, or -1 if the 'X-Weave-Backoff' header was not
    *         present.
    */
   public int weaveBackoffInSeconds() throws NumberFormatException {
     return this.getIntegerHeader("x-weave-backoff");
   }
 
   /**
+   * @return A number of seconds, or -1 if the 'X-Backoff' header was not
+   *         present.
+   */
+  public int xBackoffInSeconds() throws NumberFormatException {
+    return this.getIntegerHeader("x-backoff");
+  }
+
+  /**
    * Extract a number of seconds, or -1 if none of the specified headers were present.
    *
    * @param includeRetryAfter
    *          if <code>true</code>, the Retry-After header is excluded. This is
    *          useful for processing non-error responses where a Retry-After
    *          header would be unexpected.
    * @return the maximum of the three possible backoff headers, in seconds.
    */
@@ -42,17 +50,17 @@ public class SyncResponse extends MozRes
     int weaveBackoffInSeconds = -1;
     try {
       weaveBackoffInSeconds = weaveBackoffInSeconds();
     } catch (NumberFormatException e) {
     }
 
     int backoffInSeconds = -1;
     try {
-      backoffInSeconds = backoffInSeconds();
+      backoffInSeconds = xBackoffInSeconds();
     } catch (NumberFormatException e) {
     }
 
     int totalBackoff = Math.max(retryAfterInSeconds, Math.max(backoffInSeconds, weaveBackoffInSeconds));
     if (totalBackoff < 0) {
       return -1;
     } else {
       return totalBackoff;
--- a/mobile/android/confvars.sh
+++ b/mobile/android/confvars.sh
@@ -51,17 +51,17 @@ MOZ_ENABLE_SZIP=1
 
 # Enable navigator.mozPay
 MOZ_PAY=1
 
 # Enable UI for healthreporter
 MOZ_SERVICES_HEALTHREPORT=1
 
 # Enable reading list service integration.
-#MOZ_ANDROID_READING_LIST_SERVICE=1
+MOZ_ANDROID_READING_LIST_SERVICE=1
 
 # Enable runtime locale switching.
 MOZ_LOCALE_SWITCHER=1
 
 # Enable second screen and casting support for external devices.
 MOZ_DEVICES=1
 
 # Enable second screen using native Android libraries, provided we're
--- a/security/manager/boot/src/StaticHPKPins.errors
+++ b/security/manager/boot/src/StaticHPKPins.errors
@@ -1,13 +1,11 @@
 Can't find hash in builtin certs for Chrome nickname RapidSSL, inserting GOOGLE_PIN_RapidSSL
-Can't find hash in builtin certs for Chrome nickname Entrust_G2, inserting GOOGLE_PIN_Entrust_G2
 Can't find hash in builtin certs for Chrome nickname Entrust_SSL, inserting GOOGLE_PIN_Entrust_SSL
 Can't find hash in builtin certs for Chrome nickname GTECyberTrustGlobalRoot, inserting GOOGLE_PIN_GTECyberTrustGlobalRoot
-Can't find hash in builtin certs for Chrome nickname EntrustRootEC1, inserting GOOGLE_PIN_EntrustRootEC1
 Can't find hash in builtin certs for Chrome nickname GoDaddySecure, inserting GOOGLE_PIN_GoDaddySecure
 Can't find hash in builtin certs for Chrome nickname ThawtePremiumServer, inserting GOOGLE_PIN_ThawtePremiumServer
 Can't find hash in builtin certs for Chrome nickname SymantecClass3EVG3, inserting GOOGLE_PIN_SymantecClass3EVG3
 Can't find hash in builtin certs for Chrome nickname DigiCertECCSecureServerCA, inserting GOOGLE_PIN_DigiCertECCSecureServerCA
 Writing pinset test
 Writing pinset google
 Writing pinset tor
 Writing pinset twitterCom
--- a/security/manager/boot/src/StaticHPKPins.h
+++ b/security/manager/boot/src/StaticHPKPins.h
@@ -82,16 +82,24 @@ static const char kDigiCert_High_Assuran
 /* End Entity Test Cert */
 static const char kEnd_Entity_Test_CertFingerprint[] =
   "lzCakFt+nADIfIkgk+UE/EQ9SaT2nay2yu2iykVbvV8=";
 
 /* Entrust Root Certification Authority */
 static const char kEntrust_Root_Certification_AuthorityFingerprint[] =
   "bb+uANN7nNc/j7R95lkXrwDg3d9C286sIMF8AnXuIJU=";
 
+/* Entrust Root Certification Authority - EC1 */
+static const char kEntrust_Root_Certification_Authority___EC1Fingerprint[] =
+  "/qK31kX7pz11PB7Jp4cMQOH3sMVh6Se5hb9xGGbjbyI=";
+
+/* Entrust Root Certification Authority - G2 */
+static const char kEntrust_Root_Certification_Authority___G2Fingerprint[] =
+  "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at/U=";
+
 /* Entrust.net Premium 2048 Secure Server CA */
 static const char kEntrust_net_Premium_2048_Secure_Server_CAFingerprint[] =
   "HqPF5D7WbC2imDpCpKebHpBnhs6fG1hiFBmgBGOofTg=";
 
 /* Equifax Secure CA */
 static const char kEquifax_Secure_CAFingerprint[] =
   "/1aAzXOlcD2gSBegdf1GJQanNQbEuBoVg+9UlHjSZHY=";
 
@@ -106,24 +114,16 @@ static const char kEquifax_Secure_eBusin
 /* FacebookBackup */
 static const char kFacebookBackupFingerprint[] =
   "1ww8E0AYsR2oX5lndk2hwp2Uosk=";
 
 /* GOOGLE_PIN_DigiCertECCSecureServerCA */
 static const char kGOOGLE_PIN_DigiCertECCSecureServerCAFingerprint[] =
   "PZXN3lRAy+8tBKk2Ox6F7jIlnzr2Yzmwqc3JnyfXoCw=";
 
-/* GOOGLE_PIN_EntrustRootEC1 */
-static const char kGOOGLE_PIN_EntrustRootEC1Fingerprint[] =
-  "/qK31kX7pz11PB7Jp4cMQOH3sMVh6Se5hb9xGGbjbyI=";
-
-/* GOOGLE_PIN_Entrust_G2 */
-static const char kGOOGLE_PIN_Entrust_G2Fingerprint[] =
-  "du6FkDdMcVQ3u8prumAo6t3i3G27uMP2EOhR8R0at/U=";
-
 /* GOOGLE_PIN_Entrust_SSL */
 static const char kGOOGLE_PIN_Entrust_SSLFingerprint[] =
   "nsxRNo6G40YPZsKV5JQt1TCA8nseQQr/LRqp1Oa8fnw=";
 
 /* GOOGLE_PIN_GTECyberTrustGlobalRoot */
 static const char kGOOGLE_PIN_GTECyberTrustGlobalRootFingerprint[] =
   "EGn6R6CqT4z3ERscrqNl7q7RC//zJmDe9uBhS/rnCHU=";
 
@@ -198,24 +198,24 @@ static const char kGoogleBackup2048Finge
 /* GoogleG2 */
 static const char kGoogleG2Fingerprint[] =
   "Q9rWMO5T+KmAym79hfRqo3mQ4Oo=";
 
 /* Network Solutions Certificate Authority */
 static const char kNetwork_Solutions_Certificate_AuthorityFingerprint[] =
   "MtGA7THJNVieydu7ciEjuIO1/C3BD5/KOpXXfhv8tTQ=";
 
-/* SpiderOak1 */
-static const char kSpiderOak1Fingerprint[] =
-  "UPrvFUSrp9aal5v6Rn0Jv3YJ/wU=";
-
 /* SpiderOak2 */
 static const char kSpiderOak2Fingerprint[] =
   "D0fS/hquA6QprluciyO1hlFUAxg=";
 
+/* SpiderOak3 */
+static const char kSpiderOak3Fingerprint[] =
+  "l5JoIXv4lztZ+C6TJWgxZCHQzS4=";
+
 /* Starfield Class 2 CA */
 static const char kStarfield_Class_2_CAFingerprint[] =
   "FfFKxFycfaIz00eRZOgTf+Ne4POK6FgYPwhBDqgqxLQ=";
 
 /* Starfield Root Certificate Authority - G2 */
 static const char kStarfield_Root_Certificate_Authority___G2Fingerprint[] =
   "gI1os/q0iEpflxrOfRBVDXqVoWN3Tz7Dav/7IT++THQ=";
 
@@ -611,17 +611,17 @@ static const char* kPinset_twitterCDN_sh
   kUTN_USERFirst_Hardware_Root_CAFingerprint,
   kVeriSign_Class_3_Public_Primary_Certification_Authority___G4Fingerprint,
   kVerisign_Class_4_Public_Primary_Certification_Authority___G3Fingerprint,
   kDigiCert_High_Assurance_EV_Root_CAFingerprint,
   kBaltimore_CyberTrust_RootFingerprint,
   kEntrust_Root_Certification_AuthorityFingerprint,
   kVerisign_Class_2_Public_Primary_Certification_Authority___G3Fingerprint,
   kGlobalSign_Root_CA___R3Fingerprint,
-  kGOOGLE_PIN_Entrust_G2Fingerprint,
+  kEntrust_Root_Certification_Authority___G2Fingerprint,
   kGeoTrust_Universal_CA_2Fingerprint,
   kGeoTrust_Global_CAFingerprint,
   kGlobalSign_Root_CA___R2Fingerprint,
   kAddTrust_External_RootFingerprint,
   kVeriSign_Universal_Root_Certification_AuthorityFingerprint,
   kGeoTrust_Universal_CAFingerprint,
   kGOOGLE_PIN_Entrust_SSLFingerprint,
   kGeoTrust_Primary_Certification_Authority___G3Fingerprint,
@@ -637,30 +637,30 @@ static const StaticFingerprints kPinset_
 };
 
 static const StaticPinset kPinset_twitterCDN = {
   &kPinset_twitterCDN_sha1,
   &kPinset_twitterCDN_sha256
 };
 
 static const char* kPinset_dropbox_sha256_Data[] = {
-  kGOOGLE_PIN_EntrustRootEC1Fingerprint,
+  kEntrust_Root_Certification_Authority___EC1Fingerprint,
   kGOOGLE_PIN_ThawtePremiumServerFingerprint,
   kthawte_Primary_Root_CA___G3Fingerprint,
   kthawte_Primary_Root_CAFingerprint,
   kEntrust_net_Premium_2048_Secure_Server_CAFingerprint,
   kDigiCert_Assured_ID_Root_CAFingerprint,
   kGo_Daddy_Root_Certificate_Authority___G2Fingerprint,
   kGOOGLE_PIN_GoDaddySecureFingerprint,
   kGeoTrust_Primary_Certification_AuthorityFingerprint,
   kGo_Daddy_Class_2_CAFingerprint,
   kDigiCert_High_Assurance_EV_Root_CAFingerprint,
   kthawte_Primary_Root_CA___G2Fingerprint,
   kEntrust_Root_Certification_AuthorityFingerprint,
-  kGOOGLE_PIN_Entrust_G2Fingerprint,
+  kEntrust_Root_Certification_Authority___G2Fingerprint,
   kGeoTrust_Global_CAFingerprint,
   kGeoTrust_Primary_Certification_Authority___G3Fingerprint,
   kDigiCert_Global_Root_CAFingerprint,
   kGeoTrust_Primary_Certification_Authority___G2Fingerprint,
 };
 static const StaticFingerprints kPinset_dropbox_sha256 = {
   sizeof(kPinset_dropbox_sha256_Data) / sizeof(const char*),
   kPinset_dropbox_sha256_Data
@@ -691,25 +691,26 @@ static const StaticFingerprints kPinset_
 
 static const StaticPinset kPinset_facebook = {
   &kPinset_facebook_sha1,
   &kPinset_facebook_sha256
 };
 
 static const char* kPinset_spideroak_sha1_Data[] = {
   kSpiderOak2Fingerprint,
-  kSpiderOak1Fingerprint,
+  kSpiderOak3Fingerprint,
 };
 static const StaticFingerprints kPinset_spideroak_sha1 = {
   sizeof(kPinset_spideroak_sha1_Data) / sizeof(const char*),
   kPinset_spideroak_sha1_Data
 };
 
 static const char* kPinset_spideroak_sha256_Data[] = {
-  kGOOGLE_PIN_RapidSSLFingerprint,
+  kDigiCert_High_Assurance_EV_Root_CAFingerprint,
+  kGeoTrust_Global_CAFingerprint,
 };
 static const StaticFingerprints kPinset_spideroak_sha256 = {
   sizeof(kPinset_spideroak_sha256_Data) / sizeof(const char*),
   kPinset_spideroak_sha256_Data
 };
 
 static const StaticPinset kPinset_spideroak = {
   &kPinset_spideroak_sha1,
@@ -1076,9 +1077,9 @@ static const TransportSecurityPreload kP
   { "youtube.com", true, false, false, -1, &kPinset_google_root_pems },
   { "ytimg.com", true, false, false, -1, &kPinset_google_root_pems },
 };
 
 // Pinning Preload List Length = 347;
 
 static const int32_t kUnknownId = -1;
 
-static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1435400245288000);
+static const PRTime kPreloadPKPinsExpirationTime = INT64_C(1436005136747000);
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -1,10 +1,8 @@
-0x0a.net: could not connect to host
-301.website: did not receive HSTS header
 56ct.com: could not connect to host
 9point6.com: could not connect to host
 admin.google.com: did not receive HSTS header (error ignored - included regardless)
 adsfund.org: could not connect to host
 airbnb.com: did not receive HSTS header
 aiticon.de: did not receive HSTS header
 altmv.com: max-age too low: 7776000
 amigogeek.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
@@ -19,68 +17,70 @@ at.search.yahoo.com: did not receive HST
 atavio.at: could not connect to host
 atavio.ch: could not connect to host
 atavio.de: did not receive HSTS header
 au.search.yahoo.com: did not receive HSTS header
 auth.mail.ru: did not receive HSTS header
 auto4trade.nl: did not receive HSTS header
 az.search.yahoo.com: did not receive HSTS header
 azprep.us: could not connect to host
-balcan-underground.net: did not receive HSTS header
 baldwinkoo.com: did not receive HSTS header
 barcodeberlin.com: did not receive HSTS header
 bccx.com: could not connect to host
 bcm.com.au: max-age too low: 0
 be.search.yahoo.com: did not receive HSTS header
 bentertain.de: could not connect to host
 betnet.fr: could not connect to host
 bi.search.yahoo.com: did not receive HSTS header
 bigshinylock.minazo.net: could not connect to host
 bitfarm-archiv.com: did not receive HSTS header
 bitfarm-archiv.de: did not receive HSTS header
 bitgo.com: did not receive HSTS header
 bizon.sk: did not receive HSTS header
 blog.lookout.com: did not receive HSTS header
 br.search.yahoo.com: did not receive HSTS header
 brainfork.ml: did not receive HSTS header
-braintreegateway.com: did not receive HSTS header
 braintreepayments.com: did not receive HSTS header
 brainvation.de: did not receive HSTS header
 browserid.org: did not receive HSTS header
 business.medbank.com.mt: could not connect to host
 ca.search.yahoo.com: did not receive HSTS header
 calibreapp.com: did not receive HSTS header
 calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 carlolly.co.uk: could not connect to host
 cd.search.yahoo.com: did not receive HSTS header
+celltek-server.de: did not receive HSTS header
 cert.se: max-age too low: 2628001
 cg.search.yahoo.com: did not receive HSTS header
 ch.search.yahoo.com: did not receive HSTS header
 chainmonitor.com: could not connect to host
 changelab.cc: max-age too low: 0
 chatbot.me: could not connect to host
 checkout.google.com: did not receive HSTS header (error ignored - included regardless)
 chfr.search.yahoo.com: did not receive HSTS header
 chit.search.yahoo.com: did not receive HSTS header
+chm.vn: did not receive HSTS header
 chrome-devtools-frontend.appspot.com: did not receive HSTS header (error ignored - included regardless)
 chrome.google.com: did not receive HSTS header (error ignored - included regardless)
 cimballa.com: did not receive HSTS header
 cl.search.yahoo.com: did not receive HSTS header
 cn.search.yahoo.com: did not receive HSTS header
 co.search.yahoo.com: did not receive HSTS header
 code.google.com: did not receive HSTS header (error ignored - included regardless)
 codereview.chromium.org: did not receive HSTS header (error ignored - included regardless)
 console.python.org: did not receive HSTS header
 coursella.com: did not receive HSTS header
 cr.search.yahoo.com: did not receive HSTS header
 crate.io: did not receive HSTS header
 crbug.com: did not receive HSTS header
 crowdcurity.com: did not receive HSTS header
 crowdjuris.com: could not connect to host
 crypto.is: max-age too low: 7776000
+cs50.harvard.edu: max-age too low: 1
+cs50.net: max-age too low: 1
 csawctf.poly.edu: could not connect to host
 ct.search.yahoo.com: did not receive HSTS header
 cujanovic.com: did not receive HSTS header
 cyanogenmod.xxx: could not connect to host
 cybershambles.com: could not connect to host
 daylightcompany.com: did not receive HSTS header
 de.search.yahoo.com: did not receive HSTS header
 decibelios.li: did not receive HSTS header
@@ -93,59 +93,58 @@ dl.google.com: did not receive HSTS head
 do.search.yahoo.com: did not receive HSTS header
 docs.google.com: did not receive HSTS header (error ignored - included regardless)
 domaris.de: did not receive HSTS header
 download.jitsi.org: did not receive HSTS header
 drive.google.com: did not receive HSTS header (error ignored - included regardless)
 dropcam.com: did not receive HSTS header
 dzlibs.io: could not connect to host
 e-aut.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
+ecdn.cz: could not connect to host
 edmodo.com: did not receive HSTS header
 egit.co: could not connect to host
 elnutricionista.es: did not receive HSTS header
 email.lookout.com: could not connect to host
 en-maktoob.search.yahoo.com: did not receive HSTS header
 encrypted.google.com: did not receive HSTS header (error ignored - included regardless)
 epoxate.com: did not receive HSTS header
 errors.zenpayroll.com: could not connect to host
 es.search.yahoo.com: did not receive HSTS header
 esec.rs: did not receive HSTS header
 espanol.search.yahoo.com: did not receive HSTS header
 espra.com: could not connect to host
 ethitter.com: did not receive HSTS header
 etsysecure.com: could not connect to host
+ezequiel-garzon.com: could not connect to host
 fabianfischer.de: did not receive HSTS header
 fatzebra.com.au: did not receive HSTS header
 fi.search.yahoo.com: did not receive HSTS header
-filedir.com: could not connect to host
 fixingdns.com: could not connect to host
 fj.search.yahoo.com: did not receive HSTS header
 fm83.nl: did not receive HSTS header
 fonetiq.io: could not connect to host
 fr.search.yahoo.com: did not receive HSTS header
 friendlink.jp: did not receive HSTS header
 gamesdepartment.co.uk: did not receive HSTS header
 geekandi.com: max-age too low: 7776000
 getlantern.org: did not receive HSTS header
 gl.search.yahoo.com: did not receive HSTS header
 glass.google.com: did not receive HSTS header (error ignored - included regardless)
 gm.search.yahoo.com: did not receive HSTS header
 gmail.com: did not receive HSTS header (error ignored - included regardless)
-golf-6.com: did not receive HSTS header
-golf3.de: did not receive HSTS header
-golf4.de: did not receive HSTS header
 googlemail.com: did not receive HSTS header (error ignored - included regardless)
 googleplex.com: could not connect to host
 googleplex.com: could not connect to host (error ignored - included regardless)
 goto.google.com: did not receive HSTS header (error ignored - included regardless)
 gparent.org: did not receive HSTS header
 gr.search.yahoo.com: did not receive HSTS header
 grandmascookieblog.com: did not receive HSTS header
 greplin.com: could not connect to host
 groups.google.com: did not receive HSTS header (error ignored - included regardless)
+guidetoiceland.is: did not receive HSTS header
 hackerone-user-content.com: could not connect to host
 haste.ch: could not connect to host
 hatoko.net: could not connect to host
 history.google.com: did not receive HSTS header (error ignored - included regardless)
 hk.search.yahoo.com: did not receive HSTS header
 hn.search.yahoo.com: did not receive HSTS header
 hoerbuecher-und-hoerspiele.de: did not receive HSTS header
 honeytracks.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
@@ -160,16 +159,17 @@ id.search.yahoo.com: did not receive HST
 ie.search.yahoo.com: did not receive HSTS header
 ilmconpm.de: did not receive HSTS header
 in.search.yahoo.com: did not receive HSTS header
 inertianetworks.com: did not receive HSTS header
 iniiter.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 intercom.io: did not receive HSTS header
 interserved.com: did not receive HSTS header
 iop.intuit.com: max-age too low: 86400
+iranianlawschool.com: could not connect to host
 irccloud.com: did not receive HSTS header
 it.search.yahoo.com: did not receive HSTS header
 janoberst.com: did not receive HSTS header
 jelmer.co.uk: could not connect to host
 jonathan.ir: could not connect to host
 jottit.com: could not connect to host
 k-dev.de: could not connect to host
 keepclean.me: could not connect to host
@@ -210,24 +210,26 @@ marshut.net: could not connect to host
 matatall.com: could not connect to host
 mattmccutchen.net: could not connect to host
 mediacru.sh: could not connect to host
 megashur.se: did not receive HSTS header
 megaxchange.com: did not receive HSTS header
 meinebo.it: could not connect to host
 minikneet.nl: did not receive HSTS header
 mirindadomo.ru: did not receive HSTS header
-mnemotiv.com: did not receive HSTS header
+mnemotiv.com: could not connect to host
+mnsure.org: could not connect to host
 mobilethreat.net: could not connect to host
 mobilethreatnetwork.net: could not connect to host
 mocloud.eu: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 mqas.net: could not connect to host
 mt.search.yahoo.com: did not receive HSTS header
 mu.search.yahoo.com: did not receive HSTS header
 mujadin.se: max-age too low: 86400
+munuc.org: could not connect to host
 mw.search.yahoo.com: did not receive HSTS header
 mx.search.yahoo.com: did not receive HSTS header
 my.alfresco.com: did not receive HSTS header
 mydigipass.com: did not receive HSTS header
 mykolab.com: did not receive HSTS header
 mykreuzfahrt.de: did not receive HSTS header
 myni.io: could not connect to host
 neftaly.com: did not receive HSTS header
@@ -240,24 +242,22 @@ nexth.us: could not connect to host
 ng-security.com: could not connect to host
 ni.search.yahoo.com: did not receive HSTS header
 nl.search.yahoo.com: did not receive HSTS header
 no.search.yahoo.com: did not receive HSTS header
 noexpect.org: could not connect to host
 np.search.yahoo.com: did not receive HSTS header
 npw.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 null-sec.ru: could not connect to host
-nutsandboltsmedia.com: did not receive HSTS header
 nz.search.yahoo.com: did not receive HSTS header
 opendesk.cc: did not receive HSTS header
 openshift.redhat.com: did not receive HSTS header
 otakurepublic.com: did not receive HSTS header
 ottospora.nl: could not connect to host
 pa.search.yahoo.com: did not receive HSTS header
-passwd.io: could not connect to host
 passwordbox.com: did not receive HSTS header
 passwords.google.com: did not receive HSTS header (error ignored - included regardless)
 pe.search.yahoo.com: did not receive HSTS header
 ph.search.yahoo.com: did not receive HSTS header
 piratenlogin.de: could not connect to host
 pisidia.de: did not receive HSTS header
 pk.search.yahoo.com: did not receive HSTS header
 pl.search.yahoo.com: did not receive HSTS header
@@ -269,18 +269,20 @@ pr.search.yahoo.com: did not receive HST
 pressfreedomfoundation.org: did not receive HSTS header
 prodpad.com: did not receive HSTS header
 promecon-gmbh.de: did not receive HSTS header
 proximato.com: could not connect to host
 py.search.yahoo.com: did not receive HSTS header
 qc.search.yahoo.com: did not receive HSTS header
 rapidresearch.me: could not connect to host
 redlatam.org: did not receive HSTS header
-redports.org: could not connect to host
+redports.org: did not receive HSTS header
 regar42.fr: could not connect to host
+reserve-online.net: did not receive HSTS header
+richiemail.net: could not connect to host
 riseup.net: did not receive HSTS header
 rme.li: did not receive HSTS header
 ro.search.yahoo.com: did not receive HSTS header
 roddis.net: did not receive HSTS header
 ru.search.yahoo.com: did not receive HSTS header
 rw.search.yahoo.com: did not receive HSTS header
 sah3.net: could not connect to host
 saturngames.co.uk: could not connect to host
@@ -294,22 +296,22 @@ seowarp.net: max-age too low: 1576800
 serverdensity.io: did not receive HSTS header
 sg.search.yahoo.com: did not receive HSTS header
 shops.neonisi.com: could not connect to host
 siammedia.co: did not receive HSTS header
 silentcircle.org: could not connect to host
 simon.butcher.name: max-age too low: 2629743
 simplyfixit.co.uk: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 sites.google.com: did not receive HSTS header (error ignored - included regardless)
+smartcleaningcenter.nl: could not connect to host
 smartlend.se: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 sol.io: could not connect to host
 souyar.de: could not connect to host
 souyar.net: could not connect to host
 souyar.us: could not connect to host
-spartantheatre.org: did not receive HSTS header
 spdysync.com: did not receive HSTS header
 spreadsheets.google.com: did not receive HSTS header (error ignored - included regardless)
 square.com: did not receive HSTS header
 ssl.google-analytics.com: did not receive HSTS header (error ignored - included regardless)
 ssl.panoramio.com: did not receive HSTS header
 stocktrade.de: could not connect to host
 suite73.org: could not connect to host
 sunshinepress.org: could not connect to host
@@ -320,23 +322,23 @@ sylaps.com: [Exception... "Component ret
 t.facebook.com: did not receive HSTS header
 tablet.facebook.com: did not receive HSTS header
 talk.google.com: could not connect to host
 talk.google.com: could not connect to host (error ignored - included regardless)
 talkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 tandarts-haarlem.nl: did not receive HSTS header
 taxsquirrel.com: did not receive HSTS header
 tc-bonito.de: did not receive HSTS header
-techllage.com: did not receive HSTS header
 tektoria.de: did not receive HSTS header
 temehu.com: did not receive HSTS header
 terrax.berlin: could not connect to host
 th.search.yahoo.com: did not receive HSTS header
 the-sky-of-valkyries.com: could not connect to host
 thomasgriffin.io: did not receive HSTS header
+thorncreek.net: did not receive HSTS header
 toptexture.com: did not receive HSTS header
 tr.search.yahoo.com: did not receive HSTS header
 translate.googleapis.com: did not receive HSTS header (error ignored - included regardless)
 translatoruk.co.uk: did not receive HSTS header
 triop.se: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 tv.search.yahoo.com: could not connect to host
 tw.search.yahoo.com: did not receive HSTS header
 ua.search.yahoo.com: did not receive HSTS header
@@ -346,17 +348,16 @@ ustr.gov: max-age too low: 86400
 uy.search.yahoo.com: did not receive HSTS header
 uz.search.yahoo.com: did not receive HSTS header
 ve.search.yahoo.com: did not receive HSTS header
 vhost.co.id: could not connect to host
 viennan.net: did not receive HSTS header
 vn.search.yahoo.com: did not receive HSTS header
 vyncke.org: did not receive HSTS header
 wallet.google.com: did not receive HSTS header (error ignored - included regardless)
-wearvr.com: did not receive HSTS header
 webmail.mayfirst.org: did not receive HSTS header
 wideup.net: did not receive HSTS header
 wikidsystems.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 withustrading.com: did not receive HSTS header
 wiz.biz: could not connect to host
 wohnungsbau-ludwigsburg.de: did not receive HSTS header
 www.apollo-auto.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 www.calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
@@ -379,9 +380,8 @@ xa.search.yahoo.com: did not receive HST
 xplore-dna.net: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-periodicupdate-00000/getHSTSPreloadList.js :: processStsHeader :: line 134"  data: no]
 xtream-hosting.com: could not connect to host
 xtream-hosting.de: could not connect to host
 xtream-hosting.eu: could not connect to host
 xtreamhosting.eu: could not connect to host
 za.search.yahoo.com: did not receive HSTS header
 zh.search.yahoo.com: did not receive HSTS header
 zoo24.de: could not connect to host
-zotero.org: did not receive HSTS header
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1437819438137000);
+const PRTime gPreloadListExpirationTime = INT64_C(1438424329809000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
@@ -22,16 +22,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "0x0a.net", true },
   { "17hats.com", true },
   { "18f.gsa.gov", true },
   { "1a-diamantscheiben.de", true },
   { "1a-vermessung.at", true },
   { "1a-werkstattgeraete.de", true },
   { "2048game.co.uk", true },
   { "2600hq.com", true },
+  { "301.website", true },
   { "302.nyc", true },
   { "5apps.com", false },
   { "7183.org", true },
   { "8ack.de", true },
   { "abmahnhelfer.de", true },
   { "accounts.firefox.com", true },
   { "accounts.google.com", true },
   { "aclu.org", false },
@@ -52,17 +53,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "ahwatukeefoothillsmontessori.com", true },
   { "aids.gov", true },
   { "aie.de", true },
   { "airlea.com", true },
   { "aiticon.com", true },
   { "ajouin.com", true },
   { "akselinurmio.fi", true },
   { "al-shami.net", true },
-  { "aladdinschools.appspot.com", false },
+  { "aladdinschools.appspot.com", true },
   { "alainwolf.net", true },
   { "alanrickmanflipstable.com", true },
   { "alecvannoten.be", true },
   { "alexsexton.com", true },
   { "alexyang.me", true },
   { "alpha.irccloud.com", false },
   { "alza.cz", true },
   { "anadoluefessporkulubu.org", true },
@@ -128,16 +129,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "auraredshield.com", true },
   { "authentication.io", true },
   { "autoledky.sk", true },
   { "azabani.com", true },
   { "badges.fedoraproject.org", true },
   { "badges.stg.fedoraproject.org", true },
   { "baer.im", true },
   { "bagelsbakery.com", true },
+  { "balcan-underground.net", true },
   { "balikonos.cz", true },
   { "bank.simple.com", false },
   { "barslecht.com", true },
   { "barslecht.nl", true },
   { "baruch.me", true },
   { "bassh.net", true },
   { "bautied.de", true },
   { "bayrisch-fuer-anfaenger.de", true },
@@ -190,16 +192,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "bohramt.de", true },
   { "bonigo.de", true },
   { "bonitabrazilian.co.nz", true },
   { "bookingapp.nl", true },
   { "boxcryptor.com", true },
   { "boypoint.de", true },
   { "bradkovach.com", true },
   { "brage.info", true },
+  { "braintreegateway.com", true },
   { "brakemanpro.com", true },
   { "bran.cc", true },
   { "brandbuilderwebsites.com", true },
   { "breeswish.org", true },
   { "broeselei.at", true },
   { "brossmanit.com", true },
   { "brunosouza.org", true },
   { "buddhistische-weisheiten.org", true },
@@ -225,27 +228,25 @@ static const nsSTSPreload kSTSPreloadLis
   { "capitaltg.com", true },
   { "caremad.io", true },
   { "carezone.com", false },
   { "cartouche24.eu", true },
   { "cartucce24.it", true },
   { "casa-su.casa", true },
   { "cbhq.net", true },
   { "cdnb.co", true },
-  { "celltek-server.de", false },
   { "certible.com", true },
   { "certly.io", true },
   { "cfo.gov", true },
   { "chahub.com", true },
   { "chainmonitor.com", true },
   { "chartstoffarm.de", false },
   { "chatbot.me", true },
   { "check.torproject.org", false },
   { "checkout.google.com", true },
-  { "chm.vn", true },
   { "chontalpa.pw", true },
   { "chrisirwin.ca", true },
   { "chrisjean.com", true },
   { "chrome-devtools-frontend.appspot.com", true },
   { "chrome.com", false },
   { "chrome.google.com", true },
   { "chromiumcodereview.appspot.com", false },
   { "chulado.com", true },
@@ -288,18 +289,16 @@ static const nsSTSPreload kSTSPreloadLis
   { "crm.onlime.ch", false },
   { "crowdjuris.com", true },
   { "crypto.cat", false },
   { "crypto.graphics", true },
   { "cryptobin.org", true },
   { "cryptography.io", true },
   { "cryptopartyatx.org", true },
   { "cryptopush.com", true },
-  { "cs50.harvard.edu", true },
-  { "cs50.net", true },
   { "cspbuilder.info", true },
   { "csuw.net", true },
   { "cube.de", true },
   { "cupcake.io", true },
   { "cupcake.is", true },
   { "curiosity-driven.org", true },
   { "curlybracket.co.uk", true },
   { "curtacircuitos.com.br", false },
@@ -340,17 +339,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "deliverance.co.uk", true },
   { "denh.am", true },
   { "depechemode-live.com", true },
   { "derevtsov.com", false },
   { "derhil.de", true },
   { "detectify.com", false },
   { "developer.mydigipass.com", false },
   { "developers.facebook.com", false },
-  { "devinfo.net", true },
+  { "devinfo.net", false },
   { "diamante.ro", true },
   { "die-besten-weisheiten.de", true },
   { "diedrich.co", true },
   { "dillonkorman.com", true },
   { "dinamoelektrik.com", true },
   { "disking.co.uk", true },
   { "dist.torproject.org", false },
   { "dixmag.com", true },
@@ -375,17 +374,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "dyeager.org", true },
   { "dylanscott.com.au", true },
   { "dynaloop.net", true },
   { "dzlibs.io", true },
   { "e-kontakti.fi", true },
   { "e.mail.ru", true },
   { "earmarks.gov", true },
   { "easysimplecrm.com", false },
-  { "ebanking.indovinabank.com.vn", false },
+  { "ebanking.indovinabank.com.vn", true },
   { "ecdn.cz", true },
   { "ecfs.link", true },
   { "ecg.fr", false },
   { "ecosystem.atlassian.net", true },
   { "ectora.com", true },
   { "ed.gs", true },
   { "edit.yahoo.com", false },
   { "edix.ru", true },
@@ -412,17 +411,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "esoa.net", true },
   { "espra.com", true },
   { "ethack.org", true },
   { "eurotramp.com", true },
   { "eva.cz", true },
   { "evalesc.com", true },
   { "everhome.de", true },
   { "evstatus.com", true },
-  { "exiahost.com", true },
+  { "exiahost.com", false },
   { "exon.io", true },
   { "expatads.com", true },
   { "explodie.org", true },
   { "extendwings.com", true },
   { "ezequiel-garzon.com", true },
   { "ezequiel-garzon.net", true },
   { "f-droid.org", true },
   { "fa-works.com", true },
@@ -525,32 +524,32 @@ static const nsSTSPreload kSTSPreloadLis
   { "grc.com", false },
   { "greensolid.biz", true },
   { "grepular.com", true },
   { "groetzner.net", true },
   { "groups.google.com", true },
   { "gtraxapp.com", true },
   { "gudini.net", true },
   { "gugga.dk", false },
-  { "guidetoiceland.is", true },
   { "gunnarhafdal.com", true },
   { "guphi.net", true },
   { "guthabenkarten-billiger.de", true },
   { "gw2treasures.com", true },
   { "hachre.de", false },
   { "hack.li", true },
   { "hackerone.com", true },
   { "hansvaneijsden.com", true },
   { "happylifestyle.com", true },
   { "harvestapp.com", true },
   { "hasilocke.de", true },
   { "haste.ch", true },
   { "haufschild.de", true },
   { "hausverbrauch.de", true },
   { "hda.me", true },
+  { "healthcare.gov", false },
   { "heartlandrentals.com", true },
   { "heha.co", false },
   { "heid.ws", true },
   { "heijblok.com", true },
   { "helichat.de", true },
   { "help.simpletax.ca", false },
   { "helpadmin.net", true },
   { "helpium.de", true },
@@ -937,16 +936,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "nu3.com", true },
   { "nu3.de", true },
   { "nu3.dk", true },
   { "nu3.fi", true },
   { "nu3.fr", true },
   { "nu3.no", true },
   { "nu3.se", true },
   { "null-sec.ru", true },
+  { "nutsandboltsmedia.com", true },
   { "nuvini.com", true },
   { "nwa.xyz", true },
   { "nwgh.org", true },
   { "nymphetomania.net", true },
   { "oakslighting.co.uk", true },
   { "offshore-firma.org", true },
   { "ohling.org", true },
   { "okmx.de", true },
@@ -1071,26 +1071,24 @@ static const nsSTSPreload kSTSPreloadLis
   { "raspass.me", true },
   { "ravchat.com", true },
   { "rawstorieslondon.com", true },
   { "reaconverter.com", true },
   { "red-t-shirt.ru", true },
   { "redirect.fedoraproject.org", true },
   { "redirect.stg.fedoraproject.org", true },
   { "redletter.link", true },
-  { "redports.org", true },
   { "redteam-pentesting.de", true },
   { "reedloden.com", true },
   { "reg.ru", false },
   { "reishunger.de", true },
   { "release-monitoring.org", true },
   { "reliable-mail.de", true },
   { "renem.net", true },
   { "research.facebook.com", false },
-  { "reserve-online.net", true },
   { "residentsinsurance.co.uk", true },
   { "resources.flowfinity.com", true },
   { "reviews.anime.my", true },
   { "riccy.org", true },
   { "richiemail.net", true },
   { "ricochet.im", true },
   { "riesenmagnete.de", true },
   { "rika.me", true },
@@ -1207,16 +1205,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "sockeye.cc", true },
   { "soia.ca", true },
   { "sorz.org", true },
   { "souki.cz", true },
   { "soulogic.com", true },
   { "sour.is", true },
   { "southside-crew.com", true },
   { "souvik.me", true },
+  { "spartantheatre.org", true },
   { "spawn.cz", true },
   { "speedcounter.net", true },
   { "spencerbaer.com", true },
   { "spideroak.com", true },
   { "spongepowered.org", true },
   { "spreadsheets.google.com", true },
   { "spreed.me", true },
   { "sprueche-zum-valentinstag.de", true },
@@ -1274,16 +1273,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "taskotron.stg.fedoraproject.org", true },
   { "tatort-fanpage.de", true },
   { "tauchkater.de", true },
   { "tbspace.de", true },
   { "tdrs.info", true },
   { "teachforcanada.ca", true },
   { "teamnorthgermany.de", true },
   { "techhipster.net", true },
+  { "techllage.com", true },
   { "techloaner.com", true },
   { "tegelsensanitaironline.nl", true },
   { "tekshrek.com", true },
   { "tent.io", true },
   { "terraelectronica.ru", true },
   { "terrty.net", true },
   { "testsuite.org", true },
   { "texte-zur-taufe.de", true },
@@ -1292,19 +1292,18 @@ static const nsSTSPreload kSTSPreloadLis
   { "theescapistswiki.com", true },
   { "themoep.at", true },
   { "thepaymentscompany.com", true },
   { "therapynotes.com", true },
   { "therapyportal.com", true },
   { "theshadestore.com", true },
   { "thetomharling.com", true },
   { "thomastimepieces.com.au", true },
-  { "thorncreek.net", false },
   { "thusoy.com", true },
-  { "thyngster.com", true },
+  { "thyngster.com", false },
   { "tickopa.co.uk", true },
   { "tid.jp", true },
   { "timtaubert.de", true },
   { "tinfoilsecurity.com", false },
   { "tinte24.de", true },
   { "tintenfix.net", true },
   { "tipps-fuer-den-haushalt.de", true },
   { "tirex.media", true },
@@ -1396,16 +1395,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "vpnzoom.com", true },
   { "vrobert.fr", false },
   { "w-spotlight.appspot.com", true },
   { "wallet.google.com", true },
   { "walnutgaming.com", true },
   { "warrencreative.com", false },
   { "watsonhall.uk", true },
   { "wbg-vs.de", true },
+  { "wearvr.com", true },
   { "webandmore.de", false },
   { "webandwords.com.au", true },
   { "webassadors.com", false },
   { "webcollect.org.uk", true },
   { "webeau.com", true },
   { "webfilings-eu-mirror.appspot.com", true },
   { "webfilings-eu.appspot.com", true },
   { "webfilings-mirror-hrd.appspot.com", true },
@@ -1473,16 +1473,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "www.evernote.com", false },
   { "www.facebook.com", false },
   { "www.gamesdepartment.co.uk", false },
   { "www.getcloak.com", false },
   { "www.gmail.com", false },
   { "www.googlemail.com", false },
   { "www.gov.uk", false },
   { "www.grc.com", false },
+  { "www.healthcare.gov", false },
   { "www.heliosnet.com", true },
   { "www.honeybadger.io", false },
   { "www.intercom.io", false },
   { "www.irccloud.com", false },
   { "www.lastpass.com", false },
   { "www.ledgerscope.net", false },
   { "www.linode.com", false },
   { "www.lookout.com", false },
@@ -1527,10 +1528,11 @@ static const nsSTSPreload kSTSPreloadLis
   { "zapier.com", true },
   { "zenpayroll.com", false },
   { "zentralwolke.de", true },
   { "zeplin.io", false },
   { "zeropush.com", true },
   { "zhovner.com", false },
   { "zixiao.wang", true },
   { "zlavomat.sk", true },
+  { "zotero.org", true },
   { "zravypapir.cz", true },
 };
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1954,8 +1954,35 @@ Example
 
 ::
 
     "org.mozilla.passwordmgr.passwordmgr": {
       "_v": 1,
       "numSavedPasswords": 5,
       "enabled": 0,
     }
+
+Version 2
+^^^^^^^^^
+
+More detailed measurements of login forms & their behavior
+
+numNewSavedPasswordsInSession
+    Number of passwords saved to the password manager this session.
+
+numSuccessfulFills
+    Number of times the password manager filled in password fields for user this session.
+
+numTotalLoginsEncountered
+    Number of times a login form was encountered by the user in the session.
+
+Example
+^^^^^^^
+
+::
+    "org.mozilla.passwordmgr.passwordmgr": {
+      "_v": 2,
+      "numSavedPasswords": 32,
+      "enabled": 1,
+      "numNewSavedPasswords": 5,
+      "numSuccessfulFills": 11,
+      "numTotalLoginsEncountered": 23,
+    }
--- a/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml
+++ b/testing/taskcluster/tasks/tests/b2g_gaia_ui_test_functional.yml
@@ -29,17 +29,17 @@ task:
     artifacts:
       'public/build':
         type: directory
         path: '/home/worker/artifacts/'
         expires: '{{#from_now}}1 year{{/from_now}}'
 
   extra:
     chunks:
-      total: 3
+      total: 15
     treeherderEnv:
       - production
       - staging
     treeherder:
       groupName: Gaia Python Integration Tests
       groupSymbol: Gip
       symbol: 'f{{chunk}}'
       productName: b2g
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -256,21 +256,26 @@ var LoginManagerContent = {
   /*
    * onFormPassword
    *
    * Called when an <input type="password"> element is added to the page
    */
   onFormPassword: function (event) {
     if (!event.isTrusted)
       return;
+    let form = event.target;
+
+    let doc = form.ownerDocument;
+    let win = doc.defaultView;
+    let messageManager = messageManagerFromWindow(win);
+    messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
 
     if (!gEnabled)
       return;
 
-    let form = event.target;
     log("onFormPassword for", form.ownerDocument.documentURI);
     this._getLoginDataFromParent(form, { showMasterPassword: true })
         .then(this.loginsFound.bind(this))
         .then(null, Cu.reportError);
   },
 
   loginsFound: function({ form, loginsFound, recipes }) {
     let doc = form.ownerDocument;
@@ -801,16 +806,20 @@ var LoginManagerContent = {
           usernameField.setUserInput(selectedLogin.username);
         }
       }
       if (passwordField.value != selectedLogin.password) {
         passwordField.setUserInput(selectedLogin.password);
       }
 
       recordAutofillResult(AUTOFILL_RESULT.FILLED);
+      let doc = form.ownerDocument;
+      let win = doc.defaultView;
+      let messageManager = messageManagerFromWindow(win);
+      messageManager.sendAsyncMessage("LoginStats:LoginFillSuccessful");
     } finally {
       Services.obs.notifyObservers(form, "passwordmgr-processed-form", null);
     }
   },
 
 };
 
 var LoginUtils = {
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -16,16 +16,40 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AutoCompleteE10S",
                                   "resource://gre/modules/AutoCompleteE10S.jsm");
 
 this.EXPORTED_SYMBOLS = [ "LoginManagerParent", "PasswordsMetricsProvider" ];
 
 var gDebug;
 
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
+                                  "resource://gre/modules/Metrics.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+function recordFHRDailyCounter(aField) {
+    let reporter = Cc["@mozilla.org/datareporting/service;1"]
+                      .getService()
+                      .wrappedJSObject
+                      .healthReporter;
+    // This can happen if the FHR component of the data reporting service is
+    // disabled. This is controlled by a pref that most will never use.
+    if (!reporter) {
+      return;
+    }
+      reporter.onInit().then(() => reporter.getProvider("org.mozilla.passwordmgr")
+        .recordDailyCounter(aField));
+  }
+
+#endif
+#endif
+
 function log(...pieces) {
   function generateLogMessage(args) {
     let strings = ['Login Manager (parent):'];
 
     args.forEach(function(arg) {
       if (typeof arg === 'string') {
         strings.push(arg);
       } else if (typeof arg === 'undefined') {
@@ -48,66 +72,91 @@ function log(...pieces) {
 
   let message = generateLogMessage(pieces);
   dump(message + "\n");
   Services.console.logStringMessage(message);
 }
 
 #ifndef ANDROID
 #ifdef MOZ_SERVICES_HEALTHREPORT
-XPCOMUtils.defineLazyModuleGetter(this, "Metrics",
-                                  "resource://gre/modules/Metrics.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
 
 this.PasswordsMetricsProvider = function() {
   Metrics.Provider.call(this);
 }
 
 PasswordsMetricsProvider.prototype = Object.freeze({
   __proto__: Metrics.Provider.prototype,
 
   name: "org.mozilla.passwordmgr",
 
   measurementTypes: [
     PasswordsMeasurement1,
+    PasswordsMeasurement2,
   ],
 
-  pullOnly: true,
-
-  collectDailyData: function* () {
+  collectDailyData: function () {
     return this.storage.enqueueTransaction(this._recordDailyPasswordData.bind(this));
   },
 
-  _recordDailyPasswordData: function() {
-    let m = this.getMeasurement(PasswordsMeasurement1.prototype.name,
-                                PasswordsMeasurement1.prototype.version);
+  _recordDailyPasswordData: function *() {
+    let m = this.getMeasurement(PasswordsMeasurement2.prototype.name,
+                                PasswordsMeasurement2.prototype.version);
     let enabled = Services.prefs.getBoolPref("signon.rememberSignons");
     yield m.setDailyLastNumeric("enabled", enabled ? 1 : 0);
 
     let loginsCount = Services.logins.countLogins("", "", "");
     yield m.setDailyLastNumeric("numSavedPasswords", loginsCount);
 
   },
+
+  recordDailyCounter: function(aField) {
+    let m = this.getMeasurement(PasswordsMeasurement2.prototype.name,
+                                PasswordsMeasurement2.prototype.version);
+    if (this.storage.hasFieldFromMeasurement(m.id, aField,
+                                             Metrics.Storage.FIELD_DAILY_COUNTER)) {
+      let fieldID = this.storage.fieldIDFromMeasurement(m.id, aField, Metrics.Storage.FIELD_DAILY_COUNTER);
+      return this.enqueueStorageOperation(() => m.incrementDailyCounter(aField));
+    }
+
+    // Otherwise, we first need to create the field.
+    return this.enqueueStorageOperation (() => this.storage.registerField(m.id, aField, 
+      Metrics.Storage.FIELD_DAILY_COUNTER).then(() => m.incrementDailyCounter(aField)));
+  },
 });
 
 function PasswordsMeasurement1() {
   Metrics.Measurement.call(this);
 }
 
 PasswordsMeasurement1.prototype = Object.freeze({
   __proto__: Metrics.Measurement.prototype,
   name: "passwordmgr",
   version: 1,
   fields: {
     enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
     numSavedPasswords: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
   },
 });
 
+function PasswordsMeasurement2() {
+  Metrics.Measurement.call(this);
+}
+PasswordsMeasurement2.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+  name: "passwordmgr",
+  version: 2,
+  fields: {
+    enabled: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
+    numSavedPasswords: {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC},
+    numSuccessfulFills: {type: Metrics.Storage.FIELD_DAILY_COUNTER},
+    numNewSavedPasswordsInSession: {type: Metrics.Storage.FIELD_DAILY_COUNTER},
+    numTotalLoginsEncountered: {type: Metrics.Storage.FIELD_DAILY_COUNTER},
+  },
+});
+
 #endif
 #endif
 
 function prefChanged() {
   gDebug = Services.prefs.getBoolPref("signon.debug");
 }
 
 Services.prefs.addObserver("signon.debug", prefChanged, false);
@@ -115,22 +164,37 @@ prefChanged();
 
 var LoginManagerParent = {
   init: function() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"]
                .getService(Ci.nsIMessageListenerManager);
     mm.addMessageListener("RemoteLogins:findLogins", this);
     mm.addMessageListener("RemoteLogins:onFormSubmit", this);
     mm.addMessageListener("RemoteLogins:autoCompleteLogins", this);
+    mm.addMessageListener("LoginStats:LoginEncountered", this);
+    mm.addMessageListener("LoginStats:LoginFillSuccessful", this);
+    Services.obs.addObserver(this, "LoginStats:NewSavedPassword", false);
 
     XPCOMUtils.defineLazyGetter(this, "recipeParentPromise", () => {
       const { LoginRecipesParent } = Cu.import("resource://gre/modules/LoginRecipes.jsm", {});
       let parent = new LoginRecipesParent();
       return parent.initializationPromise;
     });
+
+  },
+
+  observe: function (aSubject, aTopic, aData) {
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+    if (aTopic == "LoginStats:NewSavedPassword") {
+      recordFHRDailyCounter("numNewSavedPasswordsInSession");
+
+    }
+#endif
+#endif
   },
 
   receiveMessage: function (msg) {
     let data = msg.data;
     switch (msg.name) {
       case "RemoteLogins:findLogins": {
         // TODO Verify msg.target's principals against the formOrigin?
         this.sendLoginDataToChild(data.options.showMasterPassword,
@@ -152,16 +216,34 @@ var LoginManagerParent = {
                           msg.target);
         break;
       }
 
       case "RemoteLogins:autoCompleteLogins": {
         this.doAutocompleteSearch(data, msg.target);
         break;
       }
+
+      case "LoginStats:LoginFillSuccessful": {
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+        recordFHRDailyCounter("numSuccessfulFills");
+#endif
+#endif
+        break;
+      }
+
+      case "LoginStats:LoginEncountered": {
+#ifndef ANDROID
+#ifdef MOZ_SERVICES_HEALTHREPORT
+        recordFHRDailyCounter("numTotalLoginsEncountered");
+#endif
+#endif
+        break;
+      }
     }
   },
 
   /**
    * Send relevant data (e.g. logins and recipes) to the child process (LoginManagerContent).
    */
   sendLoginDataToChild: Task.async(function*(showMasterPassword, formOrigin, actionOrigin,
                                              requestId, target) {
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -891,16 +891,20 @@ LoginManagerPrompter.prototype = {
     };
 
     // The main action is the "Remember" or "Update" button.
     let mainAction = {
       label: this._getLocalizedString(initialMsgNames.buttonLabel),
       accessKey: this._getLocalizedString(initialMsgNames.buttonAccessKey),
       callback: () => {
         histogram.add(PROMPT_ADD_OR_UPDATE);
+        if(histogramName == "PWMGR_PROMPT_REMEMBER_ACTION")
+        {
+          Services.obs.notifyObservers(null, 'LoginStats:NewSavedPassword', null);
+        }
         readDataFromUI();
         persistData();
         browser.focus();
       }
     };
 
     // Include a "Never for this site" button when saving a new password.
     let secondaryActions = type == "password-save" ? [{
--- a/xulrunner/stub/nsXULStub.cpp
+++ b/xulrunner/stub/nsXULStub.cpp
@@ -330,58 +330,40 @@ main(int argc, char **argv)
   rv = parser.Init(iniPath);
   if (NS_FAILED(rv)) {
     fprintf(stderr, "Could not read application.ini\n");
     return 1;
   }
 
   if (!greFound) {
 #ifdef XP_MACOSX
-    // Check for <bundle>/Contents/Frameworks/XUL.framework/Versions/Current/libmozglue.dylib
-    CFURLRef fwurl = CFBundleCopyPrivateFrameworksURL(appBundle);
-    CFURLRef absfwurl = nullptr;
-    if (fwurl) {
-      absfwurl = CFURLCopyAbsoluteURL(fwurl);
-      CFRelease(fwurl);
+    // The bundle can be found next to the executable, in the MacOS dir.
+    CFURLRef exurl = CFBundleCopyExecutableURL(appBundle);
+    CFURLRef absexurl = nullptr;
+    if (exurl) {
+      absexurl = CFURLCopyAbsoluteURL(exurl);
+      CFRelease(exurl);
     }
 
-    if (absfwurl) {
-      CFURLRef xulurl =
-        CFURLCreateCopyAppendingPathComponent(nullptr, absfwurl,
-                                              CFSTR("XUL.framework/Versions/Current"),
-                                              true);
-
-      if (xulurl) {
-        CFURLRef xpcomurl =
-          CFURLCreateCopyAppendingPathComponent(nullptr, xulurl,
-                                                CFSTR("libmozglue.dylib"),
-                                                false);
-
-        if (xpcomurl) {
-          char tbuffer[MAXPATHLEN];
+    if (absexurl) {
+      char tbuffer[MAXPATHLEN];
 
-          if (CFURLGetFileSystemRepresentation(xpcomurl, true,
-                                               (UInt8*) tbuffer,
-                                               sizeof(tbuffer)) &&
-              access(tbuffer, R_OK | X_OK) == 0) {
-            if (realpath(tbuffer, greDir)) {
-              greFound = true;
-            }
-            else {
-              greDir[0] = '\0';
-            }
-          }
-
-          CFRelease(xpcomurl);
+      if (CFURLGetFileSystemRepresentation(absexurl, true,
+                                           (UInt8*) tbuffer,
+                                           sizeof(tbuffer)) &&
+          access(tbuffer, R_OK | X_OK) == 0) {
+        if (realpath(tbuffer, greDir)) {
+          greFound = true;
         }
-
-        CFRelease(xulurl);
+        else {
+          greDir[0] = '\0';
+        }
       }
 
-      CFRelease(absfwurl);
+      CFRelease(absexurl);
     }
 #endif
     if (!greFound) {
       Output(false, "Could not find the Mozilla runtime.\n");
       return 1;
     }
   }