Merge backout
authorMats Palmgren <matspal@gmail.com>
Sun, 20 Apr 2014 19:35:54 +0000
changeset 179777 ea214ef03c3fc40f630fb16f333d05502692a6d5
parent 179776 581208970e99444a504e64f3252095779afa11fc (current diff)
parent 179768 53a6c96cea62c46160ce9ea80e0d9188fe5ed2df (diff)
child 179779 28dc0100f9b03b65d68679c528391397b5ffedb8
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
milestone31.0a1
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',