Merge backout
authorMats Palmgren <matspal@gmail.com>
Sun, 20 Apr 2014 19:35:54 +0000
changeset 179364 ea214ef03c3fc40f630fb16f333d05502692a6d5
parent 179363 581208970e99444a504e64f3252095779afa11fc (current diff)
parent 179362 53a6c96cea62c46160ce9ea80e0d9188fe5ed2df (diff)
child 179366 28dc0100f9b03b65d68679c528391397b5ffedb8
push id26619
push usermpalmgren@mozilla.com
push dateSun, 20 Apr 2014 19:37:25 +0000
treeherdermozilla-central@ea214ef03c3f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.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 backout
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="55bcc2d7e44dc805c24b57d1e783fc26e8a2ee86"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="99a67a75855d8ca077018c819aedd90bf0447d9b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="52c909ccead537f8f9dbf634f3e6639078a8b0bd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <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="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="55bcc2d7e44dc805c24b57d1e783fc26e8a2ee86"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="99a67a75855d8ca077018c819aedd90bf0447d9b"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "38d1561fe26c12f371c44a47c498722ce06518c2", 
+    "revision": "27357877d17b1547c81a26b66e97a27ed7b954ca", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- 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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <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/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <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"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--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="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <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="7e93b6de9633a162b735252e8182974456f741f4"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="eb97461e75cd44d20967bc410b5653dff031ef5a"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -666,16 +666,19 @@ pref("plugins.hideMissingPluginsNotifica
 #ifdef RELEASE_BUILD
 // For now, plugins other than Java and Flash are enabled in beta/release
 // and click-to-activate in earlier channels.
 pref("plugin.default.state", 2);
 #else
 pref("plugin.default.state", 1);
 #endif
 
+// Plugins bundled in XPIs are enabled by default.
+pref("plugin.defaultXpi.state", 2);
+
 // Flash is enabled by default, and Java is click-to-activate by default on
 // all channels.
 pref("plugin.state.flash", 2);
 pref("plugin.state.java", 1);
 
 // display door hanger if flash not installed
 pref("plugins.notifyMissingFlash", true);
 
--- a/browser/base/content/newtab/grid.js
+++ b/browser/base/content/newtab/grid.js
@@ -192,17 +192,17 @@ let gGrid = {
     if (this._cellMargin === undefined) {
       let refCell = document.querySelector(".newtab-cell");
       this._cellMargin = parseFloat(getComputedStyle(refCell).marginTop) * 2;
       this._cellHeight = refCell.offsetHeight + this._cellMargin;
       this._cellWidth = refCell.offsetWidth + this._cellMargin;
     }
 
     let availSpace = document.documentElement.clientHeight - this._cellMargin -
-                     document.querySelector("#newtab-margin-top").offsetHeight;
+                     document.querySelector("#newtab-margin-undo-container").offsetHeight;
     let visibleRows = Math.floor(availSpace / this._cellHeight);
     this._node.style.height = this._computeHeight() + "px";
     this._node.style.maxHeight = this._computeHeight(visibleRows) + "px";
     this._node.style.maxWidth = gGridPrefs.gridColumns * this._cellWidth +
                                 GRID_WIDTH_EXTRA + "px";
   },
 
   _shouldRenderGrid : function Grid_shouldRenderGrid() {
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -46,26 +46,31 @@ input[type=button] {
 /* MARGINS */
 #newtab-vertical-margin {
   display: -moz-box;
   position: relative;
   -moz-box-flex: 1;
   -moz-box-orient: vertical;
 }
 
-#newtab-margin-top {
+#newtab-margin-undo-container {
   display: -moz-box;
-  height: 40px;
-  -moz-box-align: center;
   -moz-box-pack: center;
 }
 
 #newtab-horizontal-margin {
   display: -moz-box;
-  -moz-box-flex: 5;
+  -moz-box-flex: 1;
+}
+
+#newtab-margin-top,
+#newtab-margin-bottom {
+  display: -moz-box;
+  -moz-box-flex: 1;
+  position: relative;
 }
 
 .newtab-side-margin {
   min-width: 16px;
   -moz-box-flex: 1;
 }
 
 /* GRID */
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -22,43 +22,45 @@
     <xul:label class="text-link"
                href="https://support.mozilla.org/kb/how-do-sponsored-tiles-work"
                value="&newtab.panel.link.text;" />
   </xul:panel>
 
   <div id="newtab-scrollbox">
 
     <div id="newtab-vertical-margin">
-      <div id="newtab-margin-top">
+
+      <div id="newtab-margin-top"/>
+
+      <div id="newtab-margin-undo-container">
         <div id="newtab-undo-container" undo-disabled="true">
           <xul:label id="newtab-undo-label"
                      value="&newtab.undo.removedLabel;" />
           <xul:button id="newtab-undo-button" tabindex="-1"
                       label="&newtab.undo.undoButton;"
                       class="newtab-undo-button" />
           <xul:button id="newtab-undo-restore-button" tabindex="-1"
                       label="&newtab.undo.restoreButton;"
                       class="newtab-undo-button" />
           <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
                              class="close-icon tabbable"
                              tooltiptext="&newtab.undo.closeTooltip;" />
         </div>
       </div>
 
-      <xul:spacer flex="1"/>
-
       <div id="newtab-horizontal-margin">
         <div class="newtab-side-margin"/>
 
         <div id="newtab-grid">
         </div>
 
         <div class="newtab-side-margin"/>
       </div>
 
-      <xul:spacer flex="1"/>
+      <div id="newtab-margin-bottom"/>
+
     </div>
     <input id="newtab-toggle" type="button"/>
   </div>
 
   <xul:script type="text/javascript;version=1.8"
               src="chrome://browser/content/newtab/newTab.js"/>
 </xul:window>
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -778,17 +778,17 @@ InplaceEditor.prototype = {
   },
 
   /**
    * Handle loss of focus by calling done if it hasn't been called yet.
    */
   _onBlur: function InplaceEditor_onBlur(aEvent, aDoNotClear)
   {
     if (aEvent && this.popup && this.popup.isOpen &&
-        this.contentType == CONTENT_TYPES.CSS_MIXED) {
+        this.popup.selectedIndex >= 0) {
       let label, preLabel;
       if (this._selectedIndex === undefined) {
         ({label, preLabel}) = this.popup.getItemAtIndex(this.popup.selectedIndex);
       }
       else {
         ({label, preLabel}) = this.popup.getItemAtIndex(this._selectedIndex);
       }
       let input = this.input;
@@ -814,16 +814,21 @@ InplaceEditor.prototype = {
         this.popup._panel.removeEventListener("popuphidden", onPopupHidden);
         this.doc.defaultView.setTimeout(()=> {
           input.focus();
           this.emit("after-suggest");
         }, 0);
       };
       this.popup._panel.addEventListener("popuphidden", onPopupHidden);
       this.popup.hidePopup();
+      // Content type other than CSS_MIXED is used in rule-view where the values
+      // are live previewed. So we apply the value before returning.
+      if (this.contentType != CONTENT_TYPES.CSS_MIXED) {
+        this._apply();
+      }
       return;
     }
     this._apply();
     if (!aDoNotClear) {
       this._clear();
     }
   },
 
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1993,17 +1993,18 @@ function TextPropertyEditor(aRuleEditor,
 
 TextPropertyEditor.prototype = {
   /**
    * Boolean indicating if the name or value is being currently edited.
    */
   get editing() {
     return !!(this.nameSpan.inplaceEditor || this.valueSpan.inplaceEditor ||
       this.ruleEditor.ruleView.colorPicker.tooltip.isShown() ||
-      this.ruleEditor.ruleView.colorPicker.eyedropperOpen);
+      this.ruleEditor.ruleView.colorPicker.eyedropperOpen) ||
+      this.popup.isOpen;
   },
 
   /**
    * Create the property editor's DOM.
    */
   _create: function() {
     this.element = this.doc.createElementNS(HTML_NS, "li");
     this.element.classList.add("ruleview-property");
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -338,16 +338,17 @@ AlreadyShutdownError.prototype.construct
 /**
  * Manages the experiments and provides an interface to control them.
  */
 
 Experiments.Experiments = function (policy=new Experiments.Policy()) {
   this._log = Log.repository.getLoggerWithMessagePrefix(
     "Browser.Experiments.Experiments",
     "Experiments #" + gExperimentsCounter++ + "::");
+  this._log.trace("constructor");
 
   this._policy = policy;
 
   // This is a Map of (string -> ExperimentEntry), keyed with the experiment id.
   // It holds both the current experiments and history.
   // Map() preserves insertion order, which means we preserve the manifest order.
   // This is null until we've successfully completed loading the cache from
   // disk the first time.
@@ -419,35 +420,39 @@ Experiments.Experiments.prototype = {
    * times before the previous uninit() has completed or if it is called while
    * an init() operation is being performed, the object may get in bad state
    * and/or deadlock could occur.
    *
    * @return Promise<>
    *         The promise is fulfilled when all pending tasks are finished.
    */
   uninit: Task.async(function* () {
+    this._log.trace("uninit: started");
     yield this._loadTask;
+    this._log.trace("uninit: finished with _loadTask");
 
     if (!this._shutdown) {
+      this._log.trace("uninit: no previous shutdown");
       this._unregisterWithAddonManager();
 
       gPrefs.ignore(PREF_LOGGING, configureLogging);
       gPrefs.ignore(PREF_MANIFEST_URI, this.updateManifest, this);
       gPrefs.ignore(PREF_ENABLED, this._toggleExperimentsEnabled, this);
 
       gPrefsTelemetry.ignore(PREF_TELEMETRY_ENABLED, this._telemetryStatusChanged, this);
 
       if (this._timer) {
         this._timer.clear();
       }
     }
 
     this._shutdown = true;
     if (this._mainTask) {
       try {
+        this._log.trace("uninit: waiting on _mainTask");
         yield this._mainTask;
       } catch (e if e instanceof AlreadyShutdownError) {
         // We error out of tasks after shutdown via that exception.
       }
     }
 
     this._log.info("Completed uninitialization.");
   }),
--- a/browser/themes/linux/newtab/newTab.css
+++ b/browser/themes/linux/newtab/newTab.css
@@ -4,17 +4,18 @@
 
 :root {
   -moz-appearance: none;
   font-size: 75%;
   background-color: transparent;
 }
 
 /* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]) {
+#newtab-scrollbox:not([page-disabled]),
+#newtab-scrollbox:not([page-disabled]) #newtab-margin-bottom {
   color: rgb(0,0,0);
   background-color: hsl(0,0%,95%);
 }
 
 /* UNDO */
 #newtab-undo-container {
   padding: 4px 3px;
   border: 1px solid;
--- a/browser/themes/osx/newtab/newTab.css
+++ b/browser/themes/osx/newtab/newTab.css
@@ -4,17 +4,18 @@
 
 :root {
   -moz-appearance: none;
   font-size: 75%;
   background-color: transparent;
 }
 
 /* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]) {
+#newtab-scrollbox:not([page-disabled]),
+#newtab-scrollbox:not([page-disabled]) #newtab-margin-bottom {
   color: rgb(0,0,0);
   background-color: hsl(0,0%,95%);
 }
 
 /* UNDO */
 #newtab-undo-container {
   padding: 4px 3px;
   border: 1px solid;
--- a/browser/themes/windows/newtab/newTab.css
+++ b/browser/themes/windows/newtab/newTab.css
@@ -4,17 +4,18 @@
 
 :root {
   -moz-appearance: none;
   font-size: 75%;
   background-color: transparent;
 }
 
 /* SCROLLBOX */
-#newtab-scrollbox:not([page-disabled]) {
+#newtab-scrollbox:not([page-disabled]),
+#newtab-scrollbox:not([page-disabled]) #newtab-margin-bottom {
   color: rgb(0,0,0);
   background-color: hsl(0,0%,95%);
 }
 
 /* UNDO */
 #newtab-undo-container {
   padding: 4px 3px;
   border: 1px solid;
--- a/caps/idl/nsIScriptSecurityManager.idl
+++ b/caps/idl/nsIScriptSecurityManager.idl
@@ -6,17 +6,17 @@
 #include "nsISupports.idl"
 #include "nsIPrincipal.idl"
 #include "nsIXPCSecurityManager.idl"
 interface nsIURI;
 interface nsIChannel;
 interface nsIDocShell;
 interface nsIDomainPolicy;
 
-[scriptable, uuid(3b6e408b-e774-4612-89e8-3ef303564392)]
+[scriptable, uuid(4c087cc3-e0cc-4ec3-88df-8d68f3023b45)]
 interface nsIScriptSecurityManager : nsIXPCSecurityManager
 {
     /**
      * Check that the script currently running in context "cx" can load "uri".
      *
      * Will return error code NS_ERROR_DOM_BAD_URI if the load request
      * should be denied.
      *
@@ -146,21 +146,16 @@ interface nsIScriptSecurityManager : nsI
     /**
      * Legacy name for getNoAppCodebasePrincipal.
      *
      * @deprecated use getNoAppCodebasePrincipal instead.
      */
     [deprecated] nsIPrincipal getCodebasePrincipal(in nsIURI uri);
 
     /**
-     * Return the principal of the specified object in the specified context.
-     */
-    [implicit_jscontext] nsIPrincipal getObjectPrincipal(in jsval aObject);
-
-    /**
      * Returns true if the principal of the currently running script is the
      * system principal, false otherwise.
      */
     [noscript] boolean subjectPrincipalIsSystem();
 
     /**
      * Returns OK if aJSContext and target have the same "origin"
      * (scheme, host, and port).
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -1113,28 +1113,16 @@ nsScriptSecurityManager::GetSubjectPrinc
     // The context should always be in a compartment, either one it has entered
     // or the one associated with its global.
     MOZ_ASSERT(!!compartment);
 
     JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment);
     return nsJSPrincipals::get(principals);
 }
 
-NS_IMETHODIMP
-nsScriptSecurityManager::GetObjectPrincipal(JS::Handle<JS::Value> aObjectVal,
-                                            JSContext *aCx,
-                                            nsIPrincipal **result)
-{
-    NS_ENSURE_TRUE(aObjectVal.isObject(), NS_ERROR_FAILURE);
-    JS::RootedObject obj(aCx, &aObjectVal.toObject());
-    nsCOMPtr<nsIPrincipal> principal = doGetObjectPrincipal(obj);
-    principal.forget(result);
-    return NS_OK;
-}
-
 // static
 nsIPrincipal*
 nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj)
 {
     JSCompartment *compartment = js::GetObjectCompartment(aObj);
     JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment);
     return nsJSPrincipals::get(principals);
 }
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -138,18 +138,19 @@ static const char *kPrefJavaMIME = "plug
 // 0.09 the file encoding is changed to UTF-8, bug 420285
 // 0.10 added plugin versions on appropriate platforms, bug 427743
 // 0.11 file name and full path fields now store expected values on all platforms, bug 488181
 // 0.12 force refresh due to quicktime pdf claim fix, bug 611197
 // 0.13 add architecture and list of invalid plugins, bug 616271
 // 0.14 force refresh due to locale comparison fix, bug 611296
 // 0.15 force refresh due to bug in reading Java plist MIME data, bug 638171
 // 0.16 version bump to avoid importing the plugin flags in newer versions
+// 0.17 added flag on whether plugin is loaded from an XPI
 // The current plugin registry version (and the maximum version we know how to read)
-static const char *kPluginRegistryVersion = "0.16";
+static const char *kPluginRegistryVersion = "0.17";
 // The minimum registry version we know how to read
 static const char *kMinimumRegistryVersion = "0.9";
 
 static const char kDirectoryServiceContractID[] = "@mozilla.org/file/directory_service;1";
 
 #define kPluginRegistryFilename NS_LITERAL_CSTRING("pluginreg.dat")
 
 #ifdef PLUGIN_LOGGING
@@ -1622,16 +1623,72 @@ int64_t GetPluginLastModifiedTime(const 
   }
 #else
   localfile->GetLastModifiedTime(&fileModTime);
 #endif
 
   return fileModTime;
 }
 
+bool
+GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile,
+                         const nsCOMPtr<nsISimpleEnumerator>& extensionDirs)
+{
+  if (!extensionDirs) {
+    return false;
+  }
+
+  bool hasMore;
+  while (NS_SUCCEEDED(extensionDirs->HasMoreElements(&hasMore)) && hasMore) {
+    nsCOMPtr<nsISupports> supports;
+    nsresult rv = extensionDirs->GetNext(getter_AddRefs(supports));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    nsCOMPtr<nsIFile> extDir(do_QueryInterface(supports, &rv));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    nsCOMPtr<nsIFile> dir;
+    if (NS_FAILED(extDir->Clone(getter_AddRefs(dir)))) {
+      continue;
+    }
+
+    bool contains;
+    if (NS_FAILED(dir->Contains(pluginFile, true, &contains)) || !contains) {
+      continue;
+    }
+
+    return true;
+  }
+
+  return false;
+}
+
+nsCOMPtr<nsISimpleEnumerator>
+GetExtensionDirectories()
+{
+  nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+  if (!dirService) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsISimpleEnumerator> list;
+  nsresult rv = dirService->Get(XRE_EXTENSIONS_DIR_LIST,
+                                NS_GET_IID(nsISimpleEnumerator),
+                                getter_AddRefs(list));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  return list;
+}
+
 struct CompareFilesByTime
 {
   bool
   LessThan(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const
   {
     return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b);
   }
 
@@ -1685,27 +1742,33 @@ nsresult nsPluginHost::ScanPluginsDirect
 
     if (nsPluginsDir::IsPluginFile(dirEntry)) {
       pluginFiles.AppendElement(dirEntry);
     }
   }
 
   pluginFiles.Sort(CompareFilesByTime());
 
+  nsCOMPtr<nsISimpleEnumerator> extensionDirs = GetExtensionDirectories();
+  if (!extensionDirs) {
+    PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("Could not get extension directories."));
+  }
+
   bool warnOutdated = false;
 
   for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) {
     nsCOMPtr<nsIFile>& localfile = pluginFiles[i];
 
     nsString utf16FilePath;
     rv = localfile->GetPath(utf16FilePath);
     if (NS_FAILED(rv))
       continue;
 
-    int64_t fileModTime = GetPluginLastModifiedTime(localfile);
+    const int64_t fileModTime = GetPluginLastModifiedTime(localfile);
+    const bool fromExtension = GetPluginIsFromExtension(localfile, extensionDirs);
 
     // Look for it in our cache
     NS_ConvertUTF16toUTF8 filePath(utf16FilePath);
     nsRefPtr<nsPluginTag> pluginTag;
     RemoveCachedPluginsInfo(filePath.get(), getter_AddRefs(pluginTag));
 
     bool seenBefore = false;
 
@@ -1776,17 +1839,17 @@ nsresult nsPluginHost::ScanPluginsDirect
         }
         mInvalidPlugins = invalidTag;
 
         // Mark aPluginsChanged so pluginreg is rewritten
         *aPluginsChanged = true;
         continue;
       }
 
-      pluginTag = new nsPluginTag(&info, fileModTime);
+      pluginTag = new nsPluginTag(&info, fileModTime, fromExtension);
       pluginFile.FreePluginInfo(info);
       if (!pluginTag)
         return NS_ERROR_OUT_OF_MEMORY;
 
       pluginTag->mLibrary = library;
       uint32_t state = pluginTag->GetBlocklistState();
 
       // If the blocklist says it is risky and we have never seen this
@@ -2267,24 +2330,26 @@ nsPluginHost::WritePluginInfo()
       PLUGIN_REGISTRY_END_OF_LINE_MARKER,
       (tag->mFullPath.get()),
       PLUGIN_REGISTRY_FIELD_DELIMITER,
       PLUGIN_REGISTRY_END_OF_LINE_MARKER,
       (tag->mVersion.get()),
       PLUGIN_REGISTRY_FIELD_DELIMITER,
       PLUGIN_REGISTRY_END_OF_LINE_MARKER);
 
-    // lastModifiedTimeStamp|canUnload|tag->mFlags
-    PR_fprintf(fd, "%lld%c%d%c%lu%c%c\n",
+    // lastModifiedTimeStamp|canUnload|tag->mFlags|fromExtension
+    PR_fprintf(fd, "%lld%c%d%c%lu%c%d%c%c\n",
       tag->mLastModifiedTime,
       PLUGIN_REGISTRY_FIELD_DELIMITER,
       false, // did store whether or not to unload in-process plugins
       PLUGIN_REGISTRY_FIELD_DELIMITER,
       0, // legacy field for flags
       PLUGIN_REGISTRY_FIELD_DELIMITER,
+      tag->IsFromExtension(),
+      PLUGIN_REGISTRY_FIELD_DELIMITER,
       PLUGIN_REGISTRY_END_OF_LINE_MARKER);
 
     //description, name & mtypecount are on separate line
     PR_fprintf(fd, "%s%c%c\n%s%c%c\n%d\n",
       (tag->mDescription.get()),
       PLUGIN_REGISTRY_FIELD_DELIMITER,
       PLUGIN_REGISTRY_END_OF_LINE_MARKER,
       (tag->mName.get()),
@@ -2481,30 +2546,33 @@ nsPluginHost::ReadPluginInfo()
 
     // If this is a registry from a different architecture then don't attempt to read it
     if (PL_strcmp(archValues[1], arch.get())) {
       return rv;
     }
   }
 
   // Registry v0.13 and upwards includes the list of invalid plugins
-  bool hasInvalidPlugins = (version >= "0.13");
+  const bool hasInvalidPlugins = (version >= "0.13");
 
   // Registry v0.16 and upwards always have 0 for their plugin flags, prefs are used instead
   const bool hasValidFlags = (version < "0.16");
 
+  // Registry v0.17 and upwards store whether the plugin comes from an XPI.
+  const bool hasFromExtension = (version >= "0.17");
+
+#if defined(XP_MACOSX)
+  const bool hasFullPathInFileNameField = false;
+#else
+  const bool hasFullPathInFileNameField = (version < "0.11");
+#endif
+
   if (!ReadSectionHeader(reader, "PLUGINS"))
     return rv;
 
-#if defined(XP_MACOSX)
-  bool hasFullPathInFileNameField = false;
-#else
-  bool hasFullPathInFileNameField = (version < "0.11");
-#endif
-
   while (reader.NextLine()) {
     const char *filename;
     const char *fullpath;
     nsAutoCString derivedFileName;
 
     if (hasInvalidPlugins && *reader.LinePtr() == '[') {
       break;
     }
@@ -2540,23 +2608,28 @@ nsPluginHost::ReadPluginInfo()
     if (regHasVersion) {
       version = reader.LinePtr();
       if (!reader.NextLine())
         return rv;
     } else {
       version = "0";
     }
 
-    // lastModifiedTimeStamp|canUnload|tag.mFlag
-    if (reader.ParseLine(values, 3) != 3)
+    // lastModifiedTimeStamp|canUnload|tag.mFlag|fromExtension
+    const int count = hasFromExtension ? 4 : 3;
+    if (reader.ParseLine(values, count) != count)
       return rv;
 
     // If this is an old plugin registry mark this plugin tag to be refreshed
     int64_t lastmod = (vdiff == 0) ? nsCRT::atoll(values[0]) : -1;
     uint32_t tagflag = atoi(values[2]);
+    bool fromExtension = false;
+    if (hasFromExtension) {
+      fromExtension = atoi(values[3]);
+    }
     if (!reader.NextLine())
       return rv;
 
     char *description = reader.LinePtr();
     if (!reader.NextLine())
       return rv;
 
 #if MOZ_WIDGET_ANDROID
@@ -2617,17 +2690,17 @@ nsPluginHost::ReadPluginInfo()
     nsRefPtr<nsPluginTag> tag = new nsPluginTag(name,
       description,
       filename,
       fullpath,
       version,
       (const char* const*)mimetypes,
       (const char* const*)mimedescriptions,
       (const char* const*)extensions,
-      mimetypecount, lastmod, true);
+      mimetypecount, lastmod, fromExtension, true);
     if (heapalloced)
       delete [] heapalloced;
 
     // Import flags from registry into prefs for old registry versions
     if (hasValidFlags && !pluginStateImported) {
       tag->ImportFlagsToPrefs(tagflag);
     }
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -25,16 +25,19 @@ using namespace mozilla;
 // These legacy flags are used in the plugin registry. The states are now
 // stored in prefs, but we still need to be able to import them.
 #define NS_PLUGIN_FLAG_ENABLED      0x0001    // is this plugin enabled?
 // no longer used                   0x0002    // reuse only if regenerating pluginreg.dat
 #define NS_PLUGIN_FLAG_FROMCACHE    0x0004    // this plugintag info was loaded from cache
 // no longer used                   0x0008    // reuse only if regenerating pluginreg.dat
 #define NS_PLUGIN_FLAG_CLICKTOPLAY  0x0020    // this is a click-to-play plugin
 
+static const char kPrefDefaultEnabledState[] = "plugin.default.state";
+static const char kPrefDefaultEnabledStateXpi[] = "plugin.defaultXpi.state";
+
 inline char* new_str(const char* str)
 {
   if (str == nullptr)
     return nullptr;
   
   char* result = new char[strlen(str) + 1];
   if (result != nullptr)
     return strcpy(result, str);
@@ -57,29 +60,32 @@ MakePrefNameForPlugin(const char* const 
 static nsCString
 GetStatePrefNameForPlugin(nsPluginTag* aTag)
 {
   return MakePrefNameForPlugin("state", aTag);
 }
 
 /* nsPluginTag */
 
-nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo, int64_t aLastModifiedTime)
+nsPluginTag::nsPluginTag(nsPluginInfo* aPluginInfo,
+                         int64_t aLastModifiedTime,
+                         bool fromExtension)
   : mName(aPluginInfo->fName),
     mDescription(aPluginInfo->fDescription),
     mLibrary(nullptr),
     mIsJavaPlugin(false),
     mIsFlashPlugin(false),
     mFileName(aPluginInfo->fFileName),
     mFullPath(aPluginInfo->fFullPath),
     mVersion(aPluginInfo->fVersion),
     mLastModifiedTime(aLastModifiedTime),
     mNiceFileName(),
     mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
-    mCachedBlocklistStateValid(false)
+    mCachedBlocklistStateValid(false),
+    mIsFromExtension(fromExtension)
 {
   InitMime(aPluginInfo->fMimeTypeArray,
            aPluginInfo->fMimeDescriptionArray,
            aPluginInfo->fExtensionArray,
            aPluginInfo->fVariantCount);
   EnsureMembersAreUTF8();
   FixupVersion();
 }
@@ -89,29 +95,31 @@ nsPluginTag::nsPluginTag(const char* aNa
                          const char* aFileName,
                          const char* aFullPath,
                          const char* aVersion,
                          const char* const* aMimeTypes,
                          const char* const* aMimeDescriptions,
                          const char* const* aExtensions,
                          int32_t aVariants,
                          int64_t aLastModifiedTime,
+                         bool fromExtension,
                          bool aArgsAreUTF8)
   : mName(aName),
     mDescription(aDescription),
     mLibrary(nullptr),
     mIsJavaPlugin(false),
     mIsFlashPlugin(false),
     mFileName(aFileName),
     mFullPath(aFullPath),
     mVersion(aVersion),
     mLastModifiedTime(aLastModifiedTime),
     mNiceFileName(),
     mCachedBlocklistState(nsIBlocklistService::STATE_NOT_BLOCKED),
-    mCachedBlocklistStateValid(false)
+    mCachedBlocklistStateValid(false),
+    mIsFromExtension(fromExtension)
 {
   InitMime(aMimeTypes, aMimeDescriptions, aExtensions,
            static_cast<uint32_t>(aVariants));
   if (!aArgsAreUTF8)
     EnsureMembersAreUTF8();
   FixupVersion();
 }
 
@@ -360,18 +368,20 @@ nsPluginTag::GetEnabledState(uint32_t *a
                                     &enabledState);
   if (NS_SUCCEEDED(rv) &&
       enabledState >= nsIPluginTag::STATE_DISABLED &&
       enabledState <= nsIPluginTag::STATE_ENABLED) {
     *aEnabledState = (uint32_t)enabledState;
     return rv;
   }
 
-  enabledState = Preferences::GetInt("plugin.default.state",
-                                     nsIPluginTag::STATE_ENABLED);
+  const char* const pref = mIsFromExtension ? kPrefDefaultEnabledStateXpi
+                                            : kPrefDefaultEnabledState;
+
+  enabledState = Preferences::GetInt(pref, nsIPluginTag::STATE_ENABLED);
   if (enabledState >= nsIPluginTag::STATE_DISABLED &&
       enabledState <= nsIPluginTag::STATE_ENABLED) {
     *aEnabledState = (uint32_t)enabledState;
     return NS_OK;
   }
 
   return NS_ERROR_UNEXPECTED;
 }
@@ -569,8 +579,13 @@ nsPluginTag::InvalidateBlocklistState()
 
 NS_IMETHODIMP
 nsPluginTag::GetLastModifiedTime(PRTime* aLastModifiedTime)
 {
   MOZ_ASSERT(aLastModifiedTime);
   *aLastModifiedTime = mLastModifiedTime;
   return NS_OK;
 }
+
+bool nsPluginTag::IsFromExtension() const
+{
+  return mIsFromExtension;
+}
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -31,27 +31,30 @@ public:
   // These must match the STATE_* values in nsIPluginTag.idl
   enum PluginState {
     ePluginState_Disabled = 0,
     ePluginState_Clicktoplay = 1,
     ePluginState_Enabled = 2,
     ePluginState_MaxValue = 3,
   };
 
-  nsPluginTag(nsPluginInfo* aPluginInfo, int64_t aLastModifiedTime);
+  nsPluginTag(nsPluginInfo* aPluginInfo,
+              int64_t aLastModifiedTime,
+              bool fromExtension);
   nsPluginTag(const char* aName,
               const char* aDescription,
               const char* aFileName,
               const char* aFullPath,
               const char* aVersion,
               const char* const* aMimeTypes,
               const char* const* aMimeDescriptions,
               const char* const* aExtensions,
               int32_t aVariants,
               int64_t aLastModifiedTime,
+              bool fromExtension,
               bool aArgsAreUTF8 = false);
   virtual ~nsPluginTag();
 
   void TryUnloadPlugin(bool inShutdown);
 
   // plugin is enabled and not blocklisted
   bool IsActive();
 
@@ -64,16 +67,18 @@ public:
   void SetPluginState(PluginState state);
 
   // import legacy flags from plugin registry into the preferences
   void ImportFlagsToPrefs(uint32_t flag);
 
   bool HasSameNameAndMimes(const nsPluginTag *aPluginTag) const;
   nsCString GetNiceFileName();
 
+  bool IsFromExtension() const;
+
   nsRefPtr<nsPluginTag> mNext;
   nsCString     mName; // UTF-8
   nsCString     mDescription; // UTF-8
   nsTArray<nsCString> mMimeTypes; // UTF-8
   nsTArray<nsCString> mMimeDescriptions; // UTF-8
   nsTArray<nsCString> mExtensions; // UTF-8
   PRLibrary     *mLibrary;
   nsRefPtr<nsNPAPIPlugin> mPlugin;
@@ -87,16 +92,17 @@ public:
 
   uint32_t      GetBlocklistState();
   void          InvalidateBlocklistState();
 
 private:
   nsCString     mNiceFileName; // UTF-8
   uint16_t      mCachedBlocklistState;
   bool          mCachedBlocklistStateValid;
+  bool          mIsFromExtension;
 
   void InitMime(const char* const* aMimeTypes,
                 const char* const* aMimeDescriptions,
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
 };
--- a/dom/plugins/ipc/hangui/PluginHangUIChild.cpp
+++ b/dom/plugins/ipc/hangui/PluginHangUIChild.cpp
@@ -304,16 +304,17 @@ PluginHangUIChild::HangUIDlgProc(HWND aD
           break;
         default:
           break;
       }
       break;
     }
     case WM_DESTROY: {
       EnableWindow(mParentWindow, TRUE);
+      SetForegroundWindow(mParentWindow);
       break;
     }
     default:
       break;
   }
   return FALSE;
 }
 
--- a/dom/plugins/test/mochitest/chrome.ini
+++ b/dom/plugins/test/mochitest/chrome.ini
@@ -14,16 +14,19 @@ skip-if = toolkit != "cocoa"
 [test_crash_notify.xul]
 skip-if = !crashreporter
 [test_crash_notify_no_report.xul]
 skip-if = !crashreporter
 [test_crash_submit.xul]
 skip-if = !crashreporter
 [test_hang_submit.xul]
 skip-if = !crashreporter
+[test_hangui.xul]
+skip-if = (!crashreporter) || (os != "win")
+support-files = hangui_subpage.html hangui_common.js hangui_iface.js dialog_watcher.js
 [test_idle_hang.xul]
 skip-if = (!crashreporter) || (os != "win")
 [test_npruntime.xul]
 [test_plugin_tag_clicktoplay.html]
 [test_privatemode_perwindowpb.xul]
 [test_refresh_navigator_plugins.html]
 [test_xulbrowser_plugin_visibility.xul]
 skip-if = (toolkit == "cocoa") || (os == "win")
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/dialog_watcher.js
@@ -0,0 +1,172 @@
+const EVENT_OBJECT_SHOW = 0x8002;
+const EVENT_OBJECT_HIDE = 0x8003;
+const WINEVENT_OUTOFCONTEXT = 0;
+const WINEVENT_SKIPOWNPROCESS = 2;
+const QS_ALLINPUT = 0x04FF;
+const INFINITE = 0xFFFFFFFF;
+const WAIT_OBJECT_0 = 0;
+const WAIT_TIMEOUT = 258;
+const PM_NOREMOVE = 0;
+
+function DialogWatcher(titleText, onDialogStart, onDialogEnd) {
+  this.titleText = titleText;
+  this.onDialogStart = onDialogStart;
+  this.onDialogEnd = onDialogEnd;
+}
+
+DialogWatcher.prototype.init = function() {
+  this.hwnd = undefined;
+  if (!this.user32) {
+    this.user32 = ctypes.open("user32.dll");
+  }
+  if (!this.findWindow) {
+    this.findWindow = user32.declare("FindWindowW",
+                                     ctypes.winapi_abi,
+                                     ctypes.uintptr_t,
+                                     ctypes.jschar.ptr,
+                                     ctypes.jschar.ptr);
+  }
+  if (!this.winEventProcType) {
+    this.winEventProcType = ctypes.FunctionType(ctypes.stdcall_abi,
+                                                ctypes.void_t,
+                                                [ctypes.uintptr_t,
+                                                ctypes.uint32_t,
+                                                ctypes.uintptr_t,
+                                                ctypes.long,
+                                                ctypes.long,
+                                                ctypes.uint32_t,
+                                                ctypes.uint32_t]).ptr;
+  }
+  if (!this.setWinEventHook) {
+    this.setWinEventHook = user32.declare("SetWinEventHook",
+                                          ctypes.winapi_abi,
+                                          ctypes.uintptr_t,
+                                          ctypes.uint32_t,
+                                          ctypes.uint32_t,
+                                          ctypes.uintptr_t,
+                                          this.winEventProcType,
+                                          ctypes.uint32_t,
+                                          ctypes.uint32_t,
+                                          ctypes.uint32_t);
+  }
+  if (!this.unhookWinEvent) {
+    this.unhookWinEvent = user32.declare("UnhookWinEvent",
+                                         ctypes.winapi_abi,
+                                         ctypes.int,
+                                         ctypes.uintptr_t);
+  }
+  if (!this.pointType) {
+    this.pointType = ctypes.StructType("tagPOINT",
+                                       [ { "x": ctypes.long },
+                                         { "y": ctypes.long } ] );
+  }
+  if (!this.msgType) {
+    this.msgType = ctypes.StructType("tagMSG",
+                                     [ { "hwnd": ctypes.uintptr_t },
+                                       { "message": ctypes.uint32_t },
+                                       { "wParam": ctypes.uintptr_t },
+                                       { "lParam": ctypes.intptr_t },
+                                       { "time": ctypes.uint32_t },
+                                       { "pt": this.pointType } ] );
+  }
+  if (!this.peekMessage) {
+    this.peekMessage = user32.declare("PeekMessageW",
+                                      ctypes.winapi_abi,
+                                      ctypes.int,
+                                      this.msgType.ptr,
+                                      ctypes.uintptr_t,
+                                      ctypes.uint32_t,
+                                      ctypes.uint32_t,
+                                      ctypes.uint32_t);
+  }
+  if (!this.msgWaitForMultipleObjects) {
+    this.msgWaitForMultipleObjects = user32.declare("MsgWaitForMultipleObjects",
+                                                    ctypes.winapi_abi,
+                                                    ctypes.uint32_t,
+                                                    ctypes.uint32_t,
+                                                    ctypes.uintptr_t.ptr,
+                                                    ctypes.int,
+                                                    ctypes.uint32_t,
+                                                    ctypes.uint32_t);
+  }
+  if (!this.getWindowTextW) {
+    this.getWindowTextW = user32.declare("GetWindowTextW",
+                                         ctypes.winapi_abi,
+                                         ctypes.int,
+                                         ctypes.uintptr_t,
+                                         ctypes.jschar.ptr,
+                                         ctypes.int);
+  }
+};
+
+DialogWatcher.prototype.getWindowText = function(hwnd) {
+  var bufType = ctypes.ArrayType(ctypes.jschar);
+  var buffer = new bufType(256);
+  
+  if (this.getWindowTextW(hwnd, buffer, buffer.length)) {
+    return buffer.readString();
+  }
+};
+
+DialogWatcher.prototype.processWindowEvents = function(timeout) {
+  var onWinEvent = function(self, hook, event, hwnd, idObject, idChild, dwEventThread, dwmsEventTime) {
+    var nhwnd = Number(hwnd)
+    if (event == EVENT_OBJECT_SHOW) {
+      if (nhwnd == self.hwnd) {
+        // We've already picked up this event via FindWindow
+        return;
+      }
+      var windowText = self.getWindowText(hwnd);
+      if (windowText == self.titleText && self.onDialogStart) {
+        self.hwnd = nhwnd;
+        self.onDialogStart(nhwnd);
+      }
+    } else if (event == EVENT_OBJECT_HIDE && nhwnd == self.hwnd && self.onDialogEnd) {
+      self.onDialogEnd();
+      self.hwnd = null;
+    }
+  };
+  var self = this;
+  var callback = this.winEventProcType(function(hook, event, hwnd, idObject,
+                                                idChild, dwEventThread,
+                                                dwmsEventTime) {
+      onWinEvent(self, hook, event, hwnd, idObject, idChild, dwEventThread,
+                 dwmsEventTime);
+    } );
+  var hook = this.setWinEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE,
+                                  0, callback, 0, 0,
+                                  WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS);
+  if (!hook) {
+    return;
+  }
+  // Check if the window is already showing
+  var hwnd = this.findWindow(null, this.titleText);
+  if (hwnd && hwnd > 0) {
+    this.hwnd = Number(hwnd);
+    if (this.onDialogStart) {
+      this.onDialogStart(this.hwnd);
+    }
+  }
+
+  if (!timeout) {
+    timeout = INFINITE;
+  }
+
+  var waitStatus = WAIT_OBJECT_0;
+  var expectingStart = this.onDialogStart && this.hwnd === undefined;
+  while (this.hwnd === undefined || this.onDialogEnd && this.hwnd) {
+    waitStatus = this.msgWaitForMultipleObjects(0, null, 0, expectingStart ?
+                                                            INFINITE : timeout, 0);
+    if (waitStatus == WAIT_OBJECT_0) {
+      var msg = new this.msgType;
+      this.peekMessage(msg.address(), 0, 0, 0, PM_NOREMOVE);
+    } else if (waitStatus == WAIT_TIMEOUT) {
+      break;
+    }
+  }
+
+  this.unhookWinEvent(hook);
+  // Returns true if the hook was successful, something was found, and we never timed out
+  return this.hwnd !== undefined && waitStatus == WAIT_OBJECT_0;
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/hangui_common.js
@@ -0,0 +1,20 @@
+// Plugin Hang UI constants
+const HANGUIOP_NOTHING = 0;
+const HANGUIOP_CANCEL = 1;
+const HANGUIOP_COMMAND = 2;
+const IDC_CONTINUE = 1001;
+const IDC_STOP = 1002;
+const IDC_NOFUTURE = 1003;
+
+// Windows constants
+const WM_CLOSE = 0x0010;
+const WM_COMMAND = 0x0111;
+const BM_GETCHECK = 0x00F0;
+const BM_SETCHECK = 0x00F1;
+const BN_CLICKED = 0;
+const BST_CHECKED = 1;
+
+// Test-specific constants
+const EPSILON_MS = 1000;
+const STALL_DURATION = 2;
+
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/hangui_iface.js
@@ -0,0 +1,121 @@
+var user32;
+var sendMessage;
+var getDlgItem;
+var messageBox;
+var watcher;
+
+importScripts("hangui_common.js");
+importScripts("dialog_watcher.js");
+
+function initCTypes() {
+  if (!user32) {
+    user32 = ctypes.open("user32.dll");
+  }
+  if (!getDlgItem) {
+    getDlgItem = user32.declare("GetDlgItem",
+                                ctypes.winapi_abi,
+                                ctypes.uintptr_t,
+                                ctypes.uintptr_t,
+                                ctypes.int);
+  }
+  if (!sendMessage) {
+    sendMessage = user32.declare("SendMessageW",
+                                 ctypes.winapi_abi,
+                                 ctypes.intptr_t,
+                                 ctypes.uintptr_t,
+                                 ctypes.uint32_t,
+                                 ctypes.uintptr_t,
+                                 ctypes.intptr_t);
+  }
+  if (!messageBox) {
+    // Handy for debugging the test itself
+    messageBox = user32.declare("MessageBoxW",
+                                ctypes.winapi_abi,
+                                ctypes.int,
+                                ctypes.uintptr_t,
+                                ctypes.jschar.ptr,
+                                ctypes.jschar.ptr,
+                                ctypes.uint32_t);
+  }
+  if (!watcher) {
+    watcher = new DialogWatcher("Warning: Unresponsive plugin");
+  }
+}
+
+function postSuccess(params) {
+  self.postMessage({"status": true, "params": params});
+}
+
+function postFail(params, msg) {
+  self.postMessage({"status": false, "params": params, "msg": msg});
+}
+
+function onDialogStart(inparams, hwnd) {
+  var params = Object.create(inparams);
+  params.testName += " (Start)";
+  params.callback = null;
+  if (!params.expectToFind) {
+    postFail(params, "Dialog showed when we weren't expecting it to!");
+    return;
+  }
+  if (params.opCode == HANGUIOP_CANCEL) {
+    sendMessage(hwnd, WM_CLOSE, 0, 0);
+  } else if (params.opCode == HANGUIOP_COMMAND) {
+    if (params.check) {
+      var checkbox = getDlgItem(hwnd, IDC_NOFUTURE);
+      if (!checkbox) {
+        postFail(params, "Couldn't find checkbox");
+        return;
+      }
+      sendMessage(checkbox, BM_SETCHECK, BST_CHECKED, 0);
+      sendMessage(hwnd, WM_COMMAND, (BN_CLICKED << 16) | IDC_NOFUTURE, checkbox);
+    }
+    var button = getDlgItem(hwnd, params.commandId);
+    if (!button) {
+      postFail(params,
+               "GetDlgItem failed to find button with ID " + params.commandId);
+      return;
+    }
+    sendMessage(hwnd, WM_COMMAND, (BN_CLICKED << 16) | params.commandId, button);
+  }
+  postSuccess(params);
+}
+
+function onDialogEnd(inparams) {
+  var params = Object.create(inparams);
+  params.testName += " (End)";
+  params.callback = inparams.callback;
+  postSuccess(params);
+}
+
+self.onmessage = function(event) {
+  initCTypes();
+  watcher.init();
+  var params = event.data;
+  var timeout = params.timeoutMs;
+  if (params.expectToFind) {
+    watcher.onDialogStart = function(hwnd) { onDialogStart(params, hwnd); };
+    if (params.expectToClose) {
+      watcher.onDialogEnd = function() { onDialogEnd(params); };
+    }
+  } else {
+    watcher.onDialogStart = null;
+    watcher.onDialogEnd = null;
+  }
+  var result = watcher.processWindowEvents(timeout);
+  if (result === null) {
+    postFail(params, "Hook failed");
+  } else if (!result) {
+    if (params.expectToFind) {
+      postFail(params, "The dialog didn't show but we were expecting it to");
+    } else {
+      postSuccess(params);
+    }
+  }
+}
+
+self.onerror = function(event) {
+  var msg = "Error: " + event.message + " at " + event.filename + ":" + event.lineno;
+  postFail(null, msg);
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/hangui_subpage.html
@@ -0,0 +1,4 @@
+<html>
+<body onload="window.parent.frameLoaded()">
+  <embed id="plugin1" type="application/x-test" width="400" height="400"></embed>
+
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/test_hangui.xul
@@ -0,0 +1,263 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window title="Basic Plugin Tests"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <title>Plugin Hang UI Test</title>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript"
+          src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hang_test.js" />
+  <script type="application/javascript"
+          src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hangui_common.js" />
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+  <iframe id="iframe1" src="hangui_subpage.html" width="400" height="400"></iframe>
+</body>
+<script class="testbody" type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const hangUITimeoutPref = "dom.ipc.plugins.hangUITimeoutSecs";
+const hangUIMinDisplayPref = "dom.ipc.plugins.hangUIMinDisplaySecs";
+const timeoutPref = "dom.ipc.plugins.timeoutSecs";
+
+var worker = new ChromeWorker("hangui_iface.js");
+worker.onmessage = function(event) {
+  var result = event.data;
+  var params = result.params;
+  var output = params.testName;
+  if (result.msg) {
+    output += ": " + result.msg;
+  }
+  ok(result.status, output);
+  if (params.callback) {
+    var cb = eval(params.callback);
+    var timeout = setTimeout(function() { clearTimeout(timeout); cb(); }, 100);
+  }
+};
+worker.onerror = function(event) {
+  var output = "Error: " + event.message + " at " + event.filename + ":" + event.lineno;
+  ok(false, output);
+};
+
+var iframe;
+var p;
+var os = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
+
+function hanguiOperation(testName, timeoutSec, expectFind, expectClose, opCode,
+                         commandId, check, cb) {
+  var timeoutMs = timeoutSec * 1000 + EPSILON_MS;
+  worker.postMessage({ "timeoutMs": timeoutMs, "expectToFind": expectFind,
+                       "expectToClose": expectClose, "opCode": opCode,
+                       "commandId": commandId, "check": check,
+                       "testName": testName, "callback": cb });
+}
+
+function hanguiExpect(testName, shouldBeShowing, shouldClose, cb) {
+  var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref);
+  if (!shouldBeShowing && !timeoutSec) {
+    timeoutSec = Services.prefs.getIntPref(timeoutPref);
+  }
+  hanguiOperation(testName, timeoutSec, shouldBeShowing, shouldClose, HANGUIOP_NOTHING, 0, false, cb);
+}
+
+function hanguiContinue(testName, check, cb) {
+  var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref);
+  hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_COMMAND, IDC_CONTINUE, check, cb);
+}
+
+function hanguiStop(testName, check, cb) {
+  var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref);
+  hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_COMMAND, IDC_STOP, check, cb);
+}
+
+function hanguiCancel(testName, cb) {
+  var timeoutSec = Services.prefs.getIntPref(hangUITimeoutPref);
+  hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_CANCEL, 0, false, cb);
+}
+
+function finishTest() {
+  if (obsCount > 0) {
+    os.removeObserver(testObserver, "plugin-crashed");
+    --obsCount;
+  }
+  SpecialPowers.clearUserPref(hangUITimeoutPref);
+  SpecialPowers.clearUserPref(hangUIMinDisplayPref);
+  SpecialPowers.clearUserPref(timeoutPref);
+  SimpleTest.finish();
+}
+
+function runTests() {
+  if (!SimpleTest.testPluginIsOOP()) {
+    ok(true, "Skipping this test when test plugin is not OOP.");
+    SimpleTest.finish();
+  }
+
+  resetVars();
+
+  hanguiExpect("Prime ChromeWorker", false, false, "test1");
+}
+
+window.frameLoaded = runTests;
+
+var obsCount = 0;
+
+function onPluginCrashedHangUI(aEvent) {
+  ok(true, "Plugin crashed notification received");
+  is(aEvent.type, "PluginCrashed", "event is correct type");
+
+  is(p, aEvent.target, "Plugin crashed event target is plugin element");
+
+  ok(aEvent instanceof Components.interfaces.nsIDOMCustomEvent,
+     "plugin crashed event has the right interface");
+
+  var propBag = aEvent.detail.QueryInterface(Components.interfaces.nsIPropertyBag2);
+  var pluginDumpID = propBag.getPropertyAsAString("pluginDumpID");
+  isnot(pluginDumpID, "", "got a non-empty dump ID");
+  var pluginName = propBag.getPropertyAsAString("pluginName");
+  is(pluginName, "Test Plug-in", "got correct plugin name");
+  var pluginFilename = propBag.getPropertyAsAString("pluginFilename");
+  isnot(pluginFilename, "", "got a non-empty filename");
+  var didReport = propBag.getPropertyAsBool("submittedCrashReport");
+  // The app itself may or may not have decided to submit the report, so
+  // allow either true or false here.
+  ok((didReport == true || didReport == false), "event said crash report was submitted");
+  os.removeObserver(testObserver, "plugin-crashed");
+  --obsCount;
+}
+
+function resetVars() {
+  iframe = document.getElementById('iframe1');
+  p = iframe.contentDocument.getElementById("plugin1");
+  if (obsCount == 0) {
+    os.addObserver(testObserver, "plugin-crashed", true);
+    ++obsCount;
+  }
+  iframe.contentDocument.addEventListener("PluginCrashed",
+                                          onPluginCrashedHangUI,
+                                          false);
+}
+
+function test9b() {
+  hanguiExpect("test9b: Plugin Hang UI is not showing (checkbox)", false);
+  p.stall(STALL_DURATION);
+  hanguiExpect("test9b: Plugin Hang UI is still not showing (checkbox)", false, false, "finishTest");
+  p.stall(STALL_DURATION);
+}
+
+function test9a() {
+  resetVars();
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
+  SpecialPowers.setIntPref(timeoutPref, 45);
+  hanguiContinue("test9a: Continue button works with checkbox", true, "test9b");
+  p.stall(STALL_DURATION);
+}
+
+function test9() {
+  window.frameLoaded = test9a;
+  iframe.contentWindow.location.reload();
+}
+
+function test8a() {
+  resetVars();
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 4);
+  hanguiExpect("test8a: Plugin Hang UI is not showing (disabled due to hangUIMinDisplaySecs)", false, false, "test9");
+  var exceptionThrown = false;
+  try {
+    p.hang();
+  } catch(e) {
+    exceptionThrown = true;
+  }
+  ok(exceptionThrown, "test8a: Exception thrown from hang() when plugin was terminated");
+}
+
+function test8() {
+  window.frameLoaded = test8a;
+  iframe.contentWindow.location.reload();
+}
+
+function test7a() {
+  resetVars();
+  SpecialPowers.setIntPref(hangUITimeoutPref, 0);
+  hanguiExpect("test7a: Plugin Hang UI is not showing (disabled)", false, false, "test8");
+  var exceptionThrown = false;
+  try {
+    p.hang();
+  } catch(e) {
+    exceptionThrown = true;
+  }
+  ok(exceptionThrown, "test7a: Exception thrown from hang() when plugin was terminated");
+}
+
+function test7() {
+  window.frameLoaded = test7a;
+  iframe.contentWindow.location.reload();
+}
+
+function test6() {
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
+  SpecialPowers.setIntPref(timeoutPref, 3);
+  hanguiExpect("test6: Plugin Hang UI is showing", true, true, "test7");
+  var exceptionThrown = false;
+  try {
+    p.hang();
+  } catch(e) {
+    exceptionThrown = true;
+  }
+  ok(exceptionThrown, "test6: Exception thrown from hang() when plugin was terminated (child timeout)");
+}
+
+function test5a() {
+  resetVars();
+  hanguiCancel("test5a: Close button works", "test6");
+  p.stall(STALL_DURATION);
+}
+
+function test5() {
+  window.frameLoaded = test5a;
+  iframe.contentWindow.location.reload();
+}
+
+function test4() {
+  hanguiStop("test4: Stop button works", false, "test5");
+  // We'll get an exception here because the plugin was terminated
+  var exceptionThrown = false;
+  try {
+    p.hang();
+  } catch(e) {
+    exceptionThrown = true;
+  }
+  ok(exceptionThrown, "test4: Exception thrown from hang() when plugin was terminated");
+}
+
+function test3() {
+  hanguiContinue("test3: Continue button works", false, "test4");
+  p.stall(STALL_DURATION);
+}
+
+function test2() {
+  // This test is identical to test1 because there were some bugs where the 
+  // Hang UI would show on the first hang but not on subsequent hangs
+  hanguiExpect("test2: Plugin Hang UI is showing", true, true, "test3");
+  p.stall(STALL_DURATION);
+}
+
+function test1() {
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
+  SpecialPowers.setIntPref(timeoutPref, 45);
+  hanguiExpect("test1: Plugin Hang UI is showing", true, true, "test2");
+  p.stall(STALL_DURATION);
+}
+
+]]>
+</script>
+</window>
--- a/dom/plugins/test/moz.build
+++ b/dom/plugins/test/moz.build
@@ -1,14 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-DIRS += ['testplugin']
+DIRS += ['testplugin', 'testaddon']
 
 XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3', 'cocoa', 'windows'):
     MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
     MOCHITEST_CHROME_MANIFESTS += ['mochitest/chrome.ini']
 
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/testaddon/Makefile.in
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include $(topsrcdir)/config/rules.mk
+
+ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
+plugin_file_name = Test.plugin
+addon_file_name = testaddon_$(TARGET_XPCOM_ABI).xpi
+else
+plugin_file_name = $(DLL_PREFIX)nptest$(DLL_SUFFIX)
+addon_file_name = testaddon.xpi
+endif
+
+# This is so hacky. Waiting on bug 988938.
+testdir = $(abspath $(DEPTH)/_tests/xpcshell/dom/plugins/test/unit/)
+addonpath = $(testdir)/$(addon_file_name)
+
+libs::
+	$(NSINSTALL) -D $(testdir)
+	rm -f $(addonpath)
+	cd $(srcdir) && zip -rD $(addonpath) install.rdf
+	cd $(DIST) && zip -rD $(addonpath) plugins/$(plugin_file_name)
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/testaddon/install.rdf
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+  <Description about="urn:mozilla:install-manifest">
+    <em:id>test-plugin-from-xpi@tests.mozilla.org</em:id>
+    <em:version>1</em:version>
+    <em:name>Test plugin from XPI</em:name>
+    <em:description>This tests shipping a plugin through an extensions.</em:description>
+
+    <em:targetApplication>
+      <Description>
+        <em:id>toolkit@mozilla.org</em:id>
+        <em:minVersion>0</em:minVersion>
+        <em:maxVersion>*</em:maxVersion>
+      </Description>
+    </em:targetApplication>
+
+    <em:unpack>true</em:unpack>
+
+  </Description>
+</RDF>
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/testaddon/moz.build
@@ -0,0 +1,5 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
--- a/dom/plugins/test/testplugin/nptest.cpp
+++ b/dom/plugins/test/testplugin/nptest.cpp
@@ -144,16 +144,17 @@ static bool getJavaCodebase(NPObject* np
 static bool checkObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool setCookie(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getCookie(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getAuthInfo(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool asyncCallbackTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool checkGCRace(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool destroySharedGfxStuff(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool propertyAndMethod(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getTopLevelWindowActivationState(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getTopLevelWindowActivationEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
@@ -208,16 +209,17 @@ static const NPUTF8* sPluginMethodIdenti
   "checkObjectValue",
   "enableFPExceptions",
   "setCookie",
   "getCookie",
   "getAuthInfo",
   "asyncCallbackTest",
   "checkGCRace",
   "hang",
+  "stall",
   "getClipboardText",
   "callOnDestroy",
   "reinitWidget",
   "crashInNestedLoop",
   "destroySharedGfxStuff",
   "propertyAndMethod",
   "getTopLevelWindowActivationState",
   "getTopLevelWindowActivationEventCount",
@@ -273,16 +275,17 @@ static const ScriptableFunction sPluginM
   checkObjectValue,
   enableFPExceptions,
   setCookie,
   getCookie,
   getAuthInfo,
   asyncCallbackTest,
   checkGCRace,
   hangPlugin,
+  stallPlugin,
   getClipboardText,
   callOnDestroy,
   reinitWidget,
   crashPluginInNestedLoop,
   destroySharedGfxStuff,
   propertyAndMethod,
   getTopLevelWindowActivationState,
   getTopLevelWindowActivationEventCount,
@@ -3350,16 +3353,34 @@ hangPlugin(NPObject* npobj, const NPVari
 
   // NB: returning true here means that we weren't terminated, and
   // thus the hang detection/handling didn't work correctly.  The
   // test harness will succeed in calling this function, and the
   // test will fail.
   return true;
 }
 
+bool
+stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount,
+           NPVariant* result)
+{
+  uint32_t stallTimeSeconds = 0;
+  if ((argCount == 1) && NPVARIANT_IS_INT32(args[0])) {
+    stallTimeSeconds = (uint32_t) NPVARIANT_TO_INT32(args[0]);
+  }
+
+#ifdef XP_WIN
+  Sleep(stallTimeSeconds * 1000U);
+#else
+  sleep(stallTimeSeconds);
+#endif
+
+  return true;
+}
+
 #if defined(MOZ_WIDGET_GTK)
 bool
 getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount,
                  NPVariant* result)
 {
   NPP npp = static_cast<TestNPObject*>(npobj)->npp;
   InstanceData* id = static_cast<InstanceData*>(npp->pdata);
   string sel = pluginGetClipboardText(id);
--- a/dom/plugins/test/unit/head_plugins.js
+++ b/dom/plugins/test/unit/head_plugins.js
@@ -1,20 +1,22 @@
 /* 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/.
  */
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Promise.jsm");
 
 const gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 const gIsOSX = ("nsILocalFileMac" in Ci);
 const gIsLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc) ||
   ("@mozilla.org/gio-service;1" in Cc);
+const gDirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
 
 // Finds the test plugin library
 function get_test_plugin() {
   var pluginEnum = gDirSvc.get("APluginsDL", Ci.nsISimpleEnumerator);
   while (pluginEnum.hasMoreElements()) {
     let dir = pluginEnum.getNext().QueryInterface(Ci.nsILocalFile);
     let plugin = dir.clone();
     // OSX plugin
@@ -37,17 +39,17 @@ function get_test_plugin() {
       plugin.normalize();
       return plugin;
     }
   }
   return null;
 }
 
 // Finds the test nsIPluginTag
-function get_test_plugintag(aName) {
+function get_test_plugintag(aName="Test Plug-in") {
   const Cc = Components.classes;
   const Ci = Components.interfaces;
 
   var name = aName || "Test Plug-in";
   var host = Cc["@mozilla.org/plugin/host;1"].
              getService(Ci.nsIPluginHost);
   var tags = host.getPluginTags();
 
@@ -114,8 +116,84 @@ function get_test_plugin_no_symlink() {
     let plugin = dir.clone();
     plugin.append(get_platform_specific_plugin_name());
     if (plugin.exists()) {
       return plugin;
     }
   }
   return null;
 }
+
+let gGlobalScope = this;
+function loadAddonManager() {
+  let ns = {};
+  Cu.import("resource://gre/modules/Services.jsm", ns);
+  let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js";
+  let file = do_get_file(head);
+  let uri = ns.Services.io.newFileURI(file);
+  ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope);
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+  startupManager();
+}
+
+// Install addon and return a Promise<boolean> that is
+// resolve with true on success, false otherwise.
+function installAddon(relativePath) {
+  let deferred = Promise.defer();
+  let success = () => deferred.resolve(true);
+  let fail = () => deferred.resolve(false);
+  let listener = {
+    onDownloadCancelled: fail,
+    onDownloadFailed: fail,
+    onInstallCancelled: fail,
+    onInstallFailed: fail,
+    onInstallEnded: success,
+  };
+
+  let installCallback = install => {
+    install.addListener(listener);
+    install.install();
+  };
+
+  let file = do_get_file(relativePath, false);
+  AddonManager.getInstallForFile(file, installCallback,
+                                 "application/x-xpinstall");
+
+  return deferred.promise;
+}
+
+// Uninstall addon and return a Promise<boolean> that is
+// resolve with true on success, false otherwise.
+function uninstallAddon(id) {
+  let deferred = Promise.defer();
+
+  AddonManager.getAddonByID(id, addon => {
+    if (!addon) {
+      deferred.resolve(false);
+    }
+
+    let listener = {};
+    let handler = addon => {
+      if (addon.id !== id) {
+        return;
+      }
+
+      AddonManager.removeAddonListener(listener);
+      deferred.resolve(true);
+    };
+
+    listener.onUninstalled = handler;
+    listener.onDisabled = handler;
+
+    AddonManager.addAddonListener(listener);
+    addon.uninstall();
+  });
+
+  return deferred.promise;
+}
+
+// Returns a Promise<Addon> that is resolved with
+// the corresponding addon or rejected.
+function getAddonByID(id) {
+  let deferred = Promise.defer();
+  AddonManager.getAddonByID(id, addon => deferred.resolve(addon));
+  return deferred.promise;
+}
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/unit/test_plugin_default_state_xpi.js
@@ -0,0 +1,108 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const ADDON_ID = "test-plugin-from-xpi@tests.mozilla.org";
+const XRE_EXTENSIONS_DIR_LIST = "XREExtDL";
+const NS_APP_PLUGINS_DIR_LIST = "APluginsDL";
+
+const gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+const gXPCOMABI = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).XPCOMABI;
+let gProfileDir = null;
+
+function getAddonRoot(profileDir, id) {
+  let dir = profileDir.clone();
+  dir.append("extensions");
+  Assert.ok(dir.exists(), "Extensions dir should exist: " + dir.path);
+  dir.append(id);
+  return dir;
+}
+
+function getTestaddonFilename() {
+  let abiPart = "";
+  if (gIsOSX) {
+    abiPart = "_" + gXPCOMABI;
+  }
+  return "testaddon" + abiPart + ".xpi";
+}
+
+function run_test() {
+  loadAddonManager();
+  gProfileDir = do_get_profile();
+  do_register_cleanup(() => shutdownManager());
+  run_next_test();
+}
+
+add_task(function* test_state() {
+  // Remove test so we will have only one "Test Plug-in" registered.
+  // xpcshell tests have plugins in per-test profiles, so that's fine.
+  let file = get_test_plugin();
+  file.remove(true);
+
+  let success = yield installAddon(getTestaddonFilename());
+  Assert.ok(success, "Should have installed addon.");
+  let addonDir = getAddonRoot(gProfileDir, ADDON_ID);
+
+  let provider = {
+    classID: Components.ID("{0af6b2d7-a06c-49b7-babc-636d292b0dbb}"),
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
+                                           Ci.nsIDirectoryServiceProvider2]),
+
+    getFile: function (prop, persistant) {
+      throw Cr.NS_ERROR_FAILURE;
+    },
+
+    getFiles: function (prop) {
+      let result = [];
+
+      switch (prop) {
+      case XRE_EXTENSIONS_DIR_LIST:
+        result.push(addonDir);
+        break;
+      case NS_APP_PLUGINS_DIR_LIST:
+        let pluginDir = addonDir.clone();
+        pluginDir.append("plugins");
+        result.push(pluginDir);
+        break;
+      default:
+        throw Cr.NS_ERROR_FAILURE;
+      }
+
+      return {
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsISimpleEnumerator]),
+        hasMoreElements: () => result.length > 0,
+        getNext: () => result.shift(),
+      };
+    },
+  };
+
+  let dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+  dirSvc.QueryInterface(Ci.nsIDirectoryService).registerProvider(provider);
+
+  // We installed a non-restartless addon, need to restart the manager.
+  restartManager();
+  gPluginHost.reloadPlugins();
+
+  Assert.ok(addonDir.exists(), "Addon path should exist: " + addonDir.path);
+  Assert.ok(addonDir.isDirectory(), "Addon path should be a directory: " + addonDir.path);
+  let pluginDir = addonDir.clone();
+  pluginDir.append("plugins");
+  Assert.ok(pluginDir.exists(), "Addon plugins path should exist: " + pluginDir.path);
+  Assert.ok(pluginDir.isDirectory(), "Addon plugins path should be a directory: " + pluginDir.path);
+
+  let addon = yield getAddonByID(ADDON_ID);
+  Assert.ok(!addon.appDisabled, "Addon should not be appDisabled");
+  Assert.ok(addon.isActive, "Addon should be active");
+  Assert.ok(addon.isCompatible, "Addon should be compatible");
+  Assert.ok(!addon.userDisabled, "Addon should not be user disabled");
+
+  let testPlugin = get_test_plugintag();
+  Assert.notEqual(testPlugin, null, "Test plugin should have been found");
+  Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Test plugin from addon should have state enabled");
+
+  pluginDir.append(testPlugin.filename);
+  Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path);
+});
--- a/dom/plugins/test/unit/xpcshell.ini
+++ b/dom/plugins/test/unit/xpcshell.ini
@@ -12,8 +12,9 @@ fail-if = os == "android"
 # Bug 676953: test fails consistently on Android
 fail-if = os == "android"
 [test_nice_plugin_name.js]
 # Bug 676953: test fails consistently on Android
 fail-if = os == "android"
 [test_persist_in_prefs.js]
 [test_bug854467.js]
 [test_plugin_default_state.js]
+[test_plugin_default_state_xpi.js]
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -65,16 +65,17 @@ const RILNETWORKINTERFACE_CID =
 const GSMICCINFO_CID =
   Components.ID("{d90c4261-a99d-47bc-8b05-b057bb7e8f8a}");
 const CDMAICCINFO_CID =
   Components.ID("{39ba3c08-aacc-46d0-8c04-9b619c387061}");
 
 const NS_XPCOM_SHUTDOWN_OBSERVER_ID      = "xpcom-shutdown";
 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
 const kNetworkConnStateChangedTopic      = "network-connection-state-changed";
+const kNetworkActiveChangedTopic         = "network-active-changed";
 const kSmsReceivedObserverTopic          = "sms-received";
 const kSilentSmsReceivedObserverTopic    = "silent-sms-received";
 const kSmsSendingObserverTopic           = "sms-sending";
 const kSmsSentObserverTopic              = "sms-sent";
 const kSmsFailedObserverTopic            = "sms-failed";
 const kSmsDeliverySuccessObserverTopic   = "sms-delivery-success";
 const kSmsDeliveryErrorObserverTopic     = "sms-delivery-error";
 const kMozSettingsChangedObserverTopic   = "mozsettings-changed";
@@ -1387,22 +1388,16 @@ DataConnectionHandler.prototype = {
     // We just call connect() function, so this interface should be in
     // connecting state. If this interface is already in connected state, we
     // are sure that this interface have successfully established connection
     // for other data call types before we call connect() function for current
     // data call type. In this circumstance, we have to directly update the
     // necessary data call and interface information to RILContentHelper
     // and network manager for current data call type.
     if (apnSetting.iface.connected) {
-      if (apnType == "default" && !dataInfo.connected) {
-        dataInfo.connected = true;
-        gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged",
-                                                    this.clientId, dataInfo);
-      }
-
       // Update the interface status via-registration if the interface has
       // already been registered in the network manager.
       if (apnSetting.iface.name in gNetworkManager.networkInterfaces) {
         gNetworkManager.unregisterNetworkInterface(apnSetting.iface);
       }
       gNetworkManager.registerNetworkInterface(apnSetting.iface);
 
       Services.obs.notifyObservers(apnSetting.iface,
@@ -1426,23 +1421,16 @@ DataConnectionHandler.prototype = {
     apnSetting.iface.disconnect(apnType);
     // We just call disconnect() function, so this interface should be in
     // disconnecting state. If this interface is still in connected state, we
     // are sure that other data call types still need this connection of this
     // interface. In this circumstance, we have to directly update the
     // necessary data call and interface information to RILContentHelper
     // and network manager for current data call type.
     if (apnSetting.iface.connectedTypes.length && apnSetting.iface.connected) {
-      let dataInfo = this.radioInterface.rilContext.data;
-      if (apnType == "default" && dataInfo.connected) {
-        dataInfo.connected = false;
-        gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged",
-                                                    this.clientId, dataInfo);
-      }
-
       // Update the interface status via-registration if the interface has
       // already been registered in the network manager.
       if (apnSetting.iface.name in gNetworkManager.networkInterfaces) {
         gNetworkManager.unregisterNetworkInterface(apnSetting.iface);
       }
       gNetworkManager.registerNetworkInterface(apnSetting.iface);
 
       Services.obs.notifyObservers(apnSetting.iface,
@@ -1506,35 +1494,16 @@ DataConnectionHandler.prototype = {
 
     this._deliverDataCallCallback("dataCallError", [message]);
   },
 
   /**
    * Handle data call state changes.
    */
   handleDataCallState: function(datacall) {
-    let data = this.radioInterface.rilContext.data;
-    let defaultApnSetting = this.apnSettings && this.apnSettings.byType.default;
-    let dataCallConnected =
-        (datacall.state == RIL.GECKO_NETWORK_STATE_CONNECTED);
-    if (defaultApnSetting && datacall.ifname) {
-      if (dataCallConnected && datacall.apn == defaultApnSetting.apn &&
-          defaultApnSetting.iface.inConnectedTypes("default")) {
-        data.connected = dataCallConnected;
-        gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged",
-                                                     this.clientId, data);
-        data.apn = datacall.apn;
-      } else if (!dataCallConnected && datacall.apn == data.apn) {
-        data.connected = dataCallConnected;
-        delete data.apn;
-        gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged",
-                                                     this.clientId, data);
-      }
-    }
-
     this._deliverDataCallCallback("dataCallStateChanged", [datacall]);
 
     // Process pending radio power off request after all data calls
     // are disconnected.
     if (datacall.state == RIL.GECKO_NETWORK_STATE_UNKNOWN &&
         this.allDataDisconnected()) {
       if (gRadioEnabledController.isDeactivatingDataCalls()) {
         if (DEBUG) {
@@ -1873,16 +1842,17 @@ function RadioInterface(aClientId, aWork
   // ranges separated by comma, to set listening channels.
   lock.get(kSettingsCellBroadcastSearchList, this);
 
   Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
   Services.obs.addObserver(this, kSysClockChangeObserverTopic, false);
   Services.obs.addObserver(this, kScreenStateChangedTopic, false);
 
   Services.obs.addObserver(this, kNetworkConnStateChangedTopic, false);
+  Services.obs.addObserver(this, kNetworkActiveChangedTopic, false);
   Services.prefs.addObserver(kPrefCellBroadcastDisabled, this, false);
 
   this.portAddressedSmsApps = {};
   this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
 
   this._receivedSmsSegmentsMap = {};
 
   this._sntp = new Sntp(this.setClockBySntp.bind(this),
@@ -1914,16 +1884,17 @@ RadioInterface.prototype = {
   shutdown: function() {
     // Release the CPU wake lock for handling the received SMS.
     this._releaseSmsHandledWakeLock();
 
     Services.obs.removeObserver(this, kMozSettingsChangedObserverTopic);
     Services.obs.removeObserver(this, kSysClockChangeObserverTopic);
     Services.obs.removeObserver(this, kScreenStateChangedTopic);
     Services.obs.removeObserver(this, kNetworkConnStateChangedTopic);
+    Services.obs.removeObserver(this, kNetworkActiveChangedTopic);
   },
 
   /**
    * A utility function to copy objects. The srcInfo may contain
    * "rilMessageType", should ignore it.
    */
   updateInfo: function(srcInfo, destInfo) {
     for (let key in srcInfo) {
@@ -2445,23 +2416,22 @@ RadioInterface.prototype = {
   updateDataConnection: function(newInfo, batch) {
     let dataInfo = this.rilContext.data;
     dataInfo.state = newInfo.state;
     dataInfo.roaming = newInfo.roaming;
     dataInfo.emergencyCallsOnly = newInfo.emergencyCallsOnly;
     dataInfo.type = newInfo.type;
     // For the data connection, the `connected` flag indicates whether
     // there's an active data call.
-    let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId);
-    let apnSettings = connHandler.apnSettings;
-    let apnSetting = apnSettings && apnSettings.byType.default;
     dataInfo.connected = false;
-    if (apnSetting) {
-      dataInfo.connected = (connHandler.getDataCallStateByType("default") ==
-                            RIL.GECKO_NETWORK_STATE_CONNECTED);
+    if (gNetworkManager.active &&
+        gNetworkManager.active.type ===
+          Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
+        gNetworkManager.active.serviceId === this.clientId) {
+      dataInfo.connected = true;
     }
 
     // Make sure we also reset the operator and signal strength information
     // if we drop off the network.
     if (newInfo.state !== RIL.GECKO_MOBILE_CONNECTION_STATE_REGISTERED) {
       dataInfo.cell = null;
       dataInfo.network = null;
       dataInfo.signalStrength = null;
@@ -2470,16 +2440,18 @@ RadioInterface.prototype = {
       dataInfo.cell = newInfo.cell;
       dataInfo.network = this.operatorInfo;
     }
 
     if (!batch) {
       gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged",
                                                   this.clientId, dataInfo);
     }
+
+    let connHandler = gDataConnectionManager.getConnectionHandler(this.clientId);
     connHandler.updateRILNetworkInterface();
   },
 
   getPreferredNetworkType: function(target, message) {
     this.workerMessenger.send("getPreferredNetworkType", message, (function(response) {
       if (response.success) {
         response.type = RIL.RIL_PREFERRED_NETWORK_TYPE_TO_GECKO[response.networkType];
       }
@@ -3359,16 +3331,31 @@ RadioInterface.prototype = {
           }
         }
 
         // SNTP won't update unless the SNTP is already expired.
         if (this._sntp.isExpired()) {
           this._sntp.request();
         }
         break;
+      case kNetworkActiveChangedTopic:
+        let dataInfo = this.rilContext.data;
+        let connected = false;
+        if (gNetworkManager.active &&
+            gNetworkManager.active.type ===
+              Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
+            gNetworkManager.active.serviceId === this.clientId) {
+          connected = true;
+        }
+        if (dataInfo.connected !== connected) {
+          dataInfo.connected = connected;
+          gMessageManager.sendMobileConnectionMessage("RIL:DataInfoChanged",
+                                                      this.clientId, dataInfo);
+        }
+        break;
       case kScreenStateChangedTopic:
         this.workerMessenger.send("setScreenState", { on: (data === "on") });
         break;
     }
   },
 
   supportedNetworkTypes: null,
 
--- a/js/src/assembler/assembler/X86Assembler.h
+++ b/js/src/assembler/assembler/X86Assembler.h
@@ -361,16 +361,17 @@ private:
 
         GROUP2_OP_SHL = 4,
         GROUP2_OP_SHR = 5,
         GROUP2_OP_SAR = 7,
 
         GROUP3_OP_TEST = 0,
         GROUP3_OP_NOT  = 2,
         GROUP3_OP_NEG  = 3,
+        GROUP3_OP_IMUL = 5,
         GROUP3_OP_DIV  = 6,
         GROUP3_OP_IDIV = 7,
 
         GROUP5_OP_INC   = 0,
         GROUP5_OP_DEC   = 1,
         GROUP5_OP_CALLN = 2,
         GROUP5_OP_JMPN  = 4,
         GROUP5_OP_PUSH  = 6,
@@ -1150,16 +1151,23 @@ public:
 #endif
 
     void imull_rr(RegisterID src, RegisterID dst)
     {
         spew("imull      %s, %s", nameIReg(4,src), nameIReg(4, dst));
         m_formatter.twoByteOp(OP2_IMUL_GvEv, dst, src);
     }
 
+    void imull_r(RegisterID multiplier)
+    {
+        spew("imull      %s",
+             nameIReg(4, multiplier));
+        m_formatter.oneByteOp(OP_GROUP3_Ev, GROUP3_OP_IMUL, multiplier);
+    }
+
     void imull_mr(int offset, RegisterID base, RegisterID dst)
     {
         spew("imull      %s0x%x(%s), %s",
              PRETTY_PRINT_OFFSET(offset), nameIReg(base), nameIReg(4,dst));
         m_formatter.twoByteOp(OP2_IMUL_GvEv, dst, base, offset);
     }
 
     void imull_i32r(RegisterID src, int32_t value, RegisterID dst)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testDivModWithIntMin.js
@@ -0,0 +1,19 @@
+var intMin = -2147483648;
+
+assertEq(intMin % (-2147483648), -0);
+assertEq(intMin % (-3), -2);
+assertEq(intMin % (-1), -0);
+assertEq(intMin % 1, -0);
+assertEq(intMin % 3, -2);
+assertEq(intMin % 10, -8);
+assertEq(intMin % 2147483647, -1);
+
+assertEq((-2147483648) % (-2147483648), -0);
+for (var i = -10; i <= 10; ++i)
+    assertEq(i % (-2147483648), i);
+assertEq(2147483647 % (-2147483648), 2147483647);
+
+assertEq((-2147483648) / (-2147483648), 1);
+assertEq(0 / (-2147483648), -0);
+assertEq((-2147483648) / (-1), 2147483648);
+assertEq((-0) / (-2147483648), 0);
--- a/js/src/jit/BaselineBailouts.cpp
+++ b/js/src/jit/BaselineBailouts.cpp
@@ -1334,36 +1334,36 @@ jit::BailoutIonToBaseline(JSContext *cx,
     BaselineStackBuilder builder(iter, 1024);
     if (!builder.init())
         return BAILOUT_RETURN_FATAL_ERROR;
     IonSpew(IonSpew_BaselineBailouts, "  Incoming frame ptr = %p", builder.startFrame());
 
     SnapshotIterator snapIter(iter);
 
     RootedFunction callee(cx, iter.maybeCallee());
+    RootedScript scr(cx, iter.script());
     if (callee) {
         IonSpew(IonSpew_BaselineBailouts, "  Callee function (%s:%u)",
-                callee->existingScript()->filename(), callee->existingScript()->lineno());
+                scr->filename(), scr->lineno());
     } else {
         IonSpew(IonSpew_BaselineBailouts, "  No callee!");
     }
 
     if (iter.isConstructing())
         IonSpew(IonSpew_BaselineBailouts, "  Constructing!");
     else
         IonSpew(IonSpew_BaselineBailouts, "  Not constructing!");
 
     IonSpew(IonSpew_BaselineBailouts, "  Restoring frames:");
     size_t frameNo = 0;
 
     // Reconstruct baseline frames using the builder.
     RootedScript caller(cx);
     jsbytecode *callerPC = nullptr;
     RootedFunction fun(cx, callee);
-    RootedScript scr(cx, iter.script());
     AutoValueVector startFrameFormals(cx);
 
     RootedScript topCaller(cx);
     jsbytecode *topCallerPC = nullptr;
 
     while (true) {
         MOZ_ASSERT(snapIter.instruction()->isResumePoint());
 
@@ -1395,17 +1395,17 @@ jit::BailoutIonToBaseline(JSContext *cx,
         if (handleException)
             break;
 
         JS_ASSERT(nextCallee);
         JS_ASSERT(callPC);
         caller = scr;
         callerPC = callPC;
         fun = nextCallee;
-        scr = fun->existingScript();
+        scr = fun->existingScriptForInlinedFunction();
 
         // Save top caller info for adjusting SPS frames later.
         if (!topCaller) {
             JS_ASSERT(frameNo == 0);
             topCaller = caller;
             topCallerPC = callerPC;
         }
 
--- a/js/src/jit/IonFrames.cpp
+++ b/js/src/jit/IonFrames.cpp
@@ -1647,17 +1647,17 @@ InlineFrameIteratorMaybeGC<allowGC>::fin
 
         si_.nextFrame();
 
         callee_ = &funval.toObject().as<JSFunction>();
 
         // Inlined functions may be clones that still point to the lazy script
         // for the executed script, if they are clones. The actual script
         // exists though, just make sure the function points to it.
-        script_ = callee_->existingScript();
+        script_ = callee_->existingScriptForInlinedFunction();
         MOZ_ASSERT(script_->hasBaselineScript());
 
         pc_ = script_->offsetToPC(si_.pcOffset());
     }
 
     // The first time we do not know the number of frames, we only settle on the
     // last frame, and update the number of frames based on the number of
     // iteration that we have done.
--- a/js/src/jit/shared/Assembler-x86-shared.h
+++ b/js/src/jit/shared/Assembler-x86-shared.h
@@ -1057,22 +1057,28 @@ class AssemblerX86Shared
             break;
           case Operand::MEM_REG_DISP:
             masm.andl_mr(src.disp(), src.base(), dest.code());
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("unexpected operand kind");
         }
     }
+    void imull(const Register &multiplier) {
+        masm.imull_r(multiplier.code());
+    }
     void imull(Imm32 imm, const Register &dest) {
         masm.imull_i32r(dest.code(), imm.value, dest.code());
     }
     void imull(const Register &src, const Register &dest) {
         masm.imull_rr(src.code(), dest.code());
     }
+    void imull(Imm32 imm, const Register &src, const Register &dest) {
+        masm.imull_i32r(src.code(), imm.value, dest.code());
+    }
     void imull(const Operand &src, const Register &dest) {
         switch (src.kind()) {
           case Operand::REG:
             masm.imull_rr(src.reg(), dest.code());
             break;
           case Operand::MEM_REG_DISP:
             masm.imull_mr(src.disp(), src.base(), dest.code());
             break;
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1002,16 +1002,84 @@ CodeGeneratorShared::addCacheLocations(c
         new (&runtimeData_[curIndex]) CacheLocation(iter->pc, iter->script);
         numLocations++;
     }
     JS_ASSERT(numLocations != 0);
     *numLocs = numLocations;
     return firstIndex;
 }
 
+ReciprocalMulConstants
+CodeGeneratorShared::computeDivisionConstants(int d) {
+    // In what follows, d is positive and is not a power of 2.
+    JS_ASSERT(d > 0 && (d & (d - 1)) != 0);
+
+    // Speeding up division by non power-of-2 constants is possible by
+    // calculating, during compilation, a value M such that high-order
+    // bits of M*n correspond to the result of the division. Formally,
+    // we compute values 0 <= M < 2^32 and 0 <= s < 31 such that
+    //         (M * n) >> (32 + s) = floor(n/d)    if n >= 0
+    //         (M * n) >> (32 + s) = ceil(n/d) - 1 if n < 0.
+    // The original presentation of this technique appears in Hacker's
+    // Delight, a book by Henry S. Warren, Jr.. A proof of correctness
+    // for our version follows.
+
+    // Define p = 32 + s, M = ceil(2^p/d), and assume that s satisfies
+    //                     M - 2^p/d <= 2^(s+1)/d.                 (1)
+    // (Observe that s = FloorLog32(d) satisfies this, because in this
+    // case d <= 2^(s+1) and so the RHS of (1) is at least one). Then,
+    //
+    // a) If s <= FloorLog32(d), then M <= 2^32 - 1.
+    // Proof: Indeed, M is monotone in s and, for s = FloorLog32(d),
+    // the inequalities 2^31 > d >= 2^s + 1 readily imply
+    //    2^p / d  = 2^p/(d - 1) * (d - 1)/d
+    //            <= 2^32 * (1 - 1/d) < 2 * (2^31 - 1) = 2^32 - 2.
+    // The claim follows by applying the ceiling function.
+    //
+    // b) For any 0 <= n < 2^31, floor(Mn/2^p) = floor(n/d).
+    // Proof: Put x = floor(Mn/2^p); it's the unique integer for which
+    //                    Mn/2^p - 1 < x <= Mn/2^p.                (2)
+    // Using M >= 2^p/d on the LHS and (1) on the RHS, we get
+    //           n/d - 1 < x <= n/d + n/(2^31 d) < n/d + 1/d.
+    // Since x is an integer, it's not in the interval (n/d, (n+1)/d),
+    // and so n/d - 1 < x <= n/d, which implies x = floor(n/d).
+    //
+    // c) For any -2^31 <= n < 0, floor(Mn/2^p) + 1 = ceil(n/d).
+    // Proof: The proof is similar. Equation (2) holds as above. Using
+    // M > 2^p/d (d isn't a power of 2) on the RHS and (1) on the LHS,
+    //                 n/d + n/(2^31 d) - 1 < x < n/d.
+    // Using n >= -2^31 and summing 1,
+    //                  n/d - 1/d < x + 1 < n/d + 1.
+    // Since x + 1 is an integer, this implies n/d <= x + 1 < n/d + 1.
+    // In other words, x + 1 = ceil(n/d).
+    //
+    // Condition (1) isn't necessary for the existence of M and s with
+    // the properties above. Hacker's Delight provides a slightly less
+    // restrictive condition when d >= 196611, at the cost of a 3-page
+    // proof of correctness.
+
+    // Note that, since d*M - 2^p = d - (2^p)%d, (1) can be written as
+    //                   2^(s+1) >= d - (2^p)%d.
+    // We now compute the least s with this property...
+
+    int32_t shift = 0;
+    while ((int64_t(1) << (shift+1)) + (int64_t(1) << (shift+32)) % d < d)
+        shift++;
+
+    // ...and the corresponding M. This may not fit in a signed 32-bit
+    // integer; we will compute (M - 2^32) * n + (2^32 * n) instead of
+    // M * n if this is the case (cf. item (a) above).
+    ReciprocalMulConstants rmc;
+    rmc.multiplier = int32_t((int64_t(1) << (shift+32))/d + 1);
+    rmc.shift_amount = shift;
+
+    return rmc;
+}
+
+
 #ifdef JS_TRACE_LOGGING
 
 bool
 CodeGeneratorShared::emitTracelogScript(bool isStart)
 {
     RegisterSet regs = RegisterSet::Volatile();
     Register logger = regs.takeGeneral();
     Register script = regs.takeGeneral();
--- a/js/src/jit/shared/CodeGenerator-shared.h
+++ b/js/src/jit/shared/CodeGenerator-shared.h
@@ -40,16 +40,21 @@ struct PatchableBackedgeInfo
     Label *loopHeader;
     Label *interruptCheck;
 
     PatchableBackedgeInfo(CodeOffsetJump backedge, Label *loopHeader, Label *interruptCheck)
       : backedge(backedge), loopHeader(loopHeader), interruptCheck(interruptCheck)
     {}
 };
 
+struct ReciprocalMulConstants {
+    int32_t multiplier;
+    int32_t shift_amount;
+};
+
 class CodeGeneratorShared : public LInstructionVisitor
 {
     js::Vector<OutOfLineCode *, 0, SystemAllocPolicy> outOfLineCode_;
     OutOfLineCode *oolIns;
 
     MacroAssembler &ensureMasm(MacroAssembler *masm);
     mozilla::Maybe<MacroAssembler> maybeMasm_;
 
@@ -397,16 +402,17 @@ class CodeGeneratorShared : public LInst
     inline OutOfLineCode *oolCallVM(const VMFunctionsModal &f, LInstruction *ins,
                                     const ArgSeq &args, const StoreOutputTo &out)
     {
         return oolCallVM(f[gen->info().executionMode()], ins, args, out);
     }
 
     bool addCache(LInstruction *lir, size_t cacheIndex);
     size_t addCacheLocations(const CacheLocationList &locs, size_t *numLocs);
+    ReciprocalMulConstants computeDivisionConstants(int d);
 
   protected:
     bool addOutOfLineCode(OutOfLineCode *code);
     bool hasOutOfLineCode() { return !outOfLineCode_.empty(); }
     bool generateOutOfLineCode();
 
     Label *labelForBackedgeWithImplicitCheck(MBasicBlock *mir);
 
--- a/js/src/jit/shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.cpp
@@ -17,16 +17,17 @@
 #include "jit/RangeAnalysis.h"
 #include "vm/TraceLogging.h"
 
 #include "jit/shared/CodeGenerator-shared-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
+using mozilla::Abs;
 using mozilla::FloatingPoint;
 using mozilla::FloorLog2;
 using mozilla::NegativeInfinity;
 using mozilla::SpecificNaN;
 
 namespace js {
 namespace jit {
 
@@ -864,49 +865,142 @@ CodeGeneratorX86Shared::visitMulNegative
     return true;
 }
 
 bool
 CodeGeneratorX86Shared::visitDivPowTwoI(LDivPowTwoI *ins)
 {
     Register lhs = ToRegister(ins->numerator());
     mozilla::DebugOnly<Register> output = ToRegister(ins->output());
+
     int32_t shift = ins->shift();
+    bool negativeDivisor = ins->negativeDivisor();
+    MDiv *mir = ins->mir();
 
     // We use defineReuseInput so these should always be the same, which is
     // convenient since all of our instructions here are two-address.
     JS_ASSERT(lhs == output);
 
+    if (!mir->isTruncated() && negativeDivisor) {
+        // 0 divided by a negative number must return a double.
+        masm.testl(lhs, lhs);
+        if (!bailoutIf(Assembler::Zero, ins->snapshot()))
+            return false;
+    }
+
     if (shift != 0) {
-        MDiv *mir = ins->mir();
         if (!mir->isTruncated()) {
             // If the remainder is != 0, bailout since this must be a double.
             masm.testl(lhs, Imm32(UINT32_MAX >> (32 - shift)));
             if (!bailoutIf(Assembler::NonZero, ins->snapshot()))
                 return false;
         }
 
-        if (!mir->canBeNegativeDividend()) {
-            // Numerator is unsigned, so needs no adjusting. Do the shift.
-            masm.sarl(Imm32(shift), lhs);
-            return true;
-        }
-
         // Adjust the value so that shifting produces a correctly rounded result
         // when the numerator is negative. See 10-1 "Signed Division by a Known
         // Power of 2" in Henry S. Warren, Jr.'s Hacker's Delight.
-        Register lhsCopy = ToRegister(ins->numeratorCopy());
-        JS_ASSERT(lhsCopy != lhs);
-        if (shift > 1)
-            masm.sarl(Imm32(31), lhs);
-        masm.shrl(Imm32(32 - shift), lhs);
-        masm.addl(lhsCopy, lhs);
+        if (mir->canBeNegativeDividend()) {
+            Register lhsCopy = ToRegister(ins->numeratorCopy());
+            JS_ASSERT(lhsCopy != lhs);
+            if (shift > 1)
+                masm.sarl(Imm32(31), lhs);
+            masm.shrl(Imm32(32 - shift), lhs);
+            masm.addl(lhsCopy, lhs);
+        }
+
+        masm.sarl(Imm32(shift), lhs);
+        if (negativeDivisor)
+            masm.negl(lhs);
+    } else if (shift == 0 && negativeDivisor) {
+        // INT32_MIN / -1 overflows.
+        masm.negl(lhs);
+        if (!mir->isTruncated() && !bailoutIf(Assembler::Overflow, ins->snapshot()))
+            return false;
+    }
+
+    return true;
+}
+
+bool
+CodeGeneratorX86Shared::visitDivOrModConstantI(LDivOrModConstantI *ins) {
+    Register lhs = ToRegister(ins->numerator());
+    Register output = ToRegister(ins->output());
+    int32_t d = ins->denominator();
+
+    // This emits the division answer into edx or the modulus answer into eax.
+    JS_ASSERT(output == eax || output == edx);
+    JS_ASSERT(lhs != eax && lhs != edx);
+
+    // The absolute value of the denominator isn't a power of 2 (see LDivPowTwoI
+    // and LModPowTwoI).
+    JS_ASSERT((Abs(d) & (Abs(d) - 1)) != 0);
+
+    // We will first divide by Abs(d), and negate the answer if d is negative.
+    // If desired, this can be avoided by generalizing computeDivisionConstants.
+    ReciprocalMulConstants rmc = computeDivisionConstants(Abs(d));
 
-        // Do the shift.
-        masm.sarl(Imm32(shift), lhs);
+    // As explained in the comments of computeDivisionConstants, we first compute
+    // X >> (32 + shift), where X is either (rmc.multiplier * n) if the multiplier
+    // is non-negative or (rmc.multiplier * n) + (2^32 * n) otherwise. This is the
+    // desired division result if n is non-negative, and is one less than the result
+    // otherwise.
+    masm.movl(lhs, eax);
+    masm.movl(Imm32(rmc.multiplier), edx);
+    masm.imull(edx);
+    if (rmc.multiplier < 0)
+        masm.addl(lhs, edx);
+    masm.sarl(Imm32(rmc.shift_amount), edx);
+
+    // We'll subtract -1 instead of adding 1, because (n < 0 ? -1 : 0) can be
+    // computed with just a sign-extending shift of 31 bits.
+    if (ins->canBeNegativeDividend()) {
+        masm.movl(lhs, eax);
+        masm.sarl(Imm32(31), eax);
+        masm.subl(eax, edx);
+    }
+
+    // After this, edx contains the correct division result.
+    if (d < 0)
+        masm.negl(edx);
+
+    if (output == eax) {
+        masm.imull(Imm32(-d), edx, eax);
+        masm.addl(lhs, eax);
+    }
+
+    if (!ins->mir()->isTruncated()) {
+        if (output == edx) {
+            // This is a division op. Multiply the obtained value by d to check if
+            // the correct answer is an integer. This cannot overflow, since |d| > 1.
+            masm.imull(Imm32(d), edx, eax);
+            masm.cmpl(lhs, eax);
+            if (!bailoutIf(Assembler::NotEqual, ins->snapshot()))
+                return false;
+
+            // If lhs is zero and the divisor is negative, the answer should have
+            // been -0.
+            if (d < 0) {
+                masm.testl(lhs, lhs);
+                if (!bailoutIf(Assembler::Zero, ins->snapshot()))
+                    return false;
+            }
+        } else if (ins->canBeNegativeDividend()) {
+            // This is a mod op. If the computed value is zero and lhs
+            // is negative, the answer should have been -0.
+            Label done;
+
+            masm.cmpl(lhs, Imm32(0));
+            masm.j(Assembler::GreaterThanOrEqual, &done);
+
+            masm.testl(eax, eax);
+            if (!bailoutIf(Assembler::Zero, ins->snapshot()))
+                return false;
+
+            masm.bind(&done);
+        }
     }
 
     return true;
 }
 
 bool
 CodeGeneratorX86Shared::visitDivI(LDivI *ins)
 {
@@ -1007,29 +1101,37 @@ CodeGeneratorX86Shared::visitModPowTwoI(
     Label negative;
 
     if (ins->mir()->canBeNegativeDividend()) {
         // Switch based on sign of the lhs.
         // Positive numbers are just a bitmask
         masm.branchTest32(Assembler::Signed, lhs, lhs, &negative);
     }
 
-    masm.andl(Imm32((1 << shift) - 1), lhs);
+    masm.andl(Imm32((uint32_t(1) << shift) - 1), lhs);
 
     if (ins->mir()->canBeNegativeDividend()) {
         Label done;
         masm.jump(&done);
 
         // Negative numbers need a negate, bitmask, negate
         masm.bind(&negative);
-        // visitModI has an overflow check here to catch INT_MIN % -1, but
-        // here the rhs is a power of 2, and cannot be -1, so the check is not generated.
+
+        // Unlike in the visitModI case, we are not computing the mod by means of a
+        // division. Therefore, the divisor = -1 case isn't problematic (the andl
+        // always returns 0, which is what we expect).
+        //
+        // The negl instruction overflows if lhs == INT32_MIN, but this is also not
+        // a problem: shift is at most 31, and so the andl also always returns 0.
         masm.negl(lhs);
-        masm.andl(Imm32((1 << shift) - 1), lhs);
+        masm.andl(Imm32((uint32_t(1) << shift) - 1), lhs);
         masm.negl(lhs);
+
+        // Since a%b has the same sign as b, and a is negative in this branch,
+        // an answer of 0 means the correct result is actually -0. Bail out.
         if (!ins->mir()->isTruncated() && !bailoutIf(Assembler::Zero, ins->snapshot()))
             return false;
         masm.bind(&done);
     }
     return true;
 
 }
 
--- a/js/src/jit/shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/shared/CodeGenerator-x86-shared.h
@@ -119,16 +119,17 @@ class CodeGeneratorX86Shared : public Co
     virtual bool visitSqrtD(LSqrtD *ins);
     virtual bool visitSqrtF(LSqrtF *ins);
     virtual bool visitPowHalfD(LPowHalfD *ins);
     virtual bool visitAddI(LAddI *ins);
     virtual bool visitSubI(LSubI *ins);
     virtual bool visitMulI(LMulI *ins);
     virtual bool visitDivI(LDivI *ins);
     virtual bool visitDivPowTwoI(LDivPowTwoI *ins);
+    virtual bool visitDivOrModConstantI(LDivOrModConstantI *ins);
     virtual bool visitModI(LModI *ins);
     virtual bool visitModPowTwoI(LModPowTwoI *ins);
     virtual bool visitBitNotI(LBitNotI *ins);
     virtual bool visitBitOpI(LBitOpI *ins);
     virtual bool visitShiftI(LShiftI *ins);
     virtual bool visitUrshD(LUrshD *ins);
     virtual bool visitTestIAndBranch(LTestIAndBranch *test);
     virtual bool visitTestDAndBranch(LTestDAndBranch *test);
--- a/js/src/jit/shared/LIR-x86-shared.h
+++ b/js/src/jit/shared/LIR-x86-shared.h
@@ -42,41 +42,76 @@ class LDivI : public LBinaryMath<1>
         return mir_->toDiv();
     }
 };
 
 // Signed division by a power-of-two constant.
 class LDivPowTwoI : public LBinaryMath<0>
 {
     const int32_t shift_;
+    const bool negativeDivisor_;
 
   public:
     LIR_HEADER(DivPowTwoI)
 
-    LDivPowTwoI(const LAllocation &lhs, const LAllocation &lhsCopy, int32_t shift)
-      : shift_(shift)
+    LDivPowTwoI(const LAllocation &lhs, const LAllocation &lhsCopy, int32_t shift, bool negativeDivisor)
+      : shift_(shift), negativeDivisor_(negativeDivisor)
     {
         setOperand(0, lhs);
         setOperand(1, lhsCopy);
     }
 
     const LAllocation *numerator() {
         return getOperand(0);
     }
     const LAllocation *numeratorCopy() {
         return getOperand(1);
     }
     int32_t shift() const {
         return shift_;
     }
+    bool negativeDivisor() const {
+        return negativeDivisor_;
+    }
     MDiv *mir() const {
         return mir_->toDiv();
     }
 };
 
+class LDivOrModConstantI : public LInstructionHelper<1, 1, 1>
+{
+    const int32_t denominator_;
+
+  public:
+    LIR_HEADER(DivOrModConstantI)
+
+    LDivOrModConstantI(const LAllocation &lhs, int32_t denominator, const LDefinition& temp)
+    : denominator_(denominator)
+    {
+        setOperand(0, lhs);
+        setTemp(0, temp);
+    }
+
+    const LAllocation *numerator() {
+        return getOperand(0);
+    }
+    int32_t denominator() const {
+        return denominator_;
+    }
+    MBinaryArithInstruction *mir() const {
+        JS_ASSERT(mir_->isDiv() || mir_->isMod());
+        return static_cast<MBinaryArithInstruction *>(mir_);
+    }
+    bool canBeNegativeDividend() const {
+        if (mir_->isMod())
+            return mir_->toMod()->canBeNegativeDividend();
+        return mir_->toDiv()->canBeNegativeDividend();
+    }
+};
+
 class LModI : public LBinaryMath<1>
 {
   public:
     LIR_HEADER(ModI)
 
     LModI(const LAllocation &lhs, const LAllocation &rhs, const LDefinition &temp) {
         setOperand(0, lhs);
         setOperand(1, rhs);
--- a/js/src/jit/shared/Lowering-x86-shared.cpp
+++ b/js/src/jit/shared/Lowering-x86-shared.cpp
@@ -10,16 +10,17 @@
 
 #include "jit/MIR.h"
 
 #include "jit/shared/Lowering-shared-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
+using mozilla::Abs;
 using mozilla::FloorLog2;
 
 LTableSwitch *
 LIRGeneratorX86Shared::newLTableSwitch(const LAllocation &in, const LDefinition &inputCopy,
                                        MTableSwitch *tableswitch)
 {
     return new(alloc()) LTableSwitch(in, inputCopy, temp(), tableswitch);
 }
@@ -132,36 +133,39 @@ LIRGeneratorX86Shared::lowerDivI(MDiv *d
     if (div->isUnsigned())
         return lowerUDiv(div);
 
     // Division instructions are slow. Division by constant denominators can be
     // rewritten to use other instructions.
     if (div->rhs()->isConstant()) {
         int32_t rhs = div->rhs()->toConstant()->value().toInt32();
 
-        // Check for division by a positive power of two, which is an easy and
-        // important case to optimize. Note that other optimizations are also
-        // possible; division by negative powers of two can be optimized in a
-        // similar manner as positive powers of two, and division by other
-        // constants can be optimized by a reciprocal multiplication technique.
-        int32_t shift = FloorLog2(rhs);
-        if (rhs > 0 && 1 << shift == rhs) {
+        // Division by powers of two can be done by shifting, and division by
+        // other numbers can be done by a reciprocal multiplication technique.
+        int32_t shift = FloorLog2(Abs(rhs));
+        if (rhs != 0 && uint32_t(1) << shift == Abs(rhs)) {
             LAllocation lhs = useRegisterAtStart(div->lhs());
             LDivPowTwoI *lir;
             if (!div->canBeNegativeDividend()) {
                 // Numerator is unsigned, so does not need adjusting.
-                lir = new(alloc()) LDivPowTwoI(lhs, lhs, shift);
+                lir = new(alloc()) LDivPowTwoI(lhs, lhs, shift, rhs < 0);
             } else {
                 // Numerator is signed, and needs adjusting, and an extra
                 // lhs copy register is needed.
-                lir = new(alloc()) LDivPowTwoI(lhs, useRegister(div->lhs()), shift);
+                lir = new(alloc()) LDivPowTwoI(lhs, useRegister(div->lhs()), shift, rhs < 0);
             }
             if (div->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo))
                 return false;
             return defineReuseInput(lir, div, 0);
+        } else if (rhs != 0) {
+            LDivOrModConstantI *lir;
+            lir = new(alloc()) LDivOrModConstantI(useRegister(div->lhs()), rhs, tempFixed(eax));
+            if (div->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo))
+                return false;
+            return defineFixed(lir, div, LAllocation(AnyRegister(edx)));
         }
     }
 
     LDivI *lir = new(alloc()) LDivI(useRegister(div->lhs()), useRegister(div->rhs()),
                                     tempFixed(edx));
     if (div->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo))
         return false;
     return defineFixed(lir, div, LAllocation(AnyRegister(eax)));
@@ -170,22 +174,28 @@ LIRGeneratorX86Shared::lowerDivI(MDiv *d
 bool
 LIRGeneratorX86Shared::lowerModI(MMod *mod)
 {
     if (mod->isUnsigned())
         return lowerUMod(mod);
 
     if (mod->rhs()->isConstant()) {
         int32_t rhs = mod->rhs()->toConstant()->value().toInt32();
-        int32_t shift = FloorLog2(rhs);
-        if (rhs > 0 && 1 << shift == rhs) {
+        int32_t shift = FloorLog2(Abs(rhs));
+        if (rhs != 0 && uint32_t(1) << shift == Abs(rhs)) {
             LModPowTwoI *lir = new(alloc()) LModPowTwoI(useRegisterAtStart(mod->lhs()), shift);
             if (mod->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo))
                 return false;
             return defineReuseInput(lir, mod, 0);
+        } else if (rhs != 0) {
+            LDivOrModConstantI *lir;
+            lir = new(alloc()) LDivOrModConstantI(useRegister(mod->lhs()), rhs, tempFixed(edx));
+            if (mod->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo))
+                return false;
+            return defineFixed(lir, mod, LAllocation(AnyRegister(eax)));
         }
     }
 
     LModI *lir = new(alloc()) LModI(useRegister(mod->lhs()),
                                     useRegister(mod->rhs()),
                                     tempFixed(eax));
     if (mod->fallible() && !assignSnapshot(lir, Bailout_BaselineInfo))
         return false;
--- a/js/src/jit/x64/LOpcodes-x64.h
+++ b/js/src/jit/x64/LOpcodes-x64.h
@@ -8,16 +8,17 @@
 #define jit_x64_LOpcodes_x64_h
 
 #define LIR_CPU_OPCODE_LIST(_)      \
     _(Box)                          \
     _(Unbox)                        \
     _(UnboxFloatingPoint)           \
     _(DivI)                         \
     _(DivPowTwoI)                   \
+    _(DivOrModConstantI)            \
     _(ModI)                         \
     _(ModPowTwoI)                   \
     _(PowHalfD)                     \
     _(AsmJSUInt32ToDouble)          \
     _(AsmJSUInt32ToFloat32)         \
     _(AsmJSLoadFuncPtr)             \
     _(UDivOrMod)
 
--- a/js/src/jit/x86/LOpcodes-x86.h
+++ b/js/src/jit/x86/LOpcodes-x86.h
@@ -9,16 +9,17 @@
 
 #define LIR_CPU_OPCODE_LIST(_)  \
     _(Unbox)                    \
     _(UnboxFloatingPoint)       \
     _(Box)                      \
     _(BoxFloatingPoint)         \
     _(DivI)                     \
     _(DivPowTwoI)               \
+    _(DivOrModConstantI)        \
     _(ModI)                     \
     _(ModPowTwoI)               \
     _(PowHalfD)                 \
     _(AsmJSUInt32ToDouble)      \
     _(AsmJSUInt32ToFloat32)     \
     _(AsmJSLoadFuncPtr)         \
     _(UDivOrMod)
 
--- a/js/src/jsfun.h
+++ b/js/src/jsfun.h
@@ -263,40 +263,46 @@ class JSFunction : public JSObject
     // source, or nullptr if the function is a clone of a self hosted function.
     //
     // There are several methods to get the script of an interpreted function:
     //
     // - For all interpreted functions, getOrCreateScript() will get the
     //   JSScript, delazifying the function if necessary. This is the safest to
     //   use, but has extra checks, requires a cx and may trigger a GC.
     //
-    // - For functions which may have a LazyScript but whose JSScript is known
-    //   to exist, existingScript() will get the script and delazify the
-    //   function if necessary.
+    // - For inlined functions which may have a LazyScript but whose JSScript
+    //   is known to exist, existingScriptForInlinedFunction() will get the
+    //   script and delazify the function if necessary.
     //
     // - For functions known to have a JSScript, nonLazyScript() will get it.
 
     JSScript *getOrCreateScript(JSContext *cx) {
         JS_ASSERT(isInterpreted());
         JS_ASSERT(cx);
         if (isInterpretedLazy()) {
             JS::RootedFunction self(cx, this);
             if (!createScriptForLazilyInterpretedFunction(cx, self))
                 return nullptr;
             return self->nonLazyScript();
         }
         return nonLazyScript();
     }
 
-    JSScript *existingScript() {
-        JS_ASSERT(isInterpreted());
+    JSScript *existingScriptForInlinedFunction() {
+        MOZ_ASSERT(isInterpreted());
         if (isInterpretedLazy()) {
+            // Get the script from the canonical function. Ion used the
+            // canonical function to inline the script and because it has
+            // Baseline code it has not been relazified. Note that we can't
+            // use lazyScript->script_ here as it may be null in some cases,
+            // see bug 976536.
             js::LazyScript *lazy = lazyScript();
-            JSScript *script = lazy->maybeScript();
-            JS_ASSERT(script);
+            JSFunction *fun = lazy->functionNonDelazifying();
+            MOZ_ASSERT(fun);
+            JSScript *script = fun->nonLazyScript();
 
             if (shadowZone()->needsBarrier())
                 js::LazyScript::writeBarrierPre(lazy);
 
             flags_ &= ~INTERPRETED_LAZY;
             flags_ |= INTERPRETED;
             initScript(script);
         }
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -116,17 +116,17 @@ interface nsIXPCComponents_utils_Sandbox
 interface ScheduledGCCallback : nsISupports
 {
     void callback();
 };
 
 /**
 * interface of Components.utils
 */
-[scriptable, uuid(5e253a02-f91f-4fb3-9335-db0fca23f2e0)]
+[scriptable, uuid(45b80e00-fb0d-439e-b7bf-54f24af0c4a6)]
 interface nsIXPCComponents_Utils : nsISupports
 {
 
     /* reportError is designed to be called from JavaScript only.
      *
      * It will report a JS Error object to the JS console, and return. It
      * is meant for use in exception handler blocks which want to "eat"
      * an exception, but still want to report it to the console.
@@ -563,16 +563,23 @@ interface nsIXPCComponents_Utils : nsISu
      *
      * Hence the notion of the "WebIDL Caller". If the current Entry Script on
      * the Script Settings Stack represents the invocation of JS-implemented
      * WebIDL, this API returns the principal of the caller at the time
      * of invocation. Otherwise (i.e. outside of JS-implemented WebIDL), this
      * function throws. If it throws, you probably shouldn't be using it.
      */
     nsIPrincipal getWebIDLCallerPrincipal();
+
+    /*
+     * Gets the principal of a script object, after unwrapping any cross-
+     * compartment wrappers.
+     */
+    [implicit_jscontext]
+    nsIPrincipal getObjectPrincipal(in jsval obj);
 };
 
 /**
 * Interface for the 'Components' object.
 *
 * The first interface contains things that are available to non-chrome XBL code
 * that runs in a scope with an nsExpandedPrincipal. The second interface
 * includes members that are only exposed to chrome.
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3252,33 +3252,33 @@ nsXPCComponents_Utils::NukeSandbox(Handl
 NS_IMETHODIMP
 nsXPCComponents_Utils::BlockScriptForGlobal(HandleValue globalArg,
                                             JSContext *cx)
 {
     NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
     RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(),
                                             /* stopAtOuter = */ false));
     NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG);
-    if (nsContentUtils::IsSystemPrincipal(GetObjectPrincipal(global))) {
+    if (nsContentUtils::IsSystemPrincipal(xpc::GetObjectPrincipal(global))) {
         JS_ReportError(cx, "Script may not be disabled for system globals");
         return NS_ERROR_FAILURE;
     }
     Scriptability::Get(global).Block();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::UnblockScriptForGlobal(HandleValue globalArg,
                                               JSContext *cx)
 {
     NS_ENSURE_TRUE(globalArg.isObject(), NS_ERROR_INVALID_ARG);
     RootedObject global(cx, UncheckedUnwrap(&globalArg.toObject(),
                                             /* stopAtOuter = */ false));
     NS_ENSURE_TRUE(JS_IsGlobalObject(global), NS_ERROR_INVALID_ARG);
-    if (nsContentUtils::IsSystemPrincipal(GetObjectPrincipal(global))) {
+    if (nsContentUtils::IsSystemPrincipal(xpc::GetObjectPrincipal(global))) {
         JS_ReportError(cx, "Script may not be disabled for system globals");
         return NS_ERROR_FAILURE;
     }
     Scriptability::Get(global).Unblock();
     return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -3611,16 +3611,31 @@ nsXPCComponents_Utils::GetWebIDLCallerPr
     // and we throw.
     nsCOMPtr<nsIPrincipal> callerPrin = mozilla::dom::GetWebIDLCallerPrincipal();
     if (!callerPrin)
         return NS_ERROR_NOT_AVAILABLE;
     callerPrin.forget(aResult);
     return NS_OK;
 }
 
+NS_IMETHODIMP
+nsXPCComponents_Utils::GetObjectPrincipal(HandleValue val, JSContext *cx,
+                                          nsIPrincipal **result)
+{
+    if (!val.isObject())
+        return NS_ERROR_INVALID_ARG;
+    RootedObject obj(cx, &val.toObject());
+    obj = js::CheckedUnwrap(obj);
+    MOZ_ASSERT(obj);
+
+    nsCOMPtr<nsIPrincipal> prin = nsContentUtils::GetObjectPrincipal(obj);
+    prin.forget(result);
+    return NS_OK;
+}
+
 /***************************************************************************/
 /***************************************************************************/
 /***************************************************************************/
 
 
 nsXPCComponentsBase::nsXPCComponentsBase(XPCWrappedNativeScope* aScope)
     :   mScope(aScope)
 {
--- a/js/xpconnect/tests/unit/test_getObjectPrincipal.js
+++ b/js/xpconnect/tests/unit/test_getObjectPrincipal.js
@@ -1,6 +1,11 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
 function run_test() {
-  var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"].getService(
-    Components.interfaces.nsIScriptSecurityManager);
-
-  do_check_true(secMan.isSystemPrincipal(secMan.getObjectPrincipal({})));
+  var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+  do_check_true(secMan.isSystemPrincipal(Cu.getObjectPrincipal({})));
+  var sb = new Cu.Sandbox('http://www.example.com');
+  Cu.evalInSandbox('var obj = { foo: 42 };', sb);
+  do_check_eq(Cu.getObjectPrincipal(sb.obj).origin, 'http://www.example.com');
 }
--- a/security/manager/boot/src/nsSTSPreloadList.errors
+++ b/security/manager/boot/src/nsSTSPreloadList.errors
@@ -5,32 +5,33 @@ api.recurly.com: did not receive HSTS he
 api.simple.com: did not receive HSTS header
 apis.google.com: did not receive HSTS header (error ignored - included regardless)
 appengine.google.com: did not receive HSTS header (error ignored - included regardless)
 bassh.net: did not receive HSTS header
 bcrook.com: max-age too low: 86400
 betnet.fr: could not connect to host
 bigshinylock.minazo.net: could not connect to host
 blacklane.com: did not receive HSTS header
+blueseed.co: did not receive HSTS header
 braintreegateway.com: did not receive HSTS header
 browserid.org: did not receive HSTS header
 business.medbank.com.mt: did not receive HSTS header
 calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125"  data: no]
 carlolly.co.uk: did not receive HSTS header
 cert.se: max-age too low: 2628001
 checkout.google.com: did not receive HSTS header (error ignored - included regardless)
 chrome-devtools-frontend.appspot.com: did not receive HSTS header (error ignored - included regardless)
 chrome.google.com: did not receive HSTS header (error ignored - included regardless)
 cloud.google.com: did not receive HSTS header (error ignored - included regardless)
 code.google.com: did not receive HSTS header (error ignored - included regardless)
 codereview.chromium.org: did not receive HSTS header (error ignored - included regardless)
 crate.io: did not receive HSTS header
 crowdcurity.com: did not receive HSTS header
 crypto.is: did not receive HSTS header
-csawctf.poly.edu: could not connect to host
+csawctf.poly.edu: did not receive HSTS header
 dl.google.com: did not receive HSTS header (error ignored - included regardless)
 docs.google.com: did not receive HSTS header (error ignored - included regardless)
 drive.google.com: did not receive HSTS header (error ignored - included regardless)
 dropcam.com: did not receive HSTS header
 email.lookout.com: could not connect to host
 emailprivacytester.com: did not receive HSTS header
 encrypted.google.com: did not receive HSTS header (error ignored - included regardless)
 errors.zenpayroll.com: could not connect to host
@@ -69,55 +70,51 @@ my.alfresco.com: did not receive HSTS he
 mydigipass.com: did not receive HSTS header
 mykolab.com: did not receive HSTS header
 neonisi.com: could not connect to host
 nexth.de: could not connect to host
 nexth.net: did not receive HSTS header
 nexth.us: could not connect to host
 openshift.redhat.com: did not receive HSTS header
 ottospora.nl: could not connect to host
-passport.yandex.by: did not receive HSTS header
-passport.yandex.com: did not receive HSTS header
-passport.yandex.com.tr: did not receive HSTS header
-passport.yandex.kz: did not receive HSTS header
-passport.yandex.ru: did not receive HSTS header
-passport.yandex.ua: did not receive HSTS header
 paypal.com: max-age too low: 14400
 payroll.xero.com: max-age too low: 3600
 platform.lookout.com: could not connect to host
 play.google.com: did not receive HSTS header (error ignored - included regardless)
 prodpad.com: did not receive HSTS header
 profiles.google.com: did not receive HSTS header (error ignored - included regardless)
 rapidresearch.me: did not receive HSTS header
 sah3.net: could not connect to host
-sandbox.mydigipass.com: could not connect to host
 saturngames.co.uk: did not receive HSTS header
 script.google.com: did not receive HSTS header (error ignored - included regardless)
 security.google.com: did not receive HSTS header (error ignored - included regardless)
 serverdensity.io: did not receive HSTS header
 shops.neonisi.com: could not connect to host
 silentcircle.org: could not connect to host
 simon.butcher.name: max-age too low: 2629743
 sites.google.com: did not receive HSTS header (error ignored - included regardless)
 sol.io: could not connect to host
 souyar.de: could not connect to host
 souyar.net: did not receive HSTS header
 souyar.us: could not connect to host
 spreadsheets.google.com: did not receive HSTS header (error ignored - included regardless)
 square.com: did not receive HSTS header
 ssl.google-analytics.com: did not receive HSTS header (error ignored - included regardless)
 ssl.panoramio.com: did not receive HSTS header
+stage.wepay.com: max-age too low: 2592000
+static.wepay.com: did not receive HSTS header
 sunshinepress.org: could not connect to host
 surfeasy.com: did not receive HSTS header
 talk.google.com: did not receive HSTS header (error ignored - included regardless)
 talkgadget.google.com: did not receive HSTS header (error ignored - included regardless)
 translate.googleapis.com: did not receive HSTS header (error ignored - included regardless)
 uprotect.it: could not connect to host
 wallet.google.com: did not receive HSTS header (error ignored - included regardless)
 webmail.mayfirst.org: did not receive HSTS header
+wepay.com: max-age too low: 2592000
 whonix.org: did not receive HSTS header
 www.calyxinstitute.org: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125"  data: no]
 www.cueup.com: did not receive HSTS header
 www.developer.mydigipass.com: could not connect to host
 www.dropcam.com: max-age too low: 2592000
 www.elanex.biz: did not receive HSTS header
 www.gmail.com: did not receive HSTS header (error ignored - included regardless)
 www.googlemail.com: did not receive HSTS header (error ignored - included regardless)
@@ -129,9 +126,10 @@ www.ledgerscope.net: did not receive HST
 www.logentries.com: did not receive HSTS header
 www.moneybookers.com: did not receive HSTS header
 www.neonisi.com: could not connect to host
 www.paycheckrecords.com: max-age too low: 86400
 www.paypal.com: [Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsISiteSecurityService.processHeader]"  nsresult: "0x80004005 (NS_ERROR_FAILURE)"  location: "JS frame :: /builds/slave/m-cen-l64-hsts-000000000000000/getHSTSPreloadList.js :: processStsHeader :: line 125"  data: no]
 www.roddis.net: did not receive HSTS header
 www.sandbox.mydigipass.com: could not connect to host
 www.surfeasy.com: did not receive HSTS header
+www.wepay.com: max-age too low: 2592000
 zoo24.de: max-age too low: 2592000
--- a/security/manager/boot/src/nsSTSPreloadList.inc
+++ b/security/manager/boot/src/nsSTSPreloadList.inc
@@ -3,30 +3,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /*****************************************************************************/
 /* This is an automatically generated file. If you're not                    */
 /* nsSiteSecurityService.cpp, you shouldn't be #including it.     */
 /*****************************************************************************/
 
 #include <stdint.h>
-const PRTime gPreloadListExpirationTime = INT64_C(1408184023977000);
+const PRTime gPreloadListExpirationTime = INT64_C(1408788358092000);
 
 class nsSTSPreload
 {
   public:
     const char *mHost;
     const bool mIncludeSubdomains;
 };
 
 static const nsSTSPreload kSTSPreloadList[] = {
   { "accounts.google.com", true },
   { "aclu.org", false },
   { "activiti.alfresco.com", false },
   { "admin.google.com", true },
+  { "adsfund.org", true },
   { "aladdinschools.appspot.com", false },
   { "alpha.irccloud.com", false },
   { "api.intercom.io", false },
   { "api.xero.com", false },
   { "apis.google.com", true },
   { "app.recurly.com", false },
   { "app.yinxiang.com", false },
   { "appengine.google.com", true },
@@ -34,28 +35,28 @@ static const nsSTSPreload kSTSPreloadLis
   { "arivo.com.br", true },
   { "bank.simple.com", false },
   { "bccx.com", true },
   { "bitbucket.org", false },
   { "blog.cyveillance.com", true },
   { "blog.linode.com", false },
   { "blog.lookout.com", false },
   { "blog.torproject.org", false },
-  { "blueseed.co", false },
   { "boxcryptor.com", true },
   { "braintreepayments.com", false },
   { "bugzilla.mozilla.org", true },
   { "business.lookout.com", false },
   { "carezone.com", false },
   { "check.torproject.org", false },
   { "checkout.google.com", true },
   { "chrome-devtools-frontend.appspot.com", true },
   { "chrome.google.com", true },
   { "chromiumcodereview.appspot.com", false },
   { "cloud.google.com", true },
+  { "cloudcert.org", true },
   { "cloudns.com.au", true },
   { "cloudsecurityalliance.org", true },
   { "code.google.com", true },
   { "codereview.appspot.com", false },
   { "codereview.chromium.org", true },
   { "conformal.com", true },
   { "controlcenter.gigahost.dk", true },
   { "crm.onlime.ch", false },
@@ -155,40 +156,48 @@ static const nsSTSPreload kSTSPreloadLis
   { "neg9.org", false },
   { "neilwynne.com", false },
   { "onedrive.com", true },
   { "onedrive.live.com", false },
   { "oplop.appspot.com", true },
   { "opsmate.com", false },
   { "p.linode.com", false },
   { "packagist.org", false },
+  { "passport.yandex.by", false },
+  { "passport.yandex.com", false },
+  { "passport.yandex.com.tr", false },
+  { "passport.yandex.kz", false },
+  { "passport.yandex.ru", false },
+  { "passport.yandex.ua", false },
   { "passwd.io", true },
   { "passwordbox.com", false },
   { "paste.linode.com", false },
   { "pastebin.linode.com", false },
   { "pay.gigahost.dk", true },
   { "paymill.com", true },
   { "paymill.de", false },
   { "piratenlogin.de", true },
   { "pixi.me", true },
   { "play.google.com", false },
   { "plus.google.com", false },
   { "plus.sandbox.google.com", false },
   { "profiles.google.com", true },
   { "publications.qld.gov.au", false },
+  { "pult.co", true },
   { "pypi.python.org", true },
   { "python.org", false },
   { "riseup.net", true },
   { "roddis.net", false },
   { "romab.com", true },
   { "roundcube.mayfirst.org", false },
   { "sandbox.mydigipass.com", false },
   { "script.google.com", true },
   { "security.google.com", true },
   { "securityheaders.com", true },
+  { "seifried.org", true },
   { "semenkovich.com", true },
   { "shodan.io", true },
   { "silentcircle.com", false },
   { "simbolo.co.uk", false },
   { "simple.com", false },
   { "sites.google.com", true },
   { "skydrive.live.com", false },
   { "spreadsheets.google.com", true },
@@ -202,16 +211,17 @@ static const nsSTSPreload kSTSPreloadLis
   { "talk.google.com", true },
   { "talkgadget.google.com", true },
   { "tent.io", true },
   { "therapynotes.com", false },
   { "torproject.org", false },
   { "translate.googleapis.com", true },
   { "twitter.com", false },
   { "ubertt.org", true },
+  { "vmoagents.com", false },
   { "wallet.google.com", true },
   { "webmail.gigahost.dk", false },
   { "webmail.onlime.ch", false },
   { "wiki.python.org", true },
   { "wiz.biz", true },
   { "writeapp.me", false },
   { "www.aclu.org", false },
   { "www.apollo-auto.com", true },
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -46,18 +46,17 @@ user_pref("dom.undo_manager.enabled", tr
 user_pref("dom.webcomponents.enabled", true);
 // Set a future policy version to avoid the telemetry prompt.
 user_pref("toolkit.telemetry.prompted", 999);
 user_pref("toolkit.telemetry.notifiedOptOut", 999);
 // Existing tests assume there is no font size inflation.
 user_pref("font.size.inflation.emPerLine", 0);
 user_pref("font.size.inflation.minTwips", 0);
 
-// AddonManager tests require that the experiments feature be enabled.
-user_pref("experiments.enabled", true);
+// AddonManager tests require that the experiments provider be present.
 user_pref("experiments.supported", true);
 user_pref("experiments.logging.level", "Trace");
 // Point the manifest at something local so we don't risk it hitting production
 // data and installing experiments that may vary over time.
 user_pref("experiments.manifest.uri", "http://%(server)s/experiments-dummy/manifest");
 
 // Only load extensions from the application and user profile
 // AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION
--- a/toolkit/components/startup/nsAppStartup.cpp
+++ b/toolkit/components/startup/nsAppStartup.cpp
@@ -38,16 +38,17 @@
 #include "prenv.h"
 #include "nsAppDirectoryServiceDefs.h"
 
 #if defined(XP_WIN)
 // Prevent collisions with nsAppStartup::GetStartupInfo()
 #undef GetStartupInfo
 #endif
 
+#include "mozilla/IOInterposer.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/StartupTimeline.h"
 
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #define kPrefLastSuccess "toolkit.startup.last_success"
 #define kPrefMaxResumedCrashes "toolkit.startup.max_resumed_crashes"
 #define kPrefRecentCrashes "toolkit.startup.recent_crashes"
@@ -691,30 +692,30 @@ nsAppStartup::Observe(nsISupports *aSubj
       ExitLastWindowClosingSurvivalArea();
     }
   } else if (!strcmp(aTopic, "xul-window-registered")) {
     EnterLastWindowClosingSurvivalArea();
   } else if (!strcmp(aTopic, "xul-window-destroyed")) {
     ExitLastWindowClosingSurvivalArea();
   } else if (!strcmp(aTopic, "sessionstore-windows-restored")) {
     StartupTimeline::Record(StartupTimeline::SESSION_RESTORED);
-    Telemetry::LeavingStartupStage();
+    IOInterposer::EnteringNextStage();
 #if defined(XP_WIN)
     if (mSessionWindowRestoredProbe) {
       mSessionWindowRestoredProbe->Trigger();
     }
   } else if (!strcmp(aTopic, "places-init-complete")) {
     if (mPlacesInitCompleteProbe) {
       mPlacesInitCompleteProbe->Trigger();
     }
 #endif //defined(XP_WIN)
   } else if (!strcmp(aTopic, "sessionstore-init-started")) {
     StartupTimeline::Record(StartupTimeline::SESSION_RESTORE_INIT);
   } else if (!strcmp(aTopic, "xpcom-shutdown")) {
-    Telemetry::EnteringShutdownStage();
+    IOInterposer::EnteringNextStage();
 #if defined(XP_WIN)
     if (mXPCOMShutdownProbe) {
       mXPCOMShutdownProbe->Trigger();
     }
 #endif // defined(XP_WIN)
   } else {
     NS_ERROR("Unexpected observer topic.");
   }
--- a/toolkit/components/telemetry/Telemetry.cpp
+++ b/toolkit/components/telemetry/Telemetry.cpp
@@ -321,34 +321,16 @@ public:
 
   /**
    * Adds a path for inclusion in main thread I/O report.
    * @param aPath Directory path
    * @param aSubstName Name to substitute for aPath for privacy reasons
    */
   void AddPath(const nsAString& aPath, const nsAString& aSubstName);
 
-  enum Stage
-  {
-    STAGE_STARTUP = 0,
-    STAGE_NORMAL,
-    STAGE_SHUTDOWN,
-    NUM_STAGES
-  };
-
-  /**
-   * Sets a new stage in the lifecycle of this process.
-   * @param aNewStage One of the STAGE_* enum values.
-   */
-  inline void SetStage(Stage aNewStage)
-  {
-    MOZ_ASSERT(aNewStage != NUM_STAGES);
-    mCurStage = aNewStage;
-  }
-
   /**
    * Get size of hash table with file stats
    */
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
@@ -359,16 +341,37 @@ public:
     uint32_t safeDirsLen = mSafeDirs.Length();
     for (uint32_t i = 0; i < safeDirsLen; ++i) {
       size += mSafeDirs[i].SizeOfExcludingThis(aMallocSizeOf);
     }
     return size;
   }
 
 private:
+  enum Stage
+  {
+    STAGE_STARTUP = 0,
+    STAGE_NORMAL,
+    STAGE_SHUTDOWN,
+    NUM_STAGES
+  };
+  static inline Stage NextStage(Stage aStage)
+  {
+    switch (aStage) {
+      case STAGE_STARTUP:
+        return STAGE_NORMAL;
+      case STAGE_NORMAL:
+        return STAGE_SHUTDOWN;
+      case STAGE_SHUTDOWN:
+        return STAGE_SHUTDOWN;
+      default:
+        return NUM_STAGES;
+    }
+  }
+
   struct FileStatsByStage
   {
     FileStats mStats[NUM_STAGES];
   };
   typedef nsBaseHashtableET<nsStringHashKey, FileStatsByStage> FileIOEntryType;
 
   // Statistics for each filename
   AutoHashtable<FileIOEntryType> mFileStats;
@@ -410,16 +413,22 @@ void TelemetryIOInterposeObserver::AddPa
  
 void TelemetryIOInterposeObserver::Observe(Observation& aOb)
 {
   // We only report main-thread I/O
   if (!IsMainThread()) {
     return;
   }
 
+  if (aOb.ObservedOperation() == OpNextStage) {
+    mCurStage = NextStage(mCurStage);
+    MOZ_ASSERT(mCurStage < NUM_STAGES);
+    return;
+  }
+
   // Get the filename
   const char16_t* filename = aOb.Filename();
  
   // Discard observations without filename
   if (!filename) {
     return;
   }
 
@@ -530,17 +539,18 @@ bool TelemetryIOInterposeObserver::Refle
 StaticAutoPtr<TelemetryIOInterposeObserver> sTelemetryIOObserver;
 
 void
 ClearIOReporting()
 {
   if (!sTelemetryIOObserver) {
     return;
   }
-  IOInterposer::Unregister(IOInterposeObserver::OpAll, sTelemetryIOObserver);
+  IOInterposer::Unregister(IOInterposeObserver::OpAllWithStaging,
+                           sTelemetryIOObserver);
   sTelemetryIOObserver = nullptr;
 }
 
 class TelemetryImpl MOZ_FINAL
   : public nsITelemetry
   , public nsIMemoryReporter
 {
   NS_DECL_THREADSAFE_ISUPPORTS
@@ -2988,17 +2998,18 @@ void
 InitIOReporting(nsIFile* aXreDir)
 {
   // Never initialize twice
   if (sTelemetryIOObserver) {
     return;
   }
 
   sTelemetryIOObserver = new TelemetryIOInterposeObserver(aXreDir);
-  IOInterposer::Register(IOInterposeObserver::OpAll, sTelemetryIOObserver);
+  IOInterposer::Register(IOInterposeObserver::OpAllWithStaging,
+                         sTelemetryIOObserver);
 }
 
 void
 SetProfileDir(nsIFile* aProfD)
 {
   if (!sTelemetryIOObserver || !aProfD) {
     return;
   }
@@ -3006,34 +3017,16 @@ SetProfileDir(nsIFile* aProfD)
   nsresult rv = aProfD->GetPath(profDirPath);
   if (NS_FAILED(rv)) {
     return;
   }
   sTelemetryIOObserver->AddPath(profDirPath, NS_LITERAL_STRING("{profile}"));
 }
 
 void
-LeavingStartupStage()
-{
-  if (!sTelemetryIOObserver) {
-    return;
-  }
-  sTelemetryIOObserver->SetStage(TelemetryIOInterposeObserver::STAGE_NORMAL);
-}
-
-void
-EnteringShutdownStage()
-{
-  if (!sTelemetryIOObserver) {
-    return;
-  }
-  sTelemetryIOObserver->SetStage(TelemetryIOInterposeObserver::STAGE_SHUTDOWN);
-}
-
-void
 TimeHistogram::Add(PRIntervalTime aTime)
 {
   uint32_t timeMs = PR_IntervalToMilliseconds(aTime);
   size_t index = mozilla::FloorLog2(timeMs);
   operator[](index)++;
 }
 
 uint32_t
--- a/toolkit/devtools/DevToolsUtils.js
+++ b/toolkit/devtools/DevToolsUtils.js
@@ -290,17 +290,17 @@ exports.hasSafeGetter = function hasSafe
  *         True if it is safe to read properties from aObj, or false otherwise.
  */
 exports.isSafeJSObject = function isSafeJSObject(aObj) {
   if (Cu.getGlobalForObject(aObj) ==
       Cu.getGlobalForObject(exports.isSafeJSObject)) {
     return true; // aObj is not a cross-compartment wrapper.
   }
 
-  let principal = Services.scriptSecurityManager.getObjectPrincipal(aObj);
+  let principal = Cu.getObjectPrincipal(aObj);
   if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
     return true; // allow chrome objects
   }
 
   return Cu.isXrayWrapper(aObj);
 };
 
 exports.dumpn = function dumpn(str) {
--- a/toolkit/mozapps/extensions/test/browser/browser_experiments.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_experiments.js
@@ -5,31 +5,41 @@
 let {AddonTestUtils} = Components.utils.import("resource://testing-common/AddonManagerTesting.jsm", {});
 let {HttpServer} = Components.utils.import("resource://testing-common/httpd.js", {});
 
 let gManagerWindow;
 let gCategoryUtilities;
 let gExperiments;
 let gHttpServer;
 
+let gSavedManifestURI;
+
 function getExperimentAddons() {
   let deferred = Promise.defer();
   AddonManager.getAddonsByTypes(["experiment"], (addons) => {
     deferred.resolve(addons);
   });
   return deferred.promise;
 }
 
 add_task(function* initializeState() {
   gManagerWindow = yield open_manager();
   gCategoryUtilities = new CategoryUtilities(gManagerWindow);
 
   registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("experiments.enabled");
     if (gHttpServer) {
       gHttpServer.stop(() => {});
+      Services.prefs.clearUserPref("experiments.manifest.cert.checkAttributes");
+      if (gSavedManifestURI !== undefined) {
+        Services.prefs.setCharPref("experments.manifest.uri", gSavedManifestURI);
+      }
+    }
+    if (gExperiments) {
+      gExperiments._policy.ignoreHashes = false;
     }
   });
 
   // The Experiments Manager will interfere with us by preventing installs
   // of experiments it doesn't know about. We remove it from the equation
   // because here we are only concerned with core Addon Manager operation,
   // not the superset Experiments Manager has imposed.
   if ("@mozilla.org/browser/experiments-service;1" in Components.classes) {
@@ -162,34 +172,16 @@ add_task(function* testButtonPresence() 
 // Remove the add-on we've been testing with.
 add_task(function* testCleanup() {
   yield AddonTestUtils.uninstallAddonByID("test-experiment1@experiments.mozilla.org");
   // Verify some conditions, just in case.
   let addons = yield getExperimentAddons();
   Assert.equal(addons.length, 0, "No experiment add-ons are installed.");
 });
 
-// We need to initialize the experiments service for the following tests.
-add_task(function* initializeExperiments() {
-  if (!gExperiments) {
-    return;
-  }
-
-  // We need to remove the cache file to help ensure consistent state.
-  yield OS.File.remove(gExperiments._cacheFilePath);
-
-  info("Initializing experiments service.");
-  yield gExperiments.init();
-  info("Experiments service finished first run.");
-
-  // Check conditions, just to be sure.
-  let experiments = yield gExperiments.getExperiments();
-  Assert.equal(experiments.length, 0, "No experiments known to the service.");
-});
-
 // The following tests should ideally live in browser/experiments/. However,
 // they rely on some of the helper functions from head.js, which can't easily
 // be consumed from other directories. So, they live here.
 
 add_task(function* testActivateExperiment() {
   if (!gExperiments) {
     info("Skipping experiments test because that feature isn't available.");
     return;
@@ -215,25 +207,34 @@ add_task(function* testActivateExperimen
         },
       ],
     }));
     response.processAsync();
     response.finish();
   });
 
   Services.prefs.setBoolPref("experiments.manifest.cert.checkAttributes", false);
+  gSavedManifestURI = Services.prefs.getCharPref("experiments.manifest.uri");
   Services.prefs.setCharPref("experiments.manifest.uri", root + "manifest");
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("experiments.manifest.cert.checkAttributes");
-    Services.prefs.clearUserPref("experiments.manifest.uri");
-  });
+
+  // We need to remove the cache file to help ensure consistent state.
+  yield OS.File.remove(gExperiments._cacheFilePath);
+
+  Services.prefs.setBoolPref("experiments.enabled", true);
+
+  info("Initializing experiments service.");
+  yield gExperiments.init();
+  info("Experiments service finished first run.");
+
+  // Check conditions, just to be sure.
+  let experiments = yield gExperiments.getExperiments();
+  Assert.equal(experiments.length, 0, "No experiments known to the service.");
 
   // This makes testing easier.
   gExperiments._policy.ignoreHashes = true;
-  registerCleanupFunction(() => { gExperiments._policy.ignoreHashes = false; });
 
   info("Manually updating experiments manifest.");
   yield gExperiments.updateManifest();
   info("Experiments update complete.");
 
   let deferred = Promise.defer();
   gHttpServer.stop(() => {
     gHttpServer = null;
@@ -264,16 +265,20 @@ add_task(function testDeactivateExperime
     "experiments": [],
   });
 
   yield gExperiments.disableExperiment("testing");
 });
 
 add_task(function* testCleanup() {
   if (gExperiments) {
+    Services.prefs.clearUserPref("experiments.enabled");
+    Services.prefs.clearUserPref("experiments.manifest.cert.checkAttributes");
+    Services.prefs.setCharPref("experiments.manifest.uri", gSavedManifestURI);
+
     // We perform the uninit/init cycle to purge any leftover state.
     yield OS.File.remove(gExperiments._cacheFilePath);
     yield gExperiments.uninit();
     yield gExperiments.init();
   }
 
   // Check post-conditions.
   let addons = yield getExperimentAddons();
--- a/tools/profiler/ProfilerIOInterposeObserver.cpp
+++ b/tools/profiler/ProfilerIOInterposeObserver.cpp
@@ -9,46 +9,22 @@
 using namespace mozilla;
 
 void ProfilerIOInterposeObserver::Observe(Observation& aObservation)
 {
   if (!IsMainThread()) {
     return;
   }
 
-  const char* str = nullptr;
-
-  switch (aObservation.ObservedOperation()) {
-    case IOInterposeObserver::OpCreateOrOpen:
-      str = "create/open";
-      break;
-    case IOInterposeObserver::OpRead:
-      str = "read";
-      break;
-    case IOInterposeObserver::OpWrite:
-      str = "write";
-      break;
-    case IOInterposeObserver::OpFSync:
-      str = "fsync";
-      break;
-    case IOInterposeObserver::OpStat:
-      str = "stat";
-      break;
-    case IOInterposeObserver::OpClose:
-      str = "close";
-      break;
-    default:
-      return;
-  }
   ProfilerBacktrace* stack = profiler_get_backtrace();
 
   nsCString filename;
   if (aObservation.Filename()) {
     filename = NS_ConvertUTF16toUTF8(aObservation.Filename());
   }
 
   IOMarkerPayload* markerPayload = new IOMarkerPayload(aObservation.Reference(),
                                                        filename.get(),
                                                        aObservation.Start(),
                                                        aObservation.End(),
                                                        stack);
-  PROFILER_MARKER_PAYLOAD(str, markerPayload);
+  PROFILER_MARKER_PAYLOAD(aObservation.ObservedOperationString(), markerPayload);
 }
--- a/xpcom/build/IOInterposer.cpp
+++ b/xpcom/build/IOInterposer.cpp
@@ -2,16 +2,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <algorithm>
 #include <vector>
 
 #include "IOInterposer.h"
 
+#include "IOInterposerPrivate.h"
+#include "MainThreadIOLogger.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Mutex.h"
 #if defined(MOZILLA_INTERNAL_API)
 // We need to undefine MOZILLA_INTERNAL_API for RefPtr.h because IOInterposer
 // does not clean up its data before shutdown.
 #undef MOZILLA_INTERNAL_API
 #include "mozilla/RefPtr.h"
 #define MOZILLA_INTERNAL_API
@@ -42,59 +44,43 @@ template<class T>
 void VectorRemove(std::vector<T>& vector, const T& element)
 {
   typename std::vector<T>::iterator newEnd = std::remove(vector.begin(),
                                                          vector.end(), element);
   vector.erase(newEnd, vector.end());
 }
 
 /** Lists of Observers */
-struct ObserverLists : public AtomicRefCounted<ObserverLists>
+struct ObserverLists : public mozilla::AtomicRefCounted<ObserverLists>
 {
   ObserverLists()
   {
   }
 
   ObserverLists(ObserverLists const & aOther)
     : mCreateObservers(aOther.mCreateObservers)
     , mReadObservers(aOther.mReadObservers)
     , mWriteObservers(aOther.mWriteObservers)
     , mFSyncObservers(aOther.mFSyncObservers)
     , mStatObservers(aOther.mStatObservers)
     , mCloseObservers(aOther.mCloseObservers)
+    , mStageObservers(aOther.mStageObservers)
   {
   }
-  // Lists of observers for read, write and fsync events respectively
+  // Lists of observers for I/O events.
   // These are implemented as vectors since they are allowed to survive gecko,
   // without reporting leaks. This is necessary for the IOInterposer to be used
   // for late-write checks.
   std::vector<IOInterposeObserver*>  mCreateObservers;
   std::vector<IOInterposeObserver*>  mReadObservers;
   std::vector<IOInterposeObserver*>  mWriteObservers;
   std::vector<IOInterposeObserver*>  mFSyncObservers;
   std::vector<IOInterposeObserver*>  mStatObservers;
   std::vector<IOInterposeObserver*>  mCloseObservers;
-};
-
-/**
- * A quick and dirty RAII class to automatically lock a PRLock
- */
-class AutoPRLock
-{
-  PRLock* mLock;
-public:
-  AutoPRLock(PRLock* aLock)
-   : mLock(aLock)
-  {
-    PR_Lock(aLock);
-  }
-  ~AutoPRLock()
-  {
-    PR_Unlock(mLock);
-  }
+  std::vector<IOInterposeObserver*>  mStageObservers;
 };
 
 class PerThreadData
 {
 public:
   PerThreadData(bool aIsMainThread = false)
     : mIsMainThread(aIsMainThread)
     , mIsHandlingObservation(false)
@@ -139,16 +125,21 @@ public:
           observers = &mObserverLists->mStatObservers;
         }
         break;
       case IOInterposeObserver::OpClose:
         {
           observers = &mObserverLists->mCloseObservers;
         }
         break;
+      case IOInterposeObserver::OpNextStage:
+        {
+          observers = &mObserverLists->mStageObservers;
+        }
+        break;
       default:
         {
           // Invalid IO operation, see documentation comment for
           // IOInterposer::Report()
           MOZ_ASSERT(false);
           // Just ignore it in non-debug builds.
           return;
         }
@@ -189,38 +180,35 @@ private:
   uint32_t              mCurrentGeneration;
   RefPtr<ObserverLists> mObserverLists;
 };
 
 class MasterList
 {
 public:
   MasterList()
-    : mLock(PR_NewLock())
-    , mObservedOperations(IOInterposeObserver::OpNone)
+    : mObservedOperations(IOInterposeObserver::OpNone)
     , mIsEnabled(true)
   {
   }
 
   ~MasterList()
   {
-    PR_DestroyLock(mLock);
-    mLock = nullptr;
   }
 
   inline void
   Disable()
   {
     mIsEnabled = false;
   }
 
   void
   Register(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver)
   {
-    AutoPRLock lock(mLock);
+    IOInterposer::AutoLock lock(mLock);
 
     ObserverLists* newLists = nullptr;
     if (mObserverLists) {
       newLists = new ObserverLists(*mObserverLists);
     } else {
       newLists = new ObserverLists();
     }
     // You can register to observe multiple types of observations
@@ -244,27 +232,31 @@ public:
     if (aOp & IOInterposeObserver::OpStat &&
         !VectorContains(newLists->mStatObservers, aObserver)) {
       newLists->mStatObservers.push_back(aObserver);
     }
     if (aOp & IOInterposeObserver::OpClose &&
         !VectorContains(newLists->mCloseObservers, aObserver)) {
       newLists->mCloseObservers.push_back(aObserver);
     }
+    if (aOp & IOInterposeObserver::OpNextStage &&
+        !VectorContains(newLists->mStageObservers, aObserver)) {
+      newLists->mStageObservers.push_back(aObserver);
+    }
     mObserverLists = newLists;
     mObservedOperations = (IOInterposeObserver::Operation)
                             (mObservedOperations | aOp);
 
     mCurrentGeneration++;
   }
 
   void
   Unregister(IOInterposeObserver::Operation aOp, IOInterposeObserver* aObserver)
   {
-    AutoPRLock lock(mLock);
+    IOInterposer::AutoLock lock(mLock);
 
     ObserverLists* newLists = nullptr;
     if (mObserverLists) {
       newLists = new ObserverLists(*mObserverLists);
     } else {
       newLists = new ObserverLists();
     }
 
@@ -306,29 +298,36 @@ public:
     }
     if (aOp & IOInterposeObserver::OpClose) {
       VectorRemove(newLists->mCloseObservers, aObserver);
       if (newLists->mCloseObservers.empty()) {
         mObservedOperations = (IOInterposeObserver::Operation)
                          (mObservedOperations & ~IOInterposeObserver::OpClose);
       }
     }
+    if (aOp & IOInterposeObserver::OpNextStage) {
+      VectorRemove(newLists->mStageObservers, aObserver);
+      if (newLists->mStageObservers.empty()) {
+        mObservedOperations = (IOInterposeObserver::Operation)
+                         (mObservedOperations & ~IOInterposeObserver::OpNextStage);
+      }
+    }
     mObserverLists = newLists;
     mCurrentGeneration++;
   }
  
   void
   Update(PerThreadData &aPtd)
   {
     if (mCurrentGeneration == aPtd.GetCurrentGeneration()) {
       return;
     }
     // If the generation counts don't match then we need to update the current
     // thread's observer list with the new master list.
-    AutoPRLock lock(mLock);
+    IOInterposer::AutoLock lock(mLock);
     aPtd.SetObserverLists(mCurrentGeneration, mObserverLists);
   }
 
   inline bool
   IsObservedOperation(IOInterposeObserver::Operation aOp)
   {
     // The quick reader may observe that no locks are being employed here,
     // hence the result of the operations is truly undefined. However, most
@@ -340,25 +339,37 @@ public:
 
 private:
   RefPtr<ObserverLists>             mObserverLists;
   // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked
   // (We want to monitor IO during shutdown). Furthermore, as we may have to
   // unregister observers during shutdown an OffTheBooksMutex is not an option
   // either, as its base calls into sDeadlockDetector which may be nullptr
   // during shutdown.
-  PRLock*                           mLock;
+  IOInterposer::Mutex               mLock;
   // Flags tracking which operations are being observed
   IOInterposeObserver::Operation    mObservedOperations;
   // Used for quickly disabling everything by IOInterposer::Disable()
   Atomic<bool>                      mIsEnabled;
   // Used to inform threads that the master observer list has changed
   Atomic<uint32_t>                  mCurrentGeneration;
 };
 
+// Special observation used by IOInterposer::EnteringNextStage()
+class NextStageObservation : public IOInterposeObserver::Observation
+{
+public:
+  NextStageObservation()
+    : IOInterposeObserver::Observation(IOInterposeObserver::OpNextStage,
+                                       "IOInterposer", false)
+  {
+    mStart = TimeStamp::Now();
+  }
+};
+
 // List of observers registered
 static StaticAutoPtr<MasterList> sMasterList;
 static ThreadLocal<PerThreadData*> sThreadLocalData;
 } // anonymous namespace
 
 IOInterposeObserver::Observation::Observation(Operation aOperation,
                                               const char* aReference,
                                               bool aShouldReport)
@@ -379,26 +390,49 @@ IOInterposeObserver::Observation::Observ
   : mOperation(aOperation)
   , mStart(aStart)
   , mEnd(aEnd)
   , mReference(aReference)
   , mShouldReport(false)
 {
 }
 
+const char*
+IOInterposeObserver::Observation::ObservedOperationString() const
+{
+  switch(mOperation) {
+    case OpCreateOrOpen:
+      return "create/open";
+    case OpRead:
+      return "read";
+    case OpWrite:
+      return "write";
+    case OpFSync:
+      return "fsync";
+    case OpStat:
+      return "stat";
+    case OpClose:
+      return "close";
+    case OpNextStage:
+      return "NextStage";
+    default:
+      return "unknown";
+  }
+}
+
 void
 IOInterposeObserver::Observation::Report()
 {
   if (mShouldReport) {
     mEnd = TimeStamp::Now();
     IOInterposer::Report(*this);
   }
 }
 
-/* static */ bool
+bool
 IOInterposer::Init()
 {
   // Don't initialize twice...
   if (sMasterList) {
     return true;
   }
   if (!sThreadLocalData.init()) {
     return false;
@@ -407,55 +441,57 @@ IOInterposer::Init()
   bool isMainThread = XRE_GetWindowsEnvironment() !=
                         WindowsEnvironmentType_Metro;
 #else
   bool isMainThread = true;
 #endif
   RegisterCurrentThread(isMainThread);
   sMasterList = new MasterList();
 
+  MainThreadIOLogger::Init();
+
   // Now we initialize the various interposers depending on platform
   InitPoisonIOInterposer();
   // We don't hook NSPR on Windows because PoisonIOInterposer captures a
   // superset of the former's events.
 #if !defined(XP_WIN)
   InitNSPRIOInterposing();
 #endif
   return true;
 }
 
-/* static */ bool
+bool
 IOInterposeObserver::IsMainThread()
 {
   if (!sThreadLocalData.initialized()) {
     return false;
   }
   PerThreadData *ptd = sThreadLocalData.get();
   if (!ptd) {
     return false;
   }
   return ptd->IsMainThread();
 }
 
-/* static */ void
+void
 IOInterposer::Clear()
 {
   sMasterList = nullptr;
 }
 
-/* static */ void
+void
 IOInterposer::Disable()
 {
   if (!sMasterList) {
     return;
   }
   sMasterList->Disable();
 }
 
-/* static */ void
+void
 IOInterposer::Report(IOInterposeObserver::Observation& aObservation)
 {
   MOZ_ASSERT(sMasterList);
   if (!sMasterList) {
     return;
   }
 
   PerThreadData* ptd = sThreadLocalData.get();
@@ -470,60 +506,70 @@ IOInterposer::Report(IOInterposeObserver
   // Don't try to report if there's nobody listening.
   if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) {
     return;
   }
 
   ptd->CallObservers(aObservation);
 }
 
-/* static */ bool
+bool
 IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp)
 {
   return sMasterList && sMasterList->IsObservedOperation(aOp);
 }
 
-/* static */ void
+void
 IOInterposer::Register(IOInterposeObserver::Operation aOp,
                        IOInterposeObserver* aObserver)
 {
   MOZ_ASSERT(aObserver);
   if (!sMasterList || !aObserver) {
     return;
   }
 
   sMasterList->Register(aOp, aObserver);
 }
 
-/* static */ void
+void
 IOInterposer::Unregister(IOInterposeObserver::Operation aOp,
                          IOInterposeObserver* aObserver)
 {
   if (!sMasterList) {
     return;
   }
 
   sMasterList->Unregister(aOp, aObserver);
 }
 
-/* static */ void
+void
 IOInterposer::RegisterCurrentThread(bool aIsMainThread)
 {
   if (!sThreadLocalData.initialized()) {
     return;
   }
   MOZ_ASSERT(!sThreadLocalData.get());
   PerThreadData* curThreadData = new PerThreadData(aIsMainThread);
   sThreadLocalData.set(curThreadData);
 }
 
-/* static */ void
+void
 IOInterposer::UnregisterCurrentThread()
 {
   if (!sThreadLocalData.initialized()) {
     return;
   }
   PerThreadData* curThreadData = sThreadLocalData.get();
   MOZ_ASSERT(curThreadData);
   sThreadLocalData.set(nullptr);
   delete curThreadData;
 }
 
+void
+IOInterposer::EnteringNextStage()
+{
+  if (!sMasterList) {
+    return;
+  }
+  NextStageObservation observation;
+  Report(observation);
+}
+
--- a/xpcom/build/IOInterposer.h
+++ b/xpcom/build/IOInterposer.h
@@ -22,18 +22,20 @@ public:
   {
     OpNone = 0,
     OpCreateOrOpen = (1 << 0),
     OpRead = (1 << 1),
     OpWrite = (1 << 2),
     OpFSync = (1 << 3),
     OpStat = (1 << 4),
     OpClose = (1 << 5),
+    OpNextStage = (1 << 6), // Meta - used when leaving startup, entering shutdown
     OpWriteFSync = (OpWrite | OpFSync),
-    OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose)
+    OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose),
+    OpAllWithStaging = (OpAll | OpNextStage)
   };
 
   /** A representation of an I/O observation  */
   class Observation
   {
   protected:
     /**
      * This constructor is for use by subclasses that are intended to take
@@ -51,24 +53,29 @@ public:
     /**
      * Since this constructor accepts start and end times, it does *not* take
      * its own timings, nor does it report itself.
      */
     Observation(Operation aOperation, const TimeStamp& aStart,
                 const TimeStamp& aEnd, const char* aReference);
 
     /**
-     * Operation observed, this is either OpRead, OpWrite or OpFSync,
-     * combinations of these flags are only used when registering observers.
+     * Operation observed, this is one of the individual Operation values.
+     * Combinations of these flags are only used when registering observers.
      */
     Operation ObservedOperation() const
     {
       return mOperation;
     }
 
+    /**
+     * Return the observed operation as a human-readable string.
+     */
+    const char* ObservedOperationString() const;
+
     /** Time at which the I/O operation was started */
     TimeStamp Start() const
     {
       return mStart;
     }
 
     /**
      * Time at which the I/O operation ended, for asynchronous methods this is
@@ -142,64 +149,53 @@ protected:
    * We don't use NS_IsMainThread() because we need to be able to determine the
    * main thread outside of XPCOM Initialization. IOInterposer observers should
    * call this function instead.
    */
   static bool IsMainThread();
 };
 
 /**
- * Class offering the public static IOInterposer API.
- *
- * This class is responsible for ensuring that events are routed to the
- * appropriate observers. Methods Init() and Clear() should only be called from
- * the main-thread at startup and shutdown, respectively.
- *
- * Remark: Instances of this class will never be created, you should consider it
- * to be a namespace containing static functions. The class is created to
- * facilitate to a private static instance variable sObservedOperations.
- * As we want to access this from an inline static methods, we have to do this
- * trick.
+ * These functions are responsible for ensuring that events are routed to the
+ * appropriate observers.
  */
-class IOInterposer MOZ_FINAL
+namespace IOInterposer
 {
-  // No instance of class should be created, they'd be empty anyway.
-  IOInterposer();
-public:
-
   /**
    * This function must be called from the main-thread when no other threads are
    * running before any of the other methods on this class may be used.
    *
    * IO reports can however, safely assume that IsObservedOperation() will
-   * return false, until the IOInterposer is initialized.
+   * return false until the IOInterposer is initialized.
    *
    * Remark, it's safe to call this method multiple times, so just call it when
    * you to utilize IO interposing.
+   *
+   * Using the IOInterposerInit class is preferred to calling this directly.
    */
-  static bool Init();
+  bool Init();
 
   /**
    * This function must be called from the main thread, and furthermore
    * it must be called when no other threads are executing. Effectively
    * restricting us to calling it only during shutdown.
    *
    * Callers should take care that no other consumers are subscribed to events,
    * as these events will stop when this function is called.
    *
-   * In practice, we don't use this method, as the IOInterposer is used for
+   * In practice, we don't use this method as the IOInterposer is used for
    * late-write checks.
    */
-  static void Clear();
+  void Clear();
 
   /**
    * This function immediately disables IOInterposer functionality in a fast,
    * thread-safe manner. Primarily for use by the crash reporter.
    */
-  static void Disable();
+  void Disable();
 
   /**
    * Report IO to registered observers.
    * Notice that the reported operation must be either OpRead, OpWrite or
    * OpFSync. You are not allowed to report an observation with OpWriteFSync or
    * OpAll, these are just auxiliary values for use with Register().
    *
    * If the IO call you're reporting does multiple things, write and fsync, you
@@ -211,67 +207,74 @@ public:
    * which is not being observed. Use IsObservedOperation() to check if the
    * operation you are about to report is being observed. This is especially
    * important if you are constructing expensive observations containing
    * filename and full-path.
    *
    * Remark: Init() must be called before any IO is reported. But
    * IsObservedOperation() will return false until Init() is called.
    */
-  static void Report(IOInterposeObserver::Observation& aObservation);
+  void Report(IOInterposeObserver::Observation& aObservation);
 
   /**
    * Return whether or not an operation is observed. Reporters should not
    * report operations that are not being observed by anybody. This mechanism
    * allows us to avoid reporting I/O when no observers are registered.
    */
-  static bool IsObservedOperation(IOInterposeObserver::Operation aOp);
+  bool IsObservedOperation(IOInterposeObserver::Operation aOp);
 
   /**
    * Register IOInterposeObserver, the observer object will receive all
    * observations for the given operation aOp.
    *
    * Remark: Init() must be called before observers are registered.
    */
-  static void Register(IOInterposeObserver::Operation aOp,
-                       IOInterposeObserver* aObserver);
+  void Register(IOInterposeObserver::Operation aOp,
+                IOInterposeObserver* aObserver);
 
   /**
    * Unregister an IOInterposeObserver for a given operation
    * Remark: It is always safe to unregister for all operations, even if yoú
    * didn't register for them all.
    * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver)
    *
    * Remark: Init() must be called before observers are unregistered.
    */
-  static void Unregister(IOInterposeObserver::Operation aOp,
-                         IOInterposeObserver* aObserver);
+  void Unregister(IOInterposeObserver::Operation aOp,
+                  IOInterposeObserver* aObserver);
 
   /**
    * Registers the current thread with the IOInterposer. This must be done to
    * ensure that per-thread data is created in an orderly fashion.
    * We could have written this to initialize that data lazily, however this
    * could have unintended consequences if a thread that is not aware of
    * IOInterposer was implicitly registered: its per-thread data would never
    * be deleted because it would not know to unregister itself.
    *
    * @param aIsMainThread true if IOInterposer should treat the current thread
    *                      as the main thread.
    */
-  static void
+  void
   RegisterCurrentThread(bool aIsMainThread = false);
 
   /**
    * Unregisters the current thread with the IOInterposer. This is important
    * to call when a thread is shutting down because it cleans up data that
    * is stored in a TLS slot.
    */
-  static void
+  void
   UnregisterCurrentThread();
-};
+
+  /**
+   * Called to inform observers that the process has transitioned out of the
+   * startup stage or into the shutdown stage. Main thread only.
+   */
+  void
+  EnteringNextStage();
+} // namespace IOInterposer
 
 class IOInterposerInit
 {
 public:
   IOInterposerInit()
   {
 #if defined(MOZ_ENABLE_PROFILER_SPS)
     IOInterposer::Init();
new file mode 100644
--- /dev/null
+++ b/xpcom/build/IOInterposerPrivate.h
@@ -0,0 +1,167 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef xpcom_build_IOInterposerPrivate_h
+#define xpcom_build_IOInterposerPrivate_h
+
+/* This header file contains declarations for helper classes that are
+   to be used exclusively by IOInterposer and its observers. This header
+   file is not to be used by anything else and MUST NOT be exported! */
+
+#include <prcvar.h>
+#include <prlock.h>
+
+namespace mozilla {
+namespace IOInterposer {
+
+/**
+ * The following classes are simple wrappers for PRLock and PRCondVar.
+ * IOInterposer and friends use these instead of Mozilla::Mutex et al because
+ * of the fact that IOInterposer is permitted to run until the process
+ * terminates; we can't use anything that plugs into leak checkers or deadlock
+ * detectors because IOInterposer will outlive those and generate false
+ * positives.
+ */
+
+class Monitor
+{
+public:
+  Monitor()
+    : mLock(PR_NewLock())
+    , mCondVar(PR_NewCondVar(mLock))
+  {
+  }
+
+  ~Monitor()
+  {
+    PR_DestroyCondVar(mCondVar);
+    mCondVar = nullptr;
+    PR_DestroyLock(mLock);
+    mLock = nullptr;
+  }
+
+  void Lock()
+  {
+    PR_Lock(mLock);
+  }
+
+  void Unlock()
+  {
+    PR_Unlock(mLock);
+  }
+
+  bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT)
+  {
+    return PR_WaitCondVar(mCondVar, aTimeout) == PR_SUCCESS;
+  }
+
+  bool Notify()
+  {
+    return PR_NotifyCondVar(mCondVar) == PR_SUCCESS;
+  }
+
+private:
+  PRLock*    mLock;
+  PRCondVar* mCondVar;
+};
+
+class MonitorAutoLock
+{
+public:
+  MonitorAutoLock(Monitor &aMonitor)
+    : mMonitor(aMonitor)
+  {
+    mMonitor.Lock();
+  }
+
+  ~MonitorAutoLock()
+  {
+    mMonitor.Unlock();
+  }
+
+  bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT)
+  {
+    return mMonitor.Wait(aTimeout);
+  }
+
+  bool Notify()
+  {
+    return mMonitor.Notify();
+  }
+
+private:
+  Monitor&  mMonitor;
+};
+
+class MonitorAutoUnlock
+{
+public:
+  MonitorAutoUnlock(Monitor &aMonitor)
+    : mMonitor(aMonitor)
+  {
+    mMonitor.Unlock();
+  }
+
+  ~MonitorAutoUnlock()
+  {
+    mMonitor.Lock();
+  }
+
+private:
+  Monitor&  mMonitor;
+};
+
+class Mutex
+{
+public:
+  Mutex()
+    : mPRLock(PR_NewLock())
+  {
+  }
+
+  ~Mutex()
+  {
+    PR_DestroyLock(mPRLock);
+    mPRLock = nullptr;
+  }
+
+  void Lock()
+  {
+    PR_Lock(mPRLock);
+  }
+
+  void Unlock()
+  {
+    PR_Unlock(mPRLock);
+  }
+
+private:
+  PRLock*   mPRLock;
+};
+
+class AutoLock
+{
+public:
+  AutoLock(Mutex& aLock)
+    : mLock(aLock)
+  {
+    mLock.Lock();
+  }
+
+  ~AutoLock()
+  {
+    mLock.Unlock();
+  }
+
+private:
+  Mutex&     mLock;
+};
+
+} // namespace IOInterposer
+} // namespace mozilla
+
+#endif // xpcom_build_IOInterposerPrivate_h
+
new file mode 100644
--- /dev/null
+++ b/xpcom/build/MainThreadIOLogger.cpp
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MainThreadIOLogger.h"
+
+#include "GeckoProfiler.h"
+#include "IOInterposerPrivate.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TimeStamp.h"
+
+/**
+ * This code uses NSPR stuff and STL containers because it must be detached
+ * from leak checking code; this observer runs until the process terminates.
+ */
+
+#include <prenv.h>
+#include <prprf.h>
+#include <prthread.h>
+#include <vector>
+
+namespace {
+
+struct ObservationWithStack
+{
+  ObservationWithStack(mozilla::IOInterposeObserver::Observation& aObs,
+                       ProfilerBacktrace *aStack)
+    : mObservation(aObs)
+    , mStack(aStack)
+  {
+    const char16_t* filename = aObs.Filename();
+    if (filename) {
+      mFilename = filename;
+    }
+  }
+ 
+  mozilla::IOInterposeObserver::Observation mObservation;
+  ProfilerBacktrace*                        mStack;
+  nsString                                  mFilename;
+};
+
+} // anonymous namespace
+
+namespace mozilla {
+
+class MainThreadIOLoggerImpl MOZ_FINAL : public IOInterposeObserver
+{
+public:
+  MainThreadIOLoggerImpl();
+  ~MainThreadIOLoggerImpl();
+
+  bool Init();
+
+  void Observe(Observation& aObservation);
+
+private:
+  static void sIOThreadFunc(void* aArg);
+  void IOThreadFunc();
+
+  TimeStamp             mLogStartTime;
+  const char*           mFileName;
+  PRThread*             mIOThread;
+  IOInterposer::Monitor mMonitor;
+  bool                  mShutdownRequired;
+  std::vector<ObservationWithStack> mObservations;
+};
+
+static StaticAutoPtr<MainThreadIOLoggerImpl> sImpl;
+
+MainThreadIOLoggerImpl::MainThreadIOLoggerImpl()
+  : mFileName(nullptr)
+  , mIOThread(nullptr)
+  , mShutdownRequired(false)
+{
+}
+
+MainThreadIOLoggerImpl::~MainThreadIOLoggerImpl()
+{
+  if (!mIOThread) {
+    return;
+  }
+  { // Scope for lock
+    IOInterposer::MonitorAutoLock lock(mMonitor);
+    mShutdownRequired = true;
+    lock.Notify();
+  }
+  PR_JoinThread(mIOThread);
+  mIOThread = nullptr;
+}
+
+bool
+MainThreadIOLoggerImpl::Init()
+{
+  if (mFileName) {
+    // Already initialized
+    return true;
+  }
+  mFileName = PR_GetEnv("MOZ_MAIN_THREAD_IO_LOG");
+  if (!mFileName) {
+    // Can't start
+    return false;
+  }
+  mIOThread = PR_CreateThread(PR_USER_THREAD, &sIOThreadFunc, this,
+                              PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
+                              PR_JOINABLE_THREAD, 0);
+  if (!mIOThread) {
+    return false;
+  }
+  return true;
+}
+
+/* static */ void
+MainThreadIOLoggerImpl::sIOThreadFunc(void* aArg)
+{
+  PR_SetCurrentThreadName("MainThreadIOLogger");
+  MainThreadIOLoggerImpl* obj = static_cast<MainThreadIOLoggerImpl*>(aArg);
+  obj->IOThreadFunc();
+}
+
+void
+MainThreadIOLoggerImpl::IOThreadFunc()
+{
+  PRFileDesc* fd = PR_Open(mFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE,
+                           PR_IRUSR | PR_IWUSR | PR_IRGRP);
+  if (!fd) {
+    IOInterposer::MonitorAutoLock lock(mMonitor);
+    mShutdownRequired = true;
+    std::vector<ObservationWithStack>().swap(mObservations);
+    return;
+  }
+  mLogStartTime = TimeStamp::Now();
+  { // Scope for lock
+    IOInterposer::MonitorAutoLock lock(mMonitor);
+    while (true) {
+      while (!mShutdownRequired && mObservations.empty()) {
+        lock.Wait();
+      }
+      if (mShutdownRequired) {
+        break;
+      }
+      // Pull events off the shared array onto a local one
+      std::vector<ObservationWithStack> observationsToWrite;
+      observationsToWrite.swap(mObservations);
+ 
+      // Release the lock so that we're not holding anybody up during I/O
+      IOInterposer::MonitorAutoUnlock unlock(mMonitor);
+
+      // Now write the events.
+      for (std::vector<ObservationWithStack>::iterator
+             i = observationsToWrite.begin(), e = observationsToWrite.end();
+           i != e; ++i) {
+        if (i->mObservation.ObservedOperation() == OpNextStage) {
+          PR_fprintf(fd, "%f,NEXT-STAGE\n",
+                     (TimeStamp::Now() - mLogStartTime).ToMilliseconds());
+          continue;
+        }
+        double durationMs = i->mObservation.Duration().ToMilliseconds();
+        nsAutoCString nativeFilename;
+        nativeFilename.AssignLiteral("(not available)");
+        if (!i->mFilename.IsEmpty()) {
+          if (NS_FAILED(NS_CopyUnicodeToNative(i->mFilename, nativeFilename))) {
+            nativeFilename.AssignLiteral("(conversion failed)");
+          }
+        }
+        /**
+         * Format:
+         * Start Timestamp (Milliseconds), Operation, Duration (Milliseconds), Event Source, Filename
+         */
+        if (PR_fprintf(fd, "%f,%s,%f,%s,%s\n",
+                       (i->mObservation.Start() - mLogStartTime).ToMilliseconds(),
+                       i->mObservation.ObservedOperationString(), durationMs,
+                       i->mObservation.Reference(), nativeFilename.get()) > 0) {
+          ProfilerBacktrace* stack = i->mStack;
+          if (stack) {
+            // TODO: Write out the callstack
+            //       (This will be added in a later bug)
+            profiler_free_backtrace(stack);
+          }
+        }
+      }
+    }
+  }
+  PR_Close(fd);
+}
+
+void
+MainThreadIOLoggerImpl::Observe(Observation& aObservation)
+{
+  if (!mFileName || !IsMainThread()) {
+    return;
+  }
+  IOInterposer::MonitorAutoLock lock(mMonitor);
+  if (mShutdownRequired) {
+    // The writer thread isn't running. Don't enqueue any more data.
+    return;
+  }
+  // Passing nullptr as aStack parameter for now
+  mObservations.push_back(ObservationWithStack(aObservation, nullptr));
+  lock.Notify();
+}
+
+namespace MainThreadIOLogger {
+
+bool
+Init()
+{
+  sImpl = new MainThreadIOLoggerImpl();
+  if (!sImpl->Init()) {
+    return false;
+  }
+  IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, sImpl);
+  return true;
+}
+
+} // namespace MainThreadIOLogger
+
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/xpcom/build/MainThreadIOLogger.h
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_MainThreadIOLogger_h
+#define mozilla_MainThreadIOLogger_h
+
+namespace mozilla {
+namespace MainThreadIOLogger {
+
+bool Init();
+
+} // namespace MainThreadIOLogger
+} // namespace mozilla
+
+#endif // mozilla_MainThreadIOLogger_h
+
--- a/xpcom/build/moz.build
+++ b/xpcom/build/moz.build
@@ -45,16 +45,17 @@ include('../glue/objs.mozbuild')
 
 UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs
 UNIFIED_SOURCES += xpcom_glue_src_cppsrcs
 
 UNIFIED_SOURCES += [
     'FrozenFunctions.cpp',
     'IOInterposer.cpp',
     'LateWriteChecks.cpp',
+    'MainThreadIOLogger.cpp',
     'nsXPComInit.cpp',
     'nsXPCOMStrings.cpp',
     'Services.cpp',
 ]
 
 if CONFIG['OS_ARCH'] != 'WINNT':
     SOURCES += [
         'NSPRInterposer.cpp',