Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 19 Nov 2014 13:47:55 +0100
changeset 240805 7e45350adfc51919460dffc6902915ee4d254e7c
parent 240804 ea2d1aa200e425c8fb33c4f79ad436551d0c803e (current diff)
parent 240762 aa72ddfe9f9365cf4e9e3a70ff639c0e5c3829b8 (diff)
child 240806 52280c60e9e3f38c4eaebab43422c4d59f408fb3
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.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 mozilla-central to mozilla-inbound
browser/components/loop/standalone/content/l10n/loop.en-US.properties
--- 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="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <!-- 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,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a47dd04f8f66e42fd331711140f2c3e2fed0767d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <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"/>
@@ -129,12 +129,12 @@
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="197cd9492b9fadaa915c5daf36ff557f8f4a8d1c"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="libnfcemu" path="external/libnfcemu" remote="b2g" revision="125ccf9bd5986c7728ea44508b3e1d1185ac028b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c5f8d282efe4a4e8b1e31a37300944e338e60e4f"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9f28c4faea3b2f01db227b2467b08aeba96d9bec"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5502ba6f3f7606d0142cf38b1067d3d0a669f314"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="7e257a260fb6a6c3811f9c396cfe3ccf191241f6"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,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="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="a47dd04f8f66e42fd331711140f2c3e2fed0767d"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame-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="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <!-- 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"/>
@@ -148,13 +148,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="2a1ded216a91bf62a72b1640cf01ab4998f37028"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="f8bec8a61dc0f2581fa72a31d4144084b47ef7cf"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="9883ea57b0668d8f60dba025d4522dfa69a1fbfa"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="a558dc844bf5144fc38603fd8f4df8d9557052a5"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="57ee1320ed7b4a1a1274d8f3f6c177cd6b9becb2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="12b1977cc704b35f2e9db2bb423fa405348bc2f3"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="985bf15264d865fe7b9c5b45f61c451cbaafa43d"/>
   <project name="platform/system/core" path="system/core" revision="350eac5403124dacb2a5fd9e28ac290a59fc3b8e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5502ba6f3f7606d0142cf38b1067d3d0a669f314"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="7e257a260fb6a6c3811f9c396cfe3ccf191241f6"/>
   <project name="platform/system/qcom" path="system/qcom" revision="63e3f6f176caad587d42bba4c16b66d953fb23c2"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="d8952a42771045fca73ec600e2b42a4c7129d723"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="018b44e52b2bac5d3631d559550e88a4b68c6e67"/>
 </manifest>
--- 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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <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"/>
@@ -140,13 +140,13 @@
   <project name="platform/hardware/qcom/camera" path="hardware/qcom/camera" revision="5e110615212302c5d798a3c223dcee458817651c"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="fa9ffd47948eb24466de227e48fe9c4a7c5e7711"/>
   <project name="platform/hardware/qcom/gps" path="hardware/qcom/gps" revision="cd76b19aafd4229ccf83853d02faef8c51ca8b34"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="8a0d0b0d9889ef99c4c6317c810db4c09295f15a"/>
   <project name="platform/hardware/qcom/wlan" path="hardware/qcom/wlan" revision="2208fa3537ace873b8f9ec2355055761c79dfd5f"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="c4e2ac95907a5519a0e09f01a0d8e27fec101af0"/>
   <project name="platform/system/bluetooth" path="system/bluetooth" revision="e1eb226fa3ad3874ea7b63c56a9dc7012d7ff3c2"/>
   <project name="platform/system/core" path="system/core" revision="adc485d8755af6a61641d197de7cfef667722580"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5502ba6f3f7606d0142cf38b1067d3d0a669f314"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="7e257a260fb6a6c3811f9c396cfe3ccf191241f6"/>
   <project name="platform/system/qcom" path="system/qcom" revision="1cdab258b15258b7f9657da70e6f06ebd5a2fc25"/>
   <project name="platform/vendor/qcom/msm8610" path="device/qcom/msm8610" revision="4ae5df252123591d5b941191790e7abed1bce5a4"/>
   <project name="platform/vendor/qcom-opensource/wlan/prima" path="vendor/qcom/opensource/wlan/prima" revision="ce18b47b4a4f93a581d672bbd5cb6d12fe796ca9"/>
 </manifest>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "4f3818d9c5577c26d8368fad5e87e546519c4b13", 
+    "revision": "f155f8ab67a65c066730976e0b7cef0d39579a24", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,21 +12,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- 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="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <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"/>
@@ -124,17 +124,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="5502ba6f3f7606d0142cf38b1067d3d0a669f314"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="7e257a260fb6a6c3811f9c396cfe3ccf191241f6"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,22 +12,22 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <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="0da467aba9ecf354b905f9cc08dfa1a4169659e4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e64428c5b2dce5db90b75a5055077a04f4bd4819"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="93935064318f39906400a9e9fd636dd9528f5c16"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="b6f8267794b8c7f2a33236d46a857a84388b8485"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1661,17 +1661,17 @@ pref("loop.debug.websocket", false);
 pref("loop.debug.sdk", false);
 #ifdef DEBUG
 pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*; media-src blob:");
 #else
 pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net; media-src blob:");
 #endif
 pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
 pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
-pref("loop.rooms.enabled", false);
+pref("loop.rooms.enabled", true);
 pref("loop.fxa_oauth.tokendata", "");
 pref("loop.fxa_oauth.profile", "");
 
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "wss://push.services.mozilla.com/");
 
 pref("social.sidebar.unload_timeout_ms", 10000);
 
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -295,17 +295,16 @@ skip-if = os == 'win' || e10s # Bug 1056
 [browser_bug1064280_changeUrlInPinnedTab.js]
 [browser_canonizeURL.js]
 skip-if = e10s # Bug 1094510 - test hits the network in e10s mode only
 [browser_contentAreaClick.js]
 skip-if = e10s
 [browser_contextSearchTabPosition.js]
 skip-if = os == "mac" || e10s # bug 967013; e10s: bug 1094761 - test hits the network in e10s, causing next test to crash
 [browser_ctrlTab.js]
-skip-if = e10s # Bug ????? - thumbnail captures need e10s love (tabPreviews_capture fails with Argument 1 of CanvasRenderingContext2D.drawWindow does not implement interface Window.)
 [browser_customize_popupNotification.js]
 [browser_datareporting_notification.js]
 run-if = datareporting
 [browser_devedition.js]
 [browser_devices_get_user_media.js]
 skip-if = buildapp == 'mulet' || (os == "linux" && debug) || e10s # linux: bug 976544; e10s: bug 1071623
 [browser_devices_get_user_media_about_urls.js]
 skip-if = e10s # Bug 1071623
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -1540,29 +1540,29 @@ CustomizeMode.prototype = {
 
     let buttonVisible = Services.prefs.getBoolPref(kDeveditionButtonPref);
     if (buttonVisible) {
       button.removeAttribute("hidden");
     } else {
       button.setAttribute("hidden", "true");
     }
   },
-  toggleDevEditionTheme: function() {
+
+  toggleDevEditionTheme: function(shouldEnable) {
     const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
-    let button = this.document.getElementById("customization-devedition-theme-button");
-    let shouldEnable = button.hasAttribute("checked");
 
     Services.prefs.setBoolPref(kDeveditionThemePref, shouldEnable);
+
     let currentLWT = LightweightThemeManager.currentTheme;
     if (currentLWT && shouldEnable) {
       this._lastLightweightTheme = currentLWT;
       AddonManager.getAddonByID(DEFAULT_THEME_ID, function(aDefaultTheme) {
         // Theoretically, this could race if people are /very/ quick in switching
         // something else here, so doublecheck:
-        if (button.hasAttribute("checked")) {
+        if (Services.prefs.getBoolPref(kDeveditionThemePref)) {
           aDefaultTheme.userDisabled = false;
         }
       });
     } else if (!currentLWT && !shouldEnable && this._lastLightweightTheme) {
       LightweightThemeManager.currentTheme = this._lastLightweightTheme;
     }
   },
 
--- a/browser/components/customizableui/content/customizeMode.inc.xul
+++ b/browser/components/customizableui/content/customizeMode.inc.xul
@@ -51,18 +51,18 @@
                            oncommand="gCustomizeMode.getMoreThemes(event);"/>
           </hbox>
         </panel>
       </button>
 
       <button id="customization-devedition-theme-button"
               class="customizationmode-button"
               hidden="true"
-              label="&customizeMode.deveditionTheme.label;"
-              oncommand="gCustomizeMode.toggleDevEditionTheme()"
+              label="&customizeMode.deveditionTheme.label2;"
+              oncommand="gCustomizeMode.toggleDevEditionTheme(this.hasAttribute('checked'))"
               type="checkbox" />
 
       <spacer id="customization-footer-spacer"/>
       <button id="customization-undo-reset-button"
               class="customizationmode-button"
               hidden="true"
               oncommand="gCustomizeMode.undoReset();"
               label="&undoCmd.label;"/>
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -7,16 +7,17 @@
 /* jshint newcap:false */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedViews = loop.shared.views;
 
   function noop() {}
 
   /**
    * ActiveRoomStore mixin.
    * @type {Object}
@@ -127,17 +128,21 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
   var DesktopRoomConversationView = React.createClass({displayName: 'DesktopRoomConversationView',
-    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
+    mixins: [
+      ActiveRoomStoreMixin,
+      sharedMixins.DocumentTitleMixin,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     _renderInvitationOverlay: function() {
       if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
         return DesktopRoomInvitationView({
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -7,16 +7,17 @@
 /* jshint newcap:false */
 /* global loop:true, React */
 
 var loop = loop || {};
 loop.roomViews = (function(mozL10n) {
   "use strict";
 
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedViews = loop.shared.views;
 
   function noop() {}
 
   /**
    * ActiveRoomStore mixin.
    * @type {Object}
@@ -127,17 +128,21 @@ loop.roomViews = (function(mozL10n) {
       );
     }
   });
 
   /**
    * Desktop room conversation view.
    */
   var DesktopRoomConversationView = React.createClass({
-    mixins: [ActiveRoomStoreMixin, loop.shared.mixins.DocumentTitleMixin],
+    mixins: [
+      ActiveRoomStoreMixin,
+      sharedMixins.DocumentTitleMixin,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     _renderInvitationOverlay: function() {
       if (this.state.roomState !== ROOM_STATES.HAS_PARTICIPANTS) {
         return <DesktopRoomInvitationView
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -362,16 +362,19 @@ loop.store.ActiveRoomStore = (function()
 
     /**
      * Handles recording when a remote peer has connected to the servers.
      */
     remotePeerConnected: function() {
       this.setStoreState({
         roomState: ROOM_STATES.HAS_PARTICIPANTS
       });
+
+      // We've connected with a third-party, therefore stop displaying the ToS etc.
+      this._mozLoop.setLoopCharPref("seenToS", "seen");
     },
 
     /**
      * Handles a remote peer disconnecting from the session.
      */
     remotePeerDisconnected: function() {
       // As we only support two users at the moment, we just set this
       // back to joined.
--- a/browser/components/loop/content/shared/js/mixins.js
+++ b/browser/components/loop/content/shared/js/mixins.js
@@ -221,18 +221,75 @@ loop.shared.mixins = (function() {
     /**
      * Ensures audio is stopped when the component is unmounted.
      */
     componentWillUnmount: function() {
       this._ensureAudioStopped();
     }
   };
 
+  /**
+   * A mixin especially for rooms. This plays the right sound according to
+   * the state changes. Requires AudioMixin to also be used.
+   */
+  var RoomsAudioMixin = {
+    mixins: [AudioMixin],
+
+    componentWillUpdate: function(nextProps, nextState) {
+      var ROOM_STATES = loop.store.ROOM_STATES;
+
+      function isConnectedToRoom(state) {
+        return state === ROOM_STATES.HAS_PARTICIPANTS ||
+          state === ROOM_STATES.SESSION_CONNECTED;
+      }
+
+      function notConnectedToRoom(state) {
+        // Failed and full are states that the user is not
+        // really connected o the room, but we don't want to
+        // catch those here, as they get their own sounds.
+        return state === ROOM_STATES.INIT ||
+          state === ROOM_STATES.GATHER ||
+          state === ROOM_STATES.READY ||
+          state === ROOM_STATES.JOINED;
+      }
+
+      // Joining the room.
+      if (notConnectedToRoom(this.state.roomState) &&
+          isConnectedToRoom(nextState.roomState)) {
+        this.play("room-joined");
+      }
+
+      // Other people coming and leaving.
+      if (this.state.roomState === ROOM_STATES.SESSION_CONNECTED &&
+          nextState.roomState === ROOM_STATES.HAS_PARTICIPANTS) {
+        this.play("room-joined-in");
+      }
+
+      if (this.state.roomState === ROOM_STATES.HAS_PARTICIPANTS &&
+          nextState.roomState === ROOM_STATES.SESSION_CONNECTED) {
+        this.play("room-left");
+      }
+
+      // Leaving the room - same sound as if a participant leaves
+      if (isConnectedToRoom(this.state.roomState) &&
+          notConnectedToRoom(nextState.roomState)) {
+        this.play("room-left");
+      }
+
+      // Room failures
+      if (nextState.roomState === ROOM_STATES.FAILED ||
+          nextState.roomState === ROOM_STATES.FULL) {
+        this.play("failure");
+      }
+    }
+  };
+
   return {
     AudioMixin: AudioMixin,
+    RoomsAudioMixin: RoomsAudioMixin,
     setRootObject: setRootObject,
     DropdownMenuMixin: DropdownMenuMixin,
     DocumentVisibilityMixin: DocumentVisibilityMixin,
     DocumentLocationMixin: DocumentLocationMixin,
     DocumentTitleMixin: DocumentTitleMixin,
     UrlHashChangeMixin: UrlHashChangeMixin
   };
 })();
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..164325c63d6072b4de3ca0f2a5cfb476dabf3a2e
GIT binary patch
literal 9242
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d70fb260839d2eaf7b250b31f3c7e01b25ed8431
GIT binary patch
literal 10028
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
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -1,23 +1,23 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
   <head>
     <meta charset="utf-8">
+    <meta name="locales" content="en-US" />
+    <meta name="default_locale" content="en-US" />
+
     <link rel="stylesheet" type="text/css" href="shared/css/reset.css">
     <link rel="stylesheet" type="text/css" href="shared/css/common.css">
     <link rel="stylesheet" type="text/css" href="shared/css/conversation.css">
     <link rel="stylesheet" type="text/css" href="css/webapp.css">
-    <link rel="localization" href="l10n/loop.{locale}.properties">
-
-    <meta name="locales" content="en-US" />
-    <meta name="default_locale" content="en-US" />
+    <link rel="localization" href="l10n/{locale}/loop.properties">
   </head>
   <body class="standalone">
 
     <div id="main"></div>
 
     <!-- libs -->
     <script>
       // IE9, 10, and 11 lack the window.CustomEvent constructor. IE11 has the
--- a/browser/components/loop/standalone/content/js/standaloneMozLoop.js
+++ b/browser/components/loop/standalone/content/js/standaloneMozLoop.js
@@ -185,10 +185,38 @@ loop.StandaloneMozLoop = (function(mozL1
       throw new Error("missing required baseServerUrl");
     }
 
     this._baseServerUrl = options.baseServerUrl;
 
     this.rooms = new StandaloneMozLoopRooms(options);
   };
 
+  StandaloneMozLoop.prototype = {
+    /**
+     * Stores a preference in the local storage for standalone.
+     * Note: Some prefs are filtered out as they are not applicable
+     * to the standalone UI.
+     *
+     * @param {String} prefName The name of the pref
+     * @param {String} value The value to set.
+     */
+    setLoopCharPref: function(prefName, value) {
+      if (prefName === "seenToS") {
+        return;
+      }
+
+      localStorage.setItem(prefName, value);
+    },
+
+    /**
+     * Gets a preference from the local storage for standalone.
+     *
+     * @param {String} prefName The name of the pref
+     * @param {String} value The value to set.
+     */
+    getLoopCharPref: function(prefName) {
+      return localStorage.getItem(prefName);
+    }
+  };
+
   return StandaloneMozLoop;
 })(navigator.mozL10n);
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -136,17 +136,20 @@ loop.standaloneRoomViews = (function(moz
           React.DOM.p({dangerouslySetInnerHTML: {__html: this._getContent()}}), 
           React.DOM.div({className: "footer-logo"})
         )
       );
     }
   });
 
   var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
-    mixins: [Backbone.Events],
+    mixins: [
+      Backbone.Events,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -136,17 +136,20 @@ loop.standaloneRoomViews = (function(moz
           <p dangerouslySetInnerHTML={{__html: this._getContent()}}></p>
           <div className="footer-logo" />
         </footer>
       );
     }
   });
 
   var StandaloneRoomView = React.createClass({
-    mixins: [Backbone.Events],
+    mixins: [
+      Backbone.Events,
+      sharedMixins.RoomsAudioMixin
+    ],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
rename from browser/components/loop/standalone/content/l10n/loop.en-US.properties
rename to browser/components/loop/standalone/content/l10n/en-US/loop.properties
--- a/browser/components/loop/standalone/content/l10n/loop.en-US.properties
+++ b/browser/components/loop/standalone/content/l10n/en-US/loop.properties
@@ -1,22 +1,20 @@
 ## LOCALIZATION NOTE: In this file, don't translate the part between {{..}}
 restart_call=Rejoin
 conversation_has_ended=Your conversation has ended.
 call_timeout_notification_text=Your call did not go through.
 missing_conversation_info=Missing conversation information.
 network_disconnected=The network connection terminated abruptly.
 peer_ended_conversation2=The person you were calling has ended the conversation.
 call_failed_title=Call failed.
-connection_error_see_console_notification=Call failed; see console for details.
 generic_failure_title=Something went wrong.
 generic_failure_with_reason2=You can try again or email a link to be reached at later.
 generic_failure_no_reason2=Would you like to try again?
 retry_call_button=Retry
-feedback_report_user_button=Report User
 unable_retrieve_call_info=Unable to retrieve conversation information.
 hangup_button_title=Hang up
 hangup_button_caption2=Exit
 mute_local_audio_button_title=Mute your audio
 unmute_local_audio_button_title=Unmute your audio
 mute_local_video_button_title=Mute your video
 unmute_local_video_button_title=Unmute your video
 outgoing_call_title=Start conversation?
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -72,16 +72,21 @@ function promiseGetMozLoopAPI() {
 
 /**
  * Loads the loop panel by clicking the button and waits for its open to complete.
  * It also registers
  *
  * This assumes that the tests are running in a generatorTest.
  */
 function loadLoopPanel(aOverrideOptions = {}) {
+  Services.prefs.setBoolPref("loop.rooms.enabled", false);
+  registerCleanupFunction(function() {
+     Services.prefs.clearUserPref("loop.rooms.enabled");
+  });
+
   // Set prefs to ensure we don't access the network externally.
   Services.prefs.setCharPref("services.push.serverURL", aOverrideOptions.pushURL || "ws://localhost/");
   Services.prefs.setCharPref("loop.server", aOverrideOptions.loopURL || "http://localhost/");
 
   // Turn off the network for loop tests, so that we don't
   // try to access the remote servers. If we want to turn this
   // back on in future, be careful to check for intermittent
   // failures.
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -15,16 +15,17 @@ describe("loop.store.ActiveRoomStore", f
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers();
 
     dispatcher = new loop.Dispatcher();
     sandbox.stub(dispatcher, "dispatch");
 
     fakeMozLoop = {
+      setLoopCharPref: sandbox.stub(),
       rooms: {
         get: sinon.stub(),
         join: sinon.stub(),
         refreshMembership: sinon.stub(),
         leave: sinon.stub(),
         on: sinon.stub(),
         off: sinon.stub()
       }
@@ -515,16 +516,24 @@ describe("loop.store.ActiveRoomStore", f
   });
 
   describe("#remotePeerConnected", function() {
     it("should set the state to `HAS_PARTICIPANTS`", function() {
       store.remotePeerConnected();
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.HAS_PARTICIPANTS);
     });
+
+    it("should set the pref for ToS to `seen`", function() {
+      store.remotePeerConnected();
+
+      sinon.assert.calledOnce(fakeMozLoop.setLoopCharPref);
+      sinon.assert.calledWithExactly(fakeMozLoop.setLoopCharPref,
+        "seenToS", "seen");
+    });
   });
 
   describe("#remotePeerDisconnected", function() {
     it("should set the state to `SESSION_CONNECTED`", function() {
       store.remotePeerDisconnected();
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.SESSION_CONNECTED);
     });
--- a/browser/components/loop/test/shared/mixins_test.js
+++ b/browser/components/loop/test/shared/mixins_test.js
@@ -7,16 +7,17 @@
 
 var expect = chai.expect;
 
 describe("loop.shared.mixins", function() {
   "use strict";
 
   var sandbox;
   var sharedMixins = loop.shared.mixins;
+  var ROOM_STATES = loop.store.ROOM_STATES;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
   });
 
   afterEach(function() {
     sandbox.restore();
   });
@@ -209,9 +210,86 @@ describe("loop.shared.mixins", function(
       sinon.assert.calledOnce(navigator.mozLoop.getAudioBlob);
       sinon.assert.calledWithExactly(navigator.mozLoop.getAudioBlob,
                                      "failure", sinon.match.func);
       sinon.assert.calledOnce(fakeAudio.play);
       expect(fakeAudio.loop).to.equal(false);
     });
   });
 
+  describe("loop.shared.mixins.RoomsAudioMixin", function() {
+    var view, fakeAudioMixin, TestComp, comp;
+
+    function createTestComponent(initialState) {
+      var TestComp = React.createClass({
+        mixins: [loop.shared.mixins.RoomsAudioMixin],
+        render: function() {
+          return React.DOM.div();
+        },
+
+        getInitialState: function() {
+          return { roomState: initialState};
+        }
+      });
+
+      var renderedComp = TestUtils.renderIntoDocument(TestComp());
+      sandbox.stub(renderedComp, "play");
+      return renderedComp;
+    }
+
+    beforeEach(function() {
+    });
+
+    it("should play a sound when the local user joins the room", function() {
+      comp = createTestComponent(ROOM_STATES.INIT);
+
+      comp.setState({roomState: ROOM_STATES.SESSION_CONNECTED});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-joined");
+    });
+
+    it("should play a sound when another user joins the room", function() {
+      comp = createTestComponent(ROOM_STATES.SESSION_CONNECTED);
+
+      comp.setState({roomState: ROOM_STATES.HAS_PARTICIPANTS});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-joined-in");
+    });
+
+    it("should play a sound when another user leaves the room", function() {
+      comp = createTestComponent(ROOM_STATES.HAS_PARTICIPANTS);
+
+      comp.setState({roomState: ROOM_STATES.SESSION_CONNECTED});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-left");
+    });
+
+    it("should play a sound when the local user leaves the room", function() {
+      comp = createTestComponent(ROOM_STATES.HAS_PARTICIPANTS);
+
+      comp.setState({roomState: ROOM_STATES.READY});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "room-left");
+    });
+
+    it("should play a sound when if there is a failure", function() {
+      comp = createTestComponent(ROOM_STATES.HAS_PARTICIPANTS);
+
+      comp.setState({roomState: ROOM_STATES.FAILED});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "failure");
+    });
+
+    it("should play a sound when if the room is full", function() {
+      comp = createTestComponent(ROOM_STATES.READY);
+
+      comp.setState({roomState: ROOM_STATES.FULL});
+
+      sinon.assert.calledOnce(comp.play);
+      sinon.assert.calledWithExactly(comp.play, "failure");
+    });
+  });
 });
--- a/browser/components/loop/test/standalone/standaloneMozLoop_test.js
+++ b/browser/components/loop/test/standalone/standaloneMozLoop_test.js
@@ -41,16 +41,46 @@ describe("loop.StandaloneMozLoop", funct
   describe("#constructor", function() {
     it("should require a baseServerUrl setting", function() {
       expect(function() {
         new loop.StandaloneMozLoop();
       }).to.Throw(Error, /required/);
     });
   });
 
+  describe("#setLoopCharPref", function() {
+    afterEach(function() {
+      localStorage.removeItem("fakePref");
+    });
+
+    it("should store the value of the preference", function() {
+      mozLoop.setLoopCharPref("fakePref", "fakeValue");
+
+      expect(localStorage.getItem("fakePref")).eql("fakeValue");
+    });
+
+    it("should not store the value of seenToS", function() {
+      mozLoop.setLoopCharPref("seenToS", "fakeValue1");
+
+      expect(localStorage.getItem("seenToS")).eql(null);
+    });
+  });
+
+  describe("#getLoopCharPref", function() {
+    afterEach(function() {
+      localStorage.removeItem("fakePref");
+    });
+
+    it("should return the value of the preference", function() {
+      localStorage.setItem("fakePref", "fakeValue");
+
+      expect(mozLoop.getLoopCharPref("fakePref")).eql("fakeValue");
+    });
+  });
+
   describe("#rooms.join", function() {
     it("should POST to the server", function() {
       mozLoop.rooms.join("fakeToken", callback);
 
       expect(requests).to.have.length.of(1);
       expect(requests[0].url).eql(fakeBaseServerUrl + "/rooms/fakeToken");
       expect(requests[0].method).eql("POST");
 
--- a/browser/devtools/framework/options-panel.css
+++ b/browser/devtools/framework/options-panel.css
@@ -21,16 +21,21 @@
 
 .options-vertical-pane {
   margin: 5px;
   width: calc(100%/3 - 30px);
   min-width: 320px;
   -moz-padding-start: 5px;
 }
 
+#devtools-theme-box {
+  margin-left: 0px;
+  padding-left: 0px;
+}
+
 /* Snap to 50% width once there is not room for 3 columns anymore.
    This prevents having 2 columns showing in a row, but taking up
    only ~66% of the available space. */
 @media (max-width: 1000px) {
   .options-vertical-pane {
     width: calc(100%/2 - 30px);
   }
 }
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -17,16 +17,17 @@ skip-if = e10s # Bug 1070837 - devtools/
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
 [browser_toolbox_options.js]
+[browser_toolbox_options_devedition.js]
 [browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_js.js]
 skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e10s friendly
 # [browser_toolbox_raise.js] # Bug 962258
 # skip-if = os == "win"
 [browser_toolbox_ready.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_options_devedition.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that changing preferences in the options panel updates the prefs
+// and toggles appropriate things in the toolbox.
+
+let doc = null, toolbox = null, panelWin = null;
+
+const PREF_ENABLED = "browser.devedition.theme.enabled";
+const PREF_SHOW = "browser.devedition.theme.showCustomizeButton";
+
+const URL = "data:text/html;charset=utf8,test for toggling dev edition browser theme toggling";
+
+let test = asyncTest(function*() {
+  // Set preference to false by default so this could
+  // run in Developer Edition which has it on by default.
+  Services.prefs.setBoolPref(PREF_ENABLED, false);
+  Services.prefs.setBoolPref(PREF_SHOW, true);
+
+  let tab = yield addTab(URL);
+  let target = TargetFactory.forTab(tab);
+  toolbox = yield gDevTools.showToolbox(target);
+  let tool = yield toolbox.selectTool("options");
+  panelWin = tool.panelWin;
+
+  let checkbox = tool.panelDoc.getElementById("devtools-browser-theme");
+
+  ise(Services.prefs.getBoolPref(PREF_ENABLED), false, "Dev Theme pref off on start");
+
+  let themeStatus = yield clickAndWaitForThemeChange(checkbox, panelWin);
+  ise(themeStatus, true, "Theme has been toggled on.");
+
+  themeStatus = yield clickAndWaitForThemeChange(checkbox, panelWin);
+  ise(themeStatus, false, "Theme has been toggled off.");
+
+  yield cleanup();
+});
+
+function clickAndWaitForThemeChange (el, win) {
+  let deferred = promise.defer();
+  gDevTools.on("pref-changed", function handler (event, {pref}) {
+    if (pref === PREF_ENABLED) {
+      gDevTools.off("pref-changed", handler);
+      deferred.resolve(Services.prefs.getBoolPref(PREF_ENABLED));
+    }
+  });
+
+  EventUtils.synthesizeMouseAtCenter(el, {}, win);
+
+  return deferred.promise;
+}
+
+function* cleanup() {
+  yield toolbox.destroy();
+  gBrowser.removeCurrentTab();
+  Services.prefs.clearUserPref(PREF_ENABLED);
+  Services.prefs.clearUserPref(PREF_SHOW);
+  toolbox = doc = panelWin = null;
+}
--- a/browser/devtools/framework/toolbox-options.js
+++ b/browser/devtools/framework/toolbox-options.js
@@ -4,16 +4,19 @@
 
 "use strict";
 
 const {Cu, Cc, Ci} = require("chrome");
 const Services = require("Services");
 const promise = require("promise");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "CustomizeMode", "resource:///modules/CustomizeMode.jsm");
+const kDeveditionChangedNotification = "devedition-theme-state-changed";
+const DEVEDITION_THEME_PREF = "browser.devedition.theme.enabled";
 
 exports.OptionsPanel = OptionsPanel;
 
 XPCOMUtils.defineLazyGetter(this, "l10n", function() {
   let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
   let l10n = function(aName, ...aArgs) {
     try {
       if (aArgs.length == 0) {
@@ -66,25 +69,27 @@ function InfallibleGetBoolPref(key) {
 
 
 /**
  * Represents the Options Panel in the Toolbox.
  */
 function OptionsPanel(iframeWindow, toolbox) {
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
+
   this.toolbox = toolbox;
   this.isReady = false;
 
   this._prefChanged = this._prefChanged.bind(this);
   this._themeRegistered = this._themeRegistered.bind(this);
   this._themeUnregistered = this._themeUnregistered.bind(this);
 
   this._addListeners();
 
+  Services.obs.addObserver(this, kDeveditionChangedNotification, false);
   const EventEmitter = require("devtools/toolkit/event-emitter");
   EventEmitter.decorate(this);
 }
 
 OptionsPanel.prototype = {
 
   get target() {
     return this.toolbox.target;
@@ -99,16 +104,17 @@ OptionsPanel.prototype = {
     } else {
       targetPromise = promise.resolve(this.target);
     }
 
     return targetPromise.then(() => {
       this.setupToolsList();
       this.setupToolbarButtonsList();
       this.setupThemeList();
+      this.setupBrowserThemeButton();
       this.populatePreferences();
       this.updateDefaultTheme();
 
       this._disableJSClicked = this._disableJSClicked.bind(this);
 
       let disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
       disableJSNode.addEventListener("click", this._disableJSClicked, false);
     }).then(() => {
@@ -137,16 +143,18 @@ OptionsPanel.prototype = {
     if (data.pref === "devtools.cache.disabled") {
       let cacheDisabled = data.newValue;
       let cbx = this.panelDoc.getElementById("devtools-disable-cache");
 
       cbx.checked = cacheDisabled;
     }
     else if (data.pref === "devtools.theme") {
       this.updateCurrentTheme();
+    } else if (data.pref === "browser.devedition.theme.enabled") {
+      this.updateBrowserTheme();
     }
   },
 
   _themeRegistered: function(event, themeId) {
     this.setupThemeList();
   },
 
   _themeUnregistered: function(event, theme) {
@@ -272,16 +280,68 @@ OptionsPanel.prototype = {
     let themes = gDevTools.getThemeDefinitionArray();
     for (let theme of themes) {
       themeBox.appendChild(createThemeOption(theme));
     }
 
     this.updateCurrentTheme();
   },
 
+  /**
+   * Similar to `populatePrefs`, except we want more
+   * special rules for the browser theme button.
+   */
+  setupBrowserThemeButton: function() {
+    let checkbox = this.panelDoc.getElementById("devtools-browser-theme");
+
+    checkbox.addEventListener("command", function() {
+      let data = {
+        pref: DEVEDITION_THEME_PREF,
+        newValue: this.checked
+      };
+      data.oldValue = GetPref(data.pref);
+      SetPref(data.pref, data.newValue);
+      gDevTools.emit("pref-changed", data);
+    }.bind(checkbox));
+
+    this.updateBrowserThemeButton();
+  },
+
+  /**
+   * Called on theme changed via observer of "devedition-theme-state-changed".
+   */
+  updateBrowserThemeButton: function() {
+    let checkbox = this.panelDoc.getElementById("devtools-browser-theme");
+
+    // Check if the dev edition style sheet is applied -- will not
+    // be applied when dev edition theme is disabled, or when there's
+    // a LWT applied.
+    if (this._isDevEditionThemeOn()) {
+      checkbox.setAttribute("checked", "true");
+    } else {
+      checkbox.removeAttribute("checked");
+    }
+
+    // Should the button be shown
+    if (GetPref("browser.devedition.theme.showCustomizeButton")) {
+      checkbox.removeAttribute("hidden");
+    } else {
+      checkbox.setAttribute("hidden", "true");
+    }
+  },
+
+  /**
+   * Called when clicking the browser theme button to enable/disable
+   * the dev edition browser theme.
+   */
+  updateBrowserTheme: function() {
+    let enabled = GetPref("browser.devedition.theme.enabled");
+    CustomizeMode.prototype.toggleDevEditionTheme.call(this, enabled);
+  },
+
   populatePreferences: function() {
     let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
     for (let checkbox of prefCheckboxes) {
       checkbox.checked = GetPref(checkbox.getAttribute("data-pref"));
       checkbox.addEventListener("command", function() {
         let data = {
           pref: this.getAttribute("data-pref"),
           newValue: this.checked
@@ -383,16 +443,35 @@ OptionsPanel.prototype = {
 
     let options = {
       "javascriptEnabled": !checked
     };
 
     this.target.activeTab.reconfigure(options);
   },
 
+  /**
+   * Returns a boolean indicating whether or not the dev edition
+   * browser theme is applied.
+   */
+  _isDevEditionThemeOn: function() {
+    let win = Services.wm.getMostRecentWindow("navigator:browser");
+    return !!(win && win.DevEdition.styleSheet);
+  },
+
+  /**
+   * Called on observer notification for "devedition-theme-state-changed"
+   * to possibly change the state of the dev edition button
+   */
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic === kDeveditionChangedNotification) {
+      this.updateBrowserThemeButton();
+    }
+  },
+
   destroy: function() {
     if (this.destroyPromise) {
       return this.destroyPromise;
     }
 
     let deferred = promise.defer();
 
     this.destroyPromise = deferred.promise;
@@ -409,11 +488,13 @@ OptionsPanel.prototype = {
     let options = {
       "javascriptEnabled": this._origJavascriptEnabled
     };
     this.target.activeTab.reconfigure(options, () => {
       this.toolbox = null;
       deferred.resolve();
     }, true);
 
+    Services.obs.removeObserver(this, kDeveditionChangedNotification);
+
     return deferred.promise;
   }
 };
--- a/browser/devtools/framework/toolbox-options.xul
+++ b/browser/devtools/framework/toolbox-options.xul
@@ -23,22 +23,27 @@
         <label>&options.selectEnabledToolboxButtons.label;</label>
         <vbox id="enabled-toolbox-buttons-box" class="options-groupbox"/>
         <label id="tools-not-supported-label"
                class="options-citation-label theme-comment"
         >&options.toolNotSupported.label;</label>
 
       </vbox>
       <vbox class="options-vertical-pane" flex="1">
-        <label>&options.selectDevToolsTheme.label;</label>
-        <radiogroup id="devtools-theme-box"
-                    class="options-groupbox"
-                    data-pref="devtools.theme"
-                    orient="horizontal">
-        </radiogroup>
+        <label>&options.selectDevToolsTheme.label2;</label>
+        <vbox id="theme-options" class="options-groupbox">
+          <radiogroup id="devtools-theme-box"
+                      class="options-groupbox"
+                      data-pref="devtools.theme"
+                      orient="horizontal">
+          </radiogroup>
+          <checkbox id="devtools-browser-theme"
+                    label="&options.usedeveditiontheme.label;"
+                    tooltiptext="&options.usedeveditiontheme.tooltip;"/>
+        </vbox>
         <label>&options.commonPrefs.label;</label>
         <vbox id="commonprefs-options" class="options-groupbox">
           <checkbox label="&options.enablePersistentLogs.label;"
                     tooltiptext="&options.enablePersistentLogs.tooltip;"
                     data-pref="devtools.webconsole.persistlog"/>
         </vbox>
         <label>&options.context.inspector;</label>
         <vbox id="inspector-options" class="options-groupbox">
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -44,16 +44,17 @@ function connect() {
     }
   });
 }
 
 // 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.profiler.ui.show-platform-data", true);
+  Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", false);
 }
 
 window.addEventListener("load", function() {
   let cmdClose = document.getElementById("toolbox-cmd-close");
   cmdClose.addEventListener("command", onCloseCommand);
   setPrefDefaults();
   connect();
 });
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -85,16 +85,17 @@ browser.jar:
     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/controller.js                 (performance/controller.js)
     content/browser/devtools/performance/views/main.js                 (performance/views/main.js)
+    content/browser/devtools/performance/views/overview.js             (performance/views/overview.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)
--- a/browser/devtools/performance/controller.js
+++ b/browser/devtools/performance/controller.js
@@ -4,58 +4,72 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/devtools/Loader.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 
+let require = devtools.require;
 devtools.lazyRequireGetter(this, "Services");
 devtools.lazyRequireGetter(this, "promise");
 devtools.lazyRequireGetter(this, "EventEmitter",
   "devtools/toolkit/event-emitter");
 devtools.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
+devtools.lazyRequireGetter(this, "FramerateFront",
+  "devtools/server/actors/framerate", true);
+devtools.lazyRequireGetter(this, "L10N",
+  "devtools/profiler/global", true);
+devtools.lazyImporter(this, "LineGraphWidget",
+  "resource:///modules/devtools/Graphs.jsm");
 
 // Events emitted by the `PerformanceController`
 const EVENTS = {
   // When a recording is started or stopped via the controller
   RECORDING_STARTED: "Performance:RecordingStarted",
   RECORDING_STOPPED: "Performance:RecordingStopped",
+  // When the PerformanceActor front emits `framerate` data
+  TIMELINE_DATA: "Performance:TimelineData",
 
   // Emitted by the PerformanceView on record button click
   UI_START_RECORDING: "Performance:UI:StartRecording",
-  UI_STOP_RECORDING: "Performance:UI:StopRecording"
+  UI_STOP_RECORDING: "Performance:UI:StopRecording",
+
+  // Emitted by the OverviewView when more data has been rendered
+  OVERVIEW_RENDERED: "Performance:UI:OverviewRendered"
 };
 
 /**
  * The current target and the profiler connection, set by this tool's host.
  */
 let gToolbox, gTarget, gFront;
 
 /**
  * Initializes the profiler controller and views.
  */
 let startupPerformance = Task.async(function*() {
   yield promise.all([
     PrefObserver.register(),
     PerformanceController.initialize(),
-    PerformanceView.initialize()
+    PerformanceView.initialize(),
+    OverviewView.initialize()
   ]);
 });
 
 /**
  * Destroys the profiler controller and views.
  */
 let shutdownPerformance = Task.async(function*() {
   yield promise.all([
     PrefObserver.unregister(),
     PerformanceController.destroy(),
-    PerformanceView.destroy()
+    PerformanceView.destroy(),
+    OverviewView.destroy()
   ]);
 });
 
 /**
  * Observes pref changes on the devtools.profiler branch and triggers the
  * required frontend modifications.
  */
 let PrefObserver = {
@@ -78,19 +92,21 @@ let PrefObserver = {
 let PerformanceController = {
   /**
    * Listen for events emitted by the current tab target and
    * main UI events.
    */
   initialize: function() {
     this.startRecording = this.startRecording.bind(this);
     this.stopRecording = this.stopRecording.bind(this);
+    this._onTimelineData = this._onTimelineData.bind(this);
 
     PerformanceView.on(EVENTS.UI_START_RECORDING, this.startRecording);
     PerformanceView.on(EVENTS.UI_STOP_RECORDING, this.stopRecording);
+    gFront.on("ticks", this._onTimelineData);
   },
 
   /**
    * Remove events handled by the PerformanceController
    */
   destroy: function() {
     PerformanceView.off(EVENTS.UI_START_RECORDING, this.startRecording);
     PerformanceView.off(EVENTS.UI_STOP_RECORDING, this.stopRecording);
@@ -107,17 +123,24 @@ let PerformanceController = {
 
   /**
    * Stops recording with the PerformanceFront. Emits `EVENTS.RECORDING_STOPPED`
    * when the front stops recording.
    */
   stopRecording: Task.async(function *() {
     let results = yield gFront.stopRecording();
     this.emit(EVENTS.RECORDING_STOPPED, results);
-  })
+  }),
+
+  /**
+   * Fired whenever the gFront emits a ticks, memory, or markers event.
+   */
+  _onTimelineData: function (eventName, ...data) {
+    this.emit(EVENTS.TIMELINE_DATA, eventName, ...data);
+  }
 };
 
 /**
  * Convenient way of emitting events from the controller.
  */
 EventEmitter.decorate(PerformanceController);
 
 /**
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -11,16 +11,17 @@
   <!ENTITY % profilerDTD SYSTEM "chrome://browser/locale/devtools/profiler.dtd">
   %profilerDTD;
 ]>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script src="chrome://browser/content/devtools/theme-switching.js"/>
   <script type="application/javascript" src="performance/controller.js"/>
   <script type="application/javascript" src="performance/views/main.js"/>
+  <script type="application/javascript" src="performance/views/overview.js"/>
 
   <vbox class="theme-body" flex="1">
     <toolbar id="performance-toolbar" class="devtools-toolbar">
       <hbox id="performance-toolbar-controls-recordings" class="devtools-toolbarbutton-group">
         <toolbarbutton id="record-button"
                        class="devtools-toolbarbutton"
                        tooltiptext="&profilerUI.recordButton.tooltip;"/>
         <toolbarbutton id="clear-button"
@@ -33,16 +34,17 @@
                        class="devtools-toolbarbutton"
                        label="&profilerUI.importButton;"/>
       </hbox>
     </toolbar>
     <splitter class="devtools-horizontal-splitter" />
     <box id="overview-pane"
          class="devtools-responsive-container"
          flex="1">
+      <vbox id="time-framerate" flex="1"/>
     </box>
     <splitter class="devtools-horizontal-splitter" />
     <box id="details-pane"
          class="devtools-responsive-container"
          flex="1">
     </box>
   </vbox>
 </window>
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -24,8 +24,10 @@ support-files =
 #[browser_perf-shared-connection-01.js]
 [browser_perf-shared-connection-02.js]
 [browser_perf-shared-connection-03.js]
 # bug 1077464
 #[browser_perf-shared-connection-04.js]
 [browser_perf-data-samples.js]
 [browser_perf-data-massaging-01.js]
 [browser_perf-ui-recording.js]
+[browser_perf-overview-render-01.js]
+[browser_perf-overview-render-02.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-overview-render-01.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the overview renders content when recording.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, OverviewView } = panel.panelWin;
+
+  yield startRecording(panel);
+ 
+  yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
+  yield once(OverviewView, EVENTS.OVERVIEW_RENDERED);
+
+  yield stopRecording(panel);
+
+  yield teardown(panel);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/test/browser_perf-overview-render-02.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the overview graphs cannot be selected during recording
+ * and that they're cleared upon rerecording.
+ */
+function spawnTest () {
+  let { panel } = yield initPerformance(SIMPLE_URL);
+  let { EVENTS, OverviewView } = panel.panelWin;
+
+  yield startRecording(panel);
+
+  ok("selectionEnabled" in OverviewView.framerateGraph,
+    "The selection should not be enabled for the framerate overview (1).");
+  is(OverviewView.framerateGraph.selectionEnabled, false,
+    "The selection should not be enabled for the framerate overview (2).");
+  is(OverviewView.framerateGraph.hasSelection(), false,
+    "The framerate overview shouldn't have a selection before recording.");
+
+  let updated = 0;
+  OverviewView.on(EVENTS.OVERVIEW_RENDERED, () => updated++);
+
+  ok((yield waitUntil(() => updated > 10)),
+    "The overviews were updated several times.");
+
+  ok("selectionEnabled" in OverviewView.framerateGraph,
+    "The selection should still not be enabled for the framerate overview (1).");
+  is(OverviewView.framerateGraph.selectionEnabled, false,
+    "The selection should still not be enabled for the framerate overview (2).");
+  is(OverviewView.framerateGraph.hasSelection(), false,
+    "The framerate overview still shouldn't have a selection before recording.");
+
+  yield stopRecording(panel);
+
+  is(OverviewView.framerateGraph.selectionEnabled, true,
+    "The selection should now be enabled for the framerate overview.");
+
+  yield teardown(panel);
+  finish();
+}
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -214,17 +214,17 @@ function* startRecording(panel) {
   let button = win.$("#record-button");
 
   ok(!button.hasAttribute("checked"),
     "The record button should not be checked yet.");
 
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked yet.");
 
-  EventUtils.synthesizeMouseAtCenter(button, {}, win);
+  EventUtils.sendMouseEvent({ type: "click" }, button, win);
 
   yield clicked;
 
   ok(button.hasAttribute("checked"),
     "The record button should now be checked.");
   ok(button.hasAttribute("locked"),
     "The record button should be locked.");
 
@@ -242,24 +242,43 @@ function* stopRecording(panel) {
   let ended = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
   let button = win.$("#record-button");
 
   ok(button.hasAttribute("checked"),
     "The record button should already be checked.");
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked yet.");
 
-  EventUtils.synthesizeMouseAtCenter(button, {}, win);
+  EventUtils.sendMouseEvent({ type: "click" }, button, win);
 
   yield clicked;
 
   ok(!button.hasAttribute("checked"),
     "The record button should not be checked.");
   ok(button.hasAttribute("locked"),
     "The record button should be locked.");
 
   yield ended;
 
   ok(!button.hasAttribute("checked"),
     "The record button should not be checked.");
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked.");
 }
+
+/**
+ * Waits until a predicate returns true.
+ *
+ * @param function predicate
+ *        Invoked once in a while until it returns true.
+ * @param number interval [optional]
+ *        How often the predicate is invoked, in milliseconds.
+ */
+function waitUntil(predicate, interval = 10) {
+  if (predicate()) {
+    return Promise.resolve(true);
+  }
+  let deferred = Promise.defer();
+  setTimeout(function() {
+    waitUntil(predicate).then(() => deferred.resolve(true));
+  }, interval);
+  return deferred.promise;
+}
--- a/browser/devtools/performance/views/main.js
+++ b/browser/devtools/performance/views/main.js
@@ -11,28 +11,28 @@ let PerformanceView = {
    * Sets up the view with event binding.
    */
   initialize: function () {
     this._recordButton = $("#record-button");
 
     this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
     this._unlockRecordButton = this._unlockRecordButton.bind(this);
 
-    this._recordButton.addEventListener("mouseup", this._onRecordButtonClick);
+    this._recordButton.addEventListener("click", this._onRecordButtonClick);
 
     // Bind to controller events to unlock the record button
     PerformanceController.on(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
   },
 
   /**
    * Unbinds events.
    */
   destroy: function () {
-    this._recordButton.removeEventListener("mouseup", this._onRecordButtonClick);
+    this._recordButton.removeEventListener("click", this._onRecordButtonClick);
     PerformanceController.off(EVENTS.RECORDING_STARTED, this._unlockRecordButton);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._unlockRecordButton);
   },
 
   /**
    * Removes the `locked` attribute on the record button.
    */
   _unlockRecordButton: function () {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/performance/views/overview.js
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const OVERVIEW_UPDATE_INTERVAL = 100;
+const FRAMERATE_CALC_INTERVAL = 16; // ms
+const FRAMERATE_GRAPH_HEIGHT = 60; // px
+
+/**
+ * View handler for the overview panel's time view, displaying
+ * framerate over time.
+ */
+let OverviewView = {
+
+  /**
+   * Sets up the view with event binding.
+   */
+  initialize: function () {
+    this._framerateEl = $("#time-framerate");
+    this._ticksData = [];
+
+    this._start = this._start.bind(this);
+    this._stop = this._stop.bind(this);
+    this._onTimelineData = this._onTimelineData.bind(this);
+    this._onRecordingTick = this._onRecordingTick.bind(this);
+
+    this._initializeFramerateGraph();
+
+    PerformanceController.on(EVENTS.RECORDING_STARTED, this._start);
+    PerformanceController.on(EVENTS.RECORDING_STOPPED, this._stop);
+    PerformanceController.on(EVENTS.TIMELINE_DATA, this._onTimelineData);
+  },
+
+  /**
+   * Unbinds events.
+   */
+  destroy: function () {
+    PerformanceController.off(EVENTS.RECORDING_STARTED, this._start);
+    PerformanceController.off(EVENTS.RECORDING_STOPPED, this._stop);
+  },
+
+  /**
+   * Called at most every OVERVIEW_UPDATE_INTERVAL milliseconds
+   * and uses data fetched from `_onTimelineData` to render
+   * data into all the corresponding overview graphs.
+   */
+  _onRecordingTick: Task.async(function *() {
+    yield this.framerateGraph.setDataWhenReady(this._ticksData);
+    this.emit(EVENTS.OVERVIEW_RENDERED);
+    this._draw();
+  }),
+
+  /**
+   * Sets up the framerate graph.
+   */
+  _initializeFramerateGraph: function () {
+    let graph = new LineGraphWidget(this._framerateEl, L10N.getStr("graphs.fps"));
+    graph.minDistanceBetweenPoints = 1;
+    graph.fixedHeight = FRAMERATE_GRAPH_HEIGHT;
+    graph.selectionEnabled = false;
+    this.framerateGraph = graph;
+  },
+
+  /**
+   * Called to refresh the timer to keep firing _onRecordingTick.
+   */
+  _draw: function () {
+    // Check here to see if there's still a _timeoutId, incase
+    // `stop` was called before the _draw call was executed.
+    if (this._timeoutId) {
+      this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
+    }
+  },
+
+  /**
+   * Event handlers
+   */
+
+  _start: function () {
+    this._timeoutId = setTimeout(this._onRecordingTick, OVERVIEW_UPDATE_INTERVAL);
+    this.framerateGraph.dropSelection();
+  },
+
+  _stop: function () {
+    clearTimeout(this._timeoutId);
+    this.framerateGraph.selectionEnabled = true;
+  },
+
+  /**
+   * Called when the TimelineFront has new data for
+   * framerate, markers or memory, and stores the data
+   * to be plotted subsequently.
+   */
+  _onTimelineData: function (_, eventName, ...data) {
+    if (eventName === "ticks") {
+      let [delta, timestamps] = data;
+      // the `ticks` event on the TimelineFront returns all ticks for the
+      // recording session, so just convert to plottable values
+      // and store.
+      this._ticksData = FramerateFront.plotFPS(timestamps, FRAMERATE_CALC_INTERVAL);
+    }
+  }
+};
+
+// Decorates the OverviewView as an EventEmitter
+EventEmitter.decorate(OverviewView);
--- a/browser/devtools/timeline/widgets/waterfall.js
+++ b/browser/devtools/timeline/widgets/waterfall.js
@@ -282,20 +282,20 @@ Waterfall.prototype = {
     sidebar.appendChild(bullet);
 
     let name = this._document.createElement("label");
     name.setAttribute("crop", "end");
     name.setAttribute("flex", "1");
     name.className = "plain waterfall-marker-name";
 
     let label;
-    if (marker.detail && marker.detail.causeName) {
+    if (marker.causeName) {
       label = this._l10n.getFormatStr("timeline.markerDetailFormat",
                                       blueprint.label,
-                                      marker.detail.causeName);
+                                      marker.causeName);
     } else {
       label = blueprint.label;
     }
     name.setAttribute("value", label);
     name.setAttribute("tooltiptext", label);
     sidebar.appendChild(name);
 
     container.appendChild(sidebar);
--- a/browser/installer/windows/nsis/defines.nsi.in
+++ b/browser/installer/windows/nsis/defines.nsi.in
@@ -31,18 +31,16 @@
 !define DDEApplication        "Firefox"
 !define AppRegName            "Firefox"
 
 !ifndef DEV_EDITION
 !define BrandShortName        "@MOZ_APP_DISPLAYNAME@"
 !endif
 !define BrandFullName         "${BrandFullNameInternal}"
 
-!define NO_UNINSTALL_SURVEY
-
 !define CERTIFICATE_NAME      "Mozilla Corporation"
 !define CERTIFICATE_ISSUER    "DigiCert Assured ID Code Signing CA-1"
 
 # LSP_CATEGORIES is the permitted LSP categories for the application. Each LSP
 # category value is ANDed together to set multiple permitted categories.
 # See http://msdn.microsoft.com/en-us/library/ms742253%28VS.85%29.aspx
 # The value below removes all LSP categories previously set.
 !define LSP_CATEGORIES "0x00000000"
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -91,31 +91,30 @@ VIAddVersionKey "OriginalFilename" "setu
 !insertmacro InitHashAppModelId
 !insertmacro IsHandlerForInstallDir
 !insertmacro IsPinnedToTaskBar
 !insertmacro IsUserAdmin
 !insertmacro LogDesktopShortcut
 !insertmacro LogQuickLaunchShortcut
 !insertmacro LogStartMenuShortcut
 !insertmacro ManualCloseAppPrompt
-!insertmacro OnStubInstallUninstall
 !insertmacro PinnedToStartMenuLnkCount
 !insertmacro RegCleanAppHandler
 !insertmacro RegCleanMain
 !insertmacro RegCleanUninstall
 !ifdef MOZ_METRO
 !insertmacro RemoveDEHRegistrationIfMatching
 !endif
+!insertmacro RemovePrecompleteEntries
 !insertmacro SetAppLSPCategories
 !insertmacro SetBrandNameVars
 !insertmacro UpdateShortcutAppModelIDs
 !insertmacro UnloadUAC
 !insertmacro WriteRegStr2
 !insertmacro WriteRegDWORD2
-!insertmacro CheckIfRegistryKeyExists
 
 !include shared.nsh
 
 ; Helper macros for ui callbacks. Insert these after shared.nsh
 !insertmacro CheckCustomCommon
 !insertmacro InstallEndCleanupCommon
 !insertmacro InstallOnInitCommon
 !insertmacro InstallStartCleanupCommon
@@ -206,26 +205,43 @@ Section "-InstallStartCleanup"
   ${StartInstallLog} "${BrandFullName}" "${AB_CD}" "${AppVersion}" "${GREVersion}"
 
   StrCpy $PreventRebootRequired "false"
   ${GetParameters} $R8
   ${GetOptions} "$R8" "/INI=" $R7
   ${Unless} ${Errors}
     ; The configuration file must also exist
     ${If} ${FileExists} "$R7"
+      ReadINIStr $R9 $R7 "Install" "RemoveDistributionDir"
       ReadINIStr $R8 $R7 "Install" "PreventRebootRequired"
       ${If} $R8 == "true"
         StrCpy $PreventRebootRequired "true"
-        StrCpy $R2 "false"
-        StrCpy $R3 "false"
-        ${OnStubInstallUninstall} "$R2" "$R3"
       ${EndIf}
     ${EndIf}
   ${EndUnless}
 
+  ; Remove directories and files we always control before parsing the uninstall
+  ; log so empty directories can be removed.
+  ${If} ${FileExists} "$INSTDIR\updates"
+    RmDir /r "$INSTDIR\updates"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\updated"
+    RmDir /r "$INSTDIR\updated"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\defaults\shortcuts"
+    RmDir /r "$INSTDIR\defaults\shortcuts"
+  ${EndIf}
+  ; Only remove the distribution directory if it exists and if the installer
+  ; isn't launched with an ini file that has RemoveDistributionDir=false in the
+  ; install section.
+  ${If} ${FileExists} "$INSTDIR\distribution"
+  ${AndIf} $R9 != "false"
+    RmDir /r "$INSTDIR\distribution"
+  ${EndIf}
+
   ; Delete the app exe if present to prevent launching the app while we are
   ; installing.
   ClearErrors
   ${DeleteFile} "$INSTDIR\${FileMainEXE}"
   ${If} ${Errors}
     ; If the user closed the application it can take several seconds for it to
     ; shut down completely. If the application is being used by another user we
     ; can rename the file and then delete is when the system is restarted.
@@ -237,28 +253,51 @@ Section "-InstallStartCleanup"
   ; setup the application model id registration value
   ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
 
   ; Remove the updates directory for Vista and above
   ${CleanUpdateDirectories} "Mozilla\Firefox" "Mozilla\updates"
 
   ${RemoveDeprecatedFiles}
 
+  StrCpy $R2 "false"
+  StrCpy $R3 "false"
+  ${RemovePrecompleteEntries} "$R2" "$R3"
+
+  ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
+    Delete "$INSTDIR\defaults\pref\channel-prefs.js"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\defaults\pref"
+    RmDir "$INSTDIR\defaults\pref"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\defaults"
+    RmDir "$INSTDIR\defaults"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\uninstall"
+    ; Remove the uninstall directory that we control
+    RmDir /r "$INSTDIR\uninstall"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\update-settings.ini"
+    Delete "$INSTDIR\update-settings.ini"
+  ${EndIf}
+
+  ; Explictly remove empty webapprt dir in case it exists (bug 757978).
+  RmDir "$INSTDIR\webapprt\components"
+  RmDir "$INSTDIR\webapprt"
+
   ${InstallStartCleanupCommon}
 SectionEnd
 
 Section "-Application" APP_IDX
   ${StartUninstallLog}
 
   SetDetailsPrint both
   DetailPrint $(STATUS_INSTALL_APP)
   SetDetailsPrint none
 
-  RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
-
   ${LogHeader} "Installing Main Files"
   ${CopyFilesFromDir} "$EXEDIR\core" "$INSTDIR" \
                       "$(ERROR_CREATE_DIRECTORY_PREFIX)" \
                       "$(ERROR_CREATE_DIRECTORY_SUFFIX)"
 
   ; Register DLLs
   ; XXXrstrong - AccessibleMarshal.dll can be used by multiple applications but
   ; is only registered for the last application installed. When the last
@@ -269,28 +308,16 @@ Section "-Application" APP_IDX
   ${RegisterDLL} "$INSTDIR\AccessibleMarshal.dll"
   ${If} ${Errors}
     ${LogMsg} "** ERROR Registering: $INSTDIR\AccessibleMarshal.dll **"
   ${Else}
     ${LogUninstall} "DLLReg: \AccessibleMarshal.dll"
     ${LogMsg} "Registered: $INSTDIR\AccessibleMarshal.dll"
   ${EndIf}
 
-  ; Write extra files created by the application to the uninstall log so they
-  ; will be removed when the application is uninstalled. To remove an empty
-  ; directory write a bogus filename to the deepest directory and all empty
-  ; parent directories will be removed.
-  ${LogUninstall} "File: \components\compreg.dat"
-  ${LogUninstall} "File: \components\xpti.dat"
-  ${LogUninstall} "File: \active-update.xml"
-  ${LogUninstall} "File: \install.log"
-  ${LogUninstall} "File: \install_status.log"
-  ${LogUninstall} "File: \install_wizard.log"
-  ${LogUninstall} "File: \updates.xml"
-
   ClearErrors
 
   ; Default for creating Start Menu shortcut
   ; (1 = create, 0 = don't create)
   ${If} $AddStartMenuSC == ""
     StrCpy $AddStartMenuSC "1"
   ${EndIf}
 
@@ -602,42 +629,48 @@ Section "-InstallEndCleanup"
 
   ${InstallEndCleanupCommon}
 
   ${If} $PreventRebootRequired == "true"
     SetRebootFlag false
   ${EndIf}
 
   ${If} ${RebootFlag}
-    ; When a reboot is required give SHChangeNotify time to finish the
-    ; refreshing the icons so the OS doesn't display the icons from helper.exe
-    Sleep 10000
-    ${LogHeader} "Reboot Required To Finish Installation"
-    ; ${FileMainEXE}.moz-upgrade should never exist but just in case...
-    ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-upgrade"
-      Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-upgrade"
-    ${EndUnless}
+    ; Admin is required to delete files on reboot so only add the moz-delete if
+    ; the user is an admin. After calling UAC::IsAdmin $0 will equal 1 if the
+    ; user is an admin.
+    UAC::IsAdmin
+    ${If} "$0" == "1"
+      ; When a reboot is required give SHChangeNotify time to finish the
+      ; refreshing the icons so the OS doesn't display the icons from helper.exe
+      Sleep 10000
+      ${LogHeader} "Reboot Required To Finish Installation"
+      ; ${FileMainEXE}.moz-upgrade should never exist but just in case...
+      ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-upgrade"
+        Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-upgrade"
+      ${EndUnless}
 
-    ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
-      ClearErrors
-      Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-delete"
-      ${Unless} ${Errors}
-        Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+      ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+        ClearErrors
+        Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-delete"
+        ${Unless} ${Errors}
+          Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+        ${EndUnless}
+      ${EndIf}
+
+      ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
+        CopyFiles /SILENT "$INSTDIR\uninstall\helper.exe" "$INSTDIR"
+        FileOpen $0 "$INSTDIR\${FileMainEXE}" w
+        FileWrite $0 "Will be deleted on restart"
+        Rename /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
+        FileClose $0
+        Delete "$INSTDIR\${FileMainEXE}"
+        Rename "$INSTDIR\helper.exe" "$INSTDIR\${FileMainEXE}"
       ${EndUnless}
     ${EndIf}
-
-    ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
-      CopyFiles /SILENT "$INSTDIR\uninstall\helper.exe" "$INSTDIR"
-      FileOpen $0 "$INSTDIR\${FileMainEXE}" w
-      FileWrite $0 "Will be deleted on restart"
-      Rename /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
-      FileClose $0
-      Delete "$INSTDIR\${FileMainEXE}"
-      Rename "$INSTDIR\helper.exe" "$INSTDIR\${FileMainEXE}"
-    ${EndUnless}
   ${EndIf}
 SectionEnd
 
 ################################################################################
 # Install Abort Survey Functions
 
 Function CustomAbort
   ${If} "${AB_CD}" == "en-US"
--- a/browser/installer/windows/nsis/stub.nsi
+++ b/browser/installer/windows/nsis/stub.nsi
@@ -251,17 +251,17 @@ Var ControlRightPX
 
 !insertmacro ElevateUAC
 !insertmacro GetLongPath
 !insertmacro GetPathFromString
 !insertmacro GetParent
 !insertmacro GetSingleInstallPath
 !insertmacro GetTextWidthHeight
 !insertmacro IsUserAdmin
-!insertmacro OnStubInstallUninstall
+!insertmacro RemovePrecompleteEntries
 !insertmacro SetBrandNameVars
 !insertmacro UnloadUAC
 
 VIAddVersionKey "FileDescription" "${BrandShortName} Stub Installer"
 VIAddVersionKey "OriginalFilename" "setup-stub.exe"
 
 Name "$BrandFullName"
 OutFile "setup-stub.exe"
@@ -1535,25 +1535,27 @@ Function OnDownload
       ; from creating a taskbar shortcut (Bug 791613).
       ${GetShortcutsLogPath} $0
       Delete "$0"
       ; Workaround to prevent pinning to the taskbar.
       ${If} $CheckboxShortcutOnBar == 0
         WriteIniStr "$0" "TASKBAR" "Migrated" "true"
       ${EndIf}
 
-      ${OnStubInstallUninstall} $Progressbar $InstallCounterStep
+      ${RemovePrecompleteEntries} $Progressbar $InstallCounterStep
 
       ; Delete the install.log and let the full installer create it. When the
       ; installer closes it we can detect that it has completed.
       Delete "$INSTDIR\install.log"
 
-      ; Delete firefox.exe.moz-upgrade if it exists since it being present will
-      ; require an OS restart for the full installer.
+      ; Delete firefox.exe.moz-upgrade and firefox.exe.moz-delete if it exists
+      ; since it being present will require an OS restart for the full
+      ; installer.
       Delete "$INSTDIR\${FileMainEXE}.moz-upgrade"
+      Delete "$INSTDIR\${FileMainEXE}.moz-delete"
 
       System::Call "kernel32::GetTickCount()l .s"
       Pop $EndPreInstallPhaseTickCount
 
       Exec "$\"$PLUGINSDIR\download.exe$\" /INI=$PLUGINSDIR\${CONFIG_INI}"
       ${NSD_CreateTimer} CheckInstall ${InstallIntervalMS}
     ${Else}
       ${If} $HalfOfDownload != "true"
--- a/browser/installer/windows/nsis/uninstaller.nsi
+++ b/browser/installer/windows/nsis/uninstaller.nsi
@@ -88,37 +88,36 @@ VIAddVersionKey "OriginalFilename" "help
 !insertmacro RemoveDEHRegistrationIfMatching
 !endif
 !insertmacro SetAppLSPCategories
 !insertmacro SetBrandNameVars
 !insertmacro UpdateShortcutAppModelIDs
 !insertmacro UnloadUAC
 !insertmacro WriteRegDWORD2
 !insertmacro WriteRegStr2
-!insertmacro CheckIfRegistryKeyExists
 
 !insertmacro un.ChangeMUIHeaderImage
 !insertmacro un.CheckForFilesInUse
 !insertmacro un.CleanUpdateDirectories
 !insertmacro un.CleanVirtualStore
 !insertmacro un.DeleteShortcuts
 !insertmacro un.GetLongPath
 !insertmacro un.GetSecondInstallPath
 !insertmacro un.InitHashAppModelId
 !insertmacro un.ManualCloseAppPrompt
-!insertmacro un.ParseUninstallLog
 !insertmacro un.RegCleanAppHandler
 !insertmacro un.RegCleanFileHandler
 !insertmacro un.RegCleanMain
 !insertmacro un.RegCleanUninstall
 !insertmacro un.RegCleanProtocolHandler
 !ifdef MOZ_METRO
 !insertmacro un.RemoveDEHRegistrationIfMatching
 !endif
 !insertmacro un.RemoveQuotesFromPath
+!insertmacro un.RemovePrecompleteEntries
 !insertmacro un.SetAppLSPCategories
 !insertmacro un.SetBrandNameVars
 
 !include shared.nsh
 
 ; Helper macros for ui callbacks. Insert these after shared.nsh
 !insertmacro OnEndCommon
 !insertmacro UninstallOnInitCommon
@@ -157,38 +156,31 @@ ShowUnInstDetails nevershow
  * Uninstall Pages
  */
 ; Welcome Page
 !define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
 !define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.leaveWelcome
 !insertmacro MUI_UNPAGE_WELCOME
 
 ; Custom Uninstall Confirm Page
-UninstPage custom un.preConfirm un.leaveConfirm
+UninstPage custom un.preConfirm
 
 ; Remove Files Page
 !insertmacro MUI_UNPAGE_INSTFILES
 
 ; Finish Page
 
-; Don't setup the survey controls, functions, etc. when the application has
-; defined NO_UNINSTALL_SURVEY
-!ifndef NO_UNINSTALL_SURVEY
-!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preFinish
-!define MUI_FINISHPAGE_SHOWREADME_NOTCHECKED
-!define MUI_FINISHPAGE_SHOWREADME ""
-!define MUI_FINISHPAGE_SHOWREADME_TEXT $(SURVEY_TEXT)
-!define MUI_FINISHPAGE_SHOWREADME_FUNCTION un.Survey
-!endif
-
 !insertmacro MUI_UNPAGE_FINISH
 
 ; Use the default dialog for IDD_VERIFY for a simple Banner
 ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\UIs\default.exe"
 
+################################################################################
+# Helper Functions
+
 ; This function is used to uninstall the maintenance service if the
 ; application currently being uninstalled is the last application to use the 
 ; maintenance service.
 Function un.UninstallServiceIfNotUsed
   ; $0 will store if a subkey exists
   ; $1 will store the first subkey if it exists or an empty string if it doesn't
   ; Backup the old values
   Push $0
@@ -196,22 +188,24 @@ Function un.UninstallServiceIfNotUsed
 
   ; The maintenance service always uses the 64-bit registry on x64 systems
   ${If} ${RunningX64}
     SetRegView 64
   ${EndIf}
 
   ; Figure out the number of subkeys
   StrCpy $0 0
-loop:
-  EnumRegKey $1 HKLM "Software\Mozilla\MaintenanceService" $0
-  StrCmp $1 "" doneCount
-  IntOp $0 $0 + 1
-  goto loop
-doneCount:
+  ${Do}
+    EnumRegKey $1 HKLM "Software\Mozilla\MaintenanceService" $0
+    ${If} "$1" == ""
+      ${ExitDo}
+    ${EndIf}
+    IntOp $0 $0 + 1
+  ${Loop}
+
   ; Restore back the registry view
   ${If} ${RunningX64}
     SetRegView lastUsed
   ${EndIf}
   ${If} $0 == 0
     ; Get the path of the maintenance service uninstaller
     ReadRegStr $1 HKLM ${MaintUninstallKey} "UninstallString"
 
@@ -404,91 +398,103 @@ Section "Uninstall"
     RmDir /r /REBOOTOK "$INSTDIR\updated"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\defaults\shortcuts"
     RmDir /r /REBOOTOK "$INSTDIR\defaults\shortcuts"
   ${EndIf}
   ${If} ${FileExists} "$INSTDIR\distribution"
     RmDir /r /REBOOTOK "$INSTDIR\distribution"
   ${EndIf}
-  ${If} ${FileExists} "$INSTDIR\removed-files"
-    Delete /REBOOTOK "$INSTDIR\removed-files"
-  ${EndIf}
 
   ; Remove files that may be left behind by the application in the
   ; VirtualStore directory.
   ${un.CleanVirtualStore}
 
-  ; Parse the uninstall log to unregister dll's and remove all installed
-  ; files / directories this install is responsible for.
-  ${un.ParseUninstallLog}
+  ; Only unregister the dll if the registration points to this installation
+  ReadRegStr $R1 HKCR "CLSID\{0D68D6D0-D93D-4D08-A30D-F00DD1F45B24}\InProcServer32" ""
+  ${If} "$INSTDIR\AccessibleMarshal.dll" == "$R1"
+    ${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll"
+  ${EndIf}
+
+  StrCpy $R2 "false"
+  StrCpy $R3 "false"
+  ${un.RemovePrecompleteEntries} "$R2" "$R3"
 
-  ; Remove the uninstall directory that we control
-  RmDir /r /REBOOTOK "$INSTDIR\uninstall"
+  ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
+    Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\defaults\pref"
+    RmDir /REBOOTOK "$INSTDIR\defaults\pref"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\defaults"
+    RmDir /REBOOTOK "$INSTDIR\defaults"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\uninstall"
+    ; Remove the uninstall directory that we control
+    RmDir /r /REBOOTOK "$INSTDIR\uninstall"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\install.log"
+    Delete /REBOOTOK "$INSTDIR\install.log"
+  ${EndIf}
+  ${If} ${FileExists} "$INSTDIR\update-settings.ini"
+    Delete /REBOOTOK "$INSTDIR\update-settings.ini"
+  ${EndIf}
 
-  ; Explictly remove empty webapprt dir in case it exists
-  ; See bug 757978
+  ; Explictly remove empty webapprt dir in case it exists (bug 757978).
   RmDir "$INSTDIR\webapprt\components"
   RmDir "$INSTDIR\webapprt"
 
-  RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
-
   ; Remove the installation directory if it is empty
-  ${RemoveDir} "$INSTDIR"
+  RmDir "$INSTDIR"
 
   ; If firefox.exe was successfully deleted yet we still need to restart to
   ; remove other files create a dummy firefox.exe.moz-delete to prevent the
   ; installer from allowing an install without restart when it is required
   ; to complete an uninstall.
   ${If} ${RebootFlag}
-    ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-delete"
-      FileOpen $0 "$INSTDIR\${FileMainEXE}.moz-delete" w
-      FileWrite $0 "Will be deleted on restart"
-      Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
-      FileClose $0
-    ${EndUnless}
+    ; Admin is required to delete files on reboot so only add the moz-delete if
+    ; the user is an admin. After calling UAC::IsAdmin $0 will equal 1 if the
+    ; user is an admin.
+    UAC::IsAdmin
+    ${If} "$0" == "1"
+      ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-delete"
+        FileOpen $0 "$INSTDIR\${FileMainEXE}.moz-delete" w
+        FileWrite $0 "Will be deleted on restart"
+        Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+        FileClose $0
+      ${EndUnless}
+    ${EndIf}
   ${EndIf}
 
   ; Refresh desktop icons otherwise the start menu internet item won't be
   ; removed and other ugly things will happen like recreation of the app's
   ; clients registry key by the OS under some conditions.
   System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i 0, i 0, i 0)"
 
 !ifdef MOZ_MAINTENANCE_SERVICE
   ; Get the path the allowed cert is at and remove it
   ; Keep this block of code last since it modfies the reg view
   ServicesHelper::PathToUniqueRegistryPath "$INSTDIR"
   Pop $MaintCertKey
   ${If} $MaintCertKey != ""
-    ; We always use the 64bit registry for certs
+    ; Always use the 64bit registry for certs on 64bit systems.
     ${If} ${RunningX64}
       SetRegView 64
     ${EndIf}
     DeleteRegKey HKLM "$MaintCertKey"
     ${If} ${RunningX64}
       SetRegView lastused
     ${EndIf}
   ${EndIf}
   Call un.UninstallServiceIfNotUsed
 !endif
 
 SectionEnd
 
 ################################################################################
-# Helper Functions
-
-; Don't setup the survey controls, functions, etc. when the application has
-; defined NO_UNINSTALL_SURVEY
-!ifndef NO_UNINSTALL_SURVEY
-Function un.Survey
-  Exec "$\"$TmpVal$\" $\"${SurveyURL}$\""
-FunctionEnd
-!endif
-
-################################################################################
 # Language
 
 !insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
 !verbose push
 !verbose 3
 !include "overrideLocale.nsh"
 !include "customLocale.nsh"
 !verbose pop
@@ -589,60 +595,16 @@ Function un.preConfirm
   !insertmacro MUI_INSTALLOPTIONS_INITDIALOG "unconfirm.ini"
   GetDlgItem $0 $HWNDPARENT 1
   System::Call "user32::SetFocus(i r0, i 0x0007, i,i)i"
   ${MUI_INSTALLOPTIONS_READ} $1 "unconfirm.ini" "Field 2" "HWND"
   SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
   !insertmacro MUI_INSTALLOPTIONS_SHOW
 FunctionEnd
 
-Function un.leaveConfirm
-  ; Try to delete the app executable and if we can't delete it try to find the
-  ; app's message window and prompt the user to close the app. This allows
-  ; running an instance that is located in another directory. If for whatever
-  ; reason there is no message window we will just rename the app's files and
-  ; then remove them on restart if they are in use.
-  ClearErrors
-  ${DeleteFile} "$INSTDIR\${FileMainEXE}"
-  ${If} ${Errors}
-    ${un.ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_UNINSTALL)"
-  ${EndIf}
-FunctionEnd
-
-!ifndef NO_UNINSTALL_SURVEY
-Function un.preFinish
-  ; Do not modify the finish page if there is a reboot pending
-  ${Unless} ${RebootFlag}
-    ; Setup the survey controls, functions, etc.
-    StrCpy $TmpVal "SOFTWARE\Microsoft\IE Setup\Setup"
-    ClearErrors
-    ReadRegStr $0 HKLM $TmpVal "Path"
-    ${If} ${Errors}
-      !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "settings" "NumFields" "3"
-    ${Else}
-      ExpandEnvStrings $0 "$0" ; this value will usually contain %programfiles%
-      ${If} $0 != "\"
-        StrCpy $0 "$0\"
-      ${EndIf}
-      StrCpy $0 "$0\iexplore.exe"
-      ClearErrors
-      GetFullPathName $TmpVal $0
-      ${If} ${Errors}
-        !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "settings" "NumFields" "3"
-      ${Else}
-        ; When we add an optional action to the finish page the cancel button
-        ; is enabled. This disables it and leaves the finish button as the
-        ; only choice.
-        !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "settings" "cancelenabled" "0"
-      ${EndIf}
-    ${EndIf}
-  ${EndUnless}
-FunctionEnd
-!endif
-
 ################################################################################
 # Initialization Functions
 
 Function .onInit
   ; Remove the current exe directory from the search order.
   ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
   System::Call 'kernel32::SetDllDirectoryW(w "")'
 
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -719,17 +719,17 @@ just addresses the organization to follo
 <!ENTITY customizeMode.titlebar "Title Bar">
 <!ENTITY customizeMode.lwthemes "Themes">
 <!ENTITY customizeMode.lwthemes.myThemes "My Themes">
 <!ENTITY customizeMode.lwthemes.recommended "Recommended">
 <!ENTITY customizeMode.lwthemes.menuManage "Manage">
 <!ENTITY customizeMode.lwthemes.menuManage.accessKey "M">
 <!ENTITY customizeMode.lwthemes.menuGetMore "Get More Themes">
 <!ENTITY customizeMode.lwthemes.menuGetMore.accessKey "G">
-<!ENTITY customizeMode.deveditionTheme.label "Use &brandShortName; Theme">
+<!ENTITY customizeMode.deveditionTheme.label2 "Use Developer Edition Theme">
 
 <!ENTITY social.chatBar.commandkey "c">
 <!ENTITY social.chatBar.label "Focus chats">
 <!ENTITY social.chatBar.accesskey "c">
 
 <!ENTITY social.markpageMenu.accesskey "P">
 <!ENTITY social.markpageMenu.label "Save Page To…">
 <!ENTITY social.marklinkMenu.accesskey "L">
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -109,20 +109,25 @@
   -  tool buttons. -->
 <!ENTITY options.selectEnabledToolboxButtons.label     "Available Toolbox Buttons">
 
 <!-- LOCALIZATION NOTE (options.toolNotSupported.label): This is the label for
   -  the explanation of the * marker on a tool which is currently not supported
   -  for the target of the toolbox. -->
 <!ENTITY options.toolNotSupported.label  "* Not supported for current toolbox target">
 
-<!-- LOCALIZATION NOTE (options.selectDevToolsTheme.label): This is the label for
+<!-- LOCALIZATION NOTE (options.selectDevToolsTheme.label2): This is the label for
   -  the heading of the radiobox corresponding to the theme of the developer
   -  tools. -->
-<!ENTITY options.selectDevToolsTheme.label   "Choose DevTools theme:">
+<!ENTITY options.selectDevToolsTheme.label2   "Themes">
+
+<!-- LOCALIZATION NOTE (options.usedeveditiontheme.*) Options under the
+  -  toolbox for enabling and disabling the Developer Edition browser theme. -->
+<!ENTITY options.usedeveditiontheme.label   "Use Developer Edition browser theme">
+<!ENTITY options.usedeveditiontheme.tooltip "Toggles the Developer Edition browser theme.">
 
 <!-- LOCALIZATION NOTE (options.webconsole.label): This is the label for the
   -  heading of the group of Web Console preferences in the options panel. -->
 <!ENTITY options.webconsole.label            "Web Console">
 
 <!-- LOCALIZATION NOTE (options.timestampMessages.label): This is the
    - label for the checkbox that toggles timestamps in the Web Console -->
 <!ENTITY options.timestampMessages.label      "Enable timestamps">
@@ -178,8 +183,9 @@ Gecko platform symbols">
 <!ENTITY options.sourceeditor.autoclosebrackets.tooltip "Automatically insert closing brackets">
 <!ENTITY options.sourceeditor.expandtab.label           "Indent using spaces">
 <!ENTITY options.sourceeditor.expandtab.tooltip         "Use spaces instead of the tab character">
 <!ENTITY options.sourceeditor.tabsize.label             "Tab size">
 <!ENTITY options.sourceeditor.tabsize.accesskey         "T">
 <!ENTITY options.sourceeditor.keybinding.label          "Keybindings">
 <!ENTITY options.sourceeditor.keybinding.accesskey      "K">
 <!ENTITY options.sourceeditor.keybinding.default.label  "Default">
+
--- a/browser/themes/shared/devedition.inc.css
+++ b/browser/themes/shared/devedition.inc.css
@@ -234,16 +234,21 @@ window:not([chromehidden~="toolbar"]) #u
 
 /* No extra vertical padding for nav bar */
 #nav-bar-customization-target,
 #nav-bar {
   padding-top: 0;
   padding-bottom: 0;
 }
 
+/* No extra border when customizing since the nav bar doesn't have one */
+#main-window[customize-entered] #customization-container {
+  border: none;
+}
+
 /* Use smaller back button icon */
 #back-button {
   -moz-image-region: rect(0, 54px, 18px, 36px);
 }
 
 .search-go-button {
    /* !important is needed because searchbar.css is loaded after this */
   -moz-image-region: auto !important;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2881,64 +2881,64 @@ nsDocShell::PopProfileTimelineMarkers(JS
   // docShell level but we can only be sure that a paint did happen in a
   // docShell if an Layer marker type was recorded too.
 
   nsTArray<mozilla::dom::ProfileTimelineMarker> profileTimelineMarkers;
 
   // If we see an unpaired START, we keep it around for the next call
   // to PopProfileTimelineMarkers.  We store the kept START objects in
   // this array.
-  nsTArray<InternalProfileTimelineMarker*> keptMarkers;
+  nsTArray<TimelineMarker*> keptMarkers;
 
   for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
-    ProfilerMarkerTracing* startPayload = static_cast<ProfilerMarkerTracing*>(
-      mProfileTimelineMarkers[i]->mPayload);
-    const char* startMarkerName = mProfileTimelineMarkers[i]->mName.get();
+    TimelineMarker* startPayload = mProfileTimelineMarkers[i];
+    const char* startMarkerName = startPayload->GetName();
 
     bool hasSeenPaintedLayer = false;
 
     if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
       bool hasSeenEnd = false;
 
       // DOM events can be nested, so we must take care when searching
       // for the matching end.  It doesn't hurt to apply this logic to
       // all event types.
       uint32_t markerDepth = 0;
 
       // The assumption is that the devtools timeline flushes markers frequently
       // enough for the amount of markers to always be small enough that the
       // nested for loop isn't going to be a performance problem.
       for (uint32_t j = i + 1; j < mProfileTimelineMarkers.Length(); ++j) {
-        ProfilerMarkerTracing* endPayload = static_cast<ProfilerMarkerTracing*>(
-          mProfileTimelineMarkers[j]->mPayload);
-        const char* endMarkerName = mProfileTimelineMarkers[j]->mName.get();
+        TimelineMarker* endPayload = mProfileTimelineMarkers[j];
+        const char* endMarkerName = endPayload->GetName();
 
         // Look for Layer markers to stream out paint markers.
         if (strcmp(endMarkerName, "Layer") == 0) {
           hasSeenPaintedLayer = true;
         }
 
-        if (strcmp(startMarkerName, endMarkerName) != 0) {
+        if (!startPayload->Equals(endPayload)) {
           continue;
         }
         bool isPaint = strcmp(startMarkerName, "Paint") == 0;
 
         // Pair start and end markers.
         if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
           ++markerDepth;
         } else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
           if (markerDepth > 0) {
             --markerDepth;
           } else {
             // But ignore paint start/end if no layer has been painted.
             if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
               mozilla::dom::ProfileTimelineMarker marker;
-              marker.mName = NS_ConvertUTF8toUTF16(startMarkerName);
-              marker.mStart = mProfileTimelineMarkers[i]->mTime;
-              marker.mEnd = mProfileTimelineMarkers[j]->mTime;
+
+              marker.mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
+              marker.mStart = startPayload->GetTime();
+              marker.mEnd = endPayload->GetTime();
+              startPayload->AddDetails(marker);
               profileTimelineMarkers.AppendElement(marker);
             }
 
             // We want the start to be dropped either way.
             hasSeenEnd = true;
 
             break;
           }
@@ -2977,40 +2977,28 @@ nsDocShell::Now(DOMHighResTimeStamp* aWh
 }
 
 void
 nsDocShell::AddProfileTimelineMarker(const char* aName,
                                      TracingMetadata aMetaData)
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (mProfileTimelineRecording) {
-    DOMHighResTimeStamp delta;
-    Now(&delta);
-    ProfilerMarkerTracing* payload = new ProfilerMarkerTracing("Timeline",
-                                                               aMetaData);
-    mProfileTimelineMarkers.AppendElement(
-      new InternalProfileTimelineMarker(aName, payload, delta));
+    TimelineMarker* marker = new TimelineMarker(this, aName, aMetaData);
+    mProfileTimelineMarkers.AppendElement(marker);
   }
 #endif
 }
 
 void
-nsDocShell::AddProfileTimelineMarker(const char* aName,
-                                     ProfilerBacktrace* aCause,
-                                     TracingMetadata aMetaData)
+nsDocShell::AddProfileTimelineMarker(UniquePtr<TimelineMarker>& aMarker)
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (mProfileTimelineRecording) {
-    DOMHighResTimeStamp delta;
-    Now(&delta);
-    ProfilerMarkerTracing* payload = new ProfilerMarkerTracing("Timeline",
-                                                               aMetaData,
-                                                               aCause);
-    mProfileTimelineMarkers.AppendElement(
-      new InternalProfileTimelineMarker(aName, payload, delta));
+    mProfileTimelineMarkers.AppendElement(aMarker.release());
   }
 #endif
 }
 
 void
 nsDocShell::ClearProfileTimelineMarkers()
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -13,22 +13,21 @@
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIBaseWindow.h"
 #include "nsIScrollable.h"
 #include "nsITextScroll.h"
 #include "nsIContentViewerContainer.h"
 #include "nsIDOMStorageManager.h"
 #include "nsDocLoader.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "mozilla/TimeStamp.h"
 #include "GeckoProfiler.h"
-#ifdef MOZ_ENABLE_PROFILER_SPS
-#include "ProfilerMarkers.h"
-#endif
+#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
 
 // Helper Classes
 #include "nsCOMPtr.h"
 #include "nsPoint.h" // mCurrent/mDefaultScrollbarPreferences
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsThreadUtils.h"
 
@@ -254,24 +253,96 @@ public:
 
     // Notify Scroll observers when an async panning/zooming transform
     // has started being applied
     void NotifyAsyncPanZoomStarted(const mozilla::CSSIntPoint aScrollPos);
     // Notify Scroll observers when an async panning/zooming transform
     // is no longer applied
     void NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos);
 
+    // Objects of this type can be added to the timeline.  The class
+    // can also be subclassed to let a given marker creator provide
+    // custom details.
+    class TimelineMarker
+    {
+    public:
+        TimelineMarker(nsDocShell* aDocShell, const char* aName,
+                       TracingMetadata aMetaData)
+            : mName(aName)
+            , mMetaData(aMetaData)
+        {
+            MOZ_COUNT_CTOR(TimelineMarker);
+            MOZ_ASSERT(aName);
+            aDocShell->Now(&mTime);
+        }
+
+        TimelineMarker(nsDocShell* aDocShell, const char* aName,
+                       TracingMetadata aMetaData,
+                       const nsAString& aCause)
+            : mName(aName)
+            , mMetaData(aMetaData)
+            , mCause(aCause)
+        {
+            MOZ_COUNT_CTOR(TimelineMarker);
+            MOZ_ASSERT(aName);
+            aDocShell->Now(&mTime);
+        }
+
+        virtual ~TimelineMarker()
+        {
+            MOZ_COUNT_DTOR(TimelineMarker);
+        }
+
+        // Check whether two markers should be considered the same,
+        // for the purpose of pairing start and end markers.  Normally
+        // this definition suffices.
+        virtual bool Equals(const TimelineMarker* other)
+        {
+            return strcmp(mName, other->mName) == 0;
+        }
+
+        // Add details specific to this marker type to aMarker.  The
+        // standard elements have already been set.
+        virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
+        {
+        }
+
+        const char* GetName() const
+        {
+            return mName;
+        }
+
+        TracingMetadata GetMetaData() const
+        {
+            return mMetaData;
+        }
+
+        DOMHighResTimeStamp GetTime() const
+        {
+            return mTime;
+        }
+
+        const nsString& GetCause() const
+        {
+            return mCause;
+        }
+
+    private:
+        const char* mName;
+        TracingMetadata mMetaData;
+        DOMHighResTimeStamp mTime;
+        nsString mCause;
+    };
+
     // Add new profile timeline markers to this docShell. This will only add
     // markers if the docShell is currently recording profile timeline markers.
     // See nsIDocShell::recordProfileTimelineMarkers
     void AddProfileTimelineMarker(const char* aName,
                                   TracingMetadata aMetaData);
-    void AddProfileTimelineMarker(const char* aName,
-                                  ProfilerBacktrace* aCause,
-                                  TracingMetadata aMetaData);
+    void AddProfileTimelineMarker(mozilla::UniquePtr<TimelineMarker> &aMarker);
 
     // Global counter for how many docShells are currently recording profile
     // timeline markers
     static unsigned long gProfileTimelineRecordingsCount;
 protected:
     // Object Management
     virtual ~nsDocShell();
     virtual void DestroyChildren();
@@ -955,38 +1026,17 @@ private:
 
     // A depth count of how many times NotifyRunToCompletionStart
     // has been called without a matching NotifyRunToCompletionStop.
     uint32_t          mJSRunToCompletionDepth;
 
     // True if recording profiles.
     bool mProfileTimelineRecording;
 
-#ifdef MOZ_ENABLE_PROFILER_SPS
-    struct InternalProfileTimelineMarker
-    {
-      InternalProfileTimelineMarker(const char* aName,
-                                    ProfilerMarkerTracing* aPayload,
-                                    DOMHighResTimeStamp aTime)
-        : mName(aName)
-        , mPayload(aPayload)
-        , mTime(aTime)
-      {}
-
-      ~InternalProfileTimelineMarker()
-      {
-        delete mPayload;
-      }
-
-      nsCString mName;
-      ProfilerMarkerTracing* mPayload;
-      DOMHighResTimeStamp mTime;
-    };
-    nsTArray<InternalProfileTimelineMarker*> mProfileTimelineMarkers;
-#endif
+    nsTArray<TimelineMarker*> mProfileTimelineMarkers;
 
     // Get rid of all the timeline markers accumulated so far
     void ClearProfileTimelineMarkers();
 
     // Separate function to do the actual name (i.e. not _top, _self etc.)
     // searching for FindItemWithName.
     nsresult DoFindItemWithName(const char16_t* aName,
                                 nsISupports* aRequestor,
--- a/docshell/test/browser/browser_timelineMarkers-02.js
+++ b/docshell/test/browser/browser_timelineMarkers-02.js
@@ -49,30 +49,33 @@ let TESTS = [{
   }
 }, {
   desc: "sync console.time/timeEnd",
   setup: function(div, docShell) {
     content.console.time("FOOBAR");
     content.console.timeEnd("FOOBAR");
     let markers = docShell.popProfileTimelineMarkers();
     is(markers.length, 1, "Got one marker");
-    is(markers[0].name, "ConsoleTime:FOOBAR", "Got ConsoleTime:FOOBAR marker");
+    is(markers[0].name, "ConsoleTime", "Got ConsoleTime marker");
+    is(markers[0].causeName, "FOOBAR", "Got ConsoleTime FOOBAR detail");
     content.console.time("FOO");
     content.setTimeout(() => {
       content.console.time("BAR");
       content.setTimeout(() => {
         content.console.timeEnd("FOO");
         content.console.timeEnd("BAR");
       }, 100);
     }, 100);
   },
   check: function(markers) {
     is(markers.length, 2, "Got 2 markers");
-    is(markers[0].name, "ConsoleTime:FOO", "Got ConsoleTime:FOO marker");
-    is(markers[1].name, "ConsoleTime:BAR", "Got ConsoleTime:BARmarker");
+    is(markers[0].name, "ConsoleTime", "Got first ConsoleTime marker");
+    is(markers[0].causeName, "FOO", "Got ConsoleTime FOO detail");
+    is(markers[1].name, "ConsoleTime", "Got second ConsoleTime marker");
+    is(markers[1].causeName, "BAR", "Got ConsoleTime BAR detail");
   }
 }];
 
 let test = Task.async(function*() {
   waitForExplicitFinish();
 
   yield openUrl("data:text/html;charset=utf8," + encodeURIComponent(URL));
 
--- a/docshell/test/browser/browser_timelineMarkers-03.js
+++ b/docshell/test/browser/browser_timelineMarkers-03.js
@@ -11,29 +11,43 @@ let TESTS = [{
   setup: function() {
     content.document.body.addEventListener("dog",
                                            function(e) { console.log("hi"); },
                                            true);
     content.document.body.dispatchEvent(new Event("dog"));
   },
   check: function(markers) {
     is(markers.length, 1, "Got 1 marker");
+    is(markers[0].type, "dog", "Got dog event name");
+    is(markers[0].eventPhase, 2, "Got phase 2");
   }
 }, {
   desc: "Event dispatch with a second handler",
   setup: function() {
     content.document.body.addEventListener("dog",
                                            function(e) { console.log("hi"); },
                                            false);
     content.document.body.dispatchEvent(new Event("dog"));
   },
   check: function(markers) {
     is(markers.length, 2, "Got 2 markers");
   }
 }, {
+  desc: "Event targeted at child",
+  setup: function() {
+    let child = content.document.body.firstElementChild;
+    child.addEventListener("dog", function(e) { });
+    child.dispatchEvent(new Event("dog"));
+  },
+  check: function(markers) {
+    is(markers.length, 2, "Got 2 markers");
+    is(markers[0].eventPhase, 1, "Got phase 1 marker");
+    is(markers[1].eventPhase, 2, "Got phase 2 marker");
+  }
+}, {
   desc: "Event dispatch on a new document",
   setup: function() {
     let doc = content.document.implementation.createHTMLDocument("doc");
     let p = doc.createElement("p");
     p.innerHTML = "inside";
     doc.body.appendChild(p);
 
     p.addEventListener("zebra", function(e) {console.log("hi");});
@@ -54,17 +68,17 @@ let TESTS = [{
   check: function(markers) {
     is(markers.length, 1, "Got 1 marker");
   }
 }];
 
 let test = Task.async(function*() {
   waitForExplicitFinish();
 
-  yield openUrl("data:text/html;charset=utf-8,Test page");
+  yield openUrl("data:text/html;charset=utf-8,<p>Test page</p>");
 
   let docShell = content.QueryInterface(Ci.nsIInterfaceRequestor)
                         .getInterface(Ci.nsIWebNavigation)
                         .QueryInterface(Ci.nsIDocShell);
 
   info("Start recording");
   docShell.recordProfileTimelineMarkers = true;
 
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -800,16 +800,41 @@ ReifyStack(nsIStackFrame* aStack, nsTArr
     NS_ENSURE_SUCCESS(rv, rv);
 
     stack.swap(caller);
   }
 
   return NS_OK;
 }
 
+class ConsoleTimelineMarker : public nsDocShell::TimelineMarker
+{
+public:
+  ConsoleTimelineMarker(nsDocShell* aDocShell,
+                        TracingMetadata aMetaData,
+                        const nsAString& aCause)
+    : nsDocShell::TimelineMarker(aDocShell, "ConsoleTime", aMetaData, aCause)
+  {
+  }
+
+  virtual bool Equals(const nsDocShell::TimelineMarker* aOther)
+  {
+    if (!nsDocShell::TimelineMarker::Equals(aOther)) {
+      return false;
+    }
+    // Console markers must have matching causes as well.
+    return GetCause() == aOther->GetCause();
+  }
+
+  virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
+  {
+    aMarker.mCauseName.Construct(GetCause());
+  }
+};
+
 // Queue a call to a console method. See the CALL_DELAY constant.
 void
 Console::Method(JSContext* aCx, MethodName aMethodName,
                 const nsAString& aMethodString,
                 const Sequence<JS::Value>& aData)
 {
   // This RAII class removes the last element of the mQueuedCalls if something
   // goes wrong.
@@ -920,33 +945,33 @@ Console::Method(JSContext* aCx, MethodNa
       nsRefPtr<nsPerformance> performance = win->GetPerformance();
       if (!performance) {
         return;
       }
 
       callData->mMonotonicTimer = performance->Now();
 
       // 'time' and 'timeEnd' are displayed in the devtools timeline if active.
-      // Marked as "ConsoleTime:ARG1".
       bool isTimelineRecording = false;
       nsDocShell* docShell = static_cast<nsDocShell*>(mWindow->GetDocShell());
       if (docShell) {
         docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
       }
 
       if (isTimelineRecording && aData.Length() == 1) {
         JS::Rooted<JS::Value> value(aCx, aData[0]);
         JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
         if (jsString) {
           nsAutoJSString key;
           if (key.init(aCx, jsString)) {
-            nsCString str("ConsoleTime:");
-            AppendUTF16toUTF8(key, str);
-            docShell->AddProfileTimelineMarker(str.get(),
-              aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END);
+            mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
+              MakeUnique<ConsoleTimelineMarker>(docShell,
+                                                aMethodName == MethodTime ? TRACING_INTERVAL_START : TRACING_INTERVAL_END,
+                                                key);
+            docShell->AddProfileTimelineMarker(marker);
           }
         }
       }
 
     } else {
       WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
       MOZ_ASSERT(workerPrivate);
 
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -1007,16 +1007,36 @@ EventListenerManager::GetDocShellForTarg
 
   if (doc) {
     docShell = doc->GetDocShell();
   }
 
   return docShell;
 }
 
+class EventTimelineMarker : public nsDocShell::TimelineMarker
+{
+public:
+  EventTimelineMarker(nsDocShell* aDocShell, TracingMetadata aMetaData,
+                      uint16_t aPhase, const nsAString& aCause)
+    : nsDocShell::TimelineMarker(aDocShell, "DOMEvent", aMetaData, aCause)
+    , mPhase(aPhase)
+  {
+  }
+
+  virtual void AddDetails(mozilla::dom::ProfileTimelineMarker& aMarker)
+  {
+    aMarker.mType.Construct(GetCause());
+    aMarker.mEventPhase.Construct(mPhase);
+  }
+
+private:
+  uint16_t mPhase;
+};
+
 /**
 * Causes a check for event listeners and processing by them if they exist.
 * @param an event listener
 */
 
 void
 EventListenerManager::HandleEventInternal(nsPresContext* aPresContext,
                                           WidgetEvent* aEvent,
@@ -1061,32 +1081,43 @@ EventListenerManager::HandleEventInterna
             if (!aEvent->currentTarget) {
               break;
             }
           }
 
           // Maybe add a marker to the docshell's timeline, but only
           // bother with all the logic if some docshell is recording.
           nsCOMPtr<nsIDocShell> docShell;
+          bool isTimelineRecording = false;
           if (mIsMainThreadELM &&
               nsDocShell::gProfileTimelineRecordingsCount > 0 &&
               listener->mListenerType != Listener::eNativeListener) {
             docShell = GetDocShellForTarget();
             if (docShell) {
+              docShell->GetRecordProfileTimelineMarkers(&isTimelineRecording);
+            }
+            if (isTimelineRecording) {
               nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
-              ds->AddProfileTimelineMarker("DOMEvent", TRACING_INTERVAL_START);
+              nsAutoString typeStr;
+              (*aDOMEvent)->GetType(typeStr);
+              uint16_t phase;
+              (*aDOMEvent)->GetEventPhase(&phase);
+              mozilla::UniquePtr<nsDocShell::TimelineMarker> marker =
+                MakeUnique<EventTimelineMarker>(ds, TRACING_INTERVAL_START,
+                                                phase, typeStr);
+              ds->AddProfileTimelineMarker(marker);
             }
           }
 
           if (NS_FAILED(HandleEventSubType(listener, *aDOMEvent,
                                            aCurrentTarget))) {
             aEvent->mFlags.mExceptionHasBeenRisen = true;
           }
 
-          if (docShell) {
+          if (isTimelineRecording) {
             nsDocShell* ds = static_cast<nsDocShell*>(docShell.get());
             ds->AddProfileTimelineMarker("DOMEvent", TRACING_INTERVAL_END);
           }
         }
       }
     }
   }
 
--- a/dom/nfc/NfcContentHelper.js
+++ b/dom/nfc/NfcContentHelper.js
@@ -49,16 +49,17 @@ updateDebug();
 
 const NFCCONTENTHELPER_CID =
   Components.ID("{4d72c120-da5f-11e1-9b23-0800200c9a66}");
 
 const NFC_IPC_MSG_NAMES = [
   "NFC:ReadNDEFResponse",
   "NFC:WriteNDEFResponse",
   "NFC:MakeReadOnlyResponse",
+  "NFC:FormatResponse",
   "NFC:ConnectResponse",
   "NFC:CloseResponse",
   "NFC:CheckP2PRegistrationResponse",
   "NFC:DOMEvent",
   "NFC:NotifySendFileStatusResponse",
   "NFC:ChangeRFStateResponse"
 ];
 
@@ -171,16 +172,28 @@ NfcContentHelper.prototype = {
 
     cpmm.sendAsyncMessage("NFC:MakeReadOnly", {
       requestId: requestId,
       sessionToken: sessionToken
     });
     return request;
   },
 
+  format: function format(sessionToken) {
+    let request = Services.DOMRequest.createRequest(this._window);
+    let requestId = btoa(this.getRequestId(request));
+    this._requestMap[requestId] = this._window;
+
+    cpmm.sendAsyncMessage("NFC:Format", {
+      requestId: requestId,
+      sessionToken: sessionToken
+    });
+    return request;
+  },
+
   connect: function connect(techType, sessionToken) {
     let request = Services.DOMRequest.createRequest(this._window);
     let requestId = btoa(this.getRequestId(request));
     this._requestMap[requestId] = this._window;
 
     cpmm.sendAsyncMessage("NFC:Connect", {
       requestId: requestId,
       sessionToken: sessionToken,
@@ -317,16 +330,17 @@ NfcContentHelper.prototype = {
         break;
       case "NFC:CheckP2PRegistrationResponse":
         this.handleCheckP2PRegistrationResponse(result);
         break;
       case "NFC:ConnectResponse": // Fall through.
       case "NFC:CloseResponse":
       case "NFC:WriteNDEFResponse":
       case "NFC:MakeReadOnlyResponse":
+      case "NFC:FormatResponse":
       case "NFC:NotifySendFileStatusResponse":
       case "NFC:ChangeRFStateResponse":
         if (result.errorMsg) {
           this.fireRequestError(atob(result.requestId), result.errorMsg);
         } else {
           this.fireRequestSuccess(atob(result.requestId), result);
         }
         break;
--- a/dom/nfc/gonk/Nfc.js
+++ b/dom/nfc/gonk/Nfc.js
@@ -66,16 +66,17 @@ const NFC_IPC_READ_PERM_MSG_NAMES = [
   "NFC:ReadNDEF",
   "NFC:Connect",
   "NFC:Close",
 ];
 
 const NFC_IPC_WRITE_PERM_MSG_NAMES = [
   "NFC:WriteNDEF",
   "NFC:MakeReadOnly",
+  "NFC:Format",
   "NFC:SendFile",
   "NFC:RegisterPeerReadyTarget",
   "NFC:UnregisterPeerReadyTarget"
 ];
 
 const NFC_IPC_MANAGER_PERM_MSG_NAMES = [
   "NFC:CheckP2PRegistration",
   "NFC:NotifyUserAcceptedP2P",
@@ -559,16 +560,17 @@ Nfc.prototype = {
         }
 
         this.sendNfcResponse(message);
         break;
       case "ConnectResponse": // Fall through.
       case "CloseResponse":
       case "ReadNDEFResponse":
       case "MakeReadOnlyResponse":
+      case "FormatResponse":
       case "WriteNDEFResponse":
         this.sendNfcResponse(message);
         break;
       default:
         throw new Error("Don't know about this message type: " + message.type);
     }
   },
 
@@ -617,16 +619,19 @@ Nfc.prototype = {
         break;
       case "NFC:WriteNDEF":
         message.data.isP2P = SessionHelper.isP2PSession(message.data.sessionId);
         this.sendToNfcService("writeNDEF", message.data);
         break;
       case "NFC:MakeReadOnly":
         this.sendToNfcService("makeReadOnly", message.data);
         break;
+      case "NFC:Format":
+        this.sendToNfcService("format", message.data);
+        break;
       case "NFC:Connect":
         this.sendToNfcService("connect", message.data);
         break;
       case "NFC:Close":
         this.sendToNfcService("close", message.data);
         break;
       case "NFC:SendFile":
         // Chrome process is the arbitrator / mediator between
--- a/dom/nfc/gonk/NfcGonkMessage.h
+++ b/dom/nfc/gonk/NfcGonkMessage.h
@@ -3,25 +3,26 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef NfcGonkMessage_h
 #define NfcGonkMessage_h
 
 namespace mozilla {
 
 #define NFCD_MAJOR_VERSION 1
-#define NFCD_MINOR_VERSION 16
+#define NFCD_MINOR_VERSION 17
 
 enum NfcRequest {
   ChangeRFStateReq = 0,
   ConnectReq,
   CloseReq,
   ReadNDEFReq,
   WriteNDEFReq,
   MakeReadOnlyReq,
+  FormatReq,
 };
 
 enum NfcResponse {
   GeneralRsp = 1000,
   ChangeRFStateRsp,
   ReadNDEFRsp,
 };
 
--- a/dom/nfc/gonk/NfcMessageHandler.cpp
+++ b/dom/nfc/gonk/NfcMessageHandler.cpp
@@ -15,23 +15,25 @@
 using namespace android;
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static const char* kChangeRFStateRequest = "changeRFState";
 static const char* kReadNDEFRequest = "readNDEF";
 static const char* kWriteNDEFRequest = "writeNDEF";
 static const char* kMakeReadOnlyRequest = "makeReadOnly";
+static const char* kFormatRequest = "format";
 static const char* kConnectRequest = "connect";
 static const char* kCloseRequest = "close";
 
 static const char* kChangeRFStateResponse = "ChangeRFStateResponse";
 static const char* kReadNDEFResponse = "ReadNDEFResponse";
 static const char* kWriteNDEFResponse = "WriteNDEFResponse";
 static const char* kMakeReadOnlyResponse = "MakeReadOnlyResponse";
+static const char* kFormatResponse = "FormatResponse";
 static const char* kConnectResponse = "ConnectResponse";
 static const char* kCloseResponse = "CloseResponse";
 
 static const char* kInitializedNotification = "InitializedNotification";
 static const char* kTechDiscoveredNotification = "TechDiscoveredNotification";
 static const char* kTechLostNotification = "TechLostNotification";
 static const char* kHCIEventTransactionNotification =
                      "HCIEventTransactionNotification";
@@ -47,16 +49,19 @@ NfcMessageHandler::Marshall(Parcel& aPar
   } else if (!strcmp(type, kReadNDEFRequest)) {
     result = ReadNDEFRequest(aParcel, aOptions);
   } else if (!strcmp(type, kWriteNDEFRequest)) {
     result = WriteNDEFRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::WriteNDEFReq);
   } else if (!strcmp(type, kMakeReadOnlyRequest)) {
     result = MakeReadOnlyRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::MakeReadOnlyReq);
+  } else if (!strcmp(type, kFormatRequest)) {
+    result = FormatRequest(aParcel, aOptions);
+    mPendingReqQueue.AppendElement(NfcRequest::FormatReq);
   } else if (!strcmp(type, kConnectRequest)) {
     result = ConnectRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::ConnectReq);
   } else if (!strcmp(type, kCloseRequest)) {
     result = CloseRequest(aParcel, aOptions);
     mPendingReqQueue.AppendElement(NfcRequest::CloseReq);
   } else {
     result = false;
@@ -112,16 +117,19 @@ NfcMessageHandler::GeneralResponse(const
 
   switch (pendingReq) {
     case NfcRequest::WriteNDEFReq:
       type = kWriteNDEFResponse;
       break;
     case NfcRequest::MakeReadOnlyReq:
       type = kMakeReadOnlyResponse;
       break;
+    case NfcRequest::FormatReq:
+      type = kFormatResponse;
+      break;
     case NfcRequest::ConnectReq:
       type = kConnectResponse;
       break;
     case NfcRequest::CloseReq:
       type = kCloseResponse;
       break;
     default:
       CHROMIUM_LOG("Nfcd, unknown general response %d", pendingReq);
@@ -203,16 +211,25 @@ NfcMessageHandler::MakeReadOnlyRequest(P
 {
   aParcel.writeInt32(NfcRequest::MakeReadOnlyReq);
   aParcel.writeInt32(aOptions.mSessionId);
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
 }
 
 bool
+NfcMessageHandler::FormatRequest(Parcel& aParcel, const CommandOptions& aOptions)
+{
+  aParcel.writeInt32(NfcRequest::FormatReq);
+  aParcel.writeInt32(aOptions.mSessionId);
+  mRequestIdQueue.AppendElement(aOptions.mRequestId);
+  return true;
+}
+
+bool
 NfcMessageHandler::ConnectRequest(Parcel& aParcel, const CommandOptions& aOptions)
 {
   aParcel.writeInt32(NfcRequest::ConnectReq);
   aParcel.writeInt32(aOptions.mSessionId);
   aParcel.writeInt32(aOptions.mTechType);
   mRequestIdQueue.AppendElement(aOptions.mRequestId);
   return true;
 }
--- a/dom/nfc/gonk/NfcMessageHandler.h
+++ b/dom/nfc/gonk/NfcMessageHandler.h
@@ -26,16 +26,17 @@ public:
 private:
   bool GeneralResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool ChangeRFStateRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool ChangeRFStateResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool ReadNDEFRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool ReadNDEFResponse(const android::Parcel& aParcel, EventOptions& aOptions);
   bool WriteNDEFRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool MakeReadOnlyRequest(android::Parcel& aParcel, const CommandOptions& options);
+  bool FormatRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool ConnectRequest(android::Parcel& aParcel, const CommandOptions& options);
   bool CloseRequest(android::Parcel& aParcel, const CommandOptions& options);
 
   bool InitializeNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool TechDiscoveredNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool TechLostNotification(const android::Parcel& aParcel, EventOptions& aOptions);
   bool HCIEventTransactionNotification(const android::Parcel& aParcel, EventOptions& aOptions);
 
--- a/dom/nfc/nsINfcContentHelper.idl
+++ b/dom/nfc/nsINfcContentHelper.idl
@@ -60,26 +60,27 @@ interface nsINfcEventListener : nsISuppo
    * Callback function used to notify peerlost.
    *
    * @param sessionToken
    *        SessionToken received from parent process
    */
   void notifyPeerLost(in DOMString sessionToken);
 };
 
-[scriptable, uuid(486dff99-6755-428b-834d-3647ce276b54)]
+[scriptable, uuid(9343ae1a-6e2f-11e4-b5c4-fbb166b38b62)]
 interface nsINfcContentHelper : nsISupports
 {
   void init(in nsIDOMWindow window);
 
   boolean checkSessionToken(in DOMString sessionToken, in boolean isP2P);
 
   nsIDOMDOMRequest readNDEF(in DOMString sessionToken);
   nsIDOMDOMRequest writeNDEF(in nsIVariant records, in DOMString sessionToken);
   nsIDOMDOMRequest makeReadOnly(in DOMString sessionToken);
+  nsIDOMDOMRequest format(in DOMString sessionToken);
 
   nsIDOMDOMRequest connect(in unsigned long techType, in DOMString sessionToken);
   nsIDOMDOMRequest close(in DOMString sessionToken);
 
   /**
    * Initiate send file operation.
    *
    * @param blob
--- a/dom/nfc/nsNfc.js
+++ b/dom/nfc/nsNfc.js
@@ -96,16 +96,29 @@ MozNFCTagImpl.prototype = {
     if (!this.canBeMadeReadOnly) {
       throw new this._window.DOMError("InvalidAccessError",
                                       "NFCTag object cannot be made read-only");
     }
 
     return this._nfcContentHelper.makeReadOnly(this.session);
   },
 
+  format: function format() {
+    if (this.isLost) {
+      throw new this._window.DOMError("InvalidStateError", "NFCTag object is invalid");
+    }
+
+    if (!this.isFormatable) {
+      throw new this._window.DOMError("InvalidAccessError",
+                                      "NFCTag object is not formatable");
+    }
+
+    return this._nfcContentHelper.format(this.session);
+  },
+
   classID: Components.ID("{4e1e2e90-3137-11e3-aa6e-0800200c9a66}"),
   contractID: "@mozilla.org/nfc/NFCTag;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
 };
 
 /**
  * Implementation of NFCPeer.
--- a/dom/webidl/MozNFCTag.webidl
+++ b/dom/webidl/MozNFCTag.webidl
@@ -69,16 +69,19 @@ interface MozNFCTag {
   [Throws]
   DOMRequest readNDEF();
 
   [Throws]
   DOMRequest writeNDEF(sequence<MozNDEFRecord> records);
 
   [Throws]
   DOMRequest makeReadOnly();
+
+  [Throws]
+  DOMRequest format();
 };
 
 // Mozilla Only
 partial interface MozNFCTag {
   [ChromeOnly]
   attribute DOMString session;
 
   /**
--- a/dom/webidl/ProfileTimelineMarker.webidl
+++ b/dom/webidl/ProfileTimelineMarker.webidl
@@ -3,9 +3,14 @@
  * 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/.
  */
 
 dictionary ProfileTimelineMarker {
   DOMString name = "";
   DOMHighResTimeStamp start = 0;
   DOMHighResTimeStamp end = 0;
+  /* For ConsoleTime markers.  */
+  DOMString causeName;
+  /* For DOMEvent markers.  */
+  DOMString type;
+  unsigned short eventPhase;
 };
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -469,16 +469,18 @@ example.com
 which is run by
 Example Enterprises, Inc.
 
 The layout of the identity dialog prevents combining this into a single string with
 substitution variables.  If it is difficult to translate the sense of the string
 with that structure, consider a translation which ignores the preceding domain and
 just addresses the organization to follow, e.g. "This site is run by " -->
 <!ENTITY identity_run_by "which is run by">
+<!ENTITY identity_no_info "This website does not supply identity information.">
+<!ENTITY identity_not_encrypted "Your connection to this website is not encrypted.">
 
 <!-- Mixed content notifications in site identity popup -->
 <!ENTITY loaded_mixed_content_message "This page is displaying content that isn\'t secure.">
 <!ENTITY blocked_mixed_content_message_top "&brandShortName; has blocked content that isn\'t secure.">
 <!ENTITY blocked_mixed_content_message_bottom "Most websites will still work properly even when this content is blocked.">
 
 <!-- Tracking content notifications in site identity popup -->
 <!ENTITY loaded_tracking_content_message "This page is tracking your online activity.">
--- a/mobile/android/base/resources/layout/site_identity.xml
+++ b/mobile/android/base/resources/layout/site_identity.xml
@@ -10,50 +10,58 @@
               android:padding="@dimen/doorhanger_padding">
 
     <ImageView android:id="@+id/larry"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:src="@drawable/larry"
                android:paddingRight="@dimen/doorhanger_padding"/>
 
-    <LinearLayout android:layout_width="0dip"
-                  android:layout_height="wrap_content"
-                  android:layout_weight="1.0"
-                  android:orientation="vertical">
+    <FrameLayout android:layout_width="0dip"
+                 android:layout_height="wrap_content"
+                 android:layout_weight="1.0">
+
+        <include layout="@layout/site_identity_unknown" />
+
+        <LinearLayout android:id="@+id/site_identity_known_container"
+                      android:layout_width="match_parent"
+                      android:layout_height="wrap_content"
+                      android:orientation="vertical">
 
-        <TextView android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textSize="14sp"
-                  android:textColor="@color/doorhanger_text"
-                  android:text="@string/identity_connected_to"/>
+            <TextView android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="14sp"
+                      android:textColor="@color/doorhanger_text"
+                      android:text="@string/identity_connected_to"/>
 
-        <TextView android:id="@+id/host"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textSize="20sp"
-                  android:textColor="@color/doorhanger_text"
-                  android:textStyle="bold"/>
+            <TextView android:id="@+id/host"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="20sp"
+                      android:textColor="@color/doorhanger_text"
+                      android:textStyle="bold"/>
 
-        <TextView android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textSize="14sp"
-                  android:textColor="@color/doorhanger_text"
-                  android:text="@string/identity_run_by"
-                  android:paddingTop="12dip"/>
+            <TextView android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="14sp"
+                      android:textColor="@color/doorhanger_text"
+                      android:text="@string/identity_run_by"
+                      android:paddingTop="12dip"/>
 
-        <TextView android:id="@+id/owner"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textColor="@color/doorhanger_text"
-                  android:textSize="16sp"
-                  android:textStyle="bold"/>
+            <TextView android:id="@+id/owner"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textColor="@color/doorhanger_text"
+                      android:textSize="16sp"
+                      android:textStyle="bold"/>
 
-        <TextView android:id="@+id/verifier"
-                  android:layout_width="wrap_content"
-                  android:layout_height="wrap_content"
-                  android:textSize="14sp"
-                  android:textColor="@color/doorhanger_text"
-                  android:paddingTop="12dip"/>
+            <TextView android:id="@+id/verifier"
+                      android:layout_width="wrap_content"
+                      android:layout_height="wrap_content"
+                      android:textSize="14sp"
+                      android:textColor="@color/doorhanger_text"
+                      android:paddingTop="12dip"/>
 
-    </LinearLayout>
+        </LinearLayout>
+
+     </FrameLayout>
 
 </LinearLayout>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/layout/site_identity_unknown.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/site_identity_unknown_container"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:orientation="vertical"
+              android:visibility="gone">
+
+    <TextView android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:textSize="14sp"
+              android:textColor="@color/doorhanger_text"
+              android:text="@string/identity_no_info"/>
+
+    <TextView android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:textSize="14sp"
+              android:textColor="@color/doorhanger_text"
+              android:text="@string/identity_not_encrypted"
+              android:paddingTop="12dip"/>
+
+</LinearLayout>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -406,16 +406,18 @@
 
   <!-- Icon is automatically generated from R.drawable.bookmarkdefaults_favicon_marketplace -->
   <string name="bookmarkdefaults_title_marketplace">@bookmarks_marketplace@</string>
   <string name="bookmarkdefaults_url_marketplace">https://marketplace.firefox.com/</string>
 
   <!-- Site identity popup -->
   <string name="identity_connected_to">&identity_connected_to;</string>
   <string name="identity_run_by">&identity_run_by;</string>
+  <string name="identity_no_info">&identity_no_info;</string>
+  <string name="identity_not_encrypted">&identity_not_encrypted;</string>
   <string name="loaded_mixed_content_message">&loaded_mixed_content_message;</string>
   <string name="blocked_mixed_content_message_top">&blocked_mixed_content_message_top;</string>
   <string name="blocked_mixed_content_message_bottom">&blocked_mixed_content_message_bottom;</string>
   <string name="loaded_tracking_content_message">&loaded_tracking_content_message;</string>
   <string name="blocked_tracking_content_message_top">&blocked_tracking_content_message_top;</string>
   <string name="blocked_tracking_content_message_bottom">&blocked_tracking_content_message_bottom;</string>
   <string name="learn_more">&learn_more;</string>
   <string name="enable_protection">&enable_protection;</string>
--- a/mobile/android/base/sync/SharedPreferencesClientsDataDelegate.java
+++ b/mobile/android/base/sync/SharedPreferencesClientsDataDelegate.java
@@ -2,31 +2,35 @@
  * 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;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
+import org.mozilla.gecko.util.HardwareUtils;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 
 /**
  * A <code>ClientsDataDelegate</code> implementation that persists to a
  * <code>SharedPreferences</code> instance.
  */
 public class SharedPreferencesClientsDataDelegate implements ClientsDataDelegate {
   protected final SharedPreferences sharedPreferences;
   protected final Context context;
 
   public SharedPreferencesClientsDataDelegate(SharedPreferences sharedPreferences, Context context) {
     this.sharedPreferences = sharedPreferences;
     this.context = context;
+
+    // It's safe to init this multiple times.
+    HardwareUtils.init(context);
   }
 
   @Override
   public synchronized String getAccountGUID() {
     String accountGUID = sharedPreferences.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
     if (accountGUID == null) {
       accountGUID = Utils.generateGuid();
       sharedPreferences.edit().putString(SyncConfiguration.PREF_ACCOUNT_GUID, accountGUID).commit();
@@ -87,9 +91,26 @@ public class SharedPreferencesClientsDat
   public synchronized int getClientsCount() {
     return (int) sharedPreferences.getLong(SyncConfiguration.PREF_NUM_CLIENTS, 0);
   }
 
   @Override
   public long getLastModifiedTimestamp() {
     return sharedPreferences.getLong(SyncConfiguration.PREF_CLIENT_DATA_TIMESTAMP, 0);
   }
+
+  @Override
+  public String getFormFactor() {
+    if (HardwareUtils.isLargeTablet()) {
+      return "largetablet";
+    }
+
+    if (HardwareUtils.isSmallTablet()) {
+      return "smalltablet";
+    }
+
+    if (HardwareUtils.isTelevision()) {
+      return "tv";
+    }
+
+    return "phone";
+  }
 }
--- a/mobile/android/base/sync/delegates/ClientsDataDelegate.java
+++ b/mobile/android/base/sync/delegates/ClientsDataDelegate.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.sync.delegates
 public interface ClientsDataDelegate {
   public String getAccountGUID();
   public String getDefaultClientName();
   public void setClientName(String clientName, long now);
   public String getClientName();
   public void setClientsCount(int clientsCount);
   public int getClientsCount();
   public boolean isLocalGUID(String guid);
+  public String getFormFactor();
 
   /**
    * The last time the client's data was modified in a way that should be
    * reflected remotely.
    * <p>
    * Changing the client's name should be reflected remotely, while changing the
    * clients count should not (since that data is only used to inform local
    * policy.)
--- a/mobile/android/base/sync/repositories/android/ClientsDatabase.java
+++ b/mobile/android/base/sync/repositories/android/ClientsDatabase.java
@@ -14,26 +14,34 @@ import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 
 public class ClientsDatabase extends CachedSQLiteOpenHelper {
 
   public static final String LOG_TAG = "ClientsDatabase";
 
   // Database Specifications.
   protected static final String DB_NAME = "clients_database";
-  protected static final int SCHEMA_VERSION = 2;
+  protected static final int SCHEMA_VERSION = 3;
 
   // Clients Table.
   public static final String TBL_CLIENTS      = "clients";
   public static final String COL_ACCOUNT_GUID = "guid";
   public static final String COL_PROFILE      = "profile";
   public static final String COL_NAME         = "name";
   public static final String COL_TYPE         = "device_type";
 
-  public static final String[] TBL_CLIENTS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_PROFILE, COL_NAME, COL_TYPE };
+  // Optional fields.
+  public static final String COL_FORMFACTOR = "formfactor";
+  public static final String COL_OS = "os";
+  public static final String COL_APPLICATION = "application";
+  public static final String COL_APP_PACKAGE = "appPackage";
+  public static final String COL_DEVICE = "device";
+
+  public static final String[] TBL_CLIENTS_COLUMNS = new String[] { COL_ACCOUNT_GUID, COL_PROFILE, COL_NAME, COL_TYPE,
+                                                                    COL_FORMFACTOR, COL_OS, COL_APPLICATION, COL_APP_PACKAGE, COL_DEVICE };
   public static final String TBL_CLIENTS_KEY = COL_ACCOUNT_GUID + " = ? AND " +
                                                COL_PROFILE + " = ?";
 
   // Commands Table.
   public static final String TBL_COMMANDS = "commands";
   public static final String COL_COMMAND  = "command";
   public static final String COL_ARGS     = "args";
 
@@ -60,16 +68,21 @@ public class ClientsDatabase extends Cac
 
   public static void createClientsTable(SQLiteDatabase db) {
     Logger.debug(LOG_TAG, "ClientsDatabase.createClientsTable().");
     String createClientsTableSql = "CREATE TABLE " + TBL_CLIENTS + " ("
         + COL_ACCOUNT_GUID + " TEXT, "
         + COL_PROFILE + " TEXT, "
         + COL_NAME + " TEXT, "
         + COL_TYPE + " TEXT, "
+        + COL_FORMFACTOR + " TEXT, "
+        + COL_OS + " TEXT, "
+        + COL_APPLICATION + " TEXT, "
+        + COL_APP_PACKAGE + " TEXT, "
+        + COL_DEVICE + " TEXT, "
         + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_PROFILE + "))";
     db.execSQL(createClientsTableSql);
   }
 
   public static void createCommandsTable(SQLiteDatabase db) {
     Logger.debug(LOG_TAG, "ClientsDatabase.createCommandsTable().");
     String createCommandsTableSql = "CREATE TABLE " + TBL_COMMANDS + " ("
         + COL_ACCOUNT_GUID + " TEXT, "
@@ -77,26 +90,38 @@ public class ClientsDatabase extends Cac
         + COL_ARGS + " TEXT, "
         + "PRIMARY KEY (" + COL_ACCOUNT_GUID + ", " + COL_COMMAND + ", " + COL_ARGS + "), "
         + "FOREIGN KEY (" + COL_ACCOUNT_GUID + ") REFERENCES " + TBL_CLIENTS + " (" + COL_ACCOUNT_GUID + "))";
     db.execSQL(createCommandsTableSql);
   }
 
   @Override
   public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-    Logger.debug(LOG_TAG, "ClientsDatabase.onUpgrade().");
-    // For now we'll just drop and recreate the tables.
-    db.execSQL("DROP TABLE IF EXISTS " + TBL_CLIENTS);
-    db.execSQL("DROP TABLE IF EXISTS " + TBL_COMMANDS);
-    onCreate(db);
+    Logger.debug(LOG_TAG, "ClientsDatabase.onUpgrade(" + oldVersion + ", " + newVersion + ").");
+    if (oldVersion < 2) {
+      // For now we'll just drop and recreate the tables.
+      db.execSQL("DROP TABLE IF EXISTS " + TBL_CLIENTS);
+      db.execSQL("DROP TABLE IF EXISTS " + TBL_COMMANDS);
+      onCreate(db);
+      return;
+    }
+
+    if (newVersion >= 3) {
+      // Add the optional columns to clients.
+      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_FORMFACTOR + " TEXT");
+      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_OS + " TEXT");
+      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_APPLICATION + " TEXT");
+      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_APP_PACKAGE + " TEXT");
+      db.execSQL("ALTER TABLE " + TBL_CLIENTS + " ADD COLUMN " + COL_DEVICE + " TEXT");
+    }
   }
 
   public void wipeDB() {
     SQLiteDatabase db = this.getCachedWritableDatabase();
-    onUpgrade(db, SCHEMA_VERSION, SCHEMA_VERSION);
+    onUpgrade(db, 0, SCHEMA_VERSION);
   }
 
   public void wipeClientsTable() {
     SQLiteDatabase db = this.getCachedWritableDatabase();
     db.execSQL("DELETE FROM " + TBL_CLIENTS);
   }
 
   public void wipeCommandsTable() {
@@ -110,16 +135,36 @@ public class ClientsDatabase extends Cac
     SQLiteDatabase db = this.getCachedWritableDatabase();
 
     ContentValues cv = new ContentValues();
     cv.put(COL_ACCOUNT_GUID, record.guid);
     cv.put(COL_PROFILE, profileId);
     cv.put(COL_NAME, record.name);
     cv.put(COL_TYPE, record.type);
 
+    if (record.formfactor != null) {
+      cv.put(COL_FORMFACTOR, record.formfactor);
+    }
+
+    if (record.os != null) {
+      cv.put(COL_OS, record.os);
+    }
+
+    if (record.application != null) {
+      cv.put(COL_APPLICATION, record.application);
+    }
+
+    if (record.appPackage != null) {
+      cv.put(COL_APP_PACKAGE, record.appPackage);
+    }
+
+    if (record.device != null) {
+      cv.put(COL_DEVICE, record.device);
+    }
+
     String[] args = new String[] { record.guid, profileId };
     int rowsUpdated = db.update(TBL_CLIENTS, cv, TBL_CLIENTS_KEY, args);
 
     if (rowsUpdated >= 1) {
       Logger.debug(LOG_TAG, "Replaced client record for row with accountGUID " + record.guid);
     } else {
       long rowId = db.insert(TBL_CLIENTS, null, cv);
       Logger.debug(LOG_TAG, "Inserted client record into row: " + rowId);
--- a/mobile/android/base/sync/repositories/android/ClientsDatabaseAccessor.java
+++ b/mobile/android/base/sync/repositories/android/ClientsDatabaseAccessor.java
@@ -113,22 +113,31 @@ public class ClientsDatabaseAccessor {
       }
       return Collections.unmodifiableList(commands);
     } finally {
       cur.close();
     }
   }
 
   protected static ClientRecord recordFromCursor(Cursor cur) {
-    String accountGUID = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
-    String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME);
-    String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE);
-    ClientRecord record = new ClientRecord(accountGUID);
+    final String accountGUID = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_ACCOUNT_GUID);
+    final String clientName = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_NAME);
+    final String clientType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_TYPE);
+
+    final ClientRecord record = new ClientRecord(accountGUID);
     record.name = clientName;
     record.type = clientType;
+
+    // Optional fields. These will either be null or strings.
+    record.formfactor = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_FORMFACTOR);
+    record.os = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_OS);
+    record.device = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_DEVICE);
+    record.appPackage = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_APP_PACKAGE);
+    record.application = RepoUtils.optStringFromCursor(cur, ClientsDatabase.COL_APPLICATION);
+
     return record;
   }
 
   protected static Command commandFromCursor(Cursor cur) {
     String commandType = RepoUtils.getStringFromCursor(cur, ClientsDatabase.COL_COMMAND);
     JSONArray commandArgs = RepoUtils.getJSONArrayFromCursor(cur, ClientsDatabase.COL_ARGS);
     return new Command(commandType, commandArgs);
   }
--- a/mobile/android/base/sync/repositories/android/RepoUtils.java
+++ b/mobile/android/base/sync/repositories/android/RepoUtils.java
@@ -90,17 +90,34 @@ public class RepoUtils {
       if (cursor == null) {
         Logger.error(tag, "Got null cursor exception in " + logLabel);
         throw new NullCursorException(null);
       }
       return cursor;
     }
   }
 
-  public static String getStringFromCursor(Cursor cur, String colId) {
+  /**
+   * This method exists because the behavior of <code>cur.getString()</code> is undefined
+   * when the value in the database is <code>NULL</code>.
+   * This method will return <code>null</code> in that case.
+   */
+  public static String optStringFromCursor(final Cursor cur, final String colId) {
+    final int col = cur.getColumnIndex(colId);
+    if (cur.isNull(col)) {
+      return null;
+    }
+    return cur.getString(col);
+  }
+
+  /**
+   * The behavior of this method when the value in the database is <code>NULL</code> is
+   * determined by the implementation of the {@link Cursor}.
+   */
+  public static String getStringFromCursor(final Cursor cur, final String colId) {
     // TODO: getColumnIndexOrThrow?
     // TODO: don't look up columns by name!
     return cur.getString(cur.getColumnIndex(colId));
   }
 
   public static long getLongFromCursor(Cursor cur, String colId) {
     return cur.getLong(cur.getColumnIndex(colId));
   }
--- a/mobile/android/base/sync/repositories/domain/ClientRecord.java
+++ b/mobile/android/base/sync/repositories/domain/ClientRecord.java
@@ -36,16 +36,26 @@ public class ClientRecord extends Record
    * with the other descriptive fields.
    */
   public String name = ClientRecord.DEFAULT_CLIENT_NAME;
   public String type = ClientRecord.CLIENT_TYPE;
   public String version = null;                      // Free-form string, optional.
   public JSONArray commands;
   public JSONArray protocols;
 
+  // Optional fields.
+  // See <https://github.com/mozilla-services/docs/blob/master/source/sync/objectformats.rst#user-content-clients>
+  // for full formats.
+  // If a value isn't known, the field is omitted.
+  public String formfactor;          // "phone", "largetablet", "smalltablet", "desktop", "laptop", "tv".
+  public String os;                  // One of "Android", "Darwin", "WINNT", "Linux", "iOS", "Firefox OS".
+  public String application;         // Display name, E.g., "Firefox Beta"
+  public String appPackage;          // E.g., "org.mozilla.firefox_beta"
+  public String device;              // E.g., "HTC One"
+
   public ClientRecord(String guid, String collection, long lastModified, boolean deleted) {
     super(guid, collection, lastModified, deleted);
     this.ttl = CLIENTS_TTL;
   }
 
   public ClientRecord(String guid, String collection, long lastModified) {
     this(guid, collection, lastModified, false);
   }
@@ -80,32 +90,73 @@ public class ClientRecord extends Record
     }
 
     try {
       protocols = payload.getArray("protocols");
     } catch (NonArrayJSONException e) {
       Logger.debug(LOG_TAG, "Got non-array protocols in client record " + guid, e);
       protocols = null;
     }
+
+    if (payload.containsKey("formfactor")) {
+      this.formfactor = payload.getString("formfactor");
+    }
+
+    if (payload.containsKey("os")) {
+      this.os = payload.getString("os");
+    }
+
+    if (payload.containsKey("application")) {
+      this.application = payload.getString("application");
+    }
+
+    if (payload.containsKey("appPackage")) {
+      this.appPackage = payload.getString("appPackage");
+    }
+
+    if (payload.containsKey("device")) {
+      this.device = payload.getString("device");
+    }
   }
 
   @Override
   protected void populatePayload(ExtendedJSONObject payload) {
     putPayload(payload, "id",   this.guid);
     putPayload(payload, "name", this.name);
     putPayload(payload, "type", this.type);
     putPayload(payload, "version", this.version);
 
     if (this.commands != null) {
       payload.put("commands",  this.commands);
     }
 
     if (this.protocols != null) {
       payload.put("protocols",  this.protocols);
     }
+
+
+    if (this.formfactor != null) {
+      payload.put("formfactor", this.formfactor);
+    }
+
+    if (this.os != null) {
+      payload.put("os", this.os);
+    }
+
+    if (this.application != null) {
+      payload.put("application", this.application);
+    }
+
+    if (this.appPackage != null) {
+      payload.put("appPackage", this.appPackage);
+    }
+
+    if (this.device != null) {
+      payload.put("device", this.device);
+    }
   }
 
   @Override
   public boolean equals(Object o) {
     if (!(o instanceof ClientRecord) || !super.equals(o)) {
       return false;
     }
 
@@ -118,17 +169,17 @@ public class ClientRecord extends Record
   }
 
   @Override
   public boolean equalPayloads(Object o) {
     if (!(o instanceof ClientRecord) || !super.equalPayloads(o)) {
       return false;
     }
 
-    // Don't compare versions or protocols, no matter how much we might want to.
+    // Don't compare versions, protocols, or other optional fields, no matter how much we might want to.
     // They're not required by the spec.
     ClientRecord other = (ClientRecord) o;
     if (!RepoUtils.stringsEqual(other.name, this.name) ||
         !RepoUtils.stringsEqual(other.type, this.type)) {
       return false;
     }
     return true;
   }
@@ -139,16 +190,23 @@ public class ClientRecord extends Record
     out.androidID = androidID;
     out.sortIndex = this.sortIndex;
     out.ttl       = this.ttl;
 
     out.name = this.name;
     out.type = this.type;
     out.version = this.version;
     out.protocols = this.protocols;
+
+    out.formfactor = this.formfactor;
+    out.os = this.os;
+    out.application = this.application;
+    out.appPackage = this.appPackage;
+    out.device = this.device;
+
     return out;
   }
 
 /*
 Example record:
 
 {id:"relf31w7B4F1",
  name:"marina_mac",
--- a/mobile/android/base/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/base/sync/stage/SyncClientsEngineStage.java
@@ -8,16 +8,17 @@ import java.io.UnsupportedEncodingExcept
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.CommandProcessor;
 import org.mozilla.gecko.sync.CommandProcessor.Command;
 import org.mozilla.gecko.sync.CryptoRecord;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.HTTPFailureException;
 import org.mozilla.gecko.sync.NoCollectionKeysSetException;
@@ -33,17 +34,19 @@ import org.mozilla.gecko.sync.net.SyncSt
 import org.mozilla.gecko.sync.net.WBOCollectionRequestDelegate;
 import org.mozilla.gecko.sync.net.WBORequestDelegate;
 import org.mozilla.gecko.sync.repositories.NullCursorException;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
 import org.mozilla.gecko.sync.repositories.android.RepoUtils;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
 import org.mozilla.gecko.sync.repositories.domain.ClientRecordFactory;
 import org.mozilla.gecko.sync.repositories.domain.VersionConstants;
+import org.mozilla.gecko.util.HardwareUtils;
 
+import android.content.Context;
 import ch.boye.httpclientandroidlib.HttpStatus;
 
 public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
   private static final String LOG_TAG = "SyncClientsEngineStage";
 
   public static final String COLLECTION_NAME       = "clients";
   public static final String STAGE_NAME            = COLLECTION_NAME;
   public static final int CLIENTS_TTL_REFRESH      = 604800000;   // 7 days in milliseconds.
@@ -373,16 +376,23 @@ public class SyncClientsEngineStage exte
   protected ClientRecord newLocalClientRecord(ClientsDataDelegate delegate) {
     final String ourGUID = delegate.getAccountGUID();
     final String ourName = delegate.getClientName();
 
     ClientRecord r = new ClientRecord(ourGUID);
     r.name = ourName;
     r.version = getLocalClientVersion();
     r.protocols = getLocalClientProtocols();
+
+    r.os = "Android";
+    r.application = GlobalConstants.MOZ_APP_DISPLAYNAME;
+    r.appPackage = AppConstants.ANDROID_PACKAGE_NAME;
+    r.device = android.os.Build.MODEL;
+    r.formfactor = delegate.getFormFactor();
+
     return r;
   }
 
   // TODO: Bug 726055 - More considered handling of when to sync.
   protected boolean shouldDownload() {
     // Ask info/collections whether a download is needed.
     return true;
   }
--- a/mobile/android/base/toolbar/SiteIdentityPopup.java
+++ b/mobile/android/base/toolbar/SiteIdentityPopup.java
@@ -1,25 +1,27 @@
 /* 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.toolbar;
 
+import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.SiteIdentity.SecurityMode;
 import org.mozilla.gecko.SiteIdentity.MixedMode;
 import org.mozilla.gecko.SiteIdentity.TrackingMode;
+import org.mozilla.gecko.Tab;
+import org.mozilla.gecko.Tabs;
 import org.mozilla.gecko.widget.ArrowPopup;
 import org.mozilla.gecko.widget.DoorHanger;
 import org.mozilla.gecko.widget.DoorHanger.OnButtonClickListener;
-
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -37,16 +39,20 @@ public class SiteIdentityPopup extends A
         "https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
 
     private static final String TRACKING_CONTENT_SUPPORT_URL =
         "https://support.mozilla.org/kb/how-does-insecure-content-affect-safety-android";
 
     private SiteIdentity mSiteIdentity;
 
     private LinearLayout mIdentity;
+
+    private LinearLayout mIdentityKnownContainer;
+    private LinearLayout mIdentityUnknownContainer;
+
     private TextView mHost;
     private TextView mOwner;
     private TextView mVerifier;
 
     private DoorHanger mMixedContentNotification;
     private DoorHanger mTrackingContentNotification;
 
     private final OnButtonClickListener mButtonClickListener;
@@ -64,53 +70,78 @@ public class SiteIdentityPopup extends A
         // Make the popup focusable so it doesn't inadvertently trigger click events elsewhere
         // which may reshow the popup (see bug 785156)
         setFocusable(true);
 
         LayoutInflater inflater = LayoutInflater.from(mContext);
         mIdentity = (LinearLayout) inflater.inflate(R.layout.site_identity, null);
         mContent.addView(mIdentity);
 
-        mHost = (TextView) mIdentity.findViewById(R.id.host);
-        mOwner = (TextView) mIdentity.findViewById(R.id.owner);
-        mVerifier = (TextView) mIdentity.findViewById(R.id.verifier);
+        mIdentityKnownContainer =
+                (LinearLayout) mIdentity.findViewById(R.id.site_identity_known_container);
+        mIdentityUnknownContainer =
+                (LinearLayout) mIdentity.findViewById(R.id.site_identity_unknown_container);
+
+        mHost = (TextView) mIdentityKnownContainer.findViewById(R.id.host);
+        mOwner = (TextView) mIdentityKnownContainer.findViewById(R.id.owner);
+        mVerifier = (TextView) mIdentityKnownContainer.findViewById(R.id.verifier);
     }
 
-    private void updateIdentity() {
+    private void updateIdentity(final SiteIdentity siteIdentity) {
         if (!mInflated) {
             init();
         }
 
-        final MixedMode mixedMode = mSiteIdentity.getMixedMode();
-        final TrackingMode trackingMode = mSiteIdentity.getTrackingMode();
+        final MixedMode mixedMode = siteIdentity.getMixedMode();
+        final TrackingMode trackingMode = siteIdentity.getTrackingMode();
         if (mixedMode != MixedMode.UNKNOWN || trackingMode != TrackingMode.UNKNOWN) {
             // Hide the identity data if there isn't valid site identity data.
             // Set some top padding on the popup content to create a of light blue
             // between the popup arrow and the mixed content notification.
             mContent.setPadding(0, (int) mContext.getResources().getDimension(R.dimen.identity_padding_top), 0, 0);
             mIdentity.setVisibility(View.GONE);
-        } else {
-            mHost.setText(mSiteIdentity.getHost());
+            return;
+        }
+
+        mIdentity.setVisibility(View.VISIBLE);
+        mContent.setPadding(0, 0, 0, 0);
 
-            String owner = mSiteIdentity.getOwner();
+        final boolean isIdentityKnown = (siteIdentity.getSecurityMode() != SecurityMode.UNKNOWN);
+        toggleIdentityKnownContainerVisibility(isIdentityKnown);
+
+        if (isIdentityKnown) {
+            updateIdentityInformation(siteIdentity);
+        }
+    }
 
-            // Supplemental data is optional.
-            final String supplemental = mSiteIdentity.getSupplemental();
-            if (!TextUtils.isEmpty(supplemental)) {
-                owner += "\n" + supplemental;
-            }
-            mOwner.setText(owner);
+    private void toggleIdentityKnownContainerVisibility(final boolean isIdentityKnown) {
+        if (isIdentityKnown) {
+            mIdentityKnownContainer.setVisibility(View.VISIBLE);
+            mIdentityUnknownContainer.setVisibility(View.GONE);
+        } else {
+            mIdentityKnownContainer.setVisibility(View.GONE);
+            mIdentityUnknownContainer.setVisibility(View.VISIBLE);
+        }
+    }
+
+    private void updateIdentityInformation(final SiteIdentity siteIdentity) {
+        mHost.setText(siteIdentity.getHost());
 
-            final String verifier = mSiteIdentity.getVerifier();
-            final String encrypted = mSiteIdentity.getEncrypted();
-            mVerifier.setText(verifier + "\n" + encrypted);
+        String owner = siteIdentity.getOwner();
 
-            mContent.setPadding(0, 0, 0, 0);
-            mIdentity.setVisibility(View.VISIBLE);
+        // Supplemental data is optional.
+        final String supplemental = siteIdentity.getSupplemental();
+        if (!TextUtils.isEmpty(supplemental)) {
+            owner += "\n" + supplemental;
         }
+        mOwner.setText(owner);
+
+        final String verifier = siteIdentity.getVerifier();
+        final String encrypted = siteIdentity.getEncrypted();
+        mVerifier.setText(verifier + "\n" + encrypted);
     }
 
     private void addMixedContentNotification(boolean blocked) {
         // Remove any existing mixed content notification.
         removeMixedContentNotification();
         mMixedContentNotification = new DoorHanger(mContext, DoorHanger.Theme.DARK);
 
         int icon;
@@ -190,30 +221,32 @@ public class SiteIdentityPopup extends A
 
     @Override
     public void show() {
         if (mSiteIdentity == null) {
             Log.e(LOGTAG, "Can't show site identity popup for undefined state");
             return;
         }
 
-        final SecurityMode identityMode = mSiteIdentity.getSecurityMode();
-        final MixedMode mixedMode = mSiteIdentity.getMixedMode();
-        final TrackingMode trackingMode = mSiteIdentity.getTrackingMode();
-        if (identityMode == SecurityMode.UNKNOWN && mixedMode == MixedMode.UNKNOWN && trackingMode == TrackingMode.UNKNOWN) {
-            Log.e(LOGTAG, "Can't show site identity popup in a completely unknown state");
+        // about: has an unknown SiteIdentity in code, but showing "This
+        // site's identity is unknown" is misleading! So don't show a popup.
+        final Tab selectedTab = Tabs.getInstance().getSelectedTab();
+        if (selectedTab != null && AboutPages.isAboutPage(selectedTab.getURL())) {
+            Log.d(LOGTAG, "We don't show site identity popups for about: pages");
             return;
         }
 
-        updateIdentity();
+        updateIdentity(mSiteIdentity);
 
+        final MixedMode mixedMode = mSiteIdentity.getMixedMode();
         if (mixedMode != MixedMode.UNKNOWN) {
             addMixedContentNotification(mixedMode == MixedMode.MIXED_CONTENT_BLOCKED);
         }
 
+        final TrackingMode trackingMode = mSiteIdentity.getTrackingMode();
         if (trackingMode != TrackingMode.UNKNOWN) {
             addTrackingContentNotification(trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED);
         }
 
         showDividers();
 
         super.show();
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -473,46 +473,16 @@ var BrowserApp = {
 
     if (!ParentalControls.isAllowed(ParentalControls.INSTALL_EXTENSION)) {
       // Disable extension installs
       Services.prefs.setIntPref("extensions.enabledScopes", 1);
       Services.prefs.setIntPref("extensions.autoDisableScopes", 1);
       Services.prefs.setBoolPref("xpinstall.enabled", false);
     }
 
-    // Fix fallout from Bug 1091803.
-    if (Services.prefs.prefHasUserValue("intl.locale.os")) {
-      try {
-        let currentAcceptLang = Services.prefs.getCharPref("intl.accept_languages");
-
-        // The trailing comma is very important. This means we've set it to a
-        // real char pref, and it's something like "chrome://...,en-US,en".
-        if (currentAcceptLang.startsWith("chrome://global/locale/intl.properties,")) {
-          // If general.useragent.locale was set to a plain string, we ought to fix it, too.
-          try {
-            let currentUALocale = Services.prefs.getCharPref("general.useragent.locale");
-            if (currentUALocale.startsWith("chrome://")) {
-              // We're fine. This is what happens when you read a localized string as a char pref.
-            } else {
-              // Turn it into a localized string.
-              this.setLocalizedPref("general.useragent.locale", currentUALocale);
-            }
-          } catch (ee) {
-          }
-
-          // Now compute and save a valid Accept-Languages header from the clean strings.
-          let osLocale = this.getOSLocalePref();
-          let uaLocale = this.getUALocalePref();
-          this.computeAcceptLanguages(osLocale, uaLocale);
-        }
-      } catch (e) {
-        // Phew.
-      }
-    }
-
     try {
       // Set the tiles click observer only if tiles reporting is enabled (that
       // is, a report URL is set in prefs).
       gTilesReportURL = Services.prefs.getCharPref("browser.tiles.reportURL");
       Services.obs.addObserver(this, "Tiles:Click", false);
     } catch (e) {
       // Tiles reporting is disabled.
     }
--- a/mobile/android/tests/background/junit3/src/sync/TestClientsStage.java
+++ b/mobile/android/tests/background/junit3/src/sync/TestClientsStage.java
@@ -58,26 +58,35 @@ public class TestClientsStage extends An
       public synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
         if (db == null) {
           db = dataAccessor;
         }
         return db;
       }
     };
 
-    String guid = "clientabcdef";
+    final String guid = "clientabcdef";
     long lastModified = System.currentTimeMillis();
     ClientRecord record = new ClientRecord(guid, "clients", lastModified , false);
     record.name = "John's Phone";
     record.type = "mobile";
+    record.device = "Some Device";
+    record.os = "iOS";
     record.commands = new JSONArray();
 
     dataAccessor.store(record);
     assertEquals(1, dataAccessor.clientsCount());
 
+    final ClientRecord stored = dataAccessor.fetchAllClients().get(guid);
+    assertNotNull(stored);
+    assertEquals("John's Phone", stored.name);
+    assertEquals("mobile", stored.type);
+    assertEquals("Some Device", stored.device);
+    assertEquals("iOS", stored.os);
+
     stage.wipeLocal(session);
 
     try {
       assertEquals(0, dataAccessor.clientsCount());
       assertEquals(0L, session.config.getPersistedServerClientRecordTimestamp());
       assertEquals(0, session.getClientsDelegate().getClientsCount());
     } finally {
       dataAccessor.close();
--- a/mobile/android/tests/background/junit3/src/testhelpers/MockClientsDataDelegate.java
+++ b/mobile/android/tests/background/junit3/src/testhelpers/MockClientsDataDelegate.java
@@ -53,9 +53,14 @@ public class MockClientsDataDelegate imp
   public synchronized boolean isLocalGUID(String guid) {
     return getAccountGUID().equals(guid);
   }
 
   @Override
   public synchronized long getLastModifiedTimestamp() {
     return clientDataTimestamp;
   }
+
+  @Override
+  public String getFormFactor() {
+    return "phone";
+  }
 }
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -81,21 +81,20 @@ exports.makeInfallible = function makeIn
   return function (/* arguments */) {
     try {
       return aHandler.apply(this, arguments);
     } catch (ex) {
       let who = "Handler function";
       if (aName) {
         who += " " + aName;
       }
-      exports.reportException(who, ex);
+      return exports.reportException(who, ex);
     }
   }
 }
-
 /**
  * Interleaves two arrays element by element, returning the combined array, like
  * a zip. In the case of arrays with different sizes, undefined values will be
  * interleaved at the end along with the extra values of the larger array.
  *
  * @param Array a
  * @param Array b
  * @returns Array
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/actor-registry.js
@@ -0,0 +1,153 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const protocol = require("devtools/server/protocol");
+const { method, custom, Arg, Option, RetVal } = protocol;
+
+const { Cu, CC, components } = require("chrome");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+const Services = require("Services");
+const { DebuggerServer } = require("devtools/server/main");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+
+/**
+ * The ActorActor gives you a handle to an actor you've dynamically
+ * registered and allows you to unregister it.
+ */
+const ActorActor = protocol.ActorClass({
+  typeName: "actorActor",
+
+  initialize: function (conn, options) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+
+    this.options = options;
+  },
+
+  unregister: method(function () {
+    if (this.options.tab) {
+      DebuggerServer.removeTabActor(this.options);
+    }
+
+    if (this.options.global) {
+      DebuggerServer.removeGlobalActor(this.options);
+    }
+  }, {
+    request: {},
+    response: {}
+  })
+});
+
+const ActorActorFront = protocol.FrontClass(ActorActor, {
+  initialize: function (client, form) {
+    protocol.Front.prototype.initialize.call(this, client, form);
+  }
+});
+
+exports.ActorActorFront = ActorActorFront;
+
+/*
+ * The ActorRegistryActor allows clients to define new actors on the
+ * server. This is particularly useful for addons.
+ */
+const ActorRegistryActor = protocol.ActorClass({
+  typeName: "actorRegistry",
+
+  initialize: function (conn) {
+    protocol.Actor.prototype.initialize.call(this, conn);
+  },
+
+  registerActor: method(function (sourceText, fileName, options) {
+    const principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
+    const sandbox = Cu.Sandbox(principal);
+    const exports = sandbox.exports = {};
+    sandbox.require = require;
+
+    Cu.evalInSandbox(sourceText, sandbox, "1.8", fileName, 1);
+
+    let { prefix, constructor, type } = options;
+
+    if (type.global) {
+      DebuggerServer.addGlobalActor({
+        constructorName: constructor,
+        constructorFun: sandbox[constructor]
+      }, prefix);
+    }
+
+    if (type.tab) {
+      DebuggerServer.addTabActor({
+        constructorName: constructor,
+        constructorFun: sandbox[constructor]
+      }, prefix);
+    }
+
+    return ActorActor(this.conn, {
+      name: constructor,
+      tab: type.tab,
+      global: type.global
+    });
+  }, {
+    request: {
+      sourceText: Arg(0, "string"),
+      filename: Arg(1, "string"),
+      options: Arg(2, "json")
+    },
+
+    response: {
+      actorActor: RetVal("actorActor")
+    }
+  })
+});
+
+exports.ActorRegistryActor = ActorRegistryActor;
+
+function request(uri) {
+  return new Promise((resolve, reject) => {
+    try {
+      uri = Services.io.newURI(uri, null, null);
+    } catch (e) {
+      reject(e);
+    }
+
+    if (uri.scheme != "resource") {
+      reject(new Error(
+        "Can only register actors whose URI scheme is 'resource'."));
+    }
+
+    NetUtil.asyncFetch(uri, (stream, status, req) => {
+      if (!components.isSuccessCode(status)) {
+        reject(new Error("Request failed with status code = "
+                         + status
+                         + " after NetUtil.asyncFetch for url = "
+                         + uri));
+        return;
+      }
+
+      let source = NetUtil.readInputStreamToString(stream, stream.available());
+      stream.close();
+      resolve(source);
+    });
+  });
+}
+
+const ActorRegistryFront = protocol.FrontClass(ActorRegistryActor, {
+  initialize: function (client, form) {
+    protocol.Front.prototype.initialize.call(this, client,
+      { actor: form.actorRegistryActor });
+
+    this.manage(this);
+  },
+
+  registerActor: custom(function (uri, options) {
+    return request(uri, options)
+      .then(sourceText => {
+        return this._registerActor(sourceText, uri, options);
+      });
+  }, {
+    impl: "_registerActor"
+  })
+});
+exports.ActorRegistryFront = ActorRegistryFront;
--- a/toolkit/devtools/server/actors/common.js
+++ b/toolkit/devtools/server/actors/common.js
@@ -28,45 +28,54 @@
  *          the BrowserTabActor with which it will be associated.
  *          Only used for deprecated eagerly loaded actors.
  *
  */
 function RegisteredActorFactory(options, prefix) {
   // By default the actor name will also be used for the actorID prefix.
   this._prefix = prefix;
   if (typeof(options) != "function") {
-    // Lazy actor definition, where options contains all the information
-    // required to load the actor lazily.
-    this._getConstructor = function () {
-      // Load the module
-      let mod;
-      try {
-        mod = require(options.id);
-      } catch(e) {
-        throw new Error("Unable to load actor module '" + options.id + "'.\n" +
-                        e.message + "\n" + e.stack + "\n");
-      }
-      // Fetch the actor constructor
-      let c = mod[options.constructorName];
-      if (!c) {
-        throw new Error("Unable to find actor constructor named '" +
-                        options.constructorName + "'. (Is it exported?)");
-      }
-      return c;
-    };
+    // actors definition registered by actorRegistryActor
+    if (options.constructorFun) {
+      this._getConstructor = () => options.constructorFun;
+    } else {
+      // Lazy actor definition, where options contains all the information
+      // required to load the actor lazily.
+      this._getConstructor = function () {
+        // Load the module
+        let mod;
+        try {
+          mod = require(options.id);
+        } catch(e) {
+          throw new Error("Unable to load actor module '" + options.id + "'.\n" +
+                          e.message + "\n" + e.stack + "\n");
+        }
+        // Fetch the actor constructor
+        let c = mod[options.constructorName];
+        if (!c) {
+          throw new Error("Unable to find actor constructor named '" +
+                          options.constructorName + "'. (Is it exported?)");
+        }
+        return c;
+      };
+    }
+    // Exposes `name` attribute in order to allow removeXXXActor to match
+    // the actor by its actor constructor name.
+    this.name = options.constructorName;
   } else {
     // Old actor case, where options is a function that is the actor constructor.
     this._getConstructor = () => options;
     // Exposes `name` attribute in order to allow removeXXXActor to match
-    // the actor by its actor contructor name.
+    // the actor by its actor constructor name.
     this.name = options.name;
+
     // For old actors, we allow the use of a different prefix for actorID
     // than for listTabs actor names, by fetching a prefix on the actor prototype.
     // (Used by ChromeDebuggerActor)
-    if (options.prototype.actorPrefix) {
+    if (options.prototype && options.prototype.actorPrefix) {
       this._prefix = options.prototype.actorPrefix;
     }
   }
 }
 RegisteredActorFactory.prototype.createObservedActorFactory = function (conn, parentActor) {
   return new ObservedActorFactory(this._getConstructor, this._prefix, conn, parentActor);
 }
 exports.RegisteredActorFactory = RegisteredActorFactory;
@@ -74,19 +83,19 @@ exports.RegisteredActorFactory = Registe
 /**
  * Creates "observed" actors factory meant for creating real actor instances.
  * These factories lives in actor pools and fake various actor attributes.
  * They will be replaced in actor pools by final actor instances during
  * the first request for the same actorID from DebuggerServer._getOrCreateActor.
  *
  * ObservedActorFactory fakes the following actors attributes:
  *   actorPrefix (string) Used by ActorPool.addActor to compute the actor id
- *   actorID (string) Set by ActorPool.addActor just after being instanciated
+ *   actorID (string) Set by ActorPool.addActor just after being instantiated
  *   registeredPool (object) Set by ActorPool.addActor just after being
- *                           instanciated
+ *                           instantiated
  * And exposes the following method:
  *   createActor (function) Instantiate an actor that is going to replace
  *                          this factory in the actor pool.
  */
 function ObservedActorFactory(getConstructor, prefix, conn, parentActor) {
   this._getConstructor = getConstructor;
   this._conn = conn;
   this._parentActor = parentActor;
@@ -118,17 +127,17 @@ exports.ObservedActorFactory = ObservedA
  */
 
 /**
  * Populate |this._extraActors| as specified by |aFactories|, reusing whatever
  * actors are already there. Add all actors in the final extra actors table to
  * |aPool|.
  *
  * The root actor and the tab actor use this to instantiate actors that other
- * parts of the browser have specified with DebuggerServer.addTabActor antd
+ * parts of the browser have specified with DebuggerServer.addTabActor and
  * DebuggerServer.addGlobalActor.
  *
  * @param aFactories
  *     An object whose own property names are the names of properties to add to
  *     some reply packet (say, a tab actor grip or the "listTabs" response
  *     form), and whose own property values are actor constructor functions, as
  *     documented for addTabActor and addGlobalActor.
  *
@@ -153,21 +162,26 @@ exports.ObservedActorFactory = ObservedA
 exports.createExtraActors = function createExtraActors(aFactories, aPool) {
   // Walk over global actors added by extensions.
   for (let name in aFactories) {
     let actor = this._extraActors[name];
     if (!actor) {
       // Register another factory, but this time specific to this connection.
       // It creates a fake actor that looks like an regular actor in the pool,
       // but without actually instantiating the actor.
-      // It will only be instanciated on the first request made to the actor.
+      // It will only be instantiated on the first request made to the actor.
       actor = aFactories[name].createObservedActorFactory(this.conn, this);
       this._extraActors[name] = actor;
     }
-    aPool.addActor(actor);
+
+    // If the actor already exists in the pool, it may have been instantiated,
+    // so make sure not to overwrite it by a non-instantiated version.
+    if (!aPool.has(actor.actorID)) {
+      aPool.addActor(actor);
+    }
   }
 }
 
 /**
  * Append the extra actors in |this._extraActors|, constructed by a prior call
  * to CommonCreateExtraActors, to |aObject|.
  *
  * @param aObject
@@ -265,17 +279,23 @@ ActorPool.prototype = {
   /**
    * Run all actor cleanups.
    */
   cleanup: function AP_cleanup() {
     for each (let actor in this._cleanups) {
       actor.disconnect();
     }
     this._cleanups = {};
-  }
+  },
+
+  forEach: function(callback) {
+    for (let name in this._actors) {
+      callback(this._actors[name]);
+    }
+  },
 }
 
 exports.ActorPool = ActorPool;
 
 // TODO bug 863089: use Debugger.Script.prototype.getOffsetColumn when it is
 // implemented.
 exports.getOffsetColumn = function getOffsetColumn(aOffset, aScript) {
   let bestOffsetMapping = null;
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -275,24 +275,22 @@ RootActor.prototype = {
       for (let tabActor of tabActors) {
         if (tabActor.selected) {
           selected = tabActorList.length;
         }
         tabActor.parentID = this.actorID;
         newActorPool.addActor(tabActor);
         tabActorList.push(tabActor);
       }
-
       /* DebuggerServer.addGlobalActor support: create actors. */
       if (!this._globalActorPool) {
         this._globalActorPool = new ActorPool(this.conn);
-        this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
         this.conn.addActorPool(this._globalActorPool);
       }
-
+      this._createExtraActors(this._parameters.globalActorFactories, this._globalActorPool);
       /*
        * Drop the old actorID -> actor map. Actors that still mattered were
        * added to the new map; others will go away.
        */
       if (this._tabActorPool) {
         this.conn.removeActorPool(this._tabActorPool);
       }
       this._tabActorPool = newActorPool;
@@ -431,32 +429,53 @@ RootActor.prototype = {
     }
   },
 
   /**
    * Create or return the StyleSheetActor for a style sheet. This method
    * is here because the Style Editor and Inspector share style sheet actors.
    *
    * @param DOMStyleSheet styleSheet
-   *        The style sheet to creat an actor for.
+   *        The style sheet to create an actor for.
    * @return StyleSheetActor actor
    *         The actor for this style sheet.
    *
    */
   createStyleSheetActor: function(styleSheet) {
     if (this._styleSheetActors.has(styleSheet)) {
       return this._styleSheetActors.get(styleSheet);
     }
     let actor = new StyleSheetActor(styleSheet, this);
     this._styleSheetActors.set(styleSheet, actor);
 
     this._globalActorPool.addActor(actor);
 
     return actor;
-  }
+  },
+
+  /**
+   * Remove the extra actor (added by DebuggerServer.addGlobalActor or
+   * DebuggerServer.addTabActor) name |aName|.
+   */
+  removeActorByName: function(aName) {
+    if (aName in this._extraActors) {
+      const actor = this._extraActors[aName];
+      if (this._globalActorPool.has(actor)) {
+        this._globalActorPool.removeActor(actor);
+      }
+      if (this._tabActorPool) {
+        // Iterate over TabActor instances to also remove tab actors
+        // created during listTabs for each document.
+        this._tabActorPool.forEach(tab => {
+          tab.removeActorByName(aName);
+        });
+      }
+      delete this._extraActors[aName];
+    }
+   }
 };
 
 RootActor.prototype.requestTypes = {
   "listTabs": RootActor.prototype.onListTabs,
   "listAddons": RootActor.prototype.onListAddons,
   "listProcesses": RootActor.prototype.onListProcesses,
   "attachProcess": RootActor.prototype.onAttachProcess,
   "echo": RootActor.prototype.onEcho,
--- a/toolkit/devtools/server/actors/timeline.js
+++ b/toolkit/devtools/server/actors/timeline.js
@@ -153,51 +153,31 @@ let TimelineActor = exports.TimelineActo
     let endTime = this.docShells[0].now();
     let markers = [];
 
     for (let docShell of this.docShells) {
       markers = [...markers, ...docShell.popProfileTimelineMarkers()];
     }
 
     if (markers.length > 0) {
-      this._postProcessMarkers(markers);
       events.emit(this, "markers", markers, endTime);
     }
     if (this._memoryActor) {
       events.emit(this, "memory", endTime, this._memoryActor.measure());
     }
     if (this._framerateActor) {
       events.emit(this, "ticks", endTime, this._framerateActor.getPendingTicks());
     }
 
     this._dataPullTimeout = setTimeout(() => {
       this._pullTimelineData();
     }, DEFAULT_TIMELINE_DATA_PULL_TIMEOUT);
   },
 
   /**
-   * Some markers need post processing.
-   * We will eventually do that platform side: bug 1069661
-   */
-  _postProcessMarkers: function(m) {
-    m.forEach(m => {
-      // A marker named "ConsoleTime:foobar" needs
-      // to be renamed "ConsoleTime".
-      let split = m.name.match(/ConsoleTime:(.*)/);
-      if (split && split.length > 0) {
-        if (!m.detail) {
-          m.detail = {}
-        }
-        m.detail.causeName = split[1];
-        m.name = "ConsoleTime";
-      }
-    });
-  },
-
-  /**
    * Are we recording profile markers currently?
    */
   isRecording: method(function() {
     return this._isRecording;
   }, {
     request: {},
     response: {
       value: RetVal("boolean")
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -851,17 +851,17 @@ TabActor.prototype = {
     return false;
   },
 
   /* Support for DebuggerServer.addTabActor. */
   _createExtraActors: createExtraActors,
   _appendExtraActors: appendExtraActors,
 
   /**
-   * Does the actual work of attching to a tab.
+   * Does the actual work of attaching to a tab.
    */
   _attach: function BTA_attach() {
     if (this._attached) {
       return;
     }
 
     // Create a pool for tab-lifetime actors.
     dbg_assert(!this._tabPool, "Shouldn't have a tab pool if we weren't attached.");
@@ -1507,32 +1507,42 @@ TabActor.prototype = {
     return isNative;
   },
 
   /**
    * Create or return the StyleSheetActor for a style sheet. This method
    * is here because the Style Editor and Inspector share style sheet actors.
    *
    * @param DOMStyleSheet styleSheet
-   *        The style sheet to creat an actor for.
+   *        The style sheet to create an actor for.
    * @return StyleSheetActor actor
    *         The actor for this style sheet.
    *
    */
   createStyleSheetActor: function BTA_createStyleSheetActor(styleSheet) {
     if (this._styleSheetActors.has(styleSheet)) {
       return this._styleSheetActors.get(styleSheet);
     }
     let actor = new StyleSheetActor(styleSheet, this);
     this._styleSheetActors.set(styleSheet, actor);
 
     this._tabPool.addActor(actor);
 
     return actor;
-  }
+  },
+
+  removeActorByName: function BTA_removeActor(aName) {
+    if (aName in this._extraActors) {
+      const actor = this._extraActors[aName];
+      if (this._tabActorPool.has(actor)) {
+        this._tabActorPool.removeActor(actor);
+      }
+      delete this._extraActors[aName];
+    }
+  },
 };
 
 /**
  * The request types this actor can handle.
  */
 TabActor.prototype.requestTypes = {
   "attach": TabActor.prototype.onAttach,
   "detach": TabActor.prototype.onDetach,
@@ -1545,17 +1555,17 @@ TabActor.prototype.requestTypes = {
 
 exports.TabActor = TabActor;
 
 /**
  * Creates a tab actor for handling requests to a single in-process
  * <browser> tab. Most of the implementation comes from TabActor.
  *
  * @param aConnection DebuggerServerConnection
- *        The conection to the client.
+ *        The connection to the client.
  * @param aBrowser browser
  *        The browser instance that contains this tab.
  * @param aTabBrowser tabbrowser
  *        The tabbrowser that can receive nsIWebProgressListener events.
  */
 function BrowserTabActor(aConnection, aBrowser, aTabBrowser)
 {
   TabActor.call(this, aConnection, aBrowser);
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -782,17 +782,17 @@ WebConsoleActor.prototype =
     }
 
     return {
       from: this.actorID,
       input: input,
       result: resultGrip,
       timestamp: timestamp,
       exception: errorGrip,
-      exceptionMessage: errorMessage,
+      exceptionMessage: this._createStringGrip(errorMessage),
       helperResult: helperResult,
     };
   },
 
   /**
    * The Autocomplete request handler.
    *
    * @param object aRequest
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -319,17 +319,17 @@ var DebuggerServer = {
    *            request to the root actor returns actor IDs. IDs are in
    *            dictionaries, with actor names as keys and actor IDs as values.
    *            The actor name is the prefix to which the "Actor" string is
    *            appended. So for an actor with the `console` prefix, the actor
    *            name will be `consoleActor`.
    *        - constructor (string):
    *          the name of the exported symbol to be used as the actor
    *          constructor.
-   *        - type (a dictionnary of booleans with following attribute names):
+   *        - type (a dictionary of booleans with following attribute names):
    *          - "global"
    *            registers a global actor instance, if true.
    *            A global actor has the root actor as its parent.
    *          - "tab"
    *            registers a tab actor instance, if true.
    *            A new actor will be created for each tab and each app.
    */
   registerModule: function(id, options) {
@@ -342,17 +342,17 @@ var DebuggerServer = {
       let {prefix, constructor, type} = options;
       if (typeof(prefix) !== "string") {
         throw new Error("Lazy actor definition for '" + id + "' requires a string 'prefix' option.");
       }
       if (typeof(constructor) !== "string") {
         throw new Error("Lazy actor definition for '" + id + "' requires a string 'constructor' option.");
       }
       if (!("global" in type) && !("tab" in type)) {
-        throw new Error("Lazy actor definition for '" + id + "' requires a dictionnary 'type' option whose attributes can be 'global' or 'tab'.");
+        throw new Error("Lazy actor definition for '" + id + "' requires a dictionary 'type' option whose attributes can be 'global' or 'tab'.");
       }
       let name = prefix + "Actor";
       let mod = {
         id: id,
         prefix: prefix,
         constructorName: constructor,
         type: type,
         globalActor: type.global,
@@ -428,16 +428,21 @@ var DebuggerServer = {
       this.addTabActors();
       let { ChromeDebuggerActor } = require("devtools/server/actors/script");
       this.addGlobalActor(ChromeDebuggerActor, "chromeDebugger");
       this.registerModule("devtools/server/actors/preference", {
         prefix: "preference",
         constructor: "PreferenceActor",
         type: { global: true }
       });
+      this.registerModule("devtools/server/actors/actor-registry", {
+        prefix: "actorRegistry",
+        constructor: "ActorRegistryActor",
+        type: { global: true }
+      });
     }
 
     this.registerModule("devtools/server/actors/webapps", {
       prefix: "webapps",
       constructor: "WebappsActor",
       type: { global: true }
     });
     this.registerModule("devtools/server/actors/device", {
@@ -974,29 +979,34 @@ var DebuggerServer = {
       throw Error(name + " already exists");
     }
     DebuggerServer.tabActorFactories[name] = new RegisteredActorFactory(aActor, name);
   },
 
   /**
    * Unregisters the handler for the specified tab-scoped request type.
    * This may be used for example by add-ons when shutting down or upgrading.
+   * When unregistering an existing tab actor remove related tab factory
+   * as well as all existing instances of the actor.
    *
    * @param aActor function, object
    *      In case of function:
    *        The constructor function for this request type.
    *      In case of object:
    *        Same object being given to related addTabActor call.
    */
   removeTabActor: function DS_removeTabActor(aActor) {
     for (let name in DebuggerServer.tabActorFactories) {
       let handler = DebuggerServer.tabActorFactories[name];
       if ((handler.name && handler.name == aActor.name) ||
           (handler.id && handler.id == aActor.id)) {
         delete DebuggerServer.tabActorFactories[name];
+        for (let connID of Object.getOwnPropertyNames(this._connections)) {
+          this._connections[connID].rootActor.removeActorByName(name);
+        }
       }
     }
   },
 
   /**
    * Registers handlers for new browser-scoped request types defined
    * dynamically. This is used for example by add-ons to augment the
    * functionality of the root actor. Note that the name or actorPrefix of the
@@ -1028,29 +1038,34 @@ var DebuggerServer = {
       throw Error(name + " already exists");
     }
     DebuggerServer.globalActorFactories[name] = new RegisteredActorFactory(aActor, name);
   },
 
   /**
    * Unregisters the handler for the specified browser-scoped request type.
    * This may be used for example by add-ons when shutting down or upgrading.
+   * When unregistering an existing global actor remove related global factory
+   * as well as all existing instances of the actor.
    *
    * @param aActor function, object
    *      In case of function:
    *        The constructor function for this request type.
    *      In case of object:
    *        Same object being given to related addGlobalActor call.
    */
   removeGlobalActor: function DS_removeGlobalActor(aActor) {
     for (let name in DebuggerServer.globalActorFactories) {
       let handler = DebuggerServer.globalActorFactories[name];
       if ((handler.name && handler.name == aActor.name) ||
           (handler.id && handler.id == aActor.id)) {
         delete DebuggerServer.globalActorFactories[name];
+        for (let connID of Object.getOwnPropertyNames(this._connections)) {
+          this._connections[connID].rootActor.removeActorByName(name);
+        }
       }
     }
   }
 };
 
 EventEmitter.decorate(DebuggerServer);
 
 if (this.exports) {
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -28,16 +28,17 @@ EXTRA_JS_MODULES.devtools += [
 EXTRA_JS_MODULES.devtools.server += [
     'child.js',
     'content-globals.js',
     'main.js',
     'protocol.js',
 ]
 
 EXTRA_JS_MODULES.devtools.server.actors += [
+    'actors/actor-registry.js',
     'actors/call-watcher.js',
     'actors/canvas.js',
     'actors/child-process.js',
     'actors/childtab.js',
     'actors/common.js',
     'actors/csscoverage.js',
     'actors/device.js',
     'actors/eventlooplag.js',
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/hello-actor.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const protocol = require("devtools/server/protocol");
+
+const HelloActor = protocol.ActorClass({
+  typeName: "helloActor",
+
+  hello: protocol.method(function () {
+    return;
+  }, {
+    request: {},
+    response: {}
+  })
+});
--- a/toolkit/devtools/server/tests/unit/registertestactors-02.js
+++ b/toolkit/devtools/server/tests/unit/registertestactors-02.js
@@ -4,10 +4,12 @@
 function Actor() {}
 
 exports.register = function(handle) {
   handle.addGlobalActor(Actor, "registeredActor2");
   handle.addTabActor(Actor, "registeredActor2");
 }
 
 exports.unregister = function(handle) {
+  handle.removeTabActor(Actor);
+  handle.removeGlobalActor(Actor);
 }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_actor-registry-actor.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Check that you can register new actors via the ActorRegistrationActor.
+ */
+
+var gClient;
+var gRegistryFront;
+var gActorFront;
+var gOldPref;
+
+const { ActorRegistryFront } = devtools.require("devtools/server/actors/actor-registry");
+
+function run_test()
+{
+  gOldPref = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
+  Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", false);
+  initTestDebuggerServer();
+  DebuggerServer.addBrowserActors();
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect(getRegistry);
+  do_test_pending();
+}
+
+function getRegistry() {
+  gClient.listTabs((response) => {
+    gRegistryFront = ActorRegistryFront(gClient, response);
+    registerNewActor();
+  });
+}
+
+function registerNewActor() {
+  let options = {
+    prefix: "helloActor",
+    constructor: "HelloActor",
+    type: { global: true }
+  };
+
+  gRegistryFront
+    .registerActor("resource://test/hello-actor.js", options)
+    .then(actorFront => gActorFront = actorFront)
+    .then(talkToNewActor)
+    .then(null, e => {
+      DevToolsUtils.reportException("registerNewActor", e)
+      do_check_true(false);
+    });
+}
+
+function talkToNewActor() {
+  gClient.listTabs(({ helloActor }) => {
+    do_check_true(!!helloActor);
+    gClient.request({
+      to: helloActor,
+      type: "hello"
+    }, response => {
+      do_check_true(!response.error);
+      unregisterNewActor();
+    });
+  });
+}
+
+function unregisterNewActor() {
+  gActorFront
+    .unregister()
+    .then(testActorIsUnregistered)
+    .then(null, e => {
+      DevToolsUtils.reportException("registerNewActor", e)
+      do_check_true(false);
+    });
+}
+
+function testActorIsUnregistered() {
+  gClient.listTabs(({ helloActor }) => {
+    do_check_true(!helloActor);
+
+    Services.prefs.setBoolPref("devtools.debugger.forbid-certified-apps", gOldPref);
+    finishClient(gClient);
+  });
+}
--- a/toolkit/devtools/server/tests/unit/testactors.js
+++ b/toolkit/devtools/server/tests/unit/testactors.js
@@ -47,18 +47,21 @@ TestTabList.prototype = {
   constructor: TestTabList,
   getList: function () {
     return Promise.resolve([tabActor for (tabActor of this._tabActors)]);
   }
 };
 
 function createRootActor(aConnection)
 {
-  let root = new RootActor(aConnection,
-                           { tabList: new TestTabList(aConnection) });
+  let root = new RootActor(aConnection, {
+    tabList: new TestTabList(aConnection),
+    globalActorFactories: DebuggerServer.globalActorFactories,
+  });
+
   root.applicationType = "xpcshell-tests";
   return root;
 }
 
 function TestTabActor(aConnection, aGlobal)
 {
   this.conn = aConnection;
   this._global = aGlobal;
@@ -121,16 +124,24 @@ TestTabActor.prototype = {
   },
 
   onReload: function(aRequest) {
     this.threadActor.clearDebuggees();
     this.threadActor.dbg.addDebuggees();
     return {};
   },
 
+  removeActorByName: function(aName) {
+    const actor = this._extraActors[aName];
+    if (this._tabActorPool) {
+      this._tabActorPool.removeActor(actor);
+    }
+    delete this._extraActors[aName];
+  },
+
   /* Support for DebuggerServer.addTabActor. */
   _createExtraActors: createExtraActors,
   _appendExtraActors: appendExtraActors
 };
 
 TestTabActor.prototype.requestTypes = {
   "attach": TestTabActor.prototype.onAttach,
   "detach": TestTabActor.prototype.onDetach,
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -11,17 +11,19 @@ support-files =
   pre_init_global_actors.js
   pre_init_tab_actors.js
   registertestactors-01.js
   registertestactors-02.js
   registertestactors-03.js
   sourcemapped.js
   testactors.js
   tracerlocations.js
+  hello-actor.js
 
+[test_actor-registry-actor.js]
 [test_nesting-01.js]
 [test_nesting-02.js]
 [test_nesting-03.js]
 [test_forwardingprefix.js]
 [test_getyoungestframe.js]
 [test_nsjsinspector.js]
 [test_dbgactor.js]
 [test_dbgglobal.js]
--- a/toolkit/devtools/webconsole/test/test_throw.html
+++ b/toolkit/devtools/webconsole/test/test_throw.html
@@ -50,16 +50,19 @@ function onAttach(aState, aResponse)
       shortedString = longString.substring(0,
                         DebuggerServer.LONG_STRING_INITIAL_LENGTH
                       );
   tests.push(function() {
     aState.client.evaluateJS("throw '" + longString + "';", function(aResponse) {
       is(aResponse.exception.initial, shortedString,
         "exception.initial for throw longString"
       );
+      is(aResponse.exceptionMessage.initial, shortedString,
+        "exceptionMessage.initial for throw longString"
+      );
       nextTest();
     });
   });
 
   runTests(tests, endTest.bind(null, aState));
 }
 
 function endTest(aState)
--- a/toolkit/modules/PromiseUtils.jsm
+++ b/toolkit/modules/PromiseUtils.jsm
@@ -45,10 +45,52 @@ this.PromiseUtils = {
         try {
           rejection ? reject(rejection()) : reject(new Error("Promise Timeout"));
         } catch(ex) {
           reject(ex);
         }
         clearTimeout(id);
       }, delay);
     });
-  }
+  },
+
+  /*
+   * Creates a new pending Promise and provide methods to resolve and reject this Promise.
+   *
+   * @return {Deferred} an object consisting of a pending Promise "promise"
+   * and methods "resolve" and "reject" to change its state.
+   */
+  defer : function() {
+    return new Deferred();
+  },
+}
+
+/**
+ * The definition of Deferred object which is returned by PromiseUtils.defer(),
+ * It contains a Promise and methods to resolve/reject it.
+ */
+function Deferred() {
+  /* A method to resolve the associated Promise with the value passed.
+   * If the promise is already settled it does nothing.
+   *
+   * @param {anything} value : This value is used to resolve the promise
+   * If the value is a Promise then the associated promise assumes the state
+   * of Promise passed as value.
+   */
+  this.resolve = null;
+
+  /* A method to reject the assocaited Promise with the value passed.
+   * If the promise is already settled it does nothing.
+   *
+   * @param {anything} reason: The reason for the rejection of the Promise.
+   * Generally its an Error object. If however a Promise is passed, then the Promise
+   * itself will be the reason for rejection no matter the state of the Promise.
+   */
+  this.reject = null;
+
+  /* A newly created Pomise object.
+   * Initially in pending state.
+   */
+  this.promise = new Promise((resolve, reject) => {
+    this.resolve = resolve;
+    this.reject = reject;
+  });
 }
\ No newline at end of file
--- a/toolkit/modules/tests/xpcshell/test_PromiseUtils.js
+++ b/toolkit/modules/tests/xpcshell/test_PromiseUtils.js
@@ -6,16 +6,20 @@
 Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
 Components.utils.import("resource://gre/modules/Timer.jsm");
 
 // Tests for PromiseUtils.jsm
 function run_test() {
   run_next_test();
 }
 
+///////////////////////////////////////////////////////////////////////////////////////////
+// Tests for PromiseUtils.resolveOrTimeout()
+///////////////////////////////////////////////////////////////////////////////////////////
+
 /* Tests for the case when arguments to resolveOrTimeout
  * are not of correct type */
 add_task(function* test_wrong_arguments() {
   let p = new Promise((resolve, reject) => {});
   // for the first argument
   Assert.throws(() => PromiseUtils.resolveOrTimeout("string", 200), /first argument <promise> must be a Promise object/,
                 "TypeError thrown because first argument is not a Promsie object");
   // for second argument
@@ -70,9 +74,102 @@ add_task(function* test_rejection_functi
 
 /* Tests for the case when the passed promise doesn't settles
  * and rejection throws an error */
 add_task(function* test_rejection_throw_error() {
   let p = new Promise((resolve, reject) => {});
   yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
     throw new Error("Rejection threw an Error");
   }), /Rejection threw an Error/, "Rejection threw an error");
+});
+
+///////////////////////////////////////////////////////////////////////////////////////
+// Tests for PromiseUtils.defer()
+///////////////////////////////////////////////////////////////////////////////////////
+
+/* Tests for checking the resolve method of the Deferred object
+ * returned by PromiseUtils.defer() */
+add_task(function* test_resolve_string() {
+  let def = PromiseUtils.defer();
+  let expected = "The promise is resolved " + Math.random();
+  def.resolve(expected);
+  let result = yield def.promise;
+  Assert.equal(result, expected, "def.resolve() resolves the promise");
+});
+
+/* Test for the case when undefined is passed to the resolve method
+ * of the Deferred object */
+add_task(function* test_resolve_undefined() {
+  let def = PromiseUtils.defer();
+  def.resolve();
+  let result = yield def.promise;
+  Assert.equal(result, undefined, "resolve works with undefined as well");
+});
+
+/* Test when a pending promise is passed to the resolve method
+ * of the Deferred object */
+add_task(function* test_resolve_pending_promise() {
+  let def = PromiseUtils.defer();
+  let expected = 100 + Math.random();
+  let p = new Promise((resolve, reject) => {
+    setTimeout(() => resolve(expected), 100);
+  });
+  def.resolve(p);
+  let result = yield def.promise;
+  Assert.equal(result, expected, "def.promise assumed the state of the passed promise");
+});
+
+/* Test when a resovled promise is passed
+ * to the resolve method of the Deferred object */
+add_task(function* test_resolve_resolved_promise() {
+  let def = PromiseUtils.defer();
+  let expected = "Yeah resolved" + Math.random();
+  let p = new Promise((resolve, reject) => resolve(expected));
+  def.resolve(p);
+  let result = yield def.promise;
+  Assert.equal(result, expected, "Resolved promise is passed to the resolve method");
+});
+
+/* Test for the case when a rejected promise is
+ * passed to the resolve method */
+add_task(function* test_resolve_rejected_promise() {
+  let def = PromiseUtils.defer();
+  let p = new Promise((resolve, reject) => reject(new Error("There its an rejection")));
+  def.resolve(p);
+  yield Assert.rejects(def.promise, /There its an rejection/, "Settled rejection promise passed to the resolve method");
+});
+
+/* Test for the checking the reject method of
+ * the Deferred object returned by PromiseUtils.defer() */
+add_task(function* test_reject_Error() {
+  let def = PromiseUtils.defer();
+  def.reject(new Error("This one rejects"));
+  yield Assert.rejects(def.promise, /This one rejects/, "reject method with Error for rejection");
+});
+
+/* Test for the case when a pending Promise is passed to
+ * the reject method of Deferred object */
+add_task(function* test_reject_pending_promise() {
+  let def = PromiseUtils.defer();
+  let p = new Promise((resolve, reject) => {
+    setTimeout(() => resolve(100), 100);
+  });
+  def.reject(p);
+  yield Assert.rejects(def.promise, Promise, "Rejection with a pending promise uses the passed promise itself as the reason of rejection");
+});
+
+/* Test for the case when a resolved Promise
+ * is passed to the reject method */
+add_task(function* test_reject_resolved_promise() {
+  let def = PromiseUtils.defer();
+  let p = new Promise((resolve, reject) => resolve("This resolved"));
+  def.reject(p);
+  yield Assert.rejects(def.promise, Promise, "Rejection with a resolved promise uses the passed promise itself as the reason of rejection");
+});
+
+/* Test for the case when a rejected Promise is
+ * passed to the reject method */
+add_task(function* test_reject_resolved_promise() {
+  let def = PromiseUtils.defer();
+  let p = new Promise((resolve, reject) => reject(new Error("This on rejects")));
+  def.reject(p);
+  yield Assert.rejects(def.promise, Promise, "Rejection with a rejected promise uses the passed promise itself as the reason of rejection");
 });
\ No newline at end of file
--- a/toolkit/mozapps/installer/windows/nsis/common.nsh
+++ b/toolkit/mozapps/installer/windows/nsis/common.nsh
@@ -4327,148 +4327,200 @@
 !macro OnInstallUninstallCall
   !verbose push
   !verbose ${_MOZFUNC_VERBOSE}
   Call OnInstallUninstall
   !verbose pop
 !macroend
 
 /**
- * Parses the uninstall.log for the stub installer on install to first remove a
- * previous installation's files prior to installing.
- *
- * When modifying this macro be aware that LineFind uses all registers except
- * $R0-$R3 so be cautious. Callers of this macro are not affected.
+ * Parses the precomplete file to remove an installation's files and directories.
  *
  * @param   _PROGRESSBAR
  *          The progress bar to update using PBM_STEPIT. Can also be "false" if
  *          updating a progressbar isn't needed.
  * @param   _INSTALL_STEP_COUNTER
  *          The install step counter to increment. The variable specified in
  *          this parameter is also updated. Can also be "false" if a counter
  *          isn't needed.
- *
- * $R2 = _INSTALL_STEP_COUNTER
- * $R3 = _PROGRESSBAR
+ * $R2 = false if all files were deleted or moved to the tobedeleted directory.
+ *       true if file(s) could not be moved to the tobedeleted directory.
+ * $R3 = Path to temporary precomplete file.
+ * $R4 = File handle for the temporary precomplete file.
+ * $R5 = String returned from FileRead.
+ * $R6 = First seven characters of the string returned from FileRead.
+ * $R7 = Temporary file path used to rename files that are in use.
+ * $R8 = _PROGRESSBAR
+ * $R9 = _INSTALL_STEP_COUNTER
  */
-!macro OnStubInstallUninstall
-
-  !ifndef OnStubInstallUninstall
-    !insertmacro GetParent
-    !insertmacro LineFind
-    !insertmacro TrimNewLines
+!macro RemovePrecompleteEntries
+
+  !ifndef ${_MOZFUNC_UN}RemovePrecompleteEntries
+    !define _MOZFUNC_UN_TMP ${_MOZFUNC_UN}
+    !insertmacro ${_MOZFUNC_UN_TMP}GetParent
+    !insertmacro ${_MOZFUNC_UN_TMP}TrimNewLines
+    !insertmacro ${_MOZFUNC_UN_TMP}WordReplace
+    !undef _MOZFUNC_UN
+    !define _MOZFUNC_UN ${_MOZFUNC_UN_TMP}
+    !undef _MOZFUNC_UN_TMP
 
     !verbose push
     !verbose ${_MOZFUNC_VERBOSE}
-    !define OnStubInstallUninstall "!insertmacro OnStubInstallUninstallCall"
-
-    Function OnStubInstallUninstall
-      Exch $R2
+    !define ${_MOZFUNC_UN}RemovePrecompleteEntries "!insertmacro ${_MOZFUNC_UN}RemovePrecompleteEntriesCall"
+
+    Function ${_MOZFUNC_UN}RemovePrecompleteEntries
+      Exch $R9
       Exch 1
-      Exch $R3
-      Push $R9
-      Push $R8
+      Exch $R8
       Push $R7
       Push $R6
       Push $R5
       Push $R4
-      Push $R1
-      Push $R0
-      Push $TmpVal
-
-      IfFileExists "$INSTDIR\uninstall\uninstall.log" +1 end
-
-      ; Copy the uninstall log file to a temporary file
-      GetTempFileName $TmpVal
-      CopyFiles /SILENT /FILESONLY "$INSTDIR\uninstall\uninstall.log" "$TmpVal"
-
-      CreateDirectory "$INSTDIR\${TO_BE_DELETED}"
-
-      ; Delete files
-      ${LineFind} "$TmpVal" "/NUL" "1:-1" "StubRemoveFilesCallback"
-
-      ; Delete the temporary uninstall log file
-      Delete /REBOOTOK "$TmpVal"
-
-      RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
-
-      end:
+      Push $R3
+      Push $R2
+
+      ${If} ${FileExists} "$INSTDIR\precomplete"
+        StrCpy $R2 "false"
+
+        RmDir /r "$INSTDIR\${TO_BE_DELETED}"
+        CreateDirectory "$INSTDIR\${TO_BE_DELETED}"
+        GetTempFileName $R3 "$INSTDIR\${TO_BE_DELETED}"
+        Delete "$R3"
+        Rename "$INSTDIR\precomplete" "$R3"
+
+        ClearErrors
+        ; Rename and then remove files
+        FileOpen $R4 "$R3" r
+        ${Do}
+          FileRead $R4 $R5
+          ${If} ${Errors}
+            ${Break}
+          ${EndIf}
+
+          ${${_MOZFUNC_UN}TrimNewLines} "$R5" $R5
+          ; Replace all occurrences of "/" with "\".
+          ${${_MOZFUNC_UN}WordReplace} "$R5" "/" "\" "+" $R5
+
+          ; Copy the first 7 chars
+          StrCpy $R6 "$R5" 7
+          ${If} "$R6" == "remove "
+            ; Copy the string starting after the 8th char
+            StrCpy $R5 "$R5" "" 8
+            ; Copy all but the last char to remove the double quote.
+            StrCpy $R5 "$R5" -1
+            ${If} ${FileExists} "$INSTDIR\$R5"
+              ${Unless} "$R9" == "false"
+                IntOp $R9 $R9 + 2
+              ${EndUnless}
+              ${Unless} "$R8" == "false"
+                SendMessage $R8 ${PBM_STEPIT} 0 0
+                SendMessage $R8 ${PBM_STEPIT} 0 0
+              ${EndUnless}
+
+              ClearErrors
+              Delete "$INSTDIR\$R5"
+              ${If} ${Errors}
+                GetTempFileName $R7 "$INSTDIR\${TO_BE_DELETED}"
+                Delete "$R7"
+                ClearErrors
+                Rename "$INSTDIR\$R5" "$R7"
+                ${Unless} ${Errors}
+                  Delete /REBOOTOK "$R7"
+
+                  ClearErrors
+                ${EndUnless}
+!ifdef __UNINSTALL__
+                ${If} ${Errors}
+                  Delete /REBOOTOK "$INSTDIR\$R5"
+                  StrCpy $R2 "true"
+                  ClearErrors
+                ${EndIf}
+!endif
+              ${EndIf}
+            ${EndIf}
+          ${ElseIf} "$R6" == "rmdir $\""
+            ; Copy the string starting after the 7th char.
+            StrCpy $R5 "$R5" "" 7
+            ; Copy all but the last two chars to remove the slash and the double quote.
+            StrCpy $R5 "$R5" -2
+            ${If} ${FileExists} "$INSTDIR\$R5"
+              ; Ignore directory removal errors
+              RmDir "$INSTDIR\$R5"
+              ClearErrors
+            ${EndIf}
+          ${EndIf}
+        ${Loop}
+        FileClose $R4
+
+        ; Delete the temporary precomplete file
+        Delete /REBOOTOK "$R3"
+
+        RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
+
+        ${If} ${RebootFlag}
+        ${AndIf} "$R2" == "false"
+          ; Clear the reboot flag if all files were deleted or moved to the
+          ; tobedeleted directory.
+          SetRebootFlag false
+        ${EndIf}
+      ${EndIf}
+
       ClearErrors
 
-      Pop $TmpVal
-      Pop $R0
-      Pop $R1
+      Pop $R2
+      Pop $R3
       Pop $R4
       Pop $R5
       Pop $R6
       Pop $R7
-      Pop $R8
-      Pop $R9
-      Exch $R3
+      Exch $R8
       Exch 1
-      Exch $R2
-    FunctionEnd
-
-    Function StubRemoveFilesCallback
-      ${TrimNewLines} "$R9" $R9
-      StrCpy $R1 "$R9" 5       ; Copy the first five chars
-
-      StrCmp "$R1" "File:" +1 end
-      StrCpy $R9 "$R9" "" 6    ; Copy string starting after the 6th char
-      StrCpy $R0 "$R9" 1       ; Copy the first char
-
-      StrCmp "$R0" "\" +1 end  ; If this isn't a relative path goto end
-      StrCmp "$R9" "\MapiProxy_InUse.dll" end +1 ; Skip the MapiProxy_InUse.dll
-      StrCmp "$R9" "\mozMapi32_InUse.dll" end +1 ; Skip the mozMapi32_InUse.dll
-
-      StrCpy $R1 "$INSTDIR$R9" ; Copy the install dir path and suffix it with the string
-      IfFileExists "$R1" +1 end
-
-      ${Unless} "$R2" == "false"
-        IntOp $R2 $R2 + 2
-      ${EndIf}
-      ${Unless} "$R3" == "false"
-        SendMessage $R3 ${PBM_STEPIT} 0 0
-        SendMessage $R3 ${PBM_STEPIT} 0 0
-      ${EndIf}
-
-      ClearErrors
-      Delete "$R1"
-      ${Unless} ${Errors}
-        Goto end
-      ${EndUnless}
-
-      GetTempFileName $R0 "$INSTDIR\${TO_BE_DELETED}"
-      Delete "$R0"
-      ClearErrors
-      Rename "$R1" "$R0"
-      ${If} ${Errors}
-        Delete /REBOOTOK "$R1"
-      ${EndUnless}
-
-      end:
-      ClearErrors
-
-      Push 0
+      Exch $R9
     FunctionEnd
 
     !verbose pop
   !endif
 !macroend
 
-!macro OnStubInstallUninstallCall _PROGRESSBAR _INSTALL_STEP_COUNTER
+!macro RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER
   !verbose push
   Push "${_PROGRESSBAR}"
   Push "${_INSTALL_STEP_COUNTER}"
   !verbose ${_MOZFUNC_VERBOSE}
-  Call OnStubInstallUninstall
+  Call RemovePrecompleteEntries
   Pop ${_INSTALL_STEP_COUNTER}
   !verbose pop
 !macroend
 
+!macro un.RemovePrecompleteEntriesCall _PROGRESSBAR _INSTALL_STEP_COUNTER
+  !verbose push
+  !verbose ${_MOZFUNC_VERBOSE}
+  Push "${_PROGRESSBAR}"
+  Push "${_INSTALL_STEP_COUNTER}"
+  Call un.RemovePrecompleteEntries
+  Pop ${_INSTALL_STEP_COUNTER}
+  Pop $0
+  !verbose pop
+!macroend
+
+!macro un.RemovePrecompleteEntries
+  !ifndef un.RemovePrecompleteEntries
+    !verbose push
+    !verbose ${_MOZFUNC_VERBOSE}
+    !undef _MOZFUNC_UN
+    !define _MOZFUNC_UN "un."
+
+    !insertmacro RemovePrecompleteEntries
+
+    !undef _MOZFUNC_UN
+    !define _MOZFUNC_UN
+    !verbose pop
+  !endif
+!macroend
+
 /**
  * Parses the uninstall.log to unregister dll's, remove files, and remove
  * empty directories for this installation.
  *
  * When modifying this macro be aware that LineFind uses all registers except
  * $R0-$R3 so be cautious. Callers of this macro are not affected.
  */
 !macro un.ParseUninstallLog
@@ -5670,18 +5722,16 @@
       ; Remove files not removed by parsing the uninstall.log
       Delete "$INSTDIR\install_wizard.log"
       Delete "$INSTDIR\install_status.log"
 
       RmDir /r "$INSTDIR\updates"
       Delete "$INSTDIR\updates.xml"
       Delete "$INSTDIR\active-update.xml"
 
-      RmDir /r "$INSTDIR\distribution"
-
       ; Remove files from the uninstall directory.
       ${If} ${FileExists} "$INSTDIR\uninstall"
         Delete "$INSTDIR\uninstall\*wizard*"
         Delete "$INSTDIR\uninstall\uninstall.ini"
         Delete "$INSTDIR\uninstall\cleanup.log"
         Delete "$INSTDIR\uninstall\uninstall.update"
         ${OnInstallUninstall}
       ${EndIf}
@@ -6056,18 +6106,20 @@
       ${ElseIf} ${IsWin2003}
         ${LogMsg} "OS Name    : Windows 2003"
       ${ElseIf} ${IsWinVista}
         ${LogMsg} "OS Name    : Windows Vista"
       ${ElseIf} ${IsWin7}
         ${LogMsg} "OS Name    : Windows 7"
       ${ElseIf} ${IsWin8}
         ${LogMsg} "OS Name    : Windows 8"
-      ${ElseIf} ${AtLeastWin8}
-        ${LogMsg} "OS Name    : Above Windows 8"
+      ${ElseIf} ${IsWin8.1}
+        ${LogMsg} "OS Name    : Windows 8.1"
+      ${ElseIf} ${AtLeastWin8.1}
+        ${LogMsg} "OS Name    : Above Windows 8.1"
       ${Else}
         ${LogMsg} "OS Name    : Unable to detect"
       ${EndIf}
 
       !ifdef HAVE_64BIT_BUILD
         ${LogMsg} "Target CPU : x64"
       !else
         ${LogMsg} "Target CPU : x86"
@@ -7712,18 +7764,18 @@
 
 /**
  * Gets the elapsed time in seconds between two values in milliseconds stored as
  * an int64. The caller will typically get the millisecond values using
  * GetTickCount with a long return value as follows.
  * System::Call "kernel32::GetTickCount()l .s"
  * Pop $varname
  *
- * _START_TICK_COUNT    
- * _FINISH_TICK_COUNT   
+ * _START_TICK_COUNT
+ * _FINISH_TICK_COUNT
  * _RES_ELAPSED_SECONDS return value - elapsed time between _START_TICK_COUNT
  *                      and _FINISH_TICK_COUNT in seconds.
  */
 !macro GetSecondsElapsedCall _START_TICK_COUNT _FINISH_TICK_COUNT _RES_ELAPSED_SECONDS
   Push "${_START_TICK_COUNT}"
   Push "${_FINISH_TICK_COUNT}"
   ${CallArtificialFunction} GetSecondsElapsed_
   Pop ${_RES_ELAPSED_SECONDS}
--- a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -10,26 +10,21 @@ const BIN_SUFFIX = "@BIN_SUFFIX@";
 #ifdef MOZ_APP_VENDOR
 const MOZ_APP_VENDOR = "@MOZ_APP_VENDOR@";
 #else
 const MOZ_APP_VENDOR = "";
 #endif
 
 // MOZ_APP_BASENAME is not optional for tests.
 const MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
+const APP_BIN_SUFFIX = "@BIN_SUFFIX@";
 
 const APP_INFO_NAME = "XPCShell";
 const APP_INFO_VENDOR = "Mozilla";
 
-#ifdef XP_UNIX
-const APP_BIN_SUFFIX = "-bin";
-#else
-const APP_BIN_SUFFIX = "@BIN_SUFFIX@";
-#endif
-
 #ifdef XP_WIN
 const IS_WIN = true;
 #else
 const IS_WIN = false;
 #endif
 
 #ifdef XP_MACOSX
 const IS_MACOSX = true;