Merge backout
authorMats Palmgren <matspal@gmail.com>
Sun, 20 Apr 2014 19:40:24 +0000
changeset 197862 28dc0100f9b03b65d68679c528391397b5ffedb8
parent 197861 4bc69199b7e1965805c4e9096913fbaf661731ff (current diff)
parent 197860 ea214ef03c3fc40f630fb16f333d05502692a6d5 (diff)
child 197870 9d9d84882a64fb084ca5e563ae9e7f192516feae
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge backout
layout/base/nsCSSRendering.cpp
modules/libpref/src/init/all.js
toolkit/components/places/tests/unit/test_PlacesUtils_nodeIsXXX_invalidArg.js
--- a/accessible/tests/mochitest/autocomplete.js
+++ b/accessible/tests/mochitest/autocomplete.js
@@ -194,16 +194,21 @@ AutoCompleteResult.prototype =
     return null;
   },
 
   getImageAt: function(aIndex)
   {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex)
+  {
+    return this.getValueAt(aIndex);
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: function(iid) {
     if (iid.equals(nsISupports) ||
         iid.equals(nsIAutoCompleteResult))
       return this;
 
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -778,20 +778,16 @@ var CustomEventManager = {
           JSON.stringify({ callerID: detail.chromeEventID,
                            keyword: detail.keyword,
                            manifestURL: detail.manifestURL,
                            selectedApps: detail.peers }));
         break;
       case 'inputmethod-update-layouts':
         KeyboardHelper.handleEvent(detail);
         break;
-      case 'nfc-hardware-state-change':
-        Services.obs.notifyObservers(null, 'nfc-hardware-state-change',
-          JSON.stringify({ nfcHardwareState: detail.nfcHardwareState }));
-        break;
     }
   }
 }
 
 var AlertsHelper = {
   _listeners: {},
   _count: 0,
 
--- 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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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"/>
@@ -126,11 +126,11 @@
   <!-- Emulator specific things -->
   <project name="android-development" path="development" remote="b2g" revision="dab55669da8f48b6e57df95d5af9f16b4a87b0b1"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8afce6d5d48b71b6e7cb917179fb6147fb747521"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="6a7ada569fd37c09ed4bbee6eb78cea5b467b229"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="baaf899afb158b9530690002f3656e958e3eb047"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
 </manifest>
--- 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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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": "26f2c60c0ba0e64114467663c33c82360c13f4cf", 
+    "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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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"/>
@@ -122,17 +122,17 @@
   <project name="platform/system/netd" path="system/netd" revision="56112dd7b811301b718d0643a82fd5cac9522073"/>
   <project name="platform/system/security" path="system/security" revision="f48ff68fedbcdc12b570b7699745abb6e7574907"/>
   <project name="platform/system/vold" path="system/vold" revision="8de05d4a52b5a91e7336e6baa4592f945a6ddbea"/>
   <default remote="caf" revision="refs/tags/android-4.3_r2.1" sync-j="4"/>
   <!-- Nexus 4 specific things -->
   <project name="device-mako" path="device/lge/mako" remote="b2g" revision="78d17f0c117f0c66dd55ee8d5c5dde8ccc93ecba"/>
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
   <project name="device/lge/mako-kernel" path="device/lge/mako-kernel" revision="d1729e53d71d711c8fde25eab8728ff2b9b4df0e"/>
-  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="6a7ada569fd37c09ed4bbee6eb78cea5b467b229"/>
+  <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="baaf899afb158b9530690002f3656e958e3eb047"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
   <project name="platform/hardware/broadcom/wlan" path="hardware/broadcom/wlan" revision="0e1929fa3aa38bf9d40e9e953d619fab8164c82e"/>
   <project name="platform/hardware/qcom/audio" path="hardware/qcom/audio" revision="b0a528d839cfd9d170d092fe3743b5252b4243a6"/>
   <project name="platform/hardware/qcom/bt" path="hardware/qcom/bt" revision="380945eaa249a2dbdde0daa4c8adb8ca325edba6"/>
   <project name="platform/hardware/qcom/display" path="hardware/qcom/display" revision="6f3b0272cefaffeaed2a7d2bb8f633059f163ddc"/>
   <project name="platform/hardware/qcom/keymaster" path="hardware/qcom/keymaster" revision="16da8262c997a5a0d797885788a64a0771b26910"/>
   <project name="platform/hardware/qcom/media" path="hardware/qcom/media" revision="689b476ba3eb46c34b81343295fe144a0e81a18e"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,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="76c94ac5dc3b8e17cc23d9cc3e2662b0d5d28b2e"/>
+  <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/browser.css
+++ b/browser/base/content/browser.css
@@ -117,47 +117,48 @@ tabbrowser {
 }
 
 .tabbrowser-tab:not([pinned]):not([fadein]) {
   max-width: 0.1px;
   min-width: 0.1px;
   visibility: hidden;
 }
 
-.tab-background {
-  /* Explicitly set the visibility to override the value (collapsed)
-   * we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
-  visibility: visible;
-  /* The transition is only delayed for opening tabs. */
-  transition: visibility 0ms 25ms;
-}
-
-.tab-background:not([fadein]):not([pinned]) {
-  visibility: hidden;
-  /* Closing tabs are hidden without a delay. */
-  transition-delay: 0ms;
-}
-
+.tab-background,
 .tab-close-button,
 .tab-label {
   /* Explicitly set the visibility to override the value (collapsed)
    * we inherit from #TabsToolbar[collapsed] upon opening a browser window. */
   visibility: visible;
+}
+
+.tab-background[fadein] {
+  /* This transition is only wanted for opening tabs. */
+  transition: visibility 0ms 25ms;
+}
+
+.tab-background:not([fadein]) {
+  visibility: hidden;
+}
+
+.tab-close-button[fadein],
+.tab-label[fadein] {
+  /* This transition is only wanted for opening tabs. */
   transition: opacity 70ms 230ms,
               visibility 0ms 230ms;
 }
 
-.tab-close-button:not([fadein]):not([pinned]),
-.tab-label:not([fadein]):not([pinned]) {
+.tab-close-button:not([fadein]),
+.tab-label:not([fadein]) {
   visibility: collapse;
   opacity: .6;
 }
 
-.tab-throbber:not([fadein]):not([pinned]),
-.tab-icon-image:not([fadein]):not([pinned]) {
+.tab-throbber:not([fadein]),
+.tab-icon-image:not([fadein]) {
   display: none;
 }
 
 .tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
   position: fixed !important;
   display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
 }
 
--- 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/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -14,19 +14,20 @@ let gPage = {
    */
   init: function Page_init() {
     // Add ourselves to the list of pages to receive notifications.
     gAllPages.register(this);
 
     // Listen for 'unload' to unregister this page.
     addEventListener("unload", this, false);
 
-    // Listen for toggle button clicks.
-    let button = document.getElementById("newtab-toggle");
-    button.addEventListener("click", this, false);
+    // XXX bug 991111 - Not all click events are correctly triggered when
+    // listening from xhtml nodes -- in particular middle clicks on sites, so
+    // listen from the xul window and filter then delegate
+    addEventListener("click", this, false);
 
     // Initialize sponsored panel
     this._sponsoredPanel = document.getElementById("sponsored-panel");
     let link = this._sponsoredPanel.querySelector(".text-link");
     link.addEventListener("click", () => this._sponsoredPanel.hidePopup());
 
     // Check if the new tab feature is enabled.
     let enabled = gAllPages.enabled;
@@ -191,17 +192,32 @@ let gPage = {
   handleEvent: function Page_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "unload":
         if (this._mutationObserver)
           this._mutationObserver.disconnect();
         gAllPages.unregister(this);
         break;
       case "click":
-        gAllPages.enabled = !gAllPages.enabled;
+        let {button, target} = aEvent;
+        if (target.id == "newtab-toggle") {
+          if (button == 0) {
+            gAllPages.enabled = !gAllPages.enabled;
+          }
+          break;
+        }
+
+        // Go up ancestors until we find a Site or not
+        while (target) {
+          if (target.hasOwnProperty("_newtabSite")) {
+            target._newtabSite.onClick(aEvent);
+            break;
+          }
+          target = target.parentNode;
+        }
         break;
       case "dragover":
         if (gDrag.isValid(aEvent) && gDrag.draggedSite)
           aEvent.preventDefault();
         break;
       case "drop":
         if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
           aEvent.preventDefault();
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -165,20 +165,16 @@ Site.prototype = {
    * Adds event handlers for the site and its buttons.
    */
   _addEventHandlers: function Site_addEventHandlers() {
     // Register drag-and-drop event handlers.
     this._node.addEventListener("dragstart", this, false);
     this._node.addEventListener("dragend", this, false);
     this._node.addEventListener("mouseover", this, false);
 
-    // XXX bug 991111 - Not all click events are correctly triggered when
-    // listening from the xhtml node, so listen from the xul window and filter
-    addEventListener("click", this, false);
-
     // Specially treat the sponsored icon to prevent regular hover effects
     let sponsored = this._querySelector(".newtab-control-sponsored");
     sponsored.addEventListener("mouseover", () => {
       this.cell.node.setAttribute("ignorehover", "true");
     });
     sponsored.addEventListener("mouseout", () => {
       this.cell.node.removeAttribute("ignorehover");
     });
@@ -213,21 +209,29 @@ Site.prototype = {
       Services.telemetry.getHistogramById("NEWTAB_PAGE_DIRECTORY_TYPE_CLICKED")
                         .add(typeIndex);
     }
   },
 
   /**
    * Handles site click events.
    */
-  _onClick: function Site_onClick(aEvent) {
-    let target = aEvent.target;
+  onClick: function Site_onClick(aEvent) {
+    let {button, target} = aEvent;
     if (target.classList.contains("newtab-link") ||
         target.parentElement.classList.contains("newtab-link")) {
-      this._recordSiteClicked(this.cell.index);
+      // Record for primary and middle clicks
+      if (button == 0 || button == 1) {
+        this._recordSiteClicked(this.cell.index);
+      }
+      return;
+    }
+
+    // Only handle primary clicks for the remaining targets
+    if (button != 0) {
       return;
     }
 
     aEvent.preventDefault();
     if (aEvent.target.classList.contains("newtab-control-block"))
       this.block();
     else if (target.classList.contains("newtab-control-sponsored"))
       gPage.showSponsoredPanel(target);
@@ -237,23 +241,16 @@ Site.prototype = {
       this.pin();
   },
 
   /**
    * Handles all site events.
    */
   handleEvent: function Site_handleEvent(aEvent) {
     switch (aEvent.type) {
-      case "click":
-        // Check the bitmask if the click event is for the site's descendants
-        if (this._node.compareDocumentPosition(aEvent.target) &
-            this._node.DOCUMENT_POSITION_CONTAINED_BY) {
-          this._onClick(aEvent);
-        }
-        break;
       case "mouseover":
         this._node.removeEventListener("mouseover", this, false);
         this._speculativeConnect();
         break;
       case "dragstart":
         gDrag.start(this, aEvent);
         break;
       case "dragend":
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -285,17 +285,16 @@
 
       <method name="unpinTab">
         <parameter name="aTab"/>
         <body><![CDATA[
           if (!aTab.pinned)
             return;
 
           this.moveTabTo(aTab, this._numPinnedTabs - 1);
-          aTab.setAttribute("fadein", "true");
           aTab.removeAttribute("pinned");
           aTab.style.MozMarginStart = "";
           this.tabContainer._unlockTabSizing();
           this.tabContainer._positionPinnedTabs();
           this.tabContainer.adjustTabstrip();
 
           // Bug 961867 - [e10s] Implement the logic for app tabs
           if (!gMultiProcessBrowser)
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -12,16 +12,17 @@ skip-if = e10s # Bug ?????? - about:newt
 [browser_newtab_bug734043.js]
 [browser_newtab_bug735987.js]
 skip-if = os == "mac" # Intermittent failures, bug 898317
 [browser_newtab_bug752841.js]
 [browser_newtab_bug765628.js]
 [browser_newtab_bug876313.js]
 [browser_newtab_bug991111.js]
 [browser_newtab_bug991210.js]
+[browser_newtab_bug998387.js]
 [browser_newtab_disable.js]
 [browser_newtab_drag_drop.js]
 [browser_newtab_drag_drop_ext.js]
 [browser_newtab_drop_preview.js]
 [browser_newtab_focus.js]
 [browser_newtab_perwindow_private_browsing.js]
 [browser_newtab_reset.js]
 [browser_newtab_sponsored_icon_click.js]
--- a/browser/base/content/test/newtab/browser_newtab_bug991111.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug991111.js
@@ -3,17 +3,17 @@
 
 function runTests() {
   yield setLinks("0");
   yield addNewTabPageTab();
 
   // Remember if the click handler was triggered
   let cell = getCell(0);
   let clicked = false;
-  cell.site._onClick = e => {
+  cell.site.onClick = e => {
     clicked = true;
     executeSoon(TestRunner.next);
   };
 
   // Send a middle-click and make sure it happened
   yield EventUtils.synthesizeMouseAtCenter(cell.node, {button: 1}, getContentWindow());
   ok(clicked, "middle click triggered click listener");
 }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug998387.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+  yield setLinks("0");
+  yield addNewTabPageTab();
+
+  // Remember if the click handler was triggered
+  let {site} = getCell(0);
+  let origOnClick = site.onClick;
+  let clicked = false;
+  site.onClick = e => {
+    origOnClick.call(site, e);
+    clicked = true;
+    executeSoon(TestRunner.next);
+  };
+
+  // Send a middle-click and make sure it happened
+  let block = getContentDocument().querySelector(".newtab-control-block");
+  yield EventUtils.synthesizeMouseAtCenter(block, {button: 1}, getContentWindow());
+  ok(clicked, "middle click triggered click listener");
+
+  // Make sure the cell didn't actually get blocked
+  checkGrid("0");
+}
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -144,16 +144,17 @@ support-files =
 [browser_dbg_global-method-override.js]
 [browser_dbg_globalactor.js]
 [browser_dbg_host-layout.js]
 [browser_dbg_iframes.js]
 [browser_dbg_instruments-pane-collapse.js]
 [browser_dbg_listaddons.js]
 [browser_dbg_listtabs-01.js]
 [browser_dbg_listtabs-02.js]
+[browser_dbg_listtabs-03.js]
 [browser_dbg_location-changes-01-simple.js]
 [browser_dbg_location-changes-02-blank.js]
 [browser_dbg_location-changes-03-new.js]
 [browser_dbg_location-changes-04-breakpoint.js]
 [browser_dbg_multiple-windows.js]
 [browser_dbg_navigation.js]
 [browser_dbg_no-page-sources.js]
 [browser_dbg_on-pause-highlight.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_listtabs-03.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Make sure the listTabs request works as specified.
+ */
+
+const TAB1_URL = EXAMPLE_URL + "doc_empty-tab-01.html";
+
+let gTab1, gTab1Actor, gTab2, gTab2Actor, gClient;
+
+function listTabs() {
+  let deferred = promise.defer();
+
+  gClient.listTabs(aResponse => {
+    deferred.resolve(aResponse.tabs);
+  });
+
+  return deferred.promise;
+}
+
+function request(params) {
+  let deferred = promise.defer();
+  gClient.request(params, deferred.resolve);
+  return deferred.promise;
+}
+
+function test() {
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init(() => true);
+    DebuggerServer.addBrowserActors();
+  }
+
+  let transport = DebuggerServer.connectPipe();
+  gClient = new DebuggerClient(transport);
+  gClient.connect(Task.async(function*(aType, aTraits) {
+    is(aType, "browser",
+      "Root actor should identify itself as a browser.");
+    let tab = yield addTab(TAB1_URL);
+
+    let tabs = yield listTabs();
+    is(tabs.length, 2, "Should be two tabs");
+    let tabGrip = tabs.filter(a => a.url ==TAB1_URL).pop();
+    ok(tabGrip, "Should have an actor for the tab");
+
+    let response = yield request({ to: tabGrip.actor, type: "attach" });
+    is(response.type, "tabAttached", "Should have attached");
+
+    tabs = yield listTabs();
+
+    response = yield request({ to: tabGrip.actor, type: "detach" });
+    is(response.type, "detached", "Should have detached");
+
+    let newGrip = tabs.filter(a => a.url ==TAB1_URL).pop();
+    is(newGrip.actor, tabGrip.actor, "Should have the same actor for the same tab");
+
+    response = yield request({ to: tabGrip.actor, type: "attach" });
+    is(response.type, "tabAttached", "Should have attached");
+    response = yield request({ to: tabGrip.actor, type: "detach" });
+    is(response.type, "detached", "Should have detached");
+
+    yield removeTab(tab);
+    yield closeConnection();
+    finish();
+  }));
+}
+
+function closeConnection() {
+  let deferred = promise.defer();
+  gClient.close(deferred.resolve);
+  return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+  gTab1 = null;
+  gTab1Actor = null;
+  gTab2 = null;
+  gTab2Actor = null;
+  gClient = null;
+});
--- 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/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -48,16 +48,17 @@
 #include "nsCrossSiteListenerProxy.h"
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
 #include "nsINetworkSeer.h"
 #include "mozilla/dom/EncodingUtils.h"
 
 #include "mozilla/CORSMode.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/unused.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
@@ -809,21 +810,20 @@ NotifyOffThreadScriptLoadCompletedRunnab
   }
 
   return rv;
 }
 
 static void
 OffThreadScriptLoaderCallback(void *aToken, void *aCallbackData)
 {
-  NotifyOffThreadScriptLoadCompletedRunnable* aRunnable =
-    static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData);
+  nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> aRunnable =
+    dont_AddRef(static_cast<NotifyOffThreadScriptLoadCompletedRunnable*>(aCallbackData));
   aRunnable->SetToken(aToken);
   NS_DispatchToMainThread(aRunnable);
-  NS_RELEASE(aRunnable);
 }
 
 nsresult
 nsScriptLoader::AttemptAsyncScriptParse(nsScriptLoadRequest* aRequest)
 {
   if (!aRequest->mElement->GetScriptAsync() || aRequest->mIsInline) {
     return NS_ERROR_FAILURE;
   }
@@ -843,31 +843,29 @@ nsScriptLoader::AttemptAsyncScriptParse(
 
   JS::CompileOptions options(cx);
   FillCompileOptionsForRequest(aRequest, global, &options);
 
   if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptText.Length())) {
     return NS_ERROR_FAILURE;
   }
 
-  NotifyOffThreadScriptLoadCompletedRunnable* runnable =
+  nsRefPtr<NotifyOffThreadScriptLoadCompletedRunnable> runnable =
     new NotifyOffThreadScriptLoadCompletedRunnable(aRequest, this);
 
-  // This reference will be consumed by OffThreadScriptLoaderCallback.
-  NS_ADDREF(runnable);
-
   if (!JS::CompileOffThread(cx, options,
                             aRequest->mScriptText.get(), aRequest->mScriptText.Length(),
                             OffThreadScriptLoaderCallback,
                             static_cast<void*>(runnable))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   mDocument->BlockOnload();
 
+  unused << runnable.forget();
   return NS_OK;
 }
 
 nsresult
 nsScriptLoader::ProcessRequest(nsScriptLoadRequest* aRequest, void **aOffThreadToken)
 {
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
                "Processing requests when running scripts is unsafe.");
--- a/dom/apps/src/InterAppCommService.js
+++ b/dom/apps/src/InterAppCommService.js
@@ -71,31 +71,28 @@ function InterAppCommService() {
   //
   // For example:
   //
   // {
   //   "foo": {
   //     "app://subApp1.gaiamobile.org/manifest.webapp": {
   //       pageURL: "app://subApp1.gaiamobile.org/handler.html",
   //       description: "blah blah",
-  //       appStatus: Ci.nsIPrincipal.APP_STATUS_CERTIFIED,
   //       rules: { ... }
   //     },
   //     "app://subApp2.gaiamobile.org/manifest.webapp": {
   //       pageURL: "app://subApp2.gaiamobile.org/handler.html",
   //       description: "blah blah",
-  //       appStatus: Ci.nsIPrincipal.APP_STATUS_PRIVILEGED,
   //       rules: { ... }
   //     }
   //   },
   //   "bar": {
   //     "app://subApp3.gaiamobile.org/manifest.webapp": {
   //       pageURL: "app://subApp3.gaiamobile.org/handler.html",
   //       description: "blah blah",
-  //       appStatus: Ci.nsIPrincipal.APP_STATUS_INSTALLED,
   //       rules: { ... }
   //     }
   //   }
   // }
   //
   // TODO Bug 908999 - Update registered connections when app gets uninstalled.
   this._registeredConnections = {};
 
@@ -209,38 +206,37 @@ function InterAppCommService() {
   //     }
   //   }
   // }
   this._messagePortPairs = {};
 }
 
 InterAppCommService.prototype = {
   registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
-                               aDescription, aAppStatus, aRules) {
+                               aDescription, aRules) {
     let manifestURL = aManifestURI.spec;
     let pageURL = aHandlerPageURI.spec;
 
     if (DEBUG) {
       debug("registerConnection: aKeyword: " + aKeyword +
             " manifestURL: " + manifestURL + " pageURL: " + pageURL +
-            " aDescription: " + aDescription + " aAppStatus: " + aAppStatus +
+            " aDescription: " + aDescription +
             " aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
             " aRules.manifestURLs: " + aRules.manifestURLs +
             " aRules.installOrigins: " + aRules.installOrigins);
     }
 
     let subAppManifestURLs = this._registeredConnections[aKeyword];
     if (!subAppManifestURLs) {
       subAppManifestURLs = this._registeredConnections[aKeyword] = {};
     }
 
     subAppManifestURLs[manifestURL] = {
       pageURL: pageURL,
       description: aDescription,
-      appStatus: aAppStatus,
       rules: aRules,
       manifestURL: manifestURL
     };
   },
 
   _matchMinimumAccessLevel: function(aRules, aAppStatus) {
     if (!aRules || !aRules.minimumAccessLevel) {
       if (DEBUG) {
@@ -295,77 +291,76 @@ InterAppCommService.prototype = {
     if (DEBUG) {
       debug("rules.manifestURLs is not matched!" +
             " manifestURLs: " + manifestURLs +
             " aManifestURL : " + aManifestURL);
     }
     return false;
   },
 
-  _matchInstallOrigins: function(aRules, aManifestURL) {
+  _matchInstallOrigins: function(aRules, aInstallOrigin) {
     if (!aRules || !Array.isArray(aRules.installOrigins)) {
       if (DEBUG) {
         debug("rules.installOrigins is not available. No need to match.");
       }
       return true;
     }
 
-    let installOrigin =
-      appsService.getAppByManifestURL(aManifestURL).installOrigin;
-
     let installOrigins = aRules.installOrigins;
-    if (installOrigins.indexOf(installOrigin) != -1) {
+    if (installOrigins.indexOf(aInstallOrigin) != -1) {
       return true;
     }
 
     if (DEBUG) {
       debug("rules.installOrigins is not matched!" +
-            " aManifestURL: " + aManifestURL +
             " installOrigins: " + installOrigins +
-            " installOrigin : " + installOrigin);
+            " installOrigin : " + aInstallOrigin);
     }
     return false;
   },
 
-  _matchRules: function(aPubAppManifestURL, aPubAppStatus, aPubRules,
-                        aSubAppManifestURL, aSubAppStatus, aSubRules) {
+  _matchRules: function(aPubAppManifestURL, aPubRules,
+                        aSubAppManifestURL, aSubRules) {
+    let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
+    let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
+
     // TODO Bug 907068 In the initiative step, we only expose this API to
     // certified apps to meet the time line. Eventually, we need to make
     // it available for the non-certified apps as well. For now, only the
     // certified apps can match the rules.
-    if (aPubAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
-        aSubAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
+    if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
+        subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
       if (DEBUG) {
         debug("Only certified apps are allowed to do connections.");
       }
       return false;
     }
 
     if (!aPubRules && !aSubRules) {
       if (DEBUG) {
         debug("No rules for publisher and subscriber. No need to match.");
       }
       return true;
     }
 
     // Check minimumAccessLevel.
-    if (!this._matchMinimumAccessLevel(aPubRules, aSubAppStatus) ||
-        !this._matchMinimumAccessLevel(aSubRules, aPubAppStatus)) {
+    if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) ||
+        !this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) {
       return false;
     }
 
     // Check manifestURLs.
     if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
         !this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
       return false;
     }
 
     // Check installOrigins.
-    if (!this._matchInstallOrigins(aPubRules, aSubAppManifestURL) ||
-        !this._matchInstallOrigins(aSubRules, aPubAppManifestURL)) {
+    if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) ||
+        !this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) {
       return false;
     }
 
     // Check developers.
     // TODO Do we really want to check this? This one seems naive.
 
     if (DEBUG) debug("All rules are matched.");
     return true;
@@ -447,17 +442,16 @@ InterAppCommService.prototype = {
   },
 
   _connect: function(aMessage, aTarget) {
     let keyword = aMessage.keyword;
     let pubRules = aMessage.rules;
     let pubAppManifestURL = aMessage.manifestURL;
     let outerWindowID = aMessage.outerWindowID;
     let requestID = aMessage.requestID;
-    let pubAppStatus = aMessage.appStatus;
 
     let subAppManifestURLs = this._registeredConnections[keyword];
     if (!subAppManifestURLs) {
       if (DEBUG) {
         debug("No apps are subscribed for this connection. Returning.");
       }
       this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
                                  aTarget, outerWindowID, requestID);
@@ -481,22 +475,21 @@ InterAppCommService.prototype = {
         if (DEBUG) {
           debug("Don't need to select again. Skipping: " + subAppManifestURL);
         }
         continue;
       }
 
       // Only rule-matched publishers/subscribers are allowed to connect.
       let subscribedInfo = subAppManifestURLs[subAppManifestURL];
-      let subAppStatus = subscribedInfo.appStatus;
       let subRules = subscribedInfo.rules;
 
       let matched =
-        this._matchRules(pubAppManifestURL, pubAppStatus, pubRules,
-                         subAppManifestURL, subAppStatus, subRules);
+        this._matchRules(pubAppManifestURL, pubRules,
+                         subAppManifestURL, subRules);
       if (!matched) {
         if (DEBUG) {
           debug("Rules are not matched. Skipping: " + subAppManifestURL);
         }
         continue;
       }
 
       appsToSelect.push({
new file mode 100644
--- /dev/null
+++ b/dom/apps/src/ScriptPreloader.jsm
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+this.EXPORTED_SYMBOLS = ["ScriptPreloader"];
+
+function debug(aMsg) {
+  //dump("--*-- ScriptPreloader: " + aMsg + "\n");
+}
+
+this.ScriptPreloader = {
+#ifdef MOZ_B2G
+  _enabled: true,
+#else
+  _enabled: false,
+#endif
+
+  preload: function(aApp, aManifest) {
+    debug("Preloading " + aApp.origin);
+    let deferred = Promise.defer();
+
+    if (!this._enabled) {
+      deferred.resolve();
+      return deferred.promise;
+    }
+
+    if (aManifest.precompile &&
+        Array.isArray(aManifest.precompile) &&
+        aManifest.precompile.length > 0) {
+      let origin = Services.io.newURI(aApp.origin, null, null);
+      let toLoad = aManifest.precompile.length;
+      let principal =
+        Services.scriptSecurityManager
+                .getAppCodebasePrincipal(origin, aApp.localId, false);
+
+      aManifest.precompile.forEach((aPath) => {
+        let uri = Services.io.newURI(aPath, null, origin);
+        debug("Script to compile: " + uri.spec);
+        try {
+          Services.scriptloader.precompileScript(uri, principal,
+            (aSubject, aTopic, aData) => {
+              let uri = aSubject.QueryInterface(Ci.nsIURI);
+              debug("Done compiling " + uri.spec);
+
+              toLoad--;
+              if (toLoad == 0) {
+                deferred.resolve();
+              }
+            });
+        } catch (e) {
+          // Resolve the promise if precompileScript throws.
+          deferred.resolve();
+        }
+      });
+    } else {
+      // The precompile field is not an array, let the developer know.
+      // We don't want to have to enable debug for that to show up.
+      if (aManifest.precompile) {
+        Cu.reportError("ASM.JS compilation failed: the 'precompile' manifest " +
+                       "property should be an array of script uris.\n");
+      }
+      deferred.resolve();
+    }
+
+    return deferred.promise;
+  }
+}
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -481,17 +481,16 @@ WebappsApplication.prototype = {
 
   connect: function(aKeyword, aRules) {
     return this.createPromise(function (aResolve, aReject) {
       cpmm.sendAsyncMessage("Webapps:Connect",
                             { keyword: aKeyword,
                               rules: aRules,
                               manifestURL: this.manifestURL,
                               outerWindowID: this._id,
-                              appStatus: this._appStatus,
                               requestID: this.getPromiseResolverId({
                                 resolve: aResolve,
                                 reject: aReject
                               })});
     }.bind(this));
   },
 
   getConnections: function() {
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -56,16 +56,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/SystemMessagePermissionsChecker.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
   "resource://gre/modules/WebappOSUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "ScriptPreloader",
+                                  "resource://gre/modules/ScriptPreloader.jsm");
+
 #ifdef MOZ_WIDGET_GONK
 XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
   Cu.import("resource://gre/modules/systemlibs.js");
   return libcutils;
 });
 #endif
 
 function debug(aMsg) {
@@ -729,17 +732,16 @@ this.DOMApplicationRegistry = {
         msgmgr.registerPage("connection", handlerPageURI, manifestURI);
       }
 
       interAppCommService.
         registerConnection(keyword,
                            handlerPageURI,
                            manifestURI,
                            connection.description,
-                           AppsUtils.getAppManifestStatus(manifest),
                            connection.rules);
     }
   },
 
   _registerSystemMessages: function(aManifest, aApp) {
     this._registerSystemMessagesForEntryPoint(aManifest, aApp, null);
 
     if (!aManifest.entry_points) {
@@ -1484,17 +1486,19 @@ this.DOMApplicationRegistry = {
           for (let prop in app.staged) {
             app[prop] = app.staged[prop];
           }
           delete app.staged;
         }
 
         delete app.retryingDownload;
 
-        this._saveApps().then(() => {
+        // Update the asm.js scripts we need to compile.
+        ScriptPreloader.preload(app, aData)
+          .then(() => this._saveApps()).then(() => {
           // Update the handlers and permissions for this app.
           this.updateAppHandlers(aOldManifest, aData, app);
 
           AppsUtils.loadJSONAsync(staged.path).then((aUpdateManifest) => {
             let appObject = AppsUtils.cloneAppObject(app);
             appObject.updateManifest = aUpdateManifest;
             this.notifyUpdateHandlers(appObject, aData, appFile.path);
           });
@@ -2590,23 +2594,29 @@ onInstallSuccessAck: function onInstallS
       this.updateDataStore(this.webapps[aId].localId, aNewApp.origin,
                            aNewApp.manifestURL, aManifest, aNewApp.appStatus);
 
       this.broadcastMessage("Webapps:UpdateState", {
         app: app,
         manifest: aManifest,
         manifestURL: aNewApp.manifestURL
       });
-      this.broadcastMessage("Webapps:FireEvent", {
-        eventType: ["downloadsuccess", "downloadapplied"],
-        manifestURL: aNewApp.manifestURL
-      });
-      if (aInstallSuccessCallback) {
-        aInstallSuccessCallback(aManifest, zipFile.path);
-      }
+
+      // Check if we have asm.js code to preload for this application.
+      ScriptPreloader.preload(aNewApp, aManifest)
+                     .then(() => {
+          this.broadcastMessage("Webapps:FireEvent", {
+            eventType: ["downloadsuccess", "downloadapplied"],
+            manifestURL: aNewApp.manifestURL
+          });
+          if (aInstallSuccessCallback) {
+            aInstallSuccessCallback(aManifest, zipFile.path);
+          }
+        }
+      );
     });
   },
 
   _nextLocalId: function() {
     let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
 
     while (this.getManifestURLByLocalId(id)) {
       id++;
--- a/dom/apps/src/moz.build
+++ b/dom/apps/src/moz.build
@@ -31,16 +31,17 @@ EXTRA_JS_MODULES += [
     'PermissionsInstaller.jsm',
     'PermissionsTable.jsm',
     'StoreTrustAnchor.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'AppsUtils.jsm',
     'OperatorApps.jsm',
+    'ScriptPreloader.jsm',
     'Webapps.jsm',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'gklayout'
 
 LOCAL_INCLUDES += [
--- a/dom/interfaces/apps/nsIInterAppCommService.idl
+++ b/dom/interfaces/apps/nsIInterAppCommService.idl
@@ -11,30 +11,27 @@ interface nsIURI;
  *
  * This interface contains helpers for Inter-App Communication API [1] related
  * purposes. A singleton service of this interface will be instantiated during
  * the system boot-up, which plays the role of the central service receiving
  * messages from and interacting with the content processes.
  *
  * [1] https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
  */
-[scriptable, uuid(7fdd8b68-0b0a-11e3-9b4c-afbc236da250)]
+[scriptable, uuid(b3d711a4-c6a4-11e3-8fd3-738e7fbcb6d6)]
 interface nsIInterAppCommService : nsISupports
 {
   /*
    * Registration of a page that wants to be connected to other apps through
    * the Inter-App Communication API.
    *
    * @param keyword        The connection's keyword.
    * @param handlerPageURI The URI of the handler's page.
    * @param manifestURI    The webapp's manifest URI.
    * @param description    The connection's description.
-   * @param appStatus      The app status can be Ci.nsIPrincipal.APP_STATUS_[
-   *                       NOT_INSTALLED, INSTALLED, PRIVILEGED, CERTIFIED].
    * @param rules          The connection's rules.
    */
   void registerConnection(in DOMString      keyword,
                           in nsIURI         handlerPageURI,
                           in nsIURI         manifestURI,
                           in DOMString      description,
-                          in unsigned short appStatus,
                           in jsval          rules);
 };
--- 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/Nfc.js
+++ b/dom/system/gonk/Nfc.js
@@ -411,17 +411,16 @@ XPCOMUtils.defineLazyGetter(this, "gMess
 
 function Nfc() {
   debug("Starting Worker");
   this.worker = new ChromeWorker("resource://gre/modules/nfc_worker.js");
   this.worker.onerror = this.onerror.bind(this);
   this.worker.onmessage = this.onmessage.bind(this);
 
   Services.obs.addObserver(this, NFC.TOPIC_MOZSETTINGS_CHANGED, false);
-  Services.obs.addObserver(this, NFC.TOPIC_HARDWARE_STATE, false);
 
   gMessageManager.init(this);
   let lock = gSettingsService.createLock();
   lock.get(NFC.SETTING_NFC_ENABLED, this);
   // Maps sessionId (that are generated from nfcd) with a unique guid : 'SessionToken'
   this.sessionTokenMap = {};
   this.targetsByRequestId = {};
 
@@ -652,36 +651,19 @@ Nfc.prototype = {
     switch (topic) {
       case NFC.TOPIC_MOZSETTINGS_CHANGED:
         let setting = JSON.parse(data);
         if (setting) {
           let setting = JSON.parse(data);
           this.handle(setting.key, setting.value);
         }
         break;
-      case NFC.TOPIC_HARDWARE_STATE:
-        let state = JSON.parse(data);
-        if (state) {
-          let level = this.hardwareStateToPowerlevel(state.nfcHardwareState);
-          this.setConfig({ powerLevel: level });
-        }
-        break;
     }
   },
 
   setConfig: function setConfig(prop) {
     this.sendToWorker("config", prop);
-  },
-
-  hardwareStateToPowerlevel: function hardwareStateToPowerlevel(state) {
-    switch (state) {
-      case 0:   return NFC.NFC_POWER_LEVEL_DISABLED;
-      case 1:   return NFC.NFC_POWER_LEVEL_ENABLED;
-      case 2:   return NFC.NFC_POWER_LEVEL_ENABLED;
-      case 3:   return NFC.NFC_POWER_LEVEL_LOW;
-      default:  return NFC.NFC_POWER_LEVEL_UNKNOWN;
-    }
   }
 };
 
 if (NFC_ENABLED) {
   this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Nfc]);
 }
--- 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/dom/system/gonk/nfc_consts.js
+++ b/dom/system/gonk/nfc_consts.js
@@ -40,34 +40,38 @@ this.NFC_RESPONSE_CONFIG = 1001;
 this.NFC_RESPONSE_READ_NDEF_DETAILS = 1002;
 this.NFC_RESPONSE_READ_NDEF = 1003;
 
 this.NFC_NOTIFICATION_INITIALIZED = 2000;
 this.NFC_NOTIFICATION_TECH_DISCOVERED = 2001;
 this.NFC_NOTIFICATION_TECH_LOST = 2002;
 
 this.NFC_TECHS = {
-  0:'NDEF',
-  1:'NDEF_WRITEABLE',
-  2:'NDEF_FORMATABLE',
-  3:'P2P'
+  0:"NDEF",
+  1:"NDEF_WRITEABLE",
+  2:"NDEF_FORMATABLE",
+  3:"P2P",
+  4:"NFC_A",
+  5:"NFC_B",
+  6:"NFC_F",
+  7:"NFC_V",
+  8:"NFC_ISO_DEP"
 };
 
 // TODO: Bug 933595. Fill-in all error codes for Gonk/nfcd protocol
 this.GECKO_NFC_ERROR_SUCCESS             = 0;
 this.GECKO_NFC_ERROR_GENERIC_FAILURE     = 1;
 
 // NFC powerlevels must match config PDUs.
 this.NFC_POWER_LEVEL_UNKNOWN        = -1;
 this.NFC_POWER_LEVEL_DISABLED       = 0;
 this.NFC_POWER_LEVEL_LOW            = 1;
 this.NFC_POWER_LEVEL_ENABLED        = 2;
 
 this.TOPIC_MOZSETTINGS_CHANGED      = "mozsettings-changed";
 this.TOPIC_XPCOM_SHUTDOWN           = "xpcom-shutdown";
-this.TOPIC_HARDWARE_STATE           = "nfc-hardware-state-change";
 this.SETTING_NFC_ENABLED            = "nfc.enabled";
 
 this.NFC_PEER_EVENT_READY = 0x01;
 this.NFC_PEER_EVENT_LOST  = 0x02;
 
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = Object.keys(this);
--- 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/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -153,17 +153,17 @@ MDefinition::valueHash() const
     for (size_t i = 0, e = numOperands(); i < e; i++) {
         uint32_t valueNumber = getOperand(i)->valueNumber();
         out = valueNumber + (out << 6) + (out << 16) - out;
     }
     return out;
 }
 
 bool
-MDefinition::congruentIfOperandsEqual(MDefinition *ins) const
+MDefinition::congruentIfOperandsEqual(const MDefinition *ins) const
 {
     if (op() != ins->op())
         return false;
 
     if (type() != ins->type())
         return false;
 
     if (isEffectful() || ins->isEffectful())
@@ -486,17 +486,17 @@ MConstant::MConstant(const js::Value &vp
 HashNumber
 MConstant::valueHash() const
 {
     // This disregards some state, since values are 64 bits. But for a hash,
     // it's completely acceptable.
     return (HashNumber)JSVAL_TO_IMPL(value_).asBits;
 }
 bool
-MConstant::congruentTo(MDefinition *ins) const
+MConstant::congruentTo(const MDefinition *ins) const
 {
     if (!ins->isConstant())
         return false;
     return ins->toConstant()->value() == value();
 }
 
 void
 MConstant::printOpcode(FILE *fp) const
@@ -669,17 +669,17 @@ MParameter::printOpcode(FILE *fp) const
 
 HashNumber
 MParameter::valueHash() const
 {
     return index_; // Why not?
 }
 
 bool
-MParameter::congruentTo(MDefinition *ins) const
+MParameter::congruentTo(const MDefinition *ins) const
 {
     if (!ins->isParameter())
         return false;
 
     return ins->toParameter()->index() == index_;
 }
 
 MCall *
@@ -755,25 +755,25 @@ MCallDOMNative::computeMovable()
     JS_ASSERT_IF(jitInfo->isMovable,
                  jitInfo->aliasSet() != JSJitInfo::AliasEverything);
 
     if (jitInfo->isMovable && !isEffectful())
         setMovable();
 }
 
 bool
-MCallDOMNative::congruentTo(MDefinition *ins) const
+MCallDOMNative::congruentTo(const MDefinition *ins) const
 {
     if (!isMovable())
         return false;
 
     if (!ins->isCall())
         return false;
 
-    MCall *call = ins->toCall();
+    const MCall *call = ins->toCall();
 
     if (!call->isCallDOMNative())
         return false;
 
     if (getSingleTarget() != call->getSingleTarget())
         return false;
 
     if (isConstructing() != call->isConstructing())
@@ -976,17 +976,17 @@ MPhi::foldsTo(TempAllocator &alloc, bool
 {
     if (MDefinition *def = operandIfRedundant())
         return def;
 
     return this;
 }
 
 bool
-MPhi::congruentTo(MDefinition *ins) const
+MPhi::congruentTo(const MDefinition *ins) const
 {
     if (!ins->isPhi())
         return false;
     // Since we do not know which predecessor we are merging from, we must
     // assume that phi instructions in different blocks are not equal.
     // (Bug 674656)
     if (ins->block()->id() != block()->id())
         return false;
@@ -2712,69 +2712,69 @@ MNewArray::shouldUseVM() const
     // when mir hints it needs to get allocated immediately,
     // but only when data doesn't fit the available array slots.
     bool allocating = isAllocating() && count() > arraySlots;
 
     return templateObject()->hasSingletonType() || allocating;
 }
 
 bool
-MLoadFixedSlot::mightAlias(MDefinition *store)
+MLoadFixedSlot::mightAlias(const MDefinition *store) const
 {
     if (store->isStoreFixedSlot() && store->toStoreFixedSlot()->slot() != slot())
         return false;
     return true;
 }
 
 bool
-MAsmJSLoadHeap::mightAlias(MDefinition *def)
+MAsmJSLoadHeap::mightAlias(const MDefinition *def) const
 {
     if (def->isAsmJSStoreHeap()) {
-        MAsmJSStoreHeap *store = def->toAsmJSStoreHeap();
+        const MAsmJSStoreHeap *store = def->toAsmJSStoreHeap();
         if (store->viewType() != viewType())
             return true;
         if (!ptr()->isConstant() || !store->ptr()->isConstant())
             return true;
-        MConstant *otherPtr = store->ptr()->toConstant();
+        const MConstant *otherPtr = store->ptr()->toConstant();
         return ptr()->toConstant()->value() == otherPtr->value();
     }
     return true;
 }
 
 bool
-MAsmJSLoadHeap::congruentTo(MDefinition *ins) const
+MAsmJSLoadHeap::congruentTo(const MDefinition *ins) const
 {
     if (!ins->isAsmJSLoadHeap())
         return false;
-    MAsmJSLoadHeap *load = ins->toAsmJSLoadHeap();
+    const MAsmJSLoadHeap *load = ins->toAsmJSLoadHeap();
     return load->viewType() == viewType() && congruentIfOperandsEqual(load);
 }
 
 bool
-MAsmJSLoadGlobalVar::mightAlias(MDefinition *def)
+MAsmJSLoadGlobalVar::mightAlias(const MDefinition *def) const
 {
     if (def->isAsmJSStoreGlobalVar()) {
-        MAsmJSStoreGlobalVar *store = def->toAsmJSStoreGlobalVar();
+        const MAsmJSStoreGlobalVar *store = def->toAsmJSStoreGlobalVar();
         return store->globalDataOffset() == globalDataOffset_;
     }
     return true;
 }
 
 bool
-MAsmJSLoadGlobalVar::congruentTo(MDefinition *ins) const
+MAsmJSLoadGlobalVar::congruentTo(const MDefinition *ins) const
 {
     if (ins->isAsmJSLoadGlobalVar()) {
-        MAsmJSLoadGlobalVar *load = ins->toAsmJSLoadGlobalVar();
+        const MAsmJSLoadGlobalVar *load = ins->toAsmJSLoadGlobalVar();
         return globalDataOffset_ == load->globalDataOffset_;
     }
     return false;
 }
 
 bool
-MLoadSlot::mightAlias(MDefinition *store)
+MLoadSlot::mightAlias(const MDefinition *store) const
 {
     if (store->isStoreSlot() && store->toStoreSlot()->slot() != slot())
         return false;
     return true;
 }
 
 void
 InlinePropertyTable::trimTo(ObjectVector &targets, BoolVector &choiceSet)
@@ -2875,26 +2875,26 @@ MGetElementCache::allowDoubleResult() co
 
 size_t
 MStoreTypedArrayElementStatic::length() const
 {
     return typedArray_->byteLength();
 }
 
 bool
-MGetPropertyPolymorphic::mightAlias(MDefinition *store)
+MGetPropertyPolymorphic::mightAlias(const MDefinition *store) const
 {
     // Allow hoisting this instruction if the store does not write to a
     // slot read by this instruction.
 
     if (!store->isStoreFixedSlot() && !store->isStoreSlot())
         return true;
 
     for (size_t i = 0; i < numShapes(); i++) {
-        Shape *shape = this->shape(i);
+        const Shape *shape = this->shape(i);
         if (shape->slot() < shape->numFixedSlots()) {
             // Fixed slot.
             uint32_t slot = shape->slot();
             if (store->isStoreFixedSlot() && store->toStoreFixedSlot()->slot() != slot)
                 continue;
             if (store->isStoreSlot())
                 continue;
         } else {
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -371,20 +371,20 @@ class MDefinition : public MNode
         return range_;
     }
     void setRange(Range *range) {
         JS_ASSERT(type() != MIRType_None);
         range_ = range;
     }
 
     virtual HashNumber valueHash() const;
-    virtual bool congruentTo(MDefinition *ins) const {
+    virtual bool congruentTo(const MDefinition *ins) const {
         return false;
     }
-    bool congruentIfOperandsEqual(MDefinition *ins) const;
+    bool congruentIfOperandsEqual(const MDefinition *ins) const;
     virtual MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
     virtual void analyzeEdgeCasesForward();
     virtual void analyzeEdgeCasesBackward();
 
     virtual bool truncate();
     virtual bool isOperandTruncated(size_t index) const;
 
     // Compute an absolute or symbolic range for the value of this node.
@@ -579,17 +579,17 @@ class MDefinition : public MNode
     }
     virtual AliasSet getAliasSet() const {
         // Instructions are effectful by default.
         return AliasSet::Store(AliasSet::Any);
     }
     bool isEffectful() const {
         return getAliasSet().isStore();
     }
-    virtual bool mightAlias(MDefinition *store) {
+    virtual bool mightAlias(const MDefinition *store) const {
         // Return whether this load may depend on the specified store, given
         // that the alias sets intersect. This may be refined to exclude
         // possible aliasing in cases where alias set flags are too imprecise.
         JS_ASSERT(!isEffectful() && store->isEffectful());
         JS_ASSERT(getAliasSet().flags() & store->getAliasSet().flags());
         return true;
     }
 };
@@ -740,40 +740,40 @@ class MBinaryInstruction : public MAryIn
         return op() ^ lhs->valueNumber() ^ rhs->valueNumber();
     }
     void swapOperands() {
         MDefinition *temp = getOperand(0);
         replaceOperand(0, getOperand(1));
         replaceOperand(1, temp);
     }
 
-    bool binaryCongruentTo(MDefinition *ins) const
+    bool binaryCongruentTo(const MDefinition *ins) const
     {
         if (op() != ins->op())
             return false;
 
         if (type() != ins->type())
             return false;
 
         if (isEffectful() || ins->isEffectful())
             return false;
 
-        MDefinition *left = getOperand(0);
-        MDefinition *right = getOperand(1);
-        MDefinition *tmp;
+        const MDefinition *left = getOperand(0);
+        const MDefinition *right = getOperand(1);
+        const MDefinition *tmp;
 
         if (isCommutative() && left->valueNumber() > right->valueNumber()) {
             tmp = right;
             right = left;
             left = tmp;
         }
 
-        MBinaryInstruction *bi = static_cast<MBinaryInstruction *>(ins);
-        MDefinition *insLeft = bi->getOperand(0);
-        MDefinition *insRight = bi->getOperand(1);
+        const MBinaryInstruction *bi = static_cast<const MBinaryInstruction *>(ins);
+        const MDefinition *insLeft = bi->getOperand(0);
+        const MDefinition *insRight = bi->getOperand(1);
         if (isCommutative() && insLeft->valueNumber() > insRight->valueNumber()) {
             tmp = insRight;
             insRight = insLeft;
             insLeft = tmp;
         }
 
         return (left->valueNumber() == insLeft->valueNumber()) &&
                (right->valueNumber() == insRight->valueNumber());
@@ -918,17 +918,17 @@ class MConstant : public MNullaryInstruc
     const bool valueToBoolean() const {
         // A hack to avoid this wordy pattern everywhere in the JIT.
         return ToBoolean(HandleValue::fromMarkedLocation(&value_));
     }
 
     void printOpcode(FILE *fp) const;
 
     HashNumber valueHash() const;
-    bool congruentTo(MDefinition *ins) const;
+    bool congruentTo(const MDefinition *ins) const;
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     bool updateForReplacement(MDefinition *def) {
         MConstant *c = def->toConstant();
         // During constant folding, we don't want to replace a float32
@@ -986,32 +986,32 @@ class MParameter : public MNullaryInstru
     static MParameter *New(TempAllocator &alloc, int32_t index, types::TemporaryTypeSet *types);
 
     int32_t index() const {
         return index_;
     }
     void printOpcode(FILE *fp) const;
 
     HashNumber valueHash() const;
-    bool congruentTo(MDefinition *ins) const;
+    bool congruentTo(const MDefinition *ins) const;
 };
 
 class MCallee : public MNullaryInstruction
 {
   public:
     MCallee()
     {
         setResultType(MIRType_Object);
         setMovable();
     }
 
   public:
     INSTRUCTION_HEADER(Callee)
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     static MCallee *New(TempAllocator &alloc) {
         return new(alloc) MCallee();
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
@@ -1915,17 +1915,17 @@ class MCallDOMNative : public MCall
 
     friend MCall *MCall::New(TempAllocator &alloc, JSFunction *target, size_t maxArgc,
                              size_t numActualArgs, bool construct, bool isDOMCall);
 
     const JSJitInfo *getJitInfo() const;
   public:
     virtual AliasSet getAliasSet() const MOZ_OVERRIDE;
 
-    virtual bool congruentTo(MDefinition *ins) const MOZ_OVERRIDE;
+    virtual bool congruentTo(const MDefinition *ins) const MOZ_OVERRIDE;
 
     virtual bool isCallDOMNative() const MOZ_OVERRIDE {
         return true;
     }
 
     virtual void computeMovable() MOZ_OVERRIDE;
 };
 
@@ -2294,17 +2294,17 @@ class MCompare
 # ifdef DEBUG
     bool isConsistentFloat32Use(MUse *use) const {
         // Both sides of the compare can be Float32
         return compareType_ == Compare_Float32;
     }
 # endif
 
   protected:
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!binaryCongruentTo(ins))
             return false;
         return compareType() == ins->toCompare()->compareType() &&
                jsop() == ins->toCompare()->jsop();
     }
 };
 
 // Takes a typed value and returns an untyped value.
@@ -2330,17 +2330,17 @@ class MBox : public MUnaryInstruction
     static MBox *New(TempAllocator &alloc, MDefinition *ins)
     {
         // Cannot box a box.
         JS_ASSERT(ins->type() != MIRType_Value);
 
         return new(alloc) MBox(alloc, ins);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 // Note: the op may have been inverted during lowering (to put constants in a
@@ -2412,17 +2412,17 @@ class MUnbox : public MUnaryInstruction,
     BailoutKind bailoutKind() const {
         // If infallible, no bailout should be generated.
         JS_ASSERT(fallible());
         return bailoutKind_;
     }
     bool fallible() const {
         return mode() != Infallible;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isUnbox() || ins->toUnbox()->mode() != mode())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void printOpcode(FILE *fp) const;
@@ -2840,17 +2840,17 @@ class MToDouble
         return conversion_;
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isToDouble() || ins->toToDouble()->conversion() != conversion())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
@@ -2902,17 +2902,17 @@ class MToFloat32
         return conversion_;
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
     virtual MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isToFloat32() || ins->toToFloat32()->conversion() != conversion())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
@@ -2935,17 +2935,17 @@ class MAsmJSUnsignedToDouble
 
   public:
     INSTRUCTION_HEADER(AsmJSUnsignedToDouble);
     static MAsmJSUnsignedToDouble *NewAsmJS(TempAllocator &alloc, MDefinition *def) {
         return new(alloc) MAsmJSUnsignedToDouble(def);
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 // Converts a uint32 to a float32 (coming from asm.js).
@@ -2961,17 +2961,17 @@ class MAsmJSUnsignedToFloat32
 
   public:
     INSTRUCTION_HEADER(AsmJSUnsignedToFloat32);
     static MAsmJSUnsignedToFloat32 *NewAsmJS(TempAllocator &alloc, MDefinition *def) {
         return new(alloc) MAsmJSUnsignedToFloat32(def);
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     bool canProduceFloat32() const { return true; }
 };
@@ -3019,17 +3019,17 @@ class MToInt32
     TypePolicy *typePolicy() {
         return this;
     }
 
     MacroAssembler::IntConversionInputKind conversion() const {
         return conversion_;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
 
@@ -3055,17 +3055,17 @@ class MTruncateToInt32 : public MUnaryIn
         return new(alloc) MTruncateToInt32(def);
     }
     static MTruncateToInt32 *NewAsmJS(TempAllocator &alloc, MDefinition *def) {
         return new(alloc) MTruncateToInt32(def);
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     void computeRange(TempAllocator &alloc);
     bool isOperandTruncated(size_t index) const;
@@ -3096,17 +3096,17 @@ class MToString : public MUnaryInstructi
     INSTRUCTION_HEADER(ToString)
     static MToString *New(TempAllocator &alloc, MDefinition *def)
     {
         return new(alloc) MToString(def);
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         JS_ASSERT(!input()->mightBeType(MIRType_Object));
         return AliasSet::None();
     }
 };
 
@@ -3129,17 +3129,17 @@ class MBitNot
 
     TypePolicy *typePolicy() {
         return this;
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
     void infer();
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         if (specialization_ == MIRType_None)
             return AliasSet::Store(AliasSet::Any);
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
@@ -3232,17 +3232,17 @@ class MBinaryBitwiseInstruction
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
     MDefinition *foldUnnecessaryBitop();
     virtual MDefinition *foldIfZero(size_t operand) = 0;
     virtual MDefinition *foldIfNegOne(size_t operand) = 0;
     virtual MDefinition *foldIfEqual()  = 0;
     virtual void infer(BaselineInspector *inspector, jsbytecode *pc);
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return binaryCongruentTo(ins);
     }
     AliasSet getAliasSet() const {
         if (specialization_ >= MIRType_Object)
             return AliasSet::Store(AliasSet::Any);
         return AliasSet::None();
     }
 
@@ -3448,17 +3448,17 @@ class MBinaryArithInstruction
 
     void setInt32() {
         specialization_ = MIRType_Int32;
         setResultType(MIRType_Int32);
     }
 
     virtual void trySpecializeFloat32(TempAllocator &alloc);
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return binaryCongruentTo(ins);
     }
     AliasSet getAliasSet() const {
         if (specialization_ >= MIRType_Object)
             return AliasSet::Store(AliasSet::Any);
         return AliasSet::None();
     }
 
@@ -3499,17 +3499,17 @@ class MMinMax
     }
     MIRType specialization() const {
         return specialization_;
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isMinMax())
             return false;
         if (isMax() != ins->toMinMax()->isMax())
             return false;
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
@@ -3546,17 +3546,17 @@ class MAbs
         return ins;
     }
     MDefinition *num() const {
         return getOperand(0);
     }
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     bool fallible() const;
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
@@ -3587,17 +3587,17 @@ class MSqrt
         return new(alloc) MSqrt(num, type);
     }
     MDefinition *num() const {
         return getOperand(0);
     }
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
 
@@ -3630,17 +3630,17 @@ class MAtan2
     MDefinition *x() const {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     bool possiblyCalls() const {
@@ -3673,17 +3673,17 @@ class MHypot
     MDefinition *y() const {
         return getOperand(1);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 
     bool possiblyCalls() const {
@@ -3714,17 +3714,17 @@ class MPow
     }
 
     MDefinition *input() const {
         return lhs();
     }
     MDefinition *power() const {
         return rhs();
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     TypePolicy *typePolicy() {
         return this;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
@@ -3752,17 +3752,17 @@ class MPowHalf
         setMovable();
     }
 
   public:
     INSTRUCTION_HEADER(PowHalf)
     static MPowHalf *New(TempAllocator &alloc, MDefinition *input) {
         return new(alloc) MPowHalf(input);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     bool operandIsNeverNegativeInfinity() const {
         return operandIsNeverNegativeInfinity_;
     }
     bool operandIsNeverNegativeZero() const {
         return operandIsNeverNegativeZero_;
     }
@@ -3860,17 +3860,17 @@ class MMathFunction
         return function_;
     }
     const MathCache *cache() const {
         return cache_;
     }
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isMathFunction())
             return false;
         if (ins->toMathFunction()->function() != function())
             return false;
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
@@ -4016,21 +4016,21 @@ class MMul : public MBinaryArithInstruct
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
     void analyzeEdgeCasesForward();
     void analyzeEdgeCasesBackward();
 
     double getIdentity() {
         return 1;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isMul())
             return false;
 
-        MMul *mul = ins->toMul();
+        const MMul *mul = ins->toMul();
         if (canBeNegativeZero_ != mul->canBeNegativeZero())
             return false;
 
         if (mode_ != mul->mode())
             return false;
 
         return binaryCongruentTo(ins);
     }
@@ -4055,17 +4055,17 @@ class MMul : public MBinaryArithInstruct
     }
 
     bool isFloat32Commutative() const { return true; }
 
     void computeRange(TempAllocator &alloc);
     bool truncate();
     bool isOperandTruncated(size_t index) const;
 
-    Mode mode() { return mode_; }
+    Mode mode() const { return mode_; }
 };
 
 class MDiv : public MBinaryArithInstruction
 {
     bool canBeNegativeZero_;
     bool canBeNegativeOverflow_;
     bool canBeDivideByZero_;
     bool canBeNegativeDividend_;
@@ -4252,17 +4252,17 @@ class MConcat
     INSTRUCTION_HEADER(Concat)
     static MConcat *New(TempAllocator &alloc, MDefinition *left, MDefinition *right) {
         return new(alloc) MConcat(left, right);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 class MConcatPar
@@ -4291,17 +4291,17 @@ class MConcatPar
     }
     MDefinition *lhs() const {
         return getOperand(1);
     }
     MDefinition *rhs() const {
         return getOperand(2);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 class MCharCodeAt
@@ -4321,17 +4321,17 @@ class MCharCodeAt
     static MCharCodeAt *New(TempAllocator &alloc, MDefinition *str, MDefinition *index) {
         return new(alloc) MCharCodeAt(str, index);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     virtual AliasSet getAliasSet() const {
         // Strings are immutable, so there is no implicit dependency.
         return AliasSet::None();
     }
 
@@ -4459,17 +4459,17 @@ class MLoadArrowThis
         return new(alloc) MLoadArrowThis(callee);
     }
     MDefinition *callee() const {
         return getOperand(0);
     }
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         // An arrow function's lexical |this| value is immutable.
         return AliasSet::None();
     }
 };
 
@@ -4565,17 +4565,17 @@ class MPhi MOZ_FINAL : public MDefinitio
     void addInput(MDefinition *ins);
 
     // Appends a new input to the input vector. May call realloc_().
     // Prefer reserveLength() and addInput() instead, where possible.
     bool addInputSlow(MDefinition *ins, bool *ptypeChange = nullptr);
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
-    bool congruentTo(MDefinition *ins) const;
+    bool congruentTo(const MDefinition *ins) const;
 
     bool isIterator() const {
         return isIterator_;
     }
     void setIterator() {
         isIterator_ = true;
     }
 
@@ -5050,17 +5050,17 @@ class MStringReplace
 
   public:
     INSTRUCTION_HEADER(StringReplace);
 
     static MStringReplace *New(TempAllocator &alloc, MDefinition *string, MDefinition *pattern, MDefinition *replacement) {
         return new(alloc) MStringReplace(string, pattern, replacement);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 struct LambdaFunctionInfo
@@ -5249,17 +5249,17 @@ class MSlots
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *object() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Returns obj->elements.
@@ -5282,17 +5282,17 @@ class MElements
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *object() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // A constant value for some object's array elements or typed array elements.
@@ -5319,17 +5319,17 @@ class MConstantElements : public MNullar
     }
 
     void printOpcode(FILE *fp) const;
 
     HashNumber valueHash() const {
         return (HashNumber)(size_t) value_;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return ins->isConstantElements() && ins->toConstantElements()->value() == value();
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
@@ -5350,17 +5350,17 @@ class MConvertElementsToDoubles
 
     static MConvertElementsToDoubles *New(TempAllocator &alloc, MDefinition *elements) {
         return new(alloc) MConvertElementsToDoubles(elements);
     }
 
     MDefinition *elements() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         // This instruction can read and write to the elements' contents.
         // However, it is alright to hoist this from loops which explicitly
         // read or write to the elements: such reads and writes will use double
         // values and can be reordered freely wrt this conversion, except that
         // definite double loads must follow the conversion. The latter
@@ -5398,17 +5398,17 @@ class MMaybeToDoubleElement
     }
 
     MDefinition *elements() const {
         return getOperand(0);
     }
     MDefinition *value() const {
         return getOperand(1);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Load the initialized length from an elements header.
@@ -5427,17 +5427,17 @@ class MInitializedLength
 
     static MInitializedLength *New(TempAllocator &alloc, MDefinition *elements) {
         return new(alloc) MInitializedLength(elements);
     }
 
     MDefinition *elements() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 
     void computeRange(TempAllocator &alloc);
 };
@@ -5486,17 +5486,17 @@ class MArrayLength
 
     static MArrayLength *New(TempAllocator &alloc, MDefinition *elements) {
         return new(alloc) MArrayLength(elements);
     }
 
     MDefinition *elements() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 
     void computeRange(TempAllocator &alloc);
 };
@@ -5549,17 +5549,17 @@ class MTypedArrayLength
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *object() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         // The typed array |length| property is immutable, so there is no
         // implicit dependency.
         return AliasSet::None();
     }
 
@@ -5586,17 +5586,17 @@ class MTypedArrayElements
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *object() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Checks whether a typed object is neutered.
@@ -5620,17 +5620,17 @@ class MNeuterCheck
     static MNeuterCheck *New(TempAllocator &alloc, MDefinition *object) {
         return new(alloc) MNeuterCheck(object);
     }
 
     MDefinition *object() const {
         return getOperand(0);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
@@ -5657,17 +5657,17 @@ class MTypedObjectElements
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
     MDefinition *object() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
     }
 };
 
 // Inlined version of the js::SetTypedObjectOffset() intrinsic.
@@ -5812,20 +5812,20 @@ class MBoundsCheck
         minimum_ = n;
     }
     int32_t maximum() const {
         return maximum_;
     }
     void setMaximum(int32_t n) {
         maximum_ = n;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isBoundsCheck())
             return false;
-        MBoundsCheck *other = ins->toBoundsCheck();
+        const MBoundsCheck *other = ins->toBoundsCheck();
         if (minimum() != other->minimum() || maximum() != other->maximum())
             return false;
         return congruentIfOperandsEqual(other);
     }
     virtual AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
@@ -5918,20 +5918,20 @@ class MLoadElement
         return needsHoleCheck_;
     }
     bool loadDoubles() const {
         return loadDoubles_;
     }
     bool fallible() const {
         return needsHoleCheck();
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isLoadElement())
             return false;
-        MLoadElement *other = ins->toLoadElement();
+        const MLoadElement *other = ins->toLoadElement();
         if (needsHoleCheck() != other->needsHoleCheck())
             return false;
         if (loadDoubles() != other->loadDoubles())
             return false;
         return congruentIfOperandsEqual(other);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::Element);
@@ -5981,20 +5981,20 @@ class MLoadElementHole
         return getOperand(2);
     }
     bool needsNegativeIntCheck() const {
         return needsNegativeIntCheck_;
     }
     bool needsHoleCheck() const {
         return needsHoleCheck_;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isLoadElementHole())
             return false;
-        MLoadElementHole *other = ins->toLoadElementHole();
+        const MLoadElementHole *other = ins->toLoadElementHole();
         if (needsHoleCheck() != other->needsHoleCheck())
             return false;
         if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
             return false;
         return congruentIfOperandsEqual(other);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::Element);
@@ -6300,20 +6300,20 @@ class MLoadTypedArrayElement
     }
     MDefinition *index() const {
         return getOperand(1);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::TypedArrayElement);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isLoadTypedArrayElement())
             return false;
-        MLoadTypedArrayElement *other = ins->toLoadTypedArrayElement();
+        const MLoadTypedArrayElement *other = ins->toLoadTypedArrayElement();
         if (arrayType_ != other->arrayType_)
             return false;
         return congruentIfOperandsEqual(other);
     }
 
     void printOpcode(FILE *fp) const;
 
     void computeRange(TempAllocator &alloc);
@@ -6361,20 +6361,20 @@ class MLoadTypedArrayElementHole
         return this;
     }
     MDefinition *object() const {
         return getOperand(0);
     }
     MDefinition *index() const {
         return getOperand(1);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isLoadTypedArrayElementHole())
             return false;
-        MLoadTypedArrayElementHole *other = ins->toLoadTypedArrayElementHole();
+        const MLoadTypedArrayElementHole *other = ins->toLoadTypedArrayElementHole();
         if (arrayType() != other->arrayType())
             return false;
         if (allowDouble() != other->allowDouble())
             return false;
         return congruentIfOperandsEqual(other);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::TypedArrayElement);
@@ -6679,17 +6679,17 @@ class MClampToUint8
         return new(alloc) MClampToUint8(input);
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
 };
 
@@ -6719,29 +6719,29 @@ class MLoadFixedSlot
     }
 
     MDefinition *object() const {
         return getOperand(0);
     }
     size_t slot() const {
         return slot_;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isLoadFixedSlot())
             return false;
         if (slot() != ins->toLoadFixedSlot()->slot())
             return false;
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::FixedSlot);
     }
 
-    bool mightAlias(MDefinition *store);
+    bool mightAlias(const MDefinition *store) const;
 };
 
 class MStoreFixedSlot
   : public MBinaryInstruction,
     public MixPolicy<SingleObjectPolicy, NoFloatPolicy<1> >
 {
     bool needsBarrier_;
     size_t slot_;
@@ -6933,17 +6933,17 @@ class MGetPropertyCache
     bool monitoredResult() const {
         return monitoredResult_;
     }
     CacheLocationList &location() {
         return location_;
     }
     TypePolicy *typePolicy() { return this; }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!idempotent_)
             return false;
         if (!ins->isGetPropertyCache())
             return false;
         if (name() != ins->toGetPropertyCache()->name())
             return false;
         return congruentIfOperandsEqual(ins);
     }
@@ -6993,17 +6993,17 @@ class MGetPropertyPolymorphic
 
   public:
     INSTRUCTION_HEADER(GetPropertyPolymorphic)
 
     static MGetPropertyPolymorphic *New(TempAllocator &alloc, MDefinition *obj, PropertyName *name) {
         return new(alloc) MGetPropertyPolymorphic(alloc, obj, name);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isGetPropertyPolymorphic())
             return false;
         if (name() != ins->toGetPropertyPolymorphic()->name())
             return false;
         return congruentIfOperandsEqual(ins);
     }
 
     TypePolicy *typePolicy() {
@@ -7026,17 +7026,17 @@ class MGetPropertyPolymorphic
     }
     MDefinition *obj() const {
         return getOperand(0);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields | AliasSet::FixedSlot | AliasSet::DynamicSlot);
     }
 
-    bool mightAlias(MDefinition *store);
+    bool mightAlias(const MDefinition *store) const;
 };
 
 // Emit code to store a value to an object's slots if its shape matches
 // one of the shapes observed by the baseline IC, else bails out.
 class MSetPropertyPolymorphic
   : public MBinaryInstruction,
     public SingleObjectPolicy
 {
@@ -7357,17 +7357,17 @@ class MGuardShape
         return getOperand(0);
     }
     const Shape *shape() const {
         return shape_;
     }
     BailoutKind bailoutKind() const {
         return bailoutKind_;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isGuardShape())
             return false;
         if (shape() != ins->toGuardShape()->shape())
             return false;
         if (bailoutKind() != ins->toGuardShape()->bailoutKind())
             return false;
         return congruentIfOperandsEqual(ins);
     }
@@ -7409,17 +7409,17 @@ class MGuardObjectType
         return getOperand(0);
     }
     const types::TypeObject *typeObject() const {
         return typeObject_;
     }
     bool bailOnEquality() const {
         return bailOnEquality_;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isGuardObjectType())
             return false;
         if (typeObject() != ins->toGuardObjectType()->typeObject())
             return false;
         if (bailOnEquality() != ins->toGuardObjectType()->bailOnEquality())
             return false;
         return congruentIfOperandsEqual(ins);
     }
@@ -7461,17 +7461,17 @@ class MGuardObjectIdentity
         return getOperand(0);
     }
     JSObject *singleObject() const {
         return singleObject_;
     }
     bool bailOnEquality() const {
         return bailOnEquality_;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isGuardObjectIdentity())
             return false;
         if (singleObject() != ins->toGuardObjectIdentity()->singleObject())
             return false;
         if (bailOnEquality() != ins->toGuardObjectIdentity()->bailOnEquality())
             return false;
         return congruentIfOperandsEqual(ins);
     }
@@ -7506,17 +7506,17 @@ class MGuardClass
         return this;
     }
     MDefinition *obj() const {
         return getOperand(0);
     }
     const Class *getClass() const {
         return class_;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isGuardClass())
             return false;
         if (getClass() != ins->toGuardClass()->getClass())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::ObjectFields);
@@ -7551,28 +7551,28 @@ class MLoadSlot
     }
     MDefinition *slots() const {
         return getOperand(0);
     }
     uint32_t slot() const {
         return slot_;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isLoadSlot())
             return false;
         if (slot() != ins->toLoadSlot()->slot())
             return false;
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         JS_ASSERT(slots()->type() == MIRType_Slots);
         return AliasSet::Load(AliasSet::DynamicSlot);
     }
-    bool mightAlias(MDefinition *store);
+    bool mightAlias(const MDefinition *store) const;
 };
 
 // Inline call to access a function's environment (scope chain).
 class MFunctionEnvironment
   : public MUnaryInstruction,
     public SingleObjectPolicy
 {
   public:
@@ -8292,17 +8292,17 @@ class MGetDOMProperty
     MDefinition *object() {
         return getOperand(0);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!isDomMovable())
             return false;
 
         if (!ins->isGetDOMProperty())
             return false;
 
         // Checking the jitinfo is the same as checking the constant function
         if (!(info() == ins->toGetDOMProperty()->info()))
@@ -8369,17 +8369,17 @@ class MStringLength
 
     TypePolicy *typePolicy() {
         return this;
     }
 
     MDefinition *string() const {
         return getOperand(0);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         // The string |length| property is immutable, so there is no
         // implicit dependency.
         return AliasSet::None();
     }
 
@@ -8646,20 +8646,20 @@ class MInArray
     }
     bool needsNegativeIntCheck() const {
         return needsNegativeIntCheck_;
     }
     void collectRangeInfoPreTrunc();
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::Element);
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         if (!ins->isInArray())
             return false;
-        MInArray *other = ins->toInArray();
+        const MInArray *other = ins->toInArray();
         if (needsHoleCheck() != other->needsHoleCheck())
             return false;
         if (needsNegativeIntCheck() != other->needsNegativeIntCheck())
             return false;
         return congruentIfOperandsEqual(other);
     }
     TypePolicy *typePolicy() {
         return this;
@@ -8730,17 +8730,17 @@ class MArgumentsLength : public MNullary
 
   public:
     INSTRUCTION_HEADER(ArgumentsLength)
 
     static MArgumentsLength *New(TempAllocator &alloc) {
         return new(alloc) MArgumentsLength();
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         // Arguments |length| cannot be mutated by Ion Code.
         return AliasSet::None();
    }
 
     void computeRange(TempAllocator &alloc);
@@ -8770,17 +8770,17 @@ class MGetFrameArgument
 
     MDefinition *index() const {
         return getOperand(0);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         // If the script doesn't have any JSOP_SETARG ops, then this instruction is never
         // aliased.
         if (scriptHasSetArg_)
             return AliasSet::Load(AliasSet::FrameArgument);
         return AliasSet::None();
@@ -8811,17 +8811,17 @@ class MSetFrameArgument
     uint32_t argno() const {
         return argno_;
     }
 
     MDefinition *value() const {
         return getOperand(0);
     }
 
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return false;
     }
     AliasSet getAliasSet() const {
         return AliasSet::Store(AliasSet::FrameArgument);
     }
     TypePolicy *typePolicy() {
         return this;
     }
@@ -8949,17 +8949,17 @@ class MGuardThreadExclusive
         return getOperand(0);
     }
     MDefinition *object() const {
         return getOperand(1);
     }
     BailoutKind bailoutKind() const {
         return Bailout_Normal;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     bool possiblyCalls() const {
         return true;
     }
@@ -8980,17 +8980,17 @@ class MFilterTypeSet
 
   public:
     INSTRUCTION_HEADER(FilterTypeSet)
 
     static MFilterTypeSet *New(TempAllocator &alloc, MDefinition *def, types::TemporaryTypeSet *types) {
         return new(alloc) MFilterTypeSet(def, types);
     }
 
-    bool congruentTo(MDefinition *def) const {
+    bool congruentTo(const MDefinition *def) const {
         return false;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     virtual bool neverHoist() const {
         return resultTypeSet()->empty();
     }
@@ -9023,17 +9023,17 @@ class MTypeBarrier
     }
 
     void printOpcode(FILE *fp) const;
 
     TypePolicy *typePolicy() {
         return this;
     }
 
-    bool congruentTo(MDefinition *def) const {
+    bool congruentTo(const MDefinition *def) const {
         return false;
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     virtual bool neverHoist() const {
         return resultTypeSet()->empty();
     }
@@ -9554,17 +9554,17 @@ class MHaveSameClass
 
     static MHaveSameClass *New(TempAllocator &alloc, MDefinition *left, MDefinition *right) {
         return new(alloc) MHaveSameClass(left, right);
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
-    bool congruentTo(MDefinition *ins) const {
+    bool congruentTo(const MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
 };
 
 class MHasClass
@@ -9684,21 +9684,21 @@ class MAsmJSLoadHeap : public MUnaryInst
     INSTRUCTION_HEADER(AsmJSLoadHeap);
 
     static MAsmJSLoadHeap *New(TempAllocator &alloc, ArrayBufferView::ViewType vt, MDefinition *ptr) {
         return new(alloc) MAsmJSLoadHeap(vt, ptr);
     }
 
     MDefinition *ptr() const { return getOperand(0); }
 
-    bool congruentTo(MDefinition *ins) const;
+    bool congruentTo(const MDefinition *ins) const;
     AliasSet getAliasSet() const {
         return AliasSet::Load(AliasSet::AsmJSHeap);
     }
-    bool mightAlias(MDefinition *def);
+    bool mightAlias(const MDefinition *def) const;
 };
 
 class MAsmJSStoreHeap : public MBinaryInstruction, public MAsmJSHeapAccess
 {
     MAsmJSStoreHeap(ArrayBufferView::ViewType vt, MDefinition *ptr, MDefinition *v)
       : MBinaryInstruction(ptr, v) , MAsmJSHeapAccess(vt, false)
     {}
 
@@ -9738,23 +9738,23 @@ class MAsmJSLoadGlobalVar : public MNull
     static MAsmJSLoadGlobalVar *New(TempAllocator &alloc, MIRType type, unsigned globalDataOffset,
                                     bool isConstant)
     {
         return new(alloc) MAsmJSLoadGlobalVar(type, globalDataOffset, isConstant);
     }
 
     unsigned globalDataOffset() const { return globalDataOffset_; }
 
-    bool congruentTo(MDefinition *ins) const;
+    bool congruentTo(const MDefinition *ins) const;
 
     AliasSet getAliasSet() const {
         return isConstant_ ? AliasSet::None() : AliasSet::Load(AliasSet::AsmJSGlobalVar);
     }
 
-    bool mightAlias(MDefinition *def);
+    bool mightAlias(const MDefinition *def) const;
 };
 
 class MAsmJSStoreGlobalVar : public MUnaryInstruction
 {
     MAsmJSStoreGlobalVar(unsigned globalDataOffset, MDefinition *v)
       : MUnaryInstruction(v), globalDataOffset_(globalDataOffset)
     {}
 
--- 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/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4341,16 +4341,18 @@ JS::ReadOnlyCompileOptions::copyPODOptio
     forEval = rhs.forEval;
     noScriptRval = rhs.noScriptRval;
     selfHostingMode = rhs.selfHostingMode;
     canLazilyParse = rhs.canLazilyParse;
     strictOption = rhs.strictOption;
     extraWarningsOption = rhs.extraWarningsOption;
     werrorOption = rhs.werrorOption;
     asmJSOption = rhs.asmJSOption;
+    forceAsync = rhs.forceAsync;
+    installedFile = rhs.installedFile;
     sourcePolicy = rhs.sourcePolicy;
     introductionType = rhs.introductionType;
     introductionLineno = rhs.introductionLineno;
     introductionOffset = rhs.introductionOffset;
     hasIntroductionInfo = rhs.hasIntroductionInfo;
 }
 
 JSPrincipals *
--- 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/mozIJSSubScriptLoader.idl
+++ b/js/xpconnect/idl/mozIJSSubScriptLoader.idl
@@ -1,17 +1,21 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(b21f1579-d994-4e99-a85d-a685140f3ec1)]
+interface nsIURI;
+interface nsIPrincipal;
+interface nsIObserver;
+
+[scriptable, uuid(19533e7b-f321-4ef1-bc59-6e812dc2a733)]
 interface mozIJSSubScriptLoader : nsISupports
 {
     /**
      * This method should only be called from JS!
      * In JS, the signature looks like:
      * rv loadSubScript (url [, obj] [, charset]);
      * @param url the url of the sub-script, it MUST be either a file:,
      *            resource:, or chrome: url, and MUST be local.
@@ -34,9 +38,27 @@ interface mozIJSSubScriptLoader : nsISup
      * @param optionsObject an object with parameters. Valid parameters are:
      *                      - charset: specifying the character encoding of the file (default: ASCII)
      *                      - target:  an object to evaluate onto (default: global object of the caller)
      *                      - ignoreCache: if set to true, will bypass the cache for reading the file.
      * @retval rv the value returned by the sub-script
      */
     [implicit_jscontext]
     jsval loadSubScriptWithOptions(in AString url, in jsval options);
+
+    /*
+     * Compiles a JS script off the main thread and calls back the
+     * observer once it's done.
+     * The script will be cached in temporary or persistent storage depending
+     * on the principal used.
+     * We fire the notification callback in all cases - there is no fatal
+     * error there.
+     * @param uri       the uri of the script to load.
+     * @param principal the principal from which we get the app id if any.
+     * @param observer  this observer will be called once the script has
+     *                  been precompiled. The notification topic will be
+     *                  'script-precompiled' and the subject the uri of the
+     *                  script as a nsIURI.
+     */
+    void precompileScript(in nsIURI uri,
+                          in nsIPrincipal principal,
+                          in nsIObserver observer);
 };
--- 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/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -13,30 +13,34 @@
 #include "nsIIOService.h"
 #include "nsIChannel.h"
 #include "nsIInputStream.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIFileURL.h"
 #include "nsScriptLoader.h"
 #include "nsIScriptSecurityManager.h"
+#include "nsThreadUtils.h"
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "js/OldDebugAPI.h"
 #include "nsJSPrincipals.h"
 #include "xpcpublic.h" // For xpc::SystemErrorReporter
 #include "xpcprivate.h" // For xpc::OptionsBase
+#include "jswrapper.h"
 
 #include "mozilla/scache/StartupCache.h"
 #include "mozilla/scache/StartupCacheUtils.h"
+#include "mozilla/unused.h"
 
 using namespace mozilla::scache;
 using namespace JS;
 using namespace xpc;
+using namespace mozilla;
 
 class MOZ_STACK_CLASS LoadSubScriptOptions : public OptionsBase {
 public:
     LoadSubScriptOptions(JSContext *cx = xpc_GetSafeJSContext(),
                          JSObject *options = nullptr)
         : OptionsBase(cx, options)
         , target(cx)
         , charset(NullString())
@@ -360,8 +364,216 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
     }
 
     if (cache && ok && writeScript) {
         WriteCachedScript(cache, cachePath, cx, mSystemPrincipal, script);
     }
 
     return NS_OK;
 }
+
+/**
+  * Let us compile scripts from a URI off the main thread.
+  */
+
+class ScriptPrecompiler : public nsIStreamLoaderObserver
+{
+public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSISTREAMLOADEROBSERVER
+
+    ScriptPrecompiler(nsIObserver* aObserver,
+                      nsIPrincipal* aPrincipal,
+                      nsIChannel* aChannel)
+        : mObserver(aObserver)
+        , mPrincipal(aPrincipal)
+        , mChannel(aChannel)
+    {}
+
+    virtual ~ScriptPrecompiler()
+    {}
+
+    static void OffThreadCallback(void *aToken, void *aData);
+
+    /* Sends the "done" notification back. Main thread only. */
+    void SendObserverNotification();
+
+private:
+    nsRefPtr<nsIObserver> mObserver;
+    nsRefPtr<nsIPrincipal> mPrincipal;
+    nsRefPtr<nsIChannel> mChannel;
+    nsString mScript;
+};
+
+NS_IMPL_ISUPPORTS1(ScriptPrecompiler, nsIStreamLoaderObserver);
+
+class NotifyPrecompilationCompleteRunnable : public nsRunnable
+{
+public:
+    NS_DECL_NSIRUNNABLE
+
+    NotifyPrecompilationCompleteRunnable(ScriptPrecompiler* aPrecompiler)
+        : mPrecompiler(aPrecompiler)
+        , mToken(nullptr)
+    {}
+
+    void SetToken(void* aToken) {
+        MOZ_ASSERT(aToken && !mToken);
+        mToken = aToken;
+    }
+
+protected:
+    nsRefPtr<ScriptPrecompiler> mPrecompiler;
+    void* mToken;
+};
+
+/* RAII helper class to send observer notifications */
+class AutoSendObserverNotification {
+public:
+    AutoSendObserverNotification(ScriptPrecompiler* aPrecompiler)
+        : mPrecompiler(aPrecompiler)
+    {}
+
+    ~AutoSendObserverNotification() {
+        if (mPrecompiler) {
+            mPrecompiler->SendObserverNotification();
+        }
+    }
+
+    void Disarm() {
+        mPrecompiler = nullptr;
+    }
+
+private:
+    ScriptPrecompiler* mPrecompiler;
+};
+
+NS_IMETHODIMP
+NotifyPrecompilationCompleteRunnable::Run(void)
+{
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mPrecompiler);
+
+    AutoSendObserverNotification notifier(mPrecompiler);
+
+    if (mToken) {
+        JSRuntime *rt = XPCJSRuntime::Get()->Runtime();
+        NS_ENSURE_TRUE(rt, NS_ERROR_FAILURE);
+        JS::FinishOffThreadScript(nullptr, rt, mToken);
+    }
+
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+ScriptPrecompiler::OnStreamComplete(nsIStreamLoader* aLoader,
+                                    nsISupports* aContext,
+                                    nsresult aStatus,
+                                    uint32_t aLength,
+                                    const uint8_t* aString)
+{
+    AutoSendObserverNotification notifier(this);
+
+    // Just notify that we are done with this load.
+    NS_ENSURE_SUCCESS(aStatus, NS_OK);
+
+    // Convert data to jschar* and prepare to call CompileOffThread.
+    nsAutoString hintCharset;
+    nsresult rv =
+        nsScriptLoader::ConvertToUTF16(mChannel, aString, aLength,
+                                       hintCharset, nullptr, mScript);
+
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+
+    // Our goal is to cache persistently the compiled script and to avoid quota
+    // checks. Since the caching mechanism decide the persistence type based on
+    // the principal, we create a new global with the app's principal.
+    // We then enter its compartment to compile with its principal.
+    AutoSafeJSContext cx;
+    RootedValue v(cx);
+    SandboxOptions sandboxOptions;
+    sandboxOptions.sandboxName.AssignASCII("asm.js precompilation");
+    sandboxOptions.invisibleToDebugger = true;
+    rv = CreateSandboxObject(cx, &v, mPrincipal, sandboxOptions);
+    NS_ENSURE_SUCCESS(rv, NS_OK);
+
+    JSAutoCompartment ac(cx, js::UncheckedUnwrap(&v.toObject()));
+
+    JS::CompileOptions options(cx, JSVERSION_DEFAULT);
+    options.setSourcePolicy(CompileOptions::NO_SOURCE);
+    options.forceAsync = true;
+    options.compileAndGo = true;
+    options.installedFile = true;
+
+    nsCOMPtr<nsIURI> uri;
+    mChannel->GetURI(getter_AddRefs(uri));
+    nsAutoCString spec;
+    uri->GetSpec(spec);
+    options.setFile(spec.get());
+
+    if (!JS::CanCompileOffThread(cx, options, mScript.Length())) {
+        NS_WARNING("Can't compile script off thread!");
+        return NS_OK;
+    }
+
+    nsRefPtr<NotifyPrecompilationCompleteRunnable> runnable =
+        new NotifyPrecompilationCompleteRunnable(this);
+
+    if (!JS::CompileOffThread(cx, options,
+                              mScript.get(), mScript.Length(),
+                              OffThreadCallback,
+                              static_cast<void*>(runnable))) {
+        NS_WARNING("Failed to compile script off thread!");
+        return NS_OK;
+    }
+
+    unused << runnable.forget();
+    notifier.Disarm();
+
+    return NS_OK;
+}
+
+/* static */
+void
+ScriptPrecompiler::OffThreadCallback(void* aToken, void* aData)
+{
+    nsRefPtr<NotifyPrecompilationCompleteRunnable> runnable =
+        dont_AddRef(static_cast<NotifyPrecompilationCompleteRunnable*>(aData));
+    runnable->SetToken(aToken);
+
+    NS_DispatchToMainThread(runnable);
+}
+
+void
+ScriptPrecompiler::SendObserverNotification()
+{
+    MOZ_ASSERT(mChannel && mObserver);
+    MOZ_ASSERT(NS_IsMainThread());
+
+    nsCOMPtr<nsIURI> uri;
+    mChannel->GetURI(getter_AddRefs(uri));
+    mObserver->Observe(uri, "script-precompiled", nullptr);
+}
+
+NS_IMETHODIMP
+mozJSSubScriptLoader::PrecompileScript(nsIURI* aURI,
+                                       nsIPrincipal* aPrincipal,
+                                       nsIObserver *aObserver)
+{
+    nsCOMPtr<nsIChannel> channel;
+    nsresult rv = NS_NewChannel(getter_AddRefs(channel),
+                                aURI, nullptr, nullptr, nullptr,
+                                nsIRequest::LOAD_NORMAL, nullptr);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsRefPtr<ScriptPrecompiler> loadObserver =
+        new ScriptPrecompiler(aObserver, aPrincipal, channel);
+
+    nsCOMPtr<nsIStreamLoader> loader;
+    rv = NS_NewStreamLoader(getter_AddRefs(loader), loadObserver);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsIStreamListener> listener = loader.get();
+    rv = channel->AsyncOpen(listener, nullptr);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+}
--- 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/mobile/android/base/fxa/FirefoxAccounts.java
+++ b/mobile/android/base/fxa/FirefoxAccounts.java
@@ -7,16 +7,17 @@ package org.mozilla.gecko.fxa;
 import java.io.File;
 import java.util.EnumSet;
 import java.util.concurrent.CountDownLatch;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Bundle;
@@ -56,16 +57,23 @@ public class FirefoxAccounts {
   public static final EnumSet<SyncHint> NOW = EnumSet.of(
       SyncHint.SCHEDULE_NOW);
 
   public static final EnumSet<SyncHint> FORCE = EnumSet.of(
       SyncHint.SCHEDULE_NOW,
       SyncHint.IGNORE_LOCAL_RATE_LIMIT,
       SyncHint.IGNORE_REMOTE_SERVER_BACKOFF);
 
+  public interface SyncStatusListener {
+    public Context getContext();
+    public Account getAccount();
+    public void onSyncStarted();
+    public void onSyncFinished();
+  }
+
   /**
    * Returns true if a FirefoxAccount exists, false otherwise.
    *
    * @param context Android context.
    * @return true if at least one Firefox account exists.
    */
   public static boolean firefoxAccountsExist(final Context context) {
     return getFirefoxAccounts(context).length > 0;
@@ -195,34 +203,65 @@ public class FirefoxAccounts {
   }
 
   /**
    * Request a sync for the given Android Account.
    * <p>
    * Any hints are strictly optional: the actual requested sync is scheduled by
    * the Android sync scheduler, and the sync mechanism may ignore hints as it
    * sees fit.
+   * <p>
+   * It is safe to call this method from any thread.
    *
    * @param account to sync.
    * @param syncHints to pass to sync.
    * @param stagesToSync stage names to sync.
    * @param stagesToSkip stage names to skip.
    */
-  public static void requestSync(Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
+  public static void requestSync(final Account account, EnumSet<SyncHint> syncHints, String[] stagesToSync, String[] stagesToSkip) {
     if (account == null) {
       throw new IllegalArgumentException("account must not be null");
     }
     if (syncHints == null) {
       throw new IllegalArgumentException("syncHints must not be null");
     }
 
     final Bundle extras = new Bundle();
     putHintsToSync(extras, syncHints);
     Utils.putStageNamesToSync(extras, stagesToSync, stagesToSkip);
 
     Logger.info(LOG_TAG, "Requesting sync.");
     logSyncHints(syncHints);
 
-    for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
-      ContentResolver.requestSync(account, authority, extras);
-    }
+    // We get strict mode warnings on some devices, so make the request on a
+    // background thread.
+    ThreadPool.run(new Runnable() {
+      @Override
+      public void run() {
+        for (String authority : AndroidFxAccount.getAndroidAuthorities()) {
+          ContentResolver.requestSync(account, authority, extras);
+        }
+      }
+    });
+  }
+
+  /**
+   * Start notifying <code>syncStatusListener</code> of sync status changes.
+   * <p>
+   * Only a weak reference to <code>syncStatusListener</code> is held.
+   *
+   * @param syncStatusListener to start notifying.
+   */
+  public static void addSyncStatusListener(SyncStatusListener syncStatusListener) {
+    // startObserving null-checks its argument.
+    FxAccountSyncStatusHelper.getInstance().startObserving(syncStatusListener);
+  }
+
+  /**
+   * Stop notifying <code>syncStatusListener</code> of sync status changes.
+   *
+   * @param syncStatusListener to stop notifying.
+   */
+  public static void removeSyncStatusListener(SyncStatusListener syncStatusListener) {
+    // stopObserving null-checks its argument.
+    FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusListener);
   }
 }
--- a/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountConfirmAccountActivity.java
@@ -16,16 +16,17 @@ import org.mozilla.gecko.background.fxa.
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.Action;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
 
+import android.accounts.Account;
 import android.app.Activity;
 import android.content.Context;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -38,17 +39,17 @@ public class FxAccountConfirmAccountActi
 
   // Set in onCreate.
   protected TextView verificationLinkTextView;
   protected View resendLink;
 
   // Set in onResume.
   protected AndroidFxAccount fxAccount;
 
-  protected final SyncStatusDelegate syncStatusDelegate = new SyncStatusDelegate();
+  protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate();
 
   public FxAccountConfirmAccountActivity() {
     super(CANNOT_RESUME_WHEN_NO_ACCOUNTS_EXIST);
   }
 
   /**
    * {@inheritDoc}
    */
@@ -97,36 +98,41 @@ public class FxAccountConfirmAccountActi
     super.onPause();
     FxAccountSyncStatusHelper.getInstance().stopObserving(syncStatusDelegate);
 
     if (fxAccount != null) {
       fxAccount.requestSync(FirefoxAccounts.SOON);
     }
   }
 
-  protected class SyncStatusDelegate implements FxAccountSyncStatusHelper.Delegate {
+  protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener {
     protected final Runnable refreshRunnable = new Runnable() {
       @Override
       public void run() {
         refresh();
       }
     };
 
     @Override
-    public AndroidFxAccount getAccount() {
-      return fxAccount;
+    public Context getContext() {
+      return FxAccountConfirmAccountActivity.this;
     }
 
     @Override
-    public void handleSyncStarted() {
+    public Account getAccount() {
+      return fxAccount.getAndroidAccount();
+    }
+
+    @Override
+    public void onSyncStarted() {
       Logger.info(LOG_TAG, "Got sync started message; ignoring.");
     }
 
     @Override
-    public void handleSyncFinished() {
+    public void onSyncFinished() {
       if (fxAccount == null) {
         return;
       }
       Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
       runOnUiThread(refreshRunnable);
     }
   }
 
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -14,17 +14,19 @@ import org.mozilla.gecko.background.pref
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncStatusHelper;
 import org.mozilla.gecko.sync.SyncConfiguration;
 
+import android.accounts.Account;
 import android.content.ContentResolver;
+import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 import android.os.Handler;
 import android.preference.CheckBoxPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
@@ -67,17 +69,17 @@ public class FxAccountStatusFragment ext
   // Used to post delayed sync requests.
   protected Handler handler;
 
   // Member variable so that re-posting pushes back the already posted instance.
   // This Runnable references the fxAccount above, but it is not specific to a
   // single account. (That is, it does not capture a single account instance.)
   protected Runnable requestSyncRunnable;
 
-  protected final SyncStatusDelegate syncStatusDelegate = new SyncStatusDelegate();
+  protected final InnerSyncStatusDelegate syncStatusDelegate = new InnerSyncStatusDelegate();
 
   protected Preference ensureFindPreference(String key) {
     Preference preference = findPreference(key);
     if (preference == null) {
       throw new IllegalStateException("Could not find preference with key: " + key);
     }
     return preference;
   }
@@ -235,40 +237,45 @@ public class FxAccountStatusFragment ext
   }
 
   protected void showConnected() {
     syncCategory.setTitle(R.string.fxaccount_status_sync_enabled);
     showOnlyOneErrorPreference(null);
     setCheckboxesEnabled(true);
   }
 
-  protected class SyncStatusDelegate implements FxAccountSyncStatusHelper.Delegate {
+  protected class InnerSyncStatusDelegate implements FirefoxAccounts.SyncStatusListener {
     protected final Runnable refreshRunnable = new Runnable() {
       @Override
       public void run() {
         refresh();
       }
     };
 
     @Override
-    public AndroidFxAccount getAccount() {
-      return fxAccount;
+    public Context getContext() {
+      return FxAccountStatusFragment.this.getActivity();
     }
 
     @Override
-    public void handleSyncStarted() {
+    public Account getAccount() {
+      return fxAccount.getAndroidAccount();
+    }
+
+    @Override
+    public void onSyncStarted() {
       if (fxAccount == null) {
         return;
       }
       Logger.info(LOG_TAG, "Got sync started message; refreshing.");
       getActivity().runOnUiThread(refreshRunnable);
     }
 
     @Override
-    public void handleSyncFinished() {
+    public void onSyncFinished() {
       if (fxAccount == null) {
         return;
       }
       Logger.info(LOG_TAG, "Got sync finished message; refreshing.");
       getActivity().runOnUiThread(refreshRunnable);
     }
   }
 
--- a/mobile/android/base/fxa/sync/FxAccountSyncStatusHelper.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncStatusHelper.java
@@ -3,77 +3,65 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.fxa.sync;
 
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.WeakHashMap;
 
+import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 
 import android.content.ContentResolver;
 import android.content.SyncStatusObserver;
 
 /**
  * Abstract away some details of Android's SyncStatusObserver.
  * <p>
  * Provides a simplified sync started/sync finished delegate.
- * <p>
- * We would prefer to register multiple observers, but it's of limited value
- * right now, so we support only a single observer, and we are as tolerant as
- * possible of non-paired add/remove calls.
  */
 public class FxAccountSyncStatusHelper implements SyncStatusObserver {
   @SuppressWarnings("unused")
   private static final String LOG_TAG = FxAccountSyncStatusHelper.class.getSimpleName();
 
   protected static FxAccountSyncStatusHelper sInstance = null;
 
   public synchronized static FxAccountSyncStatusHelper getInstance() {
     if (sInstance == null) {
       sInstance = new FxAccountSyncStatusHelper();
     }
     return sInstance;
   }
 
-  public interface Delegate {
-    public AndroidFxAccount getAccount();
-    public void handleSyncStarted();
-    public void handleSyncFinished();
-  }
-
   // Used to unregister this as a listener.
   protected Object handle = null;
 
   // Maps delegates to whether their underlying Android account was syncing the
   // last time we observed a status change.
-  protected Map<Delegate, Boolean> delegates = new WeakHashMap<Delegate, Boolean>();
+  protected Map<FirefoxAccounts.SyncStatusListener, Boolean> delegates = new WeakHashMap<FirefoxAccounts.SyncStatusListener, Boolean>();
 
   @Override
   public synchronized void onStatusChanged(int which) {
-    for (Entry<Delegate, Boolean> entry : delegates.entrySet()) {
-      final Delegate delegate = entry.getKey();
-      final AndroidFxAccount fxAccount = delegate.getAccount();
-      if (fxAccount == null) {
-        continue;
-      }
+    for (Entry<FirefoxAccounts.SyncStatusListener, Boolean> entry : delegates.entrySet()) {
+      final FirefoxAccounts.SyncStatusListener delegate = entry.getKey();
+      final AndroidFxAccount fxAccount = new AndroidFxAccount(delegate.getContext(), delegate.getAccount());
       final boolean active = fxAccount.isCurrentlySyncing();
       // Remember for later.
       boolean wasActiveLastTime = entry.getValue();
       // It's okay to update the value of an entry while iterating the entrySet.
       entry.setValue(active);
 
       if (active && !wasActiveLastTime) {
         // We've started a sync.
-        delegate.handleSyncStarted();
+        delegate.onSyncStarted();
       }
       if (!active && wasActiveLastTime) {
         // We've finished a sync.
-        delegate.handleSyncFinished();
+        delegate.onSyncFinished();
       }
     }
   }
 
   protected void addListener() {
     final int mask = ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE;
     if (this.handle != null) {
       throw new IllegalStateException("Already registered this as an observer?");
@@ -84,30 +72,30 @@ public class FxAccountSyncStatusHelper i
   protected void removeListener() {
     Object handle = this.handle;
     this.handle = null;
     if (handle != null) {
       ContentResolver.removeStatusChangeListener(handle);
     }
   }
 
-  public synchronized void startObserving(Delegate delegate) {
+  public synchronized void startObserving(FirefoxAccounts.SyncStatusListener delegate) {
     if (delegate == null) {
       throw new IllegalArgumentException("delegate must not be null");
     }
     if (delegates.containsKey(delegate)) {
       return;
     }
     // If we are the first delegate to the party, start listening.
     if (delegates.isEmpty()) {
       addListener();
     }
     delegates.put(delegate, Boolean.FALSE);
   }
 
-  public synchronized void stopObserving(Delegate delegate) {
+  public synchronized void stopObserving(FirefoxAccounts.SyncStatusListener delegate) {
     delegates.remove(delegate);
     // If we are the last delegate leaving the party, stop listening.
     if (delegates.isEmpty()) {
       removeListener();
     }
   }
 }
--- 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/autocomplete/nsAutoCompleteController.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.cpp
@@ -405,17 +405,17 @@ nsAutoCompleteController::HandleKeyNavig
 
       if (completeSelection)
       {
         int32_t selectedIndex;
         popup->GetSelectedIndex(&selectedIndex);
         if (selectedIndex >= 0) {
           //  A result is selected, so fill in its value
           nsAutoString value;
-          if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, true, value))) {
+          if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
             input->SetTextValue(value);
             input->SelectTextRange(value.Length(), value.Length());
           }
         } else {
           // Nothing is selected, so fill in the last typed value
           input->SetTextValue(mSearchString);
           input->SelectTextRange(mSearchString.Length(), mSearchString.Length());
         }
@@ -476,17 +476,17 @@ nsAutoCompleteController::HandleKeyNavig
     if (isOpen) {
       int32_t selectedIndex;
       popup->GetSelectedIndex(&selectedIndex);
       bool shouldComplete;
       input->GetCompleteDefaultIndex(&shouldComplete);
       if (selectedIndex >= 0) {
         // The pop-up is open and has a selection, take its value
         nsAutoString value;
-        if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, true, value))) {
+        if (NS_SUCCEEDED(GetResultValueAt(selectedIndex, false, value))) {
           input->SetTextValue(value);
           input->SelectTextRange(value.Length(), value.Length());
         }
       }
       else if (shouldComplete) {
         // We usually try to preserve the casing of what user has typed, but
         // if he wants to autocomplete, we will replace the value with the
         // actual autocomplete result.
@@ -574,17 +574,17 @@ nsAutoCompleteController::HandleDelete(b
     // There are still rows in the popup, select the current index again.
     popup->SetSelectedIndex(index);
 
     // Complete to the new current value.
     bool shouldComplete = false;
     input->GetCompleteDefaultIndex(&shouldComplete);
     if (shouldComplete) {
       nsAutoString value;
-      if (NS_SUCCEEDED(GetResultValueAt(index, true, value))) {
+      if (NS_SUCCEEDED(GetResultValueAt(index, false, value))) {
         CompleteValue(value);
       }
     }
 
     // Invalidate the popup.
     popup->Invalidate();
   } else {
     // Nothing left in the popup, clear any pending search timers and
@@ -611,25 +611,25 @@ nsAutoCompleteController::GetResultAt(in
   *aResult = mResults[searchIndex];
   NS_ENSURE_TRUE(*aResult, NS_ERROR_FAILURE);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteController::GetValueAt(int32_t aIndex, nsAString & _retval)
 {
-  GetResultLabelAt(aIndex, false, _retval);
+  GetResultLabelAt(aIndex, _retval);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteController::GetLabelAt(int32_t aIndex, nsAString & _retval)
 {
-  GetResultLabelAt(aIndex, false, _retval);
+  GetResultLabelAt(aIndex, _retval);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteController::GetCommentAt(int32_t aIndex, nsAString & _retval)
 {
   int32_t rowIndex;
@@ -658,16 +658,28 @@ nsAutoCompleteController::GetImageAt(int
   nsIAutoCompleteResult* result;
   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return result->GetImageAt(rowIndex, _retval);
 }
 
 NS_IMETHODIMP
+nsAutoCompleteController::GetFinalCompleteValueAt(int32_t aIndex,
+                                                  nsAString & _retval)
+{
+  int32_t rowIndex;
+  nsIAutoCompleteResult* result;
+  nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return result->GetFinalCompleteValueAt(rowIndex, _retval);
+}
+
+NS_IMETHODIMP
 nsAutoCompleteController::SetSearchString(const nsAString &aSearchString)
 {
   mSearchString = aSearchString;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteController::GetSearchString(nsAString &aSearchString)
@@ -1176,19 +1188,24 @@ nsAutoCompleteController::EnterMatch(boo
 
     int32_t selectedIndex;
     popup->GetSelectedIndex(&selectedIndex);
     if (selectedIndex >= 0) {
       // If completeselectedindex is false or a row was selected from the popup,
       // enter it into the textbox. If completeselectedindex is true, or
       // EnterMatch was called via other means, for instance pressing Enter,
       // don't fill in the value as it will have already been filled in as
-      // needed.
-      if (!completeSelection || aIsPopupSelection)
-        GetResultValueAt(selectedIndex, true, value);
+      // needed, unless the final complete value differs.
+      nsAutoString finalValue, inputValue;
+      GetResultValueAt(selectedIndex, true, finalValue);
+      input->GetTextValue(inputValue);
+      if (!completeSelection || aIsPopupSelection ||
+          !finalValue.Equals(inputValue)) {
+        value = finalValue;
+      }
     }
     else if (shouldComplete) {
       // We usually try to preserve the casing of what user has typed, but
       // if he wants to autocomplete, we will replace the value with the
       // actual autocomplete result.
       // The user wants explicitely to use that result, so this ensures
       // association of the result with the autocompleted text.
       nsAutoString defaultIndexValue;
@@ -1201,17 +1218,17 @@ nsAutoCompleteController::EnterMatch(boo
       // we have to find the first default match and enter it instead
       for (uint32_t i = 0; i < mResults.Length(); ++i) {
         nsIAutoCompleteResult *result = mResults[i];
 
         if (result) {
           int32_t defaultIndex;
           result->GetDefaultIndex(&defaultIndex);
           if (defaultIndex >= 0) {
-            result->GetValueAt(defaultIndex, value);
+            result->GetFinalCompleteValueAt(defaultIndex, value);
             break;
           }
         }
       }
     }
   }
 
   nsCOMPtr<nsIObserverService> obsSvc =
@@ -1544,28 +1561,20 @@ nsAutoCompleteController::GetFinalDefaul
 
   result->GetValueAt(defaultIndex, _retval);
   nsAutoString inputValue;
   mInput->GetTextValue(inputValue);
   if (!_retval.Equals(inputValue, nsCaseInsensitiveStringComparator())) {
     return NS_ERROR_FAILURE;
   }
 
-  // Hack: For typeAheadResults allow the comment to be used as the final
-  // defaultComplete value if provided, otherwise fall back to the usual
-  // value.  This allows to provide a different complete text when the user
-  // confirms the match.  Don't rely on this for production code, since it's a
-  // temporary solution that needs a dedicated API (bug 754265).
-  bool isTypeAheadResult = false;
-  nsAutoString commentValue;
-  if (NS_SUCCEEDED(result->GetTypeAheadResult(&isTypeAheadResult)) &&
-      isTypeAheadResult &&
-      NS_SUCCEEDED(result->GetCommentAt(defaultIndex, commentValue)) &&
-      !commentValue.IsEmpty()) {
-    _retval = commentValue;
+  nsAutoString finalCompleteValue;
+  rv = result->GetFinalCompleteValueAt(defaultIndex, finalCompleteValue);
+  if (NS_SUCCEEDED(rv)) {
+    _retval = finalCompleteValue;
   }
 
   MOZ_ASSERT(FindInReadable(inputValue, _retval, nsCaseInsensitiveStringComparator()),
              "Return value must include input value.");
   return NS_OK;
 }
 
 nsresult
@@ -1620,48 +1629,53 @@ nsAutoCompleteController::CompleteValue(
   }
 
   mInput->SelectTextRange(mSearchStringLength, endSelect);
 
   return NS_OK;
 }
 
 nsresult
-nsAutoCompleteController::GetResultLabelAt(int32_t aIndex, bool aValueOnly, nsAString & _retval)
+nsAutoCompleteController::GetResultLabelAt(int32_t aIndex, nsAString & _retval)
 {
-  return GetResultValueLabelAt(aIndex, aValueOnly, false, _retval);
+  return GetResultValueLabelAt(aIndex, false, false, _retval);
 }
 
 nsresult
-nsAutoCompleteController::GetResultValueAt(int32_t aIndex, bool aValueOnly, nsAString & _retval)
+nsAutoCompleteController::GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
+                                           nsAString & _retval)
 {
-  return GetResultValueLabelAt(aIndex, aValueOnly, true, _retval);
+  return GetResultValueLabelAt(aIndex, aGetFinalValue, true, _retval);
 }
 
 nsresult
-nsAutoCompleteController::GetResultValueLabelAt(int32_t aIndex, bool aValueOnly,
-                                                bool aGetValue, nsAString & _retval)
+nsAutoCompleteController::GetResultValueLabelAt(int32_t aIndex,
+                                                bool aGetFinalValue,
+                                                bool aGetValue,
+                                                nsAString & _retval)
 {
   NS_ENSURE_TRUE(aIndex >= 0 && (uint32_t) aIndex < mRowCount, NS_ERROR_ILLEGAL_VALUE);
 
   int32_t rowIndex;
   nsIAutoCompleteResult *result;
   nsresult rv = GetResultAt(aIndex, &result, &rowIndex);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint16_t searchResult;
   result->GetSearchResult(&searchResult);
 
   if (searchResult == nsIAutoCompleteResult::RESULT_FAILURE) {
-    if (aValueOnly)
+    if (aGetValue)
       return NS_ERROR_FAILURE;
     result->GetErrorDescription(_retval);
   } else if (searchResult == nsIAutoCompleteResult::RESULT_SUCCESS ||
              searchResult == nsIAutoCompleteResult::RESULT_SUCCESS_ONGOING) {
-    if (aGetValue)
+    if (aGetFinalValue)
+      result->GetFinalCompleteValueAt(rowIndex, _retval);
+    else if (aGetValue)
       result->GetValueAt(rowIndex, _retval);
     else
       result->GetLabelAt(rowIndex, _retval);
   }
 
   return NS_OK;
 }
 
--- a/toolkit/components/autocomplete/nsAutoCompleteController.h
+++ b/toolkit/components/autocomplete/nsAutoCompleteController.h
@@ -54,22 +54,21 @@ protected:
   nsresult EnterMatch(bool aIsPopupSelection);
   nsresult RevertTextValue();
 
   nsresult CompleteDefaultIndex(int32_t aResultIndex);
   nsresult CompleteValue(nsString &aValue);
 
   nsresult GetResultAt(int32_t aIndex, nsIAutoCompleteResult** aResult,
                        int32_t* aRowIndex);
-  nsresult GetResultValueAt(int32_t aIndex, bool aValueOnly,
+  nsresult GetResultValueAt(int32_t aIndex, bool aGetFinalValue,
                             nsAString & _retval);
-  nsresult GetResultLabelAt(int32_t aIndex, bool aValueOnly,
-                            nsAString & _retval);
+  nsresult GetResultLabelAt(int32_t aIndex, nsAString & _retval);
 private:
-  nsresult GetResultValueLabelAt(int32_t aIndex, bool aValueOnly,
+  nsresult GetResultValueLabelAt(int32_t aIndex, bool aGetFinalValue,
                                  bool aGetValue, nsAString & _retval);
 protected:
 
   /**
    * Gets and validates the defaultComplete result and the relative
    * defaultIndex value.
    *
    * @param aResultIndex
--- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
+++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.cpp
@@ -85,17 +85,18 @@ nsAutoCompleteSimpleResult::SetTypeAhead
   mTypeAheadResult = aTypeAheadResult;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::AppendMatch(const nsAString& aValue,
                                         const nsAString& aComment,
                                         const nsAString& aImage,
-                                        const nsAString& aStyle)
+                                        const nsAString& aStyle,
+                                        const nsAString& aFinalCompleteValue)
 {
   CheckInvariants();
 
   if (! mValues.AppendElement(aValue))
     return NS_ERROR_OUT_OF_MEMORY;
   if (! mComments.AppendElement(aComment)) {
     mValues.RemoveElementAt(mValues.Length() - 1);
     return NS_ERROR_OUT_OF_MEMORY;
@@ -106,16 +107,23 @@ nsAutoCompleteSimpleResult::AppendMatch(
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (! mStyles.AppendElement(aStyle)) {
     mValues.RemoveElementAt(mValues.Length() - 1);
     mComments.RemoveElementAt(mComments.Length() - 1);
     mImages.RemoveElementAt(mImages.Length() - 1);
     return NS_ERROR_OUT_OF_MEMORY;
   }
+  if (!mFinalCompleteValues.AppendElement(aFinalCompleteValue)) {
+    mValues.RemoveElementAt(mValues.Length() - 1);
+    mComments.RemoveElementAt(mComments.Length() - 1);
+    mImages.RemoveElementAt(mImages.Length() - 1);
+    mStyles.RemoveElementAt(mStyles.Length() - 1);
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::GetMatchCount(uint32_t *aMatchCount)
 {
   CheckInvariants();
 
@@ -166,16 +174,29 @@ nsAutoCompleteSimpleResult::GetStyleAt(i
   NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mStyles.Length()),
                  NS_ERROR_ILLEGAL_VALUE);
   CheckInvariants();
   _retval = mStyles[aIndex];
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsAutoCompleteSimpleResult::GetFinalCompleteValueAt(int32_t aIndex,
+                                                    nsAString& _retval)
+{
+  NS_ENSURE_TRUE(aIndex >= 0 && aIndex < int32_t(mFinalCompleteValues.Length()),
+                 NS_ERROR_ILLEGAL_VALUE);
+  CheckInvariants();
+  _retval = mFinalCompleteValues[aIndex];
+  if (_retval.Length() == 0)
+    _retval = mValues[aIndex];
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsAutoCompleteSimpleResult::SetListener(nsIAutoCompleteSimpleResultListener* aListener)
 {
   mListener = aListener;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsAutoCompleteSimpleResult::RemoveValueAt(int32_t aRowIndex,
@@ -184,14 +205,15 @@ nsAutoCompleteSimpleResult::RemoveValueA
   NS_ENSURE_TRUE(aRowIndex >= 0 && aRowIndex < int32_t(mValues.Length()),
                  NS_ERROR_ILLEGAL_VALUE);
 
   nsAutoString removedValue(mValues[aRowIndex]);
   mValues.RemoveElementAt(aRowIndex);
   mComments.RemoveElementAt(aRowIndex);
   mImages.RemoveElementAt(aRowIndex);
   mStyles.RemoveElementAt(aRowIndex);
+  mFinalCompleteValues.RemoveElementAt(aRowIndex);
 
   if (mListener)
     mListener->OnValueRemoved(this, removedValue, aRemoveFromDb);
 
   return NS_OK;
 }
--- a/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
+++ b/toolkit/components/autocomplete/nsAutoCompleteSimpleResult.h
@@ -16,16 +16,17 @@
 class nsAutoCompleteSimpleResult MOZ_FINAL : public nsIAutoCompleteSimpleResult
 {
 public:
   nsAutoCompleteSimpleResult();
   inline void CheckInvariants() {
     NS_ASSERTION(mValues.Length() == mComments.Length(), "Arrays out of sync");
     NS_ASSERTION(mValues.Length() == mImages.Length(),   "Arrays out of sync");
     NS_ASSERTION(mValues.Length() == mStyles.Length(),   "Arrays out of sync");
+    NS_ASSERTION(mValues.Length() == mFinalCompleteValues.Length(), "Arrays out of sync");
   }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIAUTOCOMPLETERESULT
   NS_DECL_NSIAUTOCOMPLETESIMPLERESULT
 
 private:
   ~nsAutoCompleteSimpleResult() {}
@@ -34,16 +35,17 @@ protected:
 
   // What we really want is an array of structs with value/comment/image/style contents.
   // But then we'd either have to use COM or manage object lifetimes ourselves.
   // Having four arrays of string simplifies this, but is stupid.
   nsTArray<nsString> mValues;
   nsTArray<nsString> mComments;
   nsTArray<nsString> mImages;
   nsTArray<nsString> mStyles;
+  nsTArray<nsString> mFinalCompleteValues;
 
   nsString mSearchString;
   nsString mErrorDescription;
   int32_t mDefaultIndex;
   uint32_t mSearchResult;
 
   bool mTypeAheadResult;
 
--- a/toolkit/components/autocomplete/nsIAutoCompleteController.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteController.idl
@@ -1,17 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIAutoCompleteInput;
 
-[scriptable, uuid(dd2c4489-e4bd-4702-86bc-e1691744e556)]
+[scriptable, uuid(ff9f8465-204a-47a6-b3c9-0628b3856684)]
 interface nsIAutoCompleteController : nsISupports
 {
   /*
    * Possible values for the searchStatus attribute
    */
   const unsigned short STATUS_NONE = 1;
   const unsigned short STATUS_SEARCHING = 2;
   const unsigned short STATUS_COMPLETE_NO_MATCH = 3;
@@ -130,12 +130,18 @@ interface nsIAutoCompleteController : ns
   AString getStyleAt(in long index);
 
   /*
    * Get the url of the image of the result at a given index in the last completed search
    */
   AString getImageAt(in long index);
 
   /*
+   * For the last completed search, get the final value that should be completed
+   * when the user confirms the match at the given index
+   */
+  AString getFinalCompleteValueAt(in long index);
+
+  /*
    * Get / set the current search string.  Note, setting will not start searching
    */
   attribute AString searchString;
 };
--- a/toolkit/components/autocomplete/nsIAutoCompleteResult.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteResult.idl
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(7b43fad1-c735-4b45-9383-c3f057fed20d)]
+[scriptable, uuid(9203c031-c4e7-4537-a4ec-81443d623d5a)]
 interface nsIAutoCompleteResult : nsISupports
 {
   /**
    * Possible values for the searchResult attribute
    */
   const unsigned short RESULT_IGNORED = 1; /* indicates invalid searchString */
   const unsigned short RESULT_FAILURE = 2; /* indicates failure */
   const unsigned short RESULT_NOMATCH = 3; /* indicates success with no matches
@@ -77,14 +77,20 @@ interface nsIAutoCompleteResult : nsISup
   AString getStyleAt(in long index);
 
   /**
    * Get the image of the result at the given index
    */
   AString getImageAt(in long index);
 
   /**
+   * Get the final value that should be completed when the user confirms
+   * the match at the given index.
+   */
+  AString getFinalCompleteValueAt(in long index);
+
+  /**
    * Remove the value at the given index from the autocomplete results.
    * If removeFromDb is set to true, the value should be removed from
    * persistent storage as well.
    */
   void removeValueAt(in long rowIndex, in boolean removeFromDb);
 };
--- a/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
+++ b/toolkit/components/autocomplete/nsIAutoCompleteSimpleResult.idl
@@ -9,17 +9,17 @@ interface nsIAutoCompleteSimpleResultLis
 
 /**
  * This class implements nsIAutoCompleteResult and provides simple methods
  * for setting the value and result items. It can be used whenever some basic
  * auto complete results are needed that can be pre-generated and filled into
  * an array.
  */
 
-[scriptable, uuid(c738dc26-aa71-4561-a3fd-b5a0e4aa80d2)]
+[scriptable, uuid(fe8802f9-c2b7-4141-8e5b-280df3f62251)]
 interface nsIAutoCompleteSimpleResult : nsIAutoCompleteResult
 {
   /**
    * A writer for the readonly attribute 'searchString' which should contain
    * the string that the user typed.
    */
   void setSearchString(in AString aSearchString);
 
@@ -43,22 +43,35 @@ interface nsIAutoCompleteSimpleResult : 
 
   /**
    * A writer for the readonly attribute 'typeAheadResult', typically set
    * because a result is only intended for type-ahead completion.
    */
   void setTypeAheadResult(in boolean aHidden);
 
   /**
-   * Appends a result item consisting of the given value, comment, image and style.
-   * This is how you add results.  Note:  image and style are optional. 
+   * Appends a match consisting of the given value, comment, image, style and
+   * the value to use for defaultIndex completion.
+   * @param aValue
+   *        The value to autocomplete to
+   * @param aComment
+   *        Comment shown in the autocomplete widget to describe this match
+   * @param aImage
+   *        Image shown in the autocomplete widget for this match.
+   * @param aStyle
+   *        Describes how to style the match in the autocomplete widget
+   * @param aFinalCompleteValue
+   *        Value used when the user confirms selecting this match. If not
+   *        provided, aValue will be used.
    */
-  void appendMatch(in AString aValue, in AString aComment, 
-                   [optional] in AString aImage, 
-                   [optional] in AString aStyle);
+  void appendMatch(in AString aValue,
+                   in AString aComment,
+                   [optional] in AString aImage,
+                   [optional] in AString aStyle,
+                   [optional] in AString aFinalCompleteValue);
 
   /**
    * Sets a listener for changes in the result.
    */
   void setListener(in nsIAutoCompleteSimpleResultListener aListener);
 };
 
 [scriptable, uuid(004efdc5-1989-4874-8a7a-345bf2fa33af)]
--- a/toolkit/components/autocomplete/tests/unit/head_autocomplete.js
+++ b/toolkit/components/autocomplete/tests/unit/head_autocomplete.js
@@ -73,17 +73,18 @@ function AutoCompleteResultBase(aValues)
   this._values = aValues;
 }
 AutoCompleteResultBase.prototype = {
   
   // Arrays
   _values: null,
   _comments: [],
   _styles: [],
-  
+  _finalCompleteValues: [],
+
   searchString: "",
   searchResult: null,
   
   defaultIndex: -1,
   
   _typeAheadResult: false,
   get typeAheadResult() {
     return this._typeAheadResult;
@@ -108,16 +109,20 @@ AutoCompleteResultBase.prototype = {
   getStyleAt: function(aIndex) {
     return this._styles[aIndex];
   },
   
   getImageAt: function(aIndex) {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex) {
+    return this._finalCompleteValues[aIndex] || this._values[aIndex];
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult])
 }
 
 /** 
  * nsIAutoCompleteSearch implementation that always returns
--- a/toolkit/components/autocomplete/tests/unit/test_378079.js
+++ b/toolkit/components/autocomplete/tests/unit/test_378079.js
@@ -118,16 +118,20 @@ AutoCompleteResult.prototype = {
   getStyleAt: function(aIndex) {
     return this._styles[aIndex];
   },
   
   getImageAt: function(aIndex) {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex) {
+    return this.getValueAt(aIndex);
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIAutoCompleteResult))
       return this;
 
--- a/toolkit/components/autocomplete/tests/unit/test_393191.js
+++ b/toolkit/components/autocomplete/tests/unit/test_393191.js
@@ -117,16 +117,20 @@ AutoCompleteResult.prototype = {
   getStyleAt: function(aIndex) {
     return this._styles[aIndex];
   },
   
   getImageAt: function(aIndex) {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex) {
+    return this.getValueAt(aIndex);
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIAutoCompleteResult))
       return this;
 
--- a/toolkit/components/autocomplete/tests/unit/test_440866.js
+++ b/toolkit/components/autocomplete/tests/unit/test_440866.js
@@ -116,16 +116,20 @@ AutoCompleteResult.prototype = {
   getStyleAt: function(aIndex) {
     return this._styles[aIndex];
   },
 
   getImageAt: function(aIndex) {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex) {
+    return this.getValueAt(aIndex);
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIAutoCompleteResult))
       return this;
 
--- a/toolkit/components/autocomplete/tests/unit/test_autocomplete_multiple.js
+++ b/toolkit/components/autocomplete/tests/unit/test_autocomplete_multiple.js
@@ -105,16 +105,20 @@ AutoCompleteResult.prototype = {
   getStyleAt: function(aIndex) {
     return this._styles[aIndex];
   },
   
   getImageAt: function(aIndex) {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex) {
+    return this.getValueAt(aIndex);
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIAutoCompleteResult))
       return this;
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function AutoCompleteResult(aValues, aFinalCompleteValues) {
+  this._values = aValues;
+  this._finalCompleteValues = aFinalCompleteValues;
+}
+AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
+
+function AutoCompleteInput(aSearches) {
+  this.searches = aSearches;
+  this.popup.selectedIndex = 0;
+}
+AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype);
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_handleEnter() {
+  doSearch("moz", "mozilla.com", "http://www.mozilla.com", function(aController) {
+    do_check_eq(aController.input.textValue, "moz");
+    do_check_eq(aController.getFinalCompleteValueAt(0), "http://www.mozilla.com");
+    aController.handleEnter(false);
+    do_check_eq(aController.input.textValue, "http://www.mozilla.com");
+  });
+});
+
+function doSearch(aSearchString, aResultValue, aFinalCompleteValue, aOnCompleteCallback) {
+  let search = new AutoCompleteSearchBase(
+    "search",
+    new AutoCompleteResult([ aResultValue ], [ aFinalCompleteValue ])
+  );
+  registerAutoCompleteSearch(search);
+
+  let controller = Cc["@mozilla.org/autocomplete/controller;1"].
+                   getService(Ci.nsIAutoCompleteController);  
+  
+  // Make an AutoCompleteInput that uses our searches and confirms results.
+  let input = new AutoCompleteInput([ search.name ]);
+  input.textValue = aSearchString;
+
+  controller.input = input;
+  controller.startSearch(aSearchString);
+
+  input.onSearchComplete = function onSearchComplete() {
+    aOnCompleteCallback(controller);
+
+    // Clean up.
+    unregisterAutoCompleteSearch(search);
+    run_next_test();
+  };
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/autocomplete/tests/unit/test_finalCompleteValue_forceComplete.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+function AutoCompleteResult(aValues, aFinalCompleteValues) {
+  this._values = aValues;
+  this._finalCompleteValues = aFinalCompleteValues;
+  this.defaultIndex = 0;
+}
+AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
+
+function AutoCompleteInput(aSearches) {
+  this.searches = aSearches;
+  this.popup.selectedIndex = -1;
+}
+AutoCompleteInput.prototype = Object.create(AutoCompleteInputBase.prototype);
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_handleEnter() {
+  doSearch("", "mozilla.com", "http://www.mozilla.com", function(aController) {
+    do_check_eq(aController.input.textValue, "");
+    do_check_eq(aController.getFinalCompleteValueAt(0), "http://www.mozilla.com");
+    aController.input.forceComplete = true;
+    aController.handleEnter(false);
+    do_check_eq(aController.input.textValue, "http://www.mozilla.com");
+  });
+});
+
+function doSearch(aSearchString, aResultValue, aFinalCompleteValue, aOnCompleteCallback) {
+  let search = new AutoCompleteSearchBase(
+    "search",
+    new AutoCompleteResult([ aResultValue ], [ aFinalCompleteValue ])
+  );
+  registerAutoCompleteSearch(search);
+
+  let controller = Cc["@mozilla.org/autocomplete/controller;1"].
+                   getService(Ci.nsIAutoCompleteController);  
+  
+  // Make an AutoCompleteInput that uses our searches and confirms results.
+  let input = new AutoCompleteInput([ search.name ]);
+  input.textValue = aSearchString;
+
+  controller.input = input;
+  controller.startSearch(aSearchString);
+
+  input.onSearchComplete = function onSearchComplete() {
+    aOnCompleteCallback(controller);
+
+    // Clean up.
+    unregisterAutoCompleteSearch(search);
+    run_next_test();
+  };
+}
--- a/toolkit/components/autocomplete/tests/unit/test_finalDefaultCompleteValue.js
+++ b/toolkit/components/autocomplete/tests/unit/test_finalDefaultCompleteValue.js
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-function AutoCompleteResult(aValues, aComments) {
+function AutoCompleteResult(aValues, aFinalCompleteValues) {
   this._values = aValues;
-  this._comments = aComments;
+  this._finalCompleteValues = aFinalCompleteValues;
   this.defaultIndex = 0;
-  this._typeAheadResult = true;
 }
 AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
 
 function AutoCompleteInput(aSearches) {
   this.searches = aSearches;
   this.popup.selectedIndex = -1;
   this.completeDefaultIndex = true;
 }
@@ -32,20 +31,20 @@ add_test(function test_keyNavigation() {
 add_test(function test_handleEnter() {
   doSearch("moz", "mozilla.com", "http://www.mozilla.com", function(aController) {
     do_check_eq(aController.input.textValue, "mozilla.com");
     aController.handleEnter(false);
     do_check_eq(aController.input.textValue, "http://www.mozilla.com");
   });
 });
 
-function doSearch(aSearchString, aResultValue, aCommentValue, aOnCompleteCallback) {
+function doSearch(aSearchString, aResultValue, aFinalCompleteValue, aOnCompleteCallback) {
   let search = new AutoCompleteSearchBase(
     "search",
-    new AutoCompleteResult([ aResultValue ], [ aCommentValue ], 0)
+    new AutoCompleteResult([ aResultValue ], [ aFinalCompleteValue ])
   );
   registerAutoCompleteSearch(search);
 
   let controller = Cc["@mozilla.org/autocomplete/controller;1"].
                    getService(Ci.nsIAutoCompleteController);  
   
   // Make an AutoCompleteInput that uses our searches and confirms results.
   let input = new AutoCompleteInput([ search.name ]);
--- a/toolkit/components/autocomplete/tests/unit/test_popupSelectionVsDefaultCompleteValue.js
+++ b/toolkit/components/autocomplete/tests/unit/test_popupSelectionVsDefaultCompleteValue.js
@@ -1,23 +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/. */
 
-function AutoCompleteTypeAheadResult(aValues, aComments) {
+function AutoCompleteTypeAheadResult(aValues, aFinalCompleteValues) {
   this._values = aValues;
-  this._comments = aComments;
+  this._finalCompleteValues = aFinalCompleteValues;
   this.defaultIndex = 0;
   this._typeAheadResult = true;
 }
 AutoCompleteTypeAheadResult.prototype = Object.create(AutoCompleteResultBase.prototype);
 
-function AutoCompleteResult(aValues, aComments) {
+function AutoCompleteResult(aValues) {
   this._values = aValues;
-  this._comments = aComments;
 }
 AutoCompleteResult.prototype = Object.create(AutoCompleteResultBase.prototype);
 
 function AutoCompleteInput(aSearches) {
   this.searches = aSearches;
   this.popupOpen = true;
   this.completeDefaultIndex = true;
   this.completeSelectedIndex = true;
@@ -40,17 +39,17 @@ function doSearch(aSearchString, aOnComp
   let typeAheadSearch = new AutoCompleteSearchBase(
     "typeAheadSearch",
     new AutoCompleteTypeAheadResult([ "mozilla.com" ], [ "http://www.mozilla.com" ])
   );
   registerAutoCompleteSearch(typeAheadSearch);
 
   let search = new AutoCompleteSearchBase(
     "search",
-    new AutoCompleteResult([ "mozilla.org" ], [ "http://www.mozilla.org" ])
+    new AutoCompleteResult([ "mozilla.org" ])
   );
   registerAutoCompleteSearch(search);
 
   let controller = Cc["@mozilla.org/autocomplete/controller;1"].
                    getService(Ci.nsIAutoCompleteController);
 
   // Make an AutoCompleteInput that uses our searches and confirms results.
   let input = new AutoCompleteInput([ typeAheadSearch.name, search.name ]);
--- a/toolkit/components/autocomplete/tests/unit/test_previousResult.js
+++ b/toolkit/components/autocomplete/tests/unit/test_previousResult.js
@@ -116,16 +116,20 @@ AutoCompleteResult.prototype = {
   getStyleAt: function(aIndex) {
     return this._styles[aIndex];
   },
   
   getImageAt: function(aIndex) {
     return "";
   },
 
+  getFinalCompleteValueAt: function(aIndex) {
+    return this.getValueAt(aIndex);
+  },
+
   removeValueAt: function (aRowIndex, aRemoveFromDb) {},
 
   // nsISupports implementation
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIAutoCompleteResult))
       return this;
 
--- a/toolkit/components/autocomplete/tests/unit/xpcshell.ini
+++ b/toolkit/components/autocomplete/tests/unit/xpcshell.ini
@@ -6,14 +6,16 @@ tail =
 [test_378079.js]
 [test_393191.js]
 [test_440866.js]
 [test_463023.js]
 [test_660156.js]
 [test_autocomplete_multiple.js]
 [test_badDefaultIndex.js]
 [test_completeDefaultIndex_casing.js]
+[test_finalCompleteValue.js]
+[test_finalCompleteValue_forceComplete.js]
 [test_finalDefaultCompleteValue.js]
 [test_hiddenResult.js]
 [test_immediate_search.js]
 [test_popupSelectionVsDefaultCompleteValue.js]
 [test_previousResult.js]
 [test_stopSearch.js]
--- a/toolkit/components/filepicker/nsFileView.cpp
+++ b/toolkit/components/filepicker/nsFileView.cpp
@@ -167,16 +167,21 @@ NS_IMETHODIMP nsFileResult::GetStyleAt(i
   return NS_OK;
 }
 
 NS_IMETHODIMP nsFileResult::GetImageAt(int32_t index, nsAString & aImage)
 {
   aImage.Truncate();
   return NS_OK;
 }
+NS_IMETHODIMP nsFileResult::GetFinalCompleteValueAt(int32_t index,
+                                                    nsAString & aValue)
+{
+  return GetValueAt(index, aValue);
+}
 
 NS_IMETHODIMP nsFileResult::RemoveValueAt(int32_t rowIndex, bool removeFromDb)
 {
   return NS_OK;
 }
 
 class nsFileComplete MOZ_FINAL : public nsIAutoCompleteSearch
 {
--- a/toolkit/components/passwordmgr/nsLoginManager.js
+++ b/toolkit/components/passwordmgr/nsLoginManager.js
@@ -606,16 +606,20 @@ UserAutoCompleteResult.prototype = {
     getStyleAt : function (index) {
         return "";
     },
 
     getImageAt : function (index) {
         return "";
     },
 
+    getFinalCompleteValueAt : function (index) {
+        return this.getValueAt(index);
+    },
+
     removeValueAt : function (index, removeFromDB) {
         if (index < 0 || index >= this.logins.length)
             throw "Index out of range.";
 
         var [removedLogin] = this.logins.splice(index, 1);
 
         this.matchCount--;
         if (this.defaultIndex > this.logins.length)
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -151,73 +151,58 @@ this.PlacesUtils = {
 
   /**
    * Determines whether or not a ResultNode is a Bookmark folder.
    * @param   aNode
    *          A result node
    * @returns true if the node is a Bookmark folder, false otherwise
    */
   nodeIsFolder: function PU_nodeIsFolder(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
             aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT);
   },
 
   /**
    * Determines whether or not a ResultNode represents a bookmarked URI.
    * @param   aNode
    *          A result node
    * @returns true if the node represents a bookmarked URI, false otherwise
    */
   nodeIsBookmark: function PU_nodeIsBookmark(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI &&
            aNode.itemId != -1;
   },
 
   /**
    * Determines whether or not a ResultNode is a Bookmark separator.
    * @param   aNode
    *          A result node
    * @returns true if the node is a Bookmark separator, false otherwise
    */
   nodeIsSeparator: function PU_nodeIsSeparator(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR;
   },
 
   /**
    * Determines whether or not a ResultNode is a URL item.
    * @param   aNode
    *          A result node
    * @returns true if the node is a URL item, false otherwise
    */
   nodeIsURI: function PU_nodeIsURI(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
   },
 
   /**
    * Determines whether or not a ResultNode is a Query item.
    * @param   aNode
    *          A result node
    * @returns true if the node is a Query item, false otherwise
    */
   nodeIsQuery: function PU_nodeIsQuery(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
   },
 
   /**
    * Generator for a node's ancestors.
    * @param aNode
    *        A result node
    */
@@ -367,19 +352,16 @@ this.PlacesUtils = {
   /**
    * Determines if a node is read only (children cannot be inserted, sometimes
    * they cannot be removed depending on the circumstance)
    * @param   aNode
    *          A result node
    * @returns true if the node is readonly, false otherwise
    */
   nodeIsReadOnly: function PU_nodeIsReadOnly(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     let itemId = aNode.itemId;
     if (itemId != -1) {
       return this._readOnly.indexOf(itemId) != -1;
     }
 
     if (this.nodeIsQuery(aNode) &&
         asQuery(aNode).queryOptions.resultType !=
         Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS)
@@ -389,71 +371,59 @@ this.PlacesUtils = {
 
   /**
    * Determines whether or not a ResultNode is a host container.
    * @param   aNode
    *          A result node
    * @returns true if the node is a host container, false otherwise
    */
   nodeIsHost: function PU_nodeIsHost(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
            aNode.parent &&
            asQuery(aNode.parent).queryOptions.resultType ==
              Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY;
   },
 
   /**
    * Determines whether or not a ResultNode is a day container.
    * @param   node
    *          A NavHistoryResultNode
    * @returns true if the node is a day container, false otherwise
    */
   nodeIsDay: function PU_nodeIsDay(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     var resultType;
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
            aNode.parent &&
            ((resultType = asQuery(aNode.parent).queryOptions.resultType) ==
                Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY ||
              resultType == Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY);
   },
 
   /**
    * Determines whether or not a result-node is a tag container.
    * @param   aNode
    *          A result-node
    * @returns true if the node is a tag container, false otherwise
    */
   nodeIsTagQuery: function PU_nodeIsTagQuery(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY &&
            asQuery(aNode).queryOptions.resultType ==
              Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_CONTENTS;
   },
 
   /**
    * Determines whether or not a ResultNode is a container.
    * @param   aNode
    *          A result node
    * @returns true if the node is a container item, false otherwise
    */
   containerTypes: [Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER,
                    Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT,
                    Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY],
   nodeIsContainer: function PU_nodeIsContainer(aNode) {
-    if (!(aNode instanceof Ci.nsINavHistoryResultNode)) {
-      throw new Error("Invalid Places node");
-    }
     return this.containerTypes.indexOf(aNode.type) != -1;
   },
 
   /**
    * Determines whether or not a ResultNode is an history related container.
    * @param   node
    *          A result node
    * @returns true if the node is an history related container, false otherwise
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -1399,19 +1399,17 @@ urlInlineComplete.prototype = {
         let untrimmedHost = row.getResultByIndex(1);
         // If the untrimmed value doesn't preserve the user's input just
         // ignore it and complete to the found host.
         if (untrimmedHost &&
             !untrimmedHost.toLowerCase().contains(ac._originalSearchString.toLowerCase())) {
           untrimmedHost = null;
         }
 
-        // TODO (bug 754265): this is a temporary solution introduced while
-        // waiting for a propert dedicated API.
-        ac._result.appendMatch(ac._strippedPrefix + trimmedHost, untrimmedHost);
+        ac._result.appendMatch(ac._strippedPrefix + trimmedHost, "", "", "", untrimmedHost);
 
         // handleCompletion() will cause the result listener to be called, and
         // will display the result in the UI.
       },
 
       handleError: function (aError) {
         Components.utils.reportError(
           "URL Inline Complete: An async statement encountered an " +
@@ -1475,19 +1473,17 @@ urlInlineComplete.prototype = {
         // If the untrimmed value doesn't preserve the user's input just
         // ignore it and complete to the found url.
         let untrimmedURL = prefix + url;
         if (untrimmedURL &&
             !untrimmedURL.toLowerCase().contains(ac._originalSearchString.toLowerCase())) {
           untrimmedURL = null;
          }
 
-        // TODO (bug 754265): this is a temporary solution introduced while
-        // waiting for a propert dedicated API.
-        ac._result.appendMatch(ac._strippedPrefix + url, untrimmedURL);
+        ac._result.appendMatch(ac._strippedPrefix + url, "", "", "", untrimmedURL);
 
         // handleCompletion() will cause the result listener to be called, and
         // will display the result in the UI.
       },
 
       handleError: function(aError) {
         Components.utils.reportError(
           "URL Inline Complete: An async statement encountered an " +
--- a/toolkit/components/places/nsTaggingService.js
+++ b/toolkit/components/places/nsTaggingService.js
@@ -538,16 +538,23 @@ TagAutoCompleteResult.prototype = {
   /**
    * Get the image for the result at the given index
    */
   getImageAt: function PTACR_getImageAt(index) {
     return null;
   },
 
   /**
+   * Get the image for the result at the given index
+   */
+  getFinalCompleteValueAt: function PTACR_getFinalCompleteValueAt(index) {
+    return this.getValueAt(index);
+  },
+
+  /**
    * Remove the value at the given index from the autocomplete results.
    * If removeFromDb is set to true, the value should be removed from
    * persistent storage as well.
    */
   removeValueAt: function PTACR_removeValueAt(index, removeFromDb) {
     this._results.splice(index, 1);
     this._comments.splice(index, 1);
   },
deleted file mode 100644
--- a/toolkit/components/places/tests/unit/test_PlacesUtils_nodeIsXXX_invalidArg.js
+++ /dev/null
@@ -1,24 +0,0 @@
-
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-function run_test() {
- let nodeIsMethods = [
-  "nodeIsFolder",
-  "nodeIsBookmark",
-  "nodeIsSeparator",
-  "nodeIsURI",
-  "nodeIsQuery",
-  "nodeIsReadOnly",
-  "nodeIsHost",
-  "nodeIsDay",
-  "nodeIsTagQuery",
-  "nodeIsContainer",
-  "nodeIsHistoryContainer",
-  "nodeIsQuery"
- ];
- for (let methodName of nodeIsMethods) {
-  Assert.throws(() => PlacesUtils[methodName](true), /Invalid Places node/);
- }
-}
-
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -112,17 +112,16 @@ fail-if = os == "android"
 skip-if = true
 [test_null_interfaces.js]
 [test_onItemChanged_tags.js]
 [test_pageGuid_bookmarkGuid.js]
 [test_frecency_observers.js]
 [test_placeURIs.js]
 [test_PlacesUtils_asyncGetBookmarkIds.js]
 [test_PlacesUtils_lazyobservers.js]
-[test_PlacesUtils_nodeIsXXX_invalidArg.js]
 [test_placesTxn.js]
 [test_preventive_maintenance.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_preventive_maintenance_checkAndFixDatabase.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_preventive_maintenance_runTasks.js]
--- a/toolkit/components/satchel/nsFormAutoComplete.js
+++ b/toolkit/components/satchel/nsFormAutoComplete.js
@@ -471,16 +471,20 @@ FormAutoCompleteResult.prototype = {
         return "";
     },
 
     getImageAt : function (index) {
         this._checkIndexBounds(index);
         return "";
     },
 
+    getFinalCompleteValueAt : function (index) {
+        return this.getValueAt(index);
+    },
+
     removeValueAt : function (index, removeFromDB) {
         this._checkIndexBounds(index);
 
         let [removedEntry] = this.entries.splice(index, 1);
 
         if (removeFromDB) {
           this.formHistory.update({ op: "remove",
                                     fieldname: this.fieldName,
--- a/toolkit/components/satchel/nsFormAutoCompleteResult.jsm
+++ b/toolkit/components/satchel/nsFormAutoCompleteResult.jsm
@@ -147,16 +147,25 @@ FormAutoCompleteResult.prototype = {
    * @return          the image url at the specified index
    */
   getImageAt: function(index) {
     this._checkIndexBounds(index);
     return "";
   },
 
   /**
+   * Retrieves a result
+   * @param  index    the index of the result requested
+   * @return          the result at the specified index
+   */
+  getFinalCompleteValueAt: function(index) {
+    return this.getValueAt(index);
+  },
+
+  /**
    * Removes a result from the resultset
    * @param  index    the index of the result to remove
    */
   removeValueAt: function(index, removeFromDatabase) {
     this._checkIndexBounds(index);
     // Forward the removeValueAt call to the underlying result if we have one
     // Note: this assumes that the form history results were added to the top
     // of our arrays.
--- 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/content/tests/chrome/test_autocomplete2.xul
+++ b/toolkit/content/tests/chrome/test_autocomplete2.xul
@@ -39,16 +39,17 @@ nsAutoCompleteSimpleResult.prototype = {
  searchResult: ACR.RESULT_FAILURE,
  defaultIndex: -1,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function() { return this._param; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function() { return this.getValueAt(); },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
 };
 
 // A basic autocomplete implementation that either returns one result or none
 var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
 var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple"
 var autoCompleteSimple = {
--- a/toolkit/content/tests/chrome/test_autocomplete3.xul
+++ b/toolkit/content/tests/chrome/test_autocomplete3.xul
@@ -39,16 +39,17 @@ nsAutoCompleteSimpleResult.prototype = {
  searchResult: ACR.RESULT_FAILURE,
  defaultIndex: 0,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function() { return this._param; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function() { return this.getValueAt(); },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
 };
 
 // A basic autocomplete implementation that either returns one result or none
 var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
 var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple"
 var autoCompleteSimple = {
--- a/toolkit/content/tests/chrome/test_autocomplete4.xul
+++ b/toolkit/content/tests/chrome/test_autocomplete4.xul
@@ -41,16 +41,17 @@ nsAutoCompleteSimpleResult.prototype = {
  searchResult: ACR.RESULT_FAILURE,
  defaultIndex: 0,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function() { return this._param; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function() { return this.getValueAt(); },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
 };
 
 // A basic autocomplete implementation that either returns one result or none
 var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
 var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple"
 var autoCompleteSimple = {
--- a/toolkit/content/tests/chrome/test_autocomplete5.xul
+++ b/toolkit/content/tests/chrome/test_autocomplete5.xul
@@ -37,16 +37,17 @@ nsAutoCompleteSimpleResult.prototype = {
  searchResult: ACR.RESULT_FAILURE,
  defaultIndex: -1,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function() { return this._param; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function() { return this.getValueAt(); },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
 };
 
 // A basic autocomplete implementation that either returns one result or none
 var autoCompleteSimpleID = Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
 var autoCompleteSimpleName = "@mozilla.org/autocomplete/search;1?name=simple"
 var autoCompleteSimple = {
--- a/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul
+++ b/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul
@@ -37,16 +37,17 @@ autoCompleteSimpleResult.prototype = {
  searchResult: Components.interfaces.nsIAutoCompleteResult.RESULT_FAILURE,
  defaultIndex: 0,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function() { return this._param; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function() { return this.getValueAt(); },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
 };
 
 // A basic autocomplete implementation that returns one result.
 let autoCompleteSimple = {
   classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"),
   contractID: "@mozilla.org/autocomplete/search;1?name=simple",
--- a/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul
+++ b/toolkit/content/tests/chrome/test_autocomplete_with_composition_on_textbox.xul
@@ -52,16 +52,17 @@ nsAutoCompleteSimpleResult.prototype = {
  searchResult: nsIAutoCompleteResult.RESULT_FAILURE,
  defaultIndex: 0,
  errorDescription: null,
  matchCount: 0,
  getValueAt: function(aIndex) { return aIndex == 0 ? this._value : null; },
  getCommentAt: function() { return null; },
  getStyleAt: function() { return null; },
  getImageAt: function() { return null; },
+ getFinalCompleteValueAt: function(aIndex) { return this.getValueAt(aIndex); },
  getLabelAt: function() { return null; },
  removeValueAt: function() {}
 };
 
 // A basic autocomplete implementation that either returns one result or none
 var autoCompleteSimpleID =
   Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca");
 var autoCompleteSimpleName =
--- a/toolkit/content/widgets/videocontrols.css
+++ b/toolkit/content/widgets/videocontrols.css
@@ -29,16 +29,20 @@
   font: normal normal normal 100%/normal sans-serif !important;
   text-decoration: none !important;
 }
 
 .controlsSpacer[hideCursor] {
   cursor: none;
 }
 
+.controlsOverlay[scaled] {
+  -moz-box-align: center;
+}
+
 /* CSS Transitions
  *
  * These are overriden by the default theme; the rules here just 
  * provide a fallback to drive the required transitionend event
  * (in case a 3rd party theme does not provide transitions).
  */
 .controlBar:not([immediate]) {
   transition-property: opacity;
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -320,16 +320,17 @@
                 scrubberThumb  : null,
                 scrubber       : null,
                 progressBar    : null,
                 bufferBar      : null,
                 statusOverlay  : null,
                 controlsSpacer : null,
                 clickToPlay    : null,
                 stats          : {},
+                controlsOverlay : null,
                 fullscreenButton : null,
 
                 randomID : 0,
                 videoEvents : ["play", "pause", "ended", "volumechange", "loadeddata",
                                "loadstart", "timeupdate", "progress",
                                "playing", "waiting", "canplay", "canplaythrough",
                                "seeking", "seeked", "emptied", "loadedmetadata",
                                "error", "suspend", "stalled",
@@ -1385,20 +1386,46 @@
                     }
 
                     let minHeightForControlBar = this._controlBarHeight;
                     let minWidthOnlyPlayPause = this._playButtonWidth + this._muteButtonWidth;
 
                     let videoHeight = isAudioOnly ? minHeightForControlBar : this.video.clientHeight;
                     let videoWidth = isAudioOnly ? minWidthAllControls : this.video.clientWidth;
 
-                    if ((this._overlayPlayButtonHeight + this._controlBarHeight) > videoHeight || this._overlayPlayButtonWidth > videoWidth)
+                    // Adapt the size of the controls to the size of the video
+                    if (this.video.readyState >= this.video.HAVE_METADATA) {
+                      if (!this.isAudioOnly && this.video.videoWidth && this.video.videoHeight) {
+                        var rect = this.video.getBoundingClientRect();
+                        var widthRatio = rect.width / this.video.videoWidth;
+                        var heightRatio = rect.height / this.video.videoHeight;
+                        var width = this.video.videoWidth * Math.min(widthRatio, heightRatio);
+
+                        this.controlsOverlay.setAttribute("scaled", true);
+                        this.controlsOverlay.style.width = width + "px";
+                        this.controlsSpacer.style.width = width + "px";
+                        this.controlBar.style.width = width + "px";
+                      } else {
+                        this.controlsOverlay.removeAttribute("scaled");
+                        this.controlsOverlay.style.width = "";
+                        this.controlsSpacer.style.width = "";
+                        this.controlBar.style.width = "";
+                      }
+                    }
+
+                    if ((this._overlayPlayButtonHeight + this._controlBarHeight) > videoHeight ||
+                        this._overlayPlayButtonWidth > videoWidth) {
                         this.clickToPlay.hidden = true;
-                    else if (this.clickToPlay.hidden && !this.video.played.length)
+                    } else if (this.clickToPlay.hidden &&
+                               !this.video.played.length &&
+                               this.video.paused) {
+                        // Check this.video.paused to handle when a video is
+                        // playing but hasn't processed any frames yet
                         this.clickToPlay.hidden = false;
+                    }
 
                     let size = "normal";
                     if (videoHeight < minHeightForControlBar)
                         size = "hidden";
                     else if (videoWidth < minWidthOnlyPlayPause)
                         size = "hidden";
                     else if (videoWidth < minWidthAllControls)
                         size = "small";
@@ -1417,16 +1444,17 @@
                     this.progressBar   = document.getAnonymousElementByAttribute(binding, "class", "progressBar");
                     this.bufferBar     = document.getAnonymousElementByAttribute(binding, "class", "bufferBar");
                     this.scrubber      = document.getAnonymousElementByAttribute(binding, "class", "scrubber");
                     this.scrubberThumb = document.getAnonymousElementByAttribute(this.scrubber, "class", "scale-thumb");
                     this.durationLabel = document.getAnonymousElementByAttribute(binding, "class", "durationLabel");
                     this.positionLabel = document.getAnonymousElementByAttribute(binding, "class", "positionLabel");
                     this.statusOverlay = document.getAnonymousElementByAttribute(binding, "class", "statusOverlay");
                     this.statsOverlay  = document.getAnonymousElementByAttribute(binding, "class", "statsOverlay");
+                    this.controlsOverlay = document.getAnonymousElementByAttribute(binding, "class", "controlsOverlay");
                     this.controlsSpacer     = document.getAnonymousElementByAttribute(binding, "class", "controlsSpacer");
                     this.clickToPlay        = document.getAnonymousElementByAttribute(binding, "class", "clickToPlay");
                     this.fullscreenButton   = document.getAnonymousElementByAttribute(binding, "class", "fullscreenButton");
                     this.volumeForeground   = document.getAnonymousElementByAttribute(binding, "anonid", "volumeForeground");
 
                     this.statsTable       = document.getAnonymousElementByAttribute(binding, "class", "statsTable");
                     this.stats.filename   = document.getAnonymousElementByAttribute(binding, "class", "statFilename");
                     this.stats.size       = document.getAnonymousElementByAttribute(binding, "class", "statSize");
--- 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/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -937,19 +937,25 @@ TabActor.prototype = {
    * the page.
    *
    * @param nsIDOMWindow aWindow
    *        The window object you want to check.
    * @return boolean
    *         True if the window.console object is native, or false otherwise.
    */
   hasNativeConsoleAPI: function BTA_hasNativeConsoleAPI(aWindow) {
-    // Do not expose WebConsoleActor function directly as it is always
-    // loaded after the BrowserTabActor
-    return WebConsoleActor.prototype.hasNativeConsoleAPI(aWindow);
+    let isNative = false;
+    try {
+      // We are very explicitly examining the "console" property of
+      // the non-Xrayed object here.
+      let console = aWindow.wrappedJSObject.console;
+      isNative = console instanceof aWindow.Console;
+    }
+    catch (ex) { }
+    return isNative;
   }
 };
 
 /**
  * The request types this actor can handle.
  */
 TabActor.prototype.requestTypes = {
   "attach": TabActor.prototype.onAttach,
--- a/toolkit/devtools/server/actors/webconsole.js
+++ b/toolkit/devtools/server/actors/webconsole.js
@@ -1,48 +1,54 @@
 /* -*- js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-let Cc = Components.classes;
-let Ci = Components.interfaces;
-let Cu = Components.utils;
+let {Cc, Ci, Cu} = require("chrome");
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
-let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
+let { DebuggerServer, ActorPool } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+// Symbols from script.js
+let { ThreadActor, EnvironmentActor, ObjectActor, LongStringActor } = DebuggerServer;
+
+Cu.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitor", () => {
-  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+  return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitor;
 });
 XPCOMUtils.defineLazyGetter(this, "NetworkMonitorChild", () => {
-  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+  return require("devtools/toolkit/webconsole/network-monitor")
          .NetworkMonitorChild;
 });
 XPCOMUtils.defineLazyGetter(this, "ConsoleProgressListener", () => {
-  return devtools.require("devtools/toolkit/webconsole/network-monitor")
+  return require("devtools/toolkit/webconsole/network-monitor")
          .ConsoleProgressListener;
 });
+XPCOMUtils.defineLazyGetter(this, "events", () => {
+  return require("sdk/event/core");
+});
 
 for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
                   "ConsoleAPIListener", "JSTermHelpers", "JSPropertyProvider",
                   "ConsoleReflowListener"]) {
   Object.defineProperty(this, name, {
     get: function(prop) {
       if (prop == "WebConsoleUtils") {
         prop = "Utils";
       }
-      return devtools.require("devtools/toolkit/webconsole/utils")[prop];
+      return require("devtools/toolkit/webconsole/utils")[prop];
     }.bind(null, name),
     configurable: true,
     enumerable: true
   });
 }
 
 
 /**
@@ -1823,11 +1829,17 @@ NetworkEventActor.prototype.requestTypes
   "getRequestCookies": NetworkEventActor.prototype.onGetRequestCookies,
   "getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
   "getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders,
   "getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies,
   "getResponseContent": NetworkEventActor.prototype.onGetResponseContent,
   "getEventTimings": NetworkEventActor.prototype.onGetEventTimings,
 };
 
-DebuggerServer.addTabActor(WebConsoleActor, "consoleActor");
-DebuggerServer.addGlobalActor(WebConsoleActor, "consoleActor");
+exports.register = function(handle) {
+  handle.addGlobalActor(WebConsoleActor, "consoleActor");
+  handle.addTabActor(WebConsoleActor, "consoleActor");
+};
 
+exports.unregister = function(handle) {
+  handle.removeGlobalActor(WebConsoleActor, "consoleActor");
+  handle.removeTabActor(WebConsoleActor, "consoleActor");
+};
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -355,34 +355,34 @@ var DebuggerServer = {
 
   /**
    * Install tab actors in documents loaded in content childs
    */
   addChildActors: function () {
     // In case of apps being loaded in parent process, DebuggerServer is already
     // initialized and browser actors are already loaded,
     // but childtab.js hasn't been loaded yet.
-    if (!("WebConsoleActor" in this)) {
+    if (!DebuggerServer.tabActorFactories.hasOwnProperty("consoleActor")) {
       this.addTabActors();
     }
     // But webbrowser.js and childtab.js aren't loaded from shell.js.
     if (!("BrowserTabActor" in this)) {
       this.addActors("resource://gre/modules/devtools/server/actors/webbrowser.js");
     }
     if (!("ContentActor" in this)) {
       this.addActors("resource://gre/modules/devtools/server/actors/childtab.js");
     }
   },
 
   /**
    * Install tab actors.
    */
   addTabActors: function() {
     this.addActors("resource://gre/modules/devtools/server/actors/script.js");
-    this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
+    this.registerModule("devtools/server/actors/webconsole");
     this.registerModule("devtools/server/actors/inspector");
     this.registerModule("devtools/server/actors/call-watcher");
     this.registerModule("devtools/server/actors/canvas");
     this.registerModule("devtools/server/actors/webgl");
     this.registerModule("devtools/server/actors/webaudio");
     this.registerModule("devtools/server/actors/stylesheets");
     this.registerModule("devtools/server/actors/styleeditor");
     this.registerModule("devtools/server/actors/storage");
--- 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/LulCommonExt.h
+++ b/tools/profiler/LulCommonExt.h
@@ -192,23 +192,20 @@ class scoped_ptr {
 
  public:
 
   typedef T element_type;
 
   explicit scoped_ptr(T* p = 0): ptr(p) {}
 
   ~scoped_ptr() {
-    typedef char type_must_be_complete[sizeof(T)];
     delete ptr;
   }
 
   void reset(T* p = 0) {
-    typedef char type_must_be_complete[sizeof(T)];
-
     if (ptr != p) {
       delete ptr;
       ptr = p;
     }
   }
 
   T& operator*() const {
     MOZ_ASSERT(ptr != 0);
@@ -281,23 +278,20 @@ class scoped_array {
 
  public:
 
   typedef T element_type;
 
   explicit scoped_array(T* p = 0) : ptr(p) {}
 
   ~scoped_array() {
-    typedef char type_must_be_complete[sizeof(T)];
     delete[] ptr;
   }
 
   void reset(T* p = 0) {
-    typedef char type_must_be_complete[sizeof(T)];
-
     if (ptr != p) {
       delete [] ptr;
       ptr = p;
     }
   }
 
   T& operator[](std::ptrdiff_t i) const {
     MOZ_ASSERT(ptr != 0);
@@ -375,23 +369,20 @@ class scoped_ptr_malloc {
 
  public:
 
   typedef T element_type;
 
   explicit scoped_ptr_malloc(T* p = 0): ptr(p) {}
 
   ~scoped_ptr_malloc() {
-    typedef char type_must_be_complete[sizeof(T)];
     free_((void*) ptr);
   }
 
   void reset(T* p = 0) {
-    typedef char type_must_be_complete[sizeof(T)];
-
     if (ptr != p) {
       free_((void*) ptr);
       ptr = p;
     }
   }
 
   T& operator*() const {
     MOZ_ASSERT(ptr != 0);
--- a/tools/profiler/LulElf.cpp
+++ b/tools/profiler/LulElf.cpp
@@ -414,17 +414,16 @@ template<typename ElfClass>
 bool LoadSymbols(const string& obj_file,
                  const bool big_endian,
                  const typename ElfClass::Ehdr* elf_header,
                  const bool read_gnu_debug_link,
                  LoadSymbolsInfo<ElfClass>* info,
                  SecMap* smap,
                  void* rx_avma,
                  void (*log)(const char*)) {
-  typedef typename ElfClass::Addr Addr;
   typedef typename ElfClass::Phdr Phdr;
   typedef typename ElfClass::Shdr Shdr;
 
   char buf[500];
   snprintf(buf, sizeof(buf), "LoadSymbols: BEGIN   %s\n", obj_file.c_str());
   buf[sizeof(buf)-1] = 0;
   log(buf);
 
@@ -589,17 +588,16 @@ string BaseFileName(const string &filena
 
 template<typename ElfClass>
 bool ReadSymbolDataElfClass(const typename ElfClass::Ehdr* elf_header,
                             const string& obj_filename,
                             const vector<string>& debug_dirs,
                             SecMap* smap, void* rx_avma,
                             void (*log)(const char*)) {
   typedef typename ElfClass::Ehdr Ehdr;
-  typedef typename ElfClass::Shdr Shdr;
 
   unsigned char identifier[16];
   if (!lul
       ::FileID::ElfFileIdentifierFromMappedFile(elf_header, identifier)) {
     fprintf(stderr, "%s: unable to generate file identifier\n",
             obj_filename.c_str());
     return false;
   }
--- 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',