Merge m-c to fx-team
authorWes Kocher <wkocher@mozilla.com>
Tue, 15 Jul 2014 17:50:18 -0700
changeset 215130 1fef40c21b5e9dfae647122e211ed81b96a515be
parent 215129 2ba69933e5a504ef0b3670067599a06cedef8245 (current diff)
parent 215002 869971ad9fd64529113755e571fc88ae2901559f (diff)
child 215131 9fe6a41414508f3d8855e785ccfa6bacf48ba7f3
push id3857
push userraliiev@mozilla.com
push dateTue, 02 Sep 2014 16:39:23 +0000
treeherdermozilla-beta@5638b907b505 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team
configure.in
services/sync/tests/unit/test_password_mpenabled.js
testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1555,19 +1555,19 @@ nsAccessibilityService::CreateAccessible
         newAcc = new HTMLTableCellAccessibleWrap(aContent, document);
       else
         newAcc = new HyperTextAccessibleWrap(aContent, document);
       break;
 
     case eHTMLTableRowType: {
       // Accessible HTML table row may be a child of tbody/tfoot/thead of
       // accessible HTML table or a direct child of accessible of HTML table.
-      Accessible* table = aContext->IsTable() ?
-        aContext :
-        (aContext->Parent()->IsTable() ? aContext->Parent() : nullptr);
+      Accessible* table = aContext->IsTable() ? aContext : nullptr;
+      if (!table && aContext->Parent() && aContext->Parent()->IsTable())
+        table = aContext->Parent();
 
       if (table) {
         nsIContent* parentContent = aContent->GetParent();
         nsIFrame* parentFrame = parentContent->GetPrimaryFrame();
         if (parentFrame->GetType() != nsGkAtoms::tableOuterFrame) {
           parentContent = parentContent->GetParent();
           parentFrame = parentContent->GetPrimaryFrame();
         }
--- a/b2g/app/ua-update.json.in
+++ b/b2g/app/ua-update.json.in
@@ -22,18 +22,16 @@
   // bug 826712, orkut.com
   "orkut.com": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 827622, bing.com
   "bing.com": "\\(Mobile#(Android; Mobile",
   // bug 827626, magazineluiza.com.br
   "magazineluiza.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827628, groupon.com.br
   "groupon.com.br": "\\(Mobile#(Android; Mobile",
-  // bug 827632, tecmundo.com.br
-  "tecmundo.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827633, hao123.com
   "hao123.com": "\\(Mobile#(Android; Mobile",
   // bug 827573, webmotors.com.br
   "webmotors.com.br": "\\(Mobile#(Android; Mobile",
   // bug 827670, elpais.com.co
   "elpais.com.co": "Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19",
   // bug 827674, avianca.com
   "avianca.com": "\\(Mobile#(Android; Mobile",
@@ -86,17 +84,15 @@
   // bug 878264, haber.ba
   "haber.ba": "\\(Mobile#(Android; Mobile",
   // bug 878271, kurir-info.rs
   "kurir-info.rs": "\\(Mobile#(Android; Mobile",
   // bug 878273, livescore.com
   "livescore.com": "\\(Mobile#(Android; Mobile",
   // bug 878277, naslovi.net
   "naslovi.net": "\\(Mobile#(Android; Mobile",
-  // bug 878632, banorte.com
-  "banorte.com": "\\(Mobile#(Android; Mobile",
   // bug 878649, univision.com
   "univision.com": "\\(Mobile#(Android; Mobile",
   // bug 878653, redstarbelgrade.info
   "redstarbelgrade.info": "\\(Mobile#(Android; Mobile",
   // bug 878655, vesti-online.com
   "vesti-online.com": "\\(Mobile#(Android; Mobile"
 }
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
@@ -123,15 +123,15 @@
   <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"/>
   <!-- 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="0e31f35a2a77301e91baa8a237aa9e9fa4076084"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
-  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="c61e5f15fd62888f2c33d7d542b5b65c38102e8b"/>
+  <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="749880f1942620ee1caac7c248f7cfe33f56203d"/>
   <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="832f4acaf481a19031e479a40b03d9ce5370ddee"/>
   <project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="8400a1a850d19f28137880b31582efa3416223c3"/>
   <project name="android-sdk" path="sdk" remote="b2g" revision="8b1365af38c9a653df97349ee53a3f5d64fd590a"/>
   <project name="darwinstreamingserver" path="system/darwinstreamingserver" remote="b2g" revision="cf85968c7f85e0ec36e72c87ceb4837a943b8af6"/>
 </manifest>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <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="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame/releng-flame.tt
+++ b/b2g/config/flame/releng-flame.tt
@@ -1,7 +1,7 @@
 [
-{"size": 159142772,
-"digest": "96b10af00afc6f13e556153790373ed938b7c04c9c393c1c43fa782038f37b66fa3b47578f0cf08818b8b358cc7de777ac4b15cb26d169b27d7b9aa79233d75d",
+{"size": 149922032,
+"digest": "8d1a71552ffee561e93b5b3f1bb47866592ab958f908007c75561156430eb1b85a265bfc4dc2038e58dda0264daa9854877a84ef3b591c9ac2f1ab97c098e61e",
 "filename": "backup-flame.tar.xz",
 "algorithm": "sha512"
 }
 ]
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "623b1441cb54a3105b8212589aaf2aa77d537aef", 
+    "revision": "d329fe1f9c94cc3e17bde84a0d7ea3b3ed5242fb", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,22 +12,22 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="9025e50b9d29b3cabbbb21e1dd94d0d13121a17e"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="b89fda71fcd0fa0cf969310e75be3ea33e048b44"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="2e7d5348f35575870b3c7e567a9a9f6d66f8d6c5"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,22 +12,22 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <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="46cd188fdda2397d2b8f3303a184dcd52952e2b2"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="0910be495385d90acdeaddbeaf1fba315aff57b0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="5eb9b152764b4a4cf00af94ec02a808cdbb90a20"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="dc5ca96695cab87b4c2fcd7c9f046ae3415a70a5"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="aebf432f334ec0b48eb358569b9dfbfbead48017"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="777194d772c831b5dab40cf919523d5665f2a46c"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -63,8 +63,10 @@ fi
 MOZ_FOLD_LIBS=1
 
 MOZ_JSDOWNLOADS=1
 
 MOZ_BUNDLED_FONTS=1
 
 # Enable exact rooting on b2g.
 JSGC_USE_EXACT_ROOTING=1
+
+JS_GC_SMALL_CHUNK_SIZE=1
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -646,16 +646,19 @@
 ; Services (gre) prefs
 #ifdef MOZ_SERVICES_SYNC
 @BINPATH@/defaults/pref/services-sync.js
 #endif
 
 ; [Layout Engine Resources]
 ; Style Sheets, Graphics and other Resources used by the layout engine.
 @BINPATH@/res/EditorOverride.css
+@BINPATH@/res/caret_left.svg
+@BINPATH@/res/caret_middle.svg
+@BINPATH@/res/caret_right.svg
 @BINPATH@/res/contenteditable.css
 @BINPATH@/res/designmode.css
 @BINPATH@/res/ImageDocument.css
 @BINPATH@/res/TopLevelImageDocument.css
 @BINPATH@/res/TopLevelVideoDocument.css
 @BINPATH@/res/table-add-column-after-active.gif
 @BINPATH@/res/table-add-column-after-hover.gif
 @BINPATH@/res/table-add-column-after.gif
@@ -669,31 +672,16 @@
 @BINPATH@/res/table-add-row-before-hover.gif
 @BINPATH@/res/table-add-row-before.gif
 @BINPATH@/res/table-remove-column-active.gif
 @BINPATH@/res/table-remove-column-hover.gif
 @BINPATH@/res/table-remove-column.gif
 @BINPATH@/res/table-remove-row-active.gif
 @BINPATH@/res/table-remove-row-hover.gif
 @BINPATH@/res/table-remove-row.gif
-@BINPATH@/res/text_caret.png
-@BINPATH@/res/text_caret@1.5x.png
-@BINPATH@/res/text_caret@2.25x.png
-@BINPATH@/res/text_caret@2x.png
-@BINPATH@/res/text_caret_tilt_left.png
-@BINPATH@/res/text_caret_tilt_left@1.5x.png
-@BINPATH@/res/text_caret_tilt_left@2.25x.png
-@BINPATH@/res/text_caret_tilt_left@2x.png
-@BINPATH@/res/text_caret_tilt_right.png
-@BINPATH@/res/text_caret_tilt_right@1.5x.png
-@BINPATH@/res/text_caret_tilt_right@2.25x.png
-@BINPATH@/res/text_caret_tilt_right@2x.png
-@BINPATH@/res/text_selection_handle.png
-@BINPATH@/res/text_selection_handle@1.5.png
-@BINPATH@/res/text_selection_handle@2.png
 @BINPATH@/res/grabber.gif
 #ifdef XP_MACOSX
 @BINPATH@/res/cursors/*
 #endif
 @BINPATH@/res/fonts/*
 @BINPATH@/res/dtd/*
 @BINPATH@/res/html/*
 @BINPATH@/res/langGroups.properties
--- a/b2g/simulator/build_xpi.py
+++ b/b2g/simulator/build_xpi.py
@@ -131,16 +131,19 @@ def main(platform):
       "--static-args", "{\"label\": \"Firefox OS %s\"}" % version, \
       "--output-file", xpi_path \
     ])
 
     # Ship b2g-desktop, but prevent its gaia profile to be shipped in the xpi
     add_dir_to_zip(xpi_path, os.path.join(distdir, "b2g"), "b2g", ("gaia", "B2G.app/Contents/MacOS/gaia"))
     # Then ship our own gaia profile
     add_dir_to_zip(xpi_path, os.path.join(gaia_path, "profile"), "profile")
+    # Add "defaults" directory (required by add-on runner in Firefox 31 and
+    # earlier)
+    add_dir_to_zip(xpi_path, os.path.join(srcdir, "defaults"), "defaults")
 
 if __name__ == '__main__':
     if 2 != len(sys.argv):
         print("""Usage:
   python {0} MOZ_PKG_PLATFORM
 """.format(sys.argv[0]))
         sys.exit(1)
     main(*sys.argv[1:])
new file mode 100644
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -7,16 +7,19 @@
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FxAccounts.jsm");
 
 let fxAccountsCommon = {};
 Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
 
+// for master-password utilities
+Cu.import("resource://services-sync/util.js");
+
 const PREF_LAST_FXA_USER = "identity.fxaccounts.lastSignedInUserHash";
 const PREF_SYNC_SHOW_CUSTOMIZATION = "services.sync.ui.showCustomizationDialog";
 
 const OBSERVER_TOPICS = [
   fxAccountsCommon.ONVERIFIED_NOTIFICATION,
   fxAccountsCommon.ONLOGOUT_NOTIFICATION,
 ];
 
@@ -99,16 +102,22 @@ let wrapper = {
                   .wrappedJSObject;
 
     // Don't show about:accounts with FxA disabled.
     if (!weave.fxAccountsEnabled) {
       document.body.remove();
       return;
     }
 
+    // If a master-password is enabled, we want to encourage the user to
+    // unlock it.  Things still work if not, but the user will probably need
+    // to re-auth next startup (in which case we will get here again and
+    // re-prompt)
+    Utils.ensureMPUnlocked();
+
     let iframe = document.getElementById("remote");
     this.iframe = iframe;
     iframe.addEventListener("load", this);
 
     try {
       iframe.src = url || fxAccounts.getAccountsSignUpURI();
     } catch (e) {
       error("Couldn't init Firefox Account wrapper: " + e.message);
--- a/browser/base/content/sync/customize.js
+++ b/browser/base/content/sync/customize.js
@@ -1,25 +1,11 @@
 /* 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 {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-let service = Cc["@mozilla.org/weave/service;1"]
-                .getService(Ci.nsISupports)
-                .wrappedJSObject;
-
-if (!service.allowPasswordsEngine) {
-  let checkbox = document.getElementById("fxa-pweng-chk");
-  checkbox.checked = false;
-  checkbox.disabled = true;
-}
-
 addEventListener("dialogaccept", function () {
   let pane = document.getElementById("sync-customize-pane");
   pane.writePreferences(true);
   window.arguments[0].accepted = true;
 });
--- a/browser/base/content/sync/customize.xul
+++ b/browser/base/content/sync/customize.xul
@@ -40,18 +40,17 @@
 
   <vbox align="start">
       <checkbox label="&engine.tabs.label;"
                 accesskey="&engine.tabs.accesskey;"
                 preference="engine.tabs"/>
       <checkbox label="&engine.bookmarks.label;"
                 accesskey="&engine.bookmarks.accesskey;"
                 preference="engine.bookmarks"/>
-      <checkbox id="fxa-pweng-chk"
-                label="&engine.passwords.label;"
+      <checkbox label="&engine.passwords.label;"
                 accesskey="&engine.passwords.accesskey;"
                 preference="engine.passwords"/>
       <checkbox label="&engine.history.label;"
                 accesskey="&engine.history.accesskey;"
                 preference="engine.history"/>
       <checkbox label="&engine.addons.label;"
                 accesskey="&engine.addons.accesskey;"
                 preference="engine.addons"/>
--- a/browser/base/content/sync/utils.js
+++ b/browser/base/content/sync/utils.js
@@ -82,22 +82,16 @@ let gSyncUtils = {
     this._openLink(Weave.Svc.Prefs.get(root + "termsURL"));
   },
 
   openPrivacyPolicy: function () {
     let root = this.fxAccountsEnabled ? "fxa." : "";
     this._openLink(Weave.Svc.Prefs.get(root + "privacyURL"));
   },
 
-  openMPInfoPage: function (event) {
-    event.stopPropagation();
-    let baseURL = Services.urlFormatter.formatURLPref("app.support.baseURL");
-    this._openLink(baseURL + "sync-master-password");
-  },
-
   openFirstSyncProgressPage: function () {
     this._openLink("about:sync-progress");
   },
 
   /**
    * Prepare an invisible iframe with the passphrase backup document.
    * Used by both the print and saving methods.
    *
--- a/browser/components/loop/moz.build
+++ b/browser/components/loop/moz.build
@@ -5,14 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 JAR_MANIFESTS += ['jar.mn']
 
 JS_MODULES_PATH = 'modules/loop'
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
 
+BROWSER_CHROME_MANIFESTS += [
+    'test/mochitest/browser.ini',
+]
+
 EXTRA_JS_MODULES += [
     'MozLoopAPI.jsm',
     'MozLoopPushHandler.jsm',
     'MozLoopService.jsm',
     'MozLoopWorker.js',
 ]
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+    head.js
+
+[browser_mozLoop_charPref.js]
+[browser_mozLoop_doNotDisturb.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_charPref.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This is an integration test from navigator.mozLoop through to the end
+ * effects - rather than just testing MozLoopAPI alone.
+ */
+
+add_task(loadLoopPanel);
+
+add_task(function* test_mozLoop_charPref() {
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("loop.test");
+  });
+
+  Assert.ok(gMozLoopAPI, "mozLoop should exist");
+
+  // Test setLoopCharPref
+  gMozLoopAPI.setLoopCharPref("test", "foo");
+  Assert.equal(Services.prefs.getCharPref("loop.test"), "foo",
+               "should set loop pref value correctly");
+
+  // Test getLoopCharPref
+  Assert.equal(gMozLoopAPI.getLoopCharPref("test"), "foo",
+               "should get loop pref value correctly");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_doNotDisturb.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This is an integration test from navigator.mozLoop through to the end
+ * effects - rather than just testing MozLoopAPI alone.
+ */
+
+add_task(loadLoopPanel);
+
+add_task(function* test_mozLoop_doNotDisturb() {
+  registerCleanupFunction(function () {
+    Services.prefs.clearUserPref("loop.do_not_disturb");
+  });
+
+  Assert.ok(gMozLoopAPI, "mozLoop should exist");
+
+  // Test doNotDisturb (getter)
+  Services.prefs.setBoolPref("loop.do_not_disturb", true);
+  Assert.equal(gMozLoopAPI.doNotDisturb, true,
+               "Do not disturb should be true");
+
+  // Test doNotDisturb (setter)
+  gMozLoopAPI.doNotDisturb = false;
+  Assert.equal(Services.prefs.getBoolPref("loop.do_not_disturb"), false,
+               "Do not disturb should be false");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/head.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gMozLoopAPI;
+
+function promiseGetMozLoopAPI() {
+  let deferred = Promise.defer();
+  let loopPanel = document.getElementById("loop-notification-panel");
+  let btn = document.getElementById("loop-call-button");
+
+  // Wait for the popup to be shown, then we can get the iframe and
+  // wait for the iframe's load to be completed.
+  loopPanel.addEventListener("popupshown", function onpopupshown() {
+    loopPanel.removeEventListener("popupshown", onpopupshown, true);
+    let iframe = document.getElementById(btn.getAttribute("notificationFrameId"));
+
+    if (iframe.contentDocument &&
+        iframe.contentDocument.readyState == "complete") {
+      gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop;
+
+      deferred.resolve();
+    } else {
+      iframe.addEventListener("load", function panelOnLoad(e) {
+        iframe.removeEventListener("load", panelOnLoad, true);
+
+        gMozLoopAPI = iframe.contentWindow.navigator.wrappedJSObject.mozLoop;
+
+        // We do this in an execute soon to allow any other event listeners to
+        // be handled, just in case.
+        deferred.resolve();
+      }, true);
+    }
+  }, true);
+
+  // Now we're setup, click the button.
+  btn.click();
+
+  // Remove the iframe after each test. This also avoids mochitest complaining
+  // about leaks on shutdown as we intentionally hold the iframe open for the
+  // life of the application.
+  registerCleanupFunction(function() {
+    loopPanel.hidePopup();
+    loopPanel.removeChild(document.getElementById(btn.getAttribute("notificationFrameId")));
+  });
+
+  return deferred.promise;
+}
+
+/**
+ * Loads the loop panel by clicking the button and waits for its open to complete.
+ * It also registers
+ *
+ * This assumes that the tests are running in a generatorTest.
+ */
+function loadLoopPanel() {
+  // Set prefs to ensure we don't access the network externally.
+  Services.prefs.setCharPref("services.push.serverURL", "ws://localhost/");
+  Services.prefs.setCharPref("loop.server", "http://localhost/");
+
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("services.push.serverURL");
+    Services.prefs.clearUserPref("loop.server");
+  });
+
+  // Turn off animations to make tests quicker.
+  let loopPanel = document.getElementById("loop-notification-panel");
+  loopPanel.setAttribute("animate", "false");
+
+  // Now get the actual API.
+  yield promiseGetMozLoopAPI();
+}
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -149,27 +149,16 @@ let gSyncPane = {
         document.getElementById("fxaEmailAddress1").textContent = data.email;
         document.getElementById("fxaEmailAddress2").textContent = data.email;
         document.getElementById("fxaEmailAddress3").textContent = data.email;
         document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName;
         let engines = document.getElementById("fxaSyncEngines")
         for (let checkbox of engines.querySelectorAll("checkbox")) {
           checkbox.disabled = enginesListDisabled;
         }
-
-        let checkbox = document.getElementById("fxa-pweng-chk");
-        let help = document.getElementById("fxa-pweng-help");
-        let allowPasswordsEngine = service.allowPasswordsEngine;
-
-        if (!allowPasswordsEngine) {
-          checkbox.checked = false;
-        }
-
-        checkbox.disabled = !allowPasswordsEngine || enginesListDisabled;
-        help.hidden = allowPasswordsEngine || enginesListDisabled;
       });
     // If fxAccountEnabled is false and we are in a "not configured" state,
     // then fxAccounts is probably fully disabled rather than just unconfigured,
     // so handle this case.  This block can be removed once we remove support
     // for fxAccounts being disabled.
     } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
                Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -278,30 +278,19 @@
       <hbox id="fxaSyncEngines">
         <vbox align="start">
           <checkbox label="&engine.tabs.label;"
                     accesskey="&engine.tabs.accesskey;"
                     preference="engine.tabs"/>
           <checkbox label="&engine.bookmarks.label;"
                     accesskey="&engine.bookmarks.accesskey;"
                     preference="engine.bookmarks"/>
-          <hbox>
-            <checkbox id="fxa-pweng-chk"
-                      label="&engine.passwords.label;"
-                      accesskey="&engine.passwords.accesskey;"
-                      preference="engine.passwords"/>
-
-            <vbox id="fxa-pweng-help">
-              <spacer flex="1"/>
-              <hbox id="fxa-pweng-help-link">
-                <image onclick="gSyncUtils.openMPInfoPage(event);" />
-              </hbox>
-              <spacer flex="1"/>
-            </vbox>
-          </hbox>
+          <checkbox label="&engine.passwords.label;"
+                    accesskey="&engine.passwords.accesskey;"
+                    preference="engine.passwords"/>
           <checkbox label="&engine.history.label;"
                     accesskey="&engine.history.accesskey;"
                     preference="engine.history"/>
           <checkbox label="&engine.addons.label;"
                     accesskey="&engine.addons.accesskey;"
                     preference="engine.addons"/>
           <checkbox label="&engine.prefs.label;"
                     accesskey="&engine.prefs.accesskey;"
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -149,27 +149,16 @@ let gSyncPane = {
         document.getElementById("fxaEmailAddress1").textContent = data.email;
         document.getElementById("fxaEmailAddress2").textContent = data.email;
         document.getElementById("fxaEmailAddress3").textContent = data.email;
         document.getElementById("fxaSyncComputerName").value = Weave.Service.clientsEngine.localName;
         let engines = document.getElementById("fxaSyncEngines")
         for (let checkbox of engines.querySelectorAll("checkbox")) {
           checkbox.disabled = enginesListDisabled;
         }
-
-        let checkbox = document.getElementById("fxa-pweng-chk");
-        let help = document.getElementById("fxa-pweng-help");
-        let allowPasswordsEngine = service.allowPasswordsEngine;
-
-        if (!allowPasswordsEngine) {
-          checkbox.checked = false;
-        }
-
-        checkbox.disabled = !allowPasswordsEngine || enginesListDisabled;
-        help.hidden = allowPasswordsEngine || enginesListDisabled;
       });
     // If fxAccountEnabled is false and we are in a "not configured" state,
     // then fxAccounts is probably fully disabled rather than just unconfigured,
     // so handle this case.  This block can be removed once we remove support
     // for fxAccounts being disabled.
     } else if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
                Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -260,30 +260,19 @@
             <hbox id="fxaSyncEngines">
               <vbox>
                 <checkbox label="&engine.tabs.label;"
                           accesskey="&engine.tabs.accesskey;"
                           preference="engine.tabs"/>
                 <checkbox label="&engine.bookmarks.label;"
                           accesskey="&engine.bookmarks.accesskey;"
                           preference="engine.bookmarks"/>
-                <hbox>
-                  <checkbox id="fxa-pweng-chk"
-                            label="&engine.passwords.label;"
-                            accesskey="&engine.passwords.accesskey;"
-                            preference="engine.passwords"/>
-
-                  <vbox id="fxa-pweng-help">
-                    <spacer flex="1"/>
-                    <hbox id="fxa-pweng-help-link">
-                      <image onclick="gSyncUtils.openMPInfoPage(event);" />
-                    </hbox>
-                    <spacer flex="1"/>
-                  </vbox>
-                </hbox>
+                <checkbox label="&engine.passwords.label;"
+                          accesskey="&engine.passwords.accesskey;"
+                          preference="engine.passwords"/>
                 <checkbox label="&engine.history.label;"
                           accesskey="&engine.history.accesskey;"
                           preference="engine.history"/>
                 <checkbox label="&engine.addons.label;"
                           accesskey="&engine.addons.accesskey;"
                           preference="engine.addons"/>
                 <checkbox label="&engine.prefs.label;"
                           accesskey="&engine.prefs.accesskey;"
--- a/browser/themes/linux/preferences/preferences.css
+++ b/browser/themes/linux/preferences/preferences.css
@@ -166,17 +166,9 @@ label.small {
   margin: 5px;
   line-height: 1.2em;
 }
 
 #noFxaAccount > label:first-child {
   margin-bottom: 0.6em;
 }
 
-#fxa-pweng-help-link > label {
-  margin: 0;
-}
-
-#fxa-pweng-help-link > image {
-  list-style-image: url("chrome://global/skin/icons/question-16.png");
-}
-
 %endif
--- a/browser/themes/osx/preferences/preferences.css
+++ b/browser/themes/osx/preferences/preferences.css
@@ -228,25 +228,9 @@ html|a.inline-link:-moz-focusring {
   margin: 12px 4px;
   line-height: 1.2em;
 }
 
 #noFxaAccount > label:first-child {
   margin-bottom: 0.6em;
 }
 
-#fxa-pweng-help-link > label {
-  margin: 0;
-}
-
-#fxa-pweng-help-link > image {
-  width: 16px;
-  height: 16px;
-  list-style-image: url("chrome://global/skin/icons/question-16.png");
-}
-
-@media (min-resolution: 2dppx) {
-  #fxa-pweng-help-link > image {
-    list-style-image: url("chrome://global/skin/icons/question-32.png");
-  }
-}
-
 %endif
--- a/browser/themes/windows/preferences/preferences.css
+++ b/browser/themes/windows/preferences/preferences.css
@@ -156,17 +156,9 @@ label.small {
   margin: 6px;
   line-height: 1.2em;
 }
 
 #noFxaAccount > label:first-child {
   margin-bottom: 0.6em;
 }
 
-#fxa-pweng-help-link > label {
-  margin: 0;
-}
-
-#fxa-pweng-help-link > image {
-  list-style-image: url("chrome://global/skin/icons/question-16.png");
-}
-
 %endif
--- a/configure.in
+++ b/configure.in
@@ -7233,16 +7233,27 @@ MOZ_ARG_DISABLE_BOOL(gcgenerational,
 [  --disable-gcgenerational Disable generational GC],
     JSGC_GENERATIONAL= ,
     JSGC_GENERATIONAL=1 )
 if test -n "$JSGC_GENERATIONAL"; then
     AC_DEFINE(JSGC_GENERATIONAL)
 fi
 
 dnl ========================================================
+dnl = Use a smaller chunk size for GC chunks
+dnl ========================================================
+MOZ_ARG_ENABLE_BOOL(small-chunk-size,
+[  --enable-small-chunk-size  Allocate memory for JS GC things in smaller chunks],
+    JS_GC_SMALL_CHUNK_SIZE=1,
+    JS_GC_SMALL_CHUNK_SIZE= )
+if test -n "$JS_GC_SMALL_CHUNK_SIZE"; then
+    AC_DEFINE(JS_GC_SMALL_CHUNK_SIZE)
+fi
+
+dnl ========================================================
 dnl Zealous JavaScript GC
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(gczeal,
 [  --enable-gczeal         Enable zealous JavaScript GCing],
     JS_GC_ZEAL=1,
     JS_GC_ZEAL= )
 if test -n "$JS_GC_ZEAL" -o -n "$MOZ_DEBUG"; then
     AC_DEFINE(JS_GC_ZEAL)
@@ -9256,16 +9267,19 @@ if test -z "$JS_SHARED_LIBRARY" ; then
     fi
 fi
 if test -z "$JSGC_USE_EXACT_ROOTING" ; then
     ac_configure_args="$ac_configure_args --disable-exact-rooting"
 fi
 if test -z "$JSGC_GENERATIONAL" ; then
     ac_configure_args="$ac_configure_args --disable-gcgenerational"
 fi
+if test -n "$JS_GC_SMALL_CHUNK_SIZE" ; then
+    ac_configure_args="$ac_configure_args --enable-small-chunk-size"
+fi
 if test -z "$MOZ_NATIVE_NSPR"; then
     ac_configure_args="$ac_configure_args --with-nspr-cflags='$NSPR_CFLAGS'"
     ac_configure_args="$ac_configure_args --with-nspr-libs='$NSPR_LIBS'"
 fi
 ac_configure_args="$ac_configure_args --prefix=$dist"
 if test "$MOZ_MEMORY"; then
    ac_configure_args="$ac_configure_args --enable-jemalloc"
 fi
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -392,16 +392,22 @@ public:
    *
    * @param aFlags what types you want to test for (see above)
    * @return whether the content matches ALL flags passed in
    */
   virtual bool IsNodeOfType(uint32_t aFlags) const = 0;
 
   virtual JSObject* WrapObject(JSContext *aCx) MOZ_OVERRIDE;
 
+  /**
+   * returns true if we are in priviliged code or
+   * layout.css.getBoxQuads.enabled == true.
+   */
+  static bool HasBoxQuadsSupport(JSContext* aCx, JSObject* /* unused */);
+
 protected:
   /**
    * WrapNode is called from WrapObject to actually wrap this node, WrapObject
    * does some additional checks and fix-up that's common to all nodes. WrapNode
    * should just call the DOM binding's Wrap function.
    */
   virtual JSObject* WrapNode(JSContext *aCx)
   {
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -86,16 +86,17 @@
 #include "nsString.h"
 #include "nsStyleConsts.h"
 #include "nsSVGFeatures.h"
 #include "nsSVGUtils.h"
 #include "nsTextNode.h"
 #include "nsUnicharUtils.h"
 #include "nsXBLBinding.h"
 #include "nsXBLPrototypeBinding.h"
+#include "mozilla/Preferences.h"
 #include "prprf.h"
 #include "xpcpublic.h"
 #include "nsCSSRuleProcessor.h"
 #include "nsCSSParser.h"
 #include "HTMLLegendElement.h"
 #include "nsWrapperCacheInlines.h"
 #include "WrapperFactory.h"
 #include "DocumentType.h"
@@ -2753,8 +2754,16 @@ nsINode::GetParentElementCrossingShadowR
     nsIContent* host = shadowRoot->GetHost();
     MOZ_ASSERT(host, "ShowRoots should always have a host");
     MOZ_ASSERT(host->IsElement(), "ShadowRoot hosts should always be Elements");
     return host->AsElement();
   }
 
   return nullptr;
 }
+
+bool
+nsINode::HasBoxQuadsSupport(JSContext* aCx, JSObject* /* unused */)
+{
+  return xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) ||
+         Preferences::GetBool("layout.css.getBoxQuads.enabled");
+}
+
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -1425,72 +1425,72 @@ HTMLMediaElement::Seek(double aTime,
 
   if (mReadyState == nsIDOMHTMLMediaElement::HAVE_NOTHING) {
     LOG(PR_LOG_DEBUG, ("%p SetCurrentTime(%f) failed: no source", this, aTime));
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   // Clamp the seek target to inside the seekable ranges.
-  dom::TimeRanges seekable;
-  if (NS_FAILED(mDecoder->GetSeekable(&seekable))) {
+  nsRefPtr<dom::TimeRanges> seekable = new dom::TimeRanges();
+  if (NS_FAILED(mDecoder->GetSeekable(seekable))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   uint32_t length = 0;
-  seekable.GetLength(&length);
+  seekable->GetLength(&length);
   if (!length) {
     return;
   }
 
   // If the position we want to seek to is not in a seekable range, we seek
   // to the closest position in the seekable ranges instead. If two positions
   // are equally close, we seek to the closest position from the currentTime.
   // See seeking spec, point 7 :
   // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
   int32_t range = 0;
   bool isInRange = false;
-  if (NS_FAILED(IsInRanges(seekable, aTime, isInRange, range))) {
+  if (NS_FAILED(IsInRanges(*seekable, aTime, isInRange, range))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (!isInRange) {
     if (range != -1) {
       // |range + 1| can't be negative, because the only possible negative value
       // for |range| is -1.
       if (uint32_t(range + 1) < length) {
         double leftBound, rightBound;
-        if (NS_FAILED(seekable.End(range, &leftBound))) {
+        if (NS_FAILED(seekable->End(range, &leftBound))) {
           aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
           return;
         }
-        if (NS_FAILED(seekable.Start(range + 1, &rightBound))) {
+        if (NS_FAILED(seekable->Start(range + 1, &rightBound))) {
           aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
           return;
         }
         double distanceLeft = Abs(leftBound - aTime);
         double distanceRight = Abs(rightBound - aTime);
         if (distanceLeft == distanceRight) {
           double currentTime = CurrentTime();
           distanceLeft = Abs(leftBound - currentTime);
           distanceRight = Abs(rightBound - currentTime);
         }
         aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
       } else {
         // Seek target is after the end last range in seekable data.
         // Clamp the seek target to the end of the last seekable range.
-        if (NS_FAILED(seekable.End(length - 1, &aTime))) {
+        if (NS_FAILED(seekable->End(length - 1, &aTime))) {
           aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
           return;
         }
       }
     } else {
       // aTime is before the first range in |seekable|, the closest point we can
       // seek to is the start of the first range.
-      seekable.Start(0, &aTime);
+      seekable->Start(0, &aTime);
     }
   }
 
   // TODO: The spec requires us to update the current time to reflect the
   //       actual seek target before beginning the synchronous section, but
   //       that requires changing all MediaDecoderReaders to support telling
   //       us the fastSeek target, and it's currently not possible to get
   //       this information as we don't yet control the demuxer for all
--- a/content/html/content/src/TimeRanges.h
+++ b/content/html/content/src/TimeRanges.h
@@ -16,34 +16,27 @@
 
 namespace mozilla {
 namespace dom {
 
 class TimeRanges;
 
 }
 
-template<>
-struct HasDangerousPublicDestructor<dom::TimeRanges>
-{
-  static const bool value = true;
-};
-
 namespace dom {
 
 // Implements media TimeRanges:
 // http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#timeranges
 class TimeRanges MOZ_FINAL : public nsIDOMTimeRanges
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMTIMERANGES
 
   TimeRanges();
-  ~TimeRanges();
 
   void Add(double aStart, double aEnd);
 
   // Returns the start time of the first range, or -1 if no ranges exist.
   double GetStartTime();
 
   // Returns the end time of the last range, or -1 if no ranges exist.
   double GetEndTime();
@@ -58,16 +51,17 @@ public:
     return mRanges.Length();
   }
 
   virtual double Start(uint32_t aIndex, ErrorResult& aRv);
 
   virtual double End(uint32_t aIndex, ErrorResult& aRv);
 
 private:
+  ~TimeRanges();
 
   // Comparator which orders TimeRanges by start time. Used by Normalize().
   struct TimeRange
   {
     TimeRange(double aStart, double aEnd)
       : mStart(aStart),
         mEnd(aEnd) {}
     double mStart;
--- a/content/media/MediaDecoderStateMachine.cpp
+++ b/content/media/MediaDecoderStateMachine.cpp
@@ -1424,25 +1424,25 @@ void MediaDecoderStateMachine::NotifyDat
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   mReader->NotifyDataArrived(aBuffer, aLength, aOffset);
 
   // While playing an unseekable stream of unknown duration, mEndTime is
   // updated (in AdvanceFrame()) as we play. But if data is being downloaded
   // faster than played, mEndTime won't reflect the end of playable data
   // since we haven't played the frame at the end of buffered data. So update
   // mEndTime here as new data is downloaded to prevent such a lag.
-  dom::TimeRanges buffered;
+  nsRefPtr<dom::TimeRanges> buffered = new dom::TimeRanges();
   if (mDecoder->IsInfinite() &&
-      NS_SUCCEEDED(mDecoder->GetBuffered(&buffered)))
+      NS_SUCCEEDED(mDecoder->GetBuffered(buffered)))
   {
     uint32_t length = 0;
-    buffered.GetLength(&length);
+    buffered->GetLength(&length);
     if (length) {
       double end = 0;
-      buffered.End(length - 1, &end);
+      buffered->End(length - 1, &end);
       ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
       mEndTime = std::max<int64_t>(mEndTime, end * USECS_PER_S);
     }
   }
 }
 
 void MediaDecoderStateMachine::Seek(const SeekTarget& aTarget)
 {
--- a/dom/base/DOMRequestHelper.jsm
+++ b/dom/base/DOMRequestHelper.jsm
@@ -181,18 +181,18 @@ DOMRequestIpcHelper.prototype = {
     }
 
     this._destroyed = true;
 
     Services.obs.removeObserver(this, "inner-window-destroyed");
 
     if (this._listeners) {
       Object.keys(this._listeners).forEach((aName) => {
-        this._listeners[aName] ? cpmm.removeWeakMessageListener(aName, this)
-                               : cpmm.removeMessageListener(aName, this);
+        this._listeners[aName].weakRef ? cpmm.removeWeakMessageListener(aName, this)
+                                       : cpmm.removeMessageListener(aName, this);
         delete this._listeners[aName];
       });
     }
 
     this._listeners = null;
     this._requests = null;
 
     // Objects inheriting from DOMRequestIPCHelper may have an uninit function.
--- a/dom/base/test/test_domrequesthelper.xul
+++ b/dom/base/test/test_domrequesthelper.xul
@@ -16,16 +16,18 @@
   <script type="application/javascript">
   <![CDATA[
     const Cc = Components.classes;
     const Cu = Components.utils;
     const Ci = Components.interfaces;
     Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
     let obs = Cc["@mozilla.org/observer-service;1"].
               getService(Ci.nsIObserverService);
+    let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"].
+              getService(Ci.nsIMessageBroadcaster);
 
     function DummyHelperSubclass() {
       this.onuninit = null;
     }
     DummyHelperSubclass.prototype = {
       __proto__: DOMRequestIpcHelper.prototype,
       uninit: function() {
         if (typeof this.onuninit === "function") {
@@ -138,17 +140,31 @@
 
       let frame = document.createElement("iframe");
       frame.onload = function() {
         obs.addObserver(observer, TOPIC, false);
         // Create dummy DOMRequestHelper specific to checkWindowDestruction()
         let cwdDummy = new DummyHelperSubclass();
         cwdDummy.onuninit = function() {
           uninitCalled = true;
+
+          if (!aOptions.messages || !aOptions.messages.length) {
+            return;
+          }
+
+          // If all message listeners are removed, cwdDummy.receiveMessage
+          // should never be called.
+          ppmm.broadcastAsyncMessage(aOptions.messages[0].name);
         };
+
+        // Test if we still receive messages from ppmm.
+        cwdDummy.receiveMessage = function(aMessage) {
+          ok(false, "cwdDummy.receiveMessage should NOT be called: " + aMessage.name);
+        };
+
         cwdDummy.initDOMRequestHelper(frame.contentWindow, aOptions.messages);
         // Make sure to clear our strong ref here so that we can test our
         // weak reference listeners and observer.
         cwdDummy = null;
         if (aOptions.gc) {
           Cu.schedulePreciseGC(function() {
             SpecialPowers.DOMWindowUtils.cycleCollect();
             SpecialPowers.DOMWindowUtils.garbageCollect();
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -49,18 +49,17 @@ namespace dom {
 JSErrorFormatString ErrorFormatString[] = {
 #define MSG_DEF(_name, _argc, _str) \
   { _str, _argc, JSEXN_TYPEERR },
 #include "mozilla/dom/Errors.msg"
 #undef MSG_DEF
 };
 
 const JSErrorFormatString*
-GetErrorMessage(void* aUserRef, const char* aLocale,
-                const unsigned aErrorNumber)
+GetErrorMessage(void* aUserRef, const unsigned aErrorNumber)
 {
   MOZ_ASSERT(aErrorNumber < ArrayLength(ErrorFormatString));
   return &ErrorFormatString[aErrorNumber];
 }
 
 bool
 ThrowErrorMessage(JSContext* aCx, const ErrNum aErrorNumber, ...)
 {
@@ -134,18 +133,17 @@ ErrorResult::ThrowTypeError(const dom::E
     return;
   }
   if (IsTypeError()) {
     delete mMessage;
   }
   mResult = NS_ERROR_TYPE_ERR;
   Message* message = new Message();
   message->mErrorNumber = errorNumber;
-  uint16_t argCount =
-    dom::GetErrorMessage(nullptr, nullptr, errorNumber)->argCount;
+  uint16_t argCount = dom::GetErrorMessage(nullptr, errorNumber)->argCount;
   MOZ_ASSERT(argCount <= 10);
   argCount = std::min<uint16_t>(argCount, 10);
   while (argCount--) {
     message->mArgs.AppendElement(*va_arg(ap, nsString*));
   }
   mMessage = message;
   va_end(ap);
 }
--- a/dom/bluetooth/BluetoothProfileManagerBase.h
+++ b/dom/bluetooth/BluetoothProfileManagerBase.h
@@ -23,16 +23,28 @@
 #define ERR_OPERATION_TIMEOUT           "OperationTimeout"
 
 #include "BluetoothCommon.h"
 #include "nsIObserver.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 class BluetoothProfileController;
 
+class BluetoothProfileResultHandler
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothProfileResultHandler);
+
+  virtual ~BluetoothProfileResultHandler() { }
+
+  virtual void OnError(nsresult aResult) { }
+  virtual void Init() { }
+  virtual void Deinit() { }
+};
+
 class BluetoothProfileManagerBase : public nsIObserver
 {
 public:
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) = 0;
   virtual void OnUpdateSdpRecords(const nsAString& aDeviceAddress) = 0;
 
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
@@ -495,17 +495,17 @@ static btrc_callbacks_t sBtAvrcpCallback
 /*
  * This function will be only called when Bluetooth is turning on.
  * It is important to register a2dp callbacks before enable() gets called.
  * It is required to register a2dp callbacks before a2dp media task
  * starts up.
  */
 // static
 void
-BluetoothA2dpManager::InitA2dpInterface()
+BluetoothA2dpManager::InitA2dpInterface(BluetoothProfileResultHandler* aRes)
 {
   BluetoothInterface* btInf = BluetoothInterface::GetInstance();
   NS_ENSURE_TRUE_VOID(btInf);
 
   sBtA2dpInterface = btInf->GetBluetoothA2dpInterface();
   NS_ENSURE_TRUE_VOID(sBtA2dpInterface);
 
   int ret = sBtA2dpInterface->Init(&sBtA2dpCallbacks);
@@ -517,16 +517,20 @@ BluetoothA2dpManager::InitA2dpInterface(
   sBtAvrcpInterface = btInf->GetBluetoothAvrcpInterface();
   NS_ENSURE_TRUE_VOID(sBtAvrcpInterface);
 
   ret = sBtAvrcpInterface->Init(&sBtAvrcpCallbacks);
   if (ret != BT_STATUS_SUCCESS) {
     BT_LOGR("Warning: failed to init avrcp module");
   }
 #endif
+
+  if (aRes) {
+    aRes->Init();
+  }
 }
 
 BluetoothA2dpManager::~BluetoothA2dpManager()
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   NS_ENSURE_TRUE_VOID(obs);
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
     BT_WARNING("Failed to remove shutdown observer!");
@@ -592,30 +596,33 @@ BluetoothA2dpManager::Get()
   // Create a new instance, register, and return
   BluetoothA2dpManager* manager = new BluetoothA2dpManager();
   sBluetoothA2dpManager = manager;
   return sBluetoothA2dpManager;
 }
 
 // static
 void
-BluetoothA2dpManager::DeinitA2dpInterface()
+BluetoothA2dpManager::DeinitA2dpInterface(BluetoothProfileResultHandler* aRes)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (sBtA2dpInterface) {
     sBtA2dpInterface->Cleanup();
     sBtA2dpInterface = nullptr;
   }
 #if ANDROID_VERSION > 17
   if (sBtAvrcpInterface) {
     sBtAvrcpInterface->Cleanup();
     sBtAvrcpInterface = nullptr;
   }
 #endif
+  if (aRes) {
+    aRes->Deinit();
+  }
 }
 
 void
 BluetoothA2dpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   Disconnect(nullptr);
--- a/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
@@ -25,18 +25,18 @@ public:
     SINK_UNKNOWN,
     SINK_DISCONNECTED,
     SINK_CONNECTING,
     SINK_CONNECTED,
     SINK_PLAYING,
   };
 
   static BluetoothA2dpManager* Get();
-  static void InitA2dpInterface();
-  static void DeinitA2dpInterface();
+  static void InitA2dpInterface(BluetoothProfileResultHandler* aRes);
+  static void DeinitA2dpInterface(BluetoothProfileResultHandler* aRes);
   virtual ~BluetoothA2dpManager();
 
   // A2DP-specific functions
   void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
 
   // AVRCP-specific functions
   void SetAvrcpConnected(bool aConnected);
   bool IsAvrcpConnected();
--- a/dom/bluetooth/bluedroid/BluetoothInterface.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothInterface.cpp
@@ -571,152 +571,270 @@ struct interface_traits<BluetoothHandsfr
   typedef const bthf_interface_t const_interface_type;
 
   static const char* profile_id()
   {
     return BT_PROFILE_HANDSFREE_ID;
   }
 };
 
+typedef
+  BluetoothInterfaceRunnable0<BluetoothHandsfreeResultHandler, void>
+  BluetoothHandsfreeResultRunnable;
+
+typedef
+  BluetoothInterfaceRunnable1<BluetoothHandsfreeResultHandler, void, bt_status_t>
+  BluetoothHandsfreeErrorRunnable;
+
+static nsresult
+DispatchBluetoothHandsfreeResult(
+  BluetoothHandsfreeResultHandler* aRes,
+  void (BluetoothHandsfreeResultHandler::*aMethod)(),
+  bt_status_t aStatus)
+{
+  MOZ_ASSERT(aRes);
+
+  nsRunnable* runnable;
+
+  if (aStatus == BT_STATUS_SUCCESS) {
+    runnable = new BluetoothHandsfreeResultRunnable(aRes, aMethod);
+  } else {
+    runnable = new BluetoothHandsfreeErrorRunnable(aRes,
+      &BluetoothHandsfreeResultHandler::OnError, aStatus);
+  }
+  nsresult rv = NS_DispatchToMainThread(runnable);
+  if (NS_FAILED(rv)) {
+    BT_WARNING("NS_DispatchToMainThread failed: %X", rv);
+  }
+  return rv;
+}
+
 BluetoothHandsfreeInterface::BluetoothHandsfreeInterface(
   const bthf_interface_t* aInterface)
 : mInterface(aInterface)
 {
   MOZ_ASSERT(mInterface);
 }
 
 BluetoothHandsfreeInterface::~BluetoothHandsfreeInterface()
 { }
 
-bt_status_t
-BluetoothHandsfreeInterface::Init(bthf_callbacks_t* aCallbacks)
+void
+BluetoothHandsfreeInterface::Init(bthf_callbacks_t* aCallbacks,
+                                  BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->init(aCallbacks);
+  bt_status_t status = mInterface->init(aCallbacks);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(aRes,
+                                     &BluetoothHandsfreeResultHandler::Init,
+                                     status);
+  }
 }
 
 void
-BluetoothHandsfreeInterface::Cleanup()
+BluetoothHandsfreeInterface::Cleanup(BluetoothHandsfreeResultHandler* aRes)
 {
   mInterface->cleanup();
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(aRes,
+                                     &BluetoothHandsfreeResultHandler::Cleanup,
+                                     BT_STATUS_SUCCESS);
+  }
 }
 
 /* Connect / Disconnect */
 
-bt_status_t
-BluetoothHandsfreeInterface::Connect(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::Connect(bt_bdaddr_t* aBdAddr,
+                                     BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->connect(aBdAddr);
+  bt_status_t status = mInterface->connect(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::Connect, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::Disconnect(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::Disconnect(bt_bdaddr_t* aBdAddr,
+                                        BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->disconnect(aBdAddr);
+  bt_status_t status = mInterface->disconnect(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::Disconnect, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::ConnectAudio(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::ConnectAudio(
+  bt_bdaddr_t* aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->connect_audio(aBdAddr);
+  bt_status_t status = mInterface->connect_audio(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::ConnectAudio, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::DisconnectAudio(bt_bdaddr_t* aBdAddr)
+void
+BluetoothHandsfreeInterface::DisconnectAudio(
+  bt_bdaddr_t* aBdAddr, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->disconnect_audio(aBdAddr);
+  bt_status_t status = mInterface->disconnect_audio(aBdAddr);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::DisconnectAudio, status);
+  }
 }
 
 /* Voice Recognition */
 
-bt_status_t
-BluetoothHandsfreeInterface::StartVoiceRecognition()
+void
+BluetoothHandsfreeInterface::StartVoiceRecognition(
+  BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->start_voice_recognition();
+  bt_status_t status = mInterface->start_voice_recognition();
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::StartVoiceRecognition, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::StopVoiceRecognition()
+void
+BluetoothHandsfreeInterface::StopVoiceRecognition(
+  BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->stop_voice_recognition();
+  bt_status_t status = mInterface->stop_voice_recognition();
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::StopVoiceRecognition, status);
+  }
 }
 
 /* Volume */
 
-bt_status_t
-BluetoothHandsfreeInterface::VolumeControl(bthf_volume_type_t aType,
-                                           int aVolume)
+void
+BluetoothHandsfreeInterface::VolumeControl(
+  bthf_volume_type_t aType, int aVolume, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->volume_control(aType, aVolume);
+  bt_status_t status = mInterface->volume_control(aType, aVolume);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::VolumeControl, status);
+  }
 }
 
 /* Device status */
 
-bt_status_t
+void
 BluetoothHandsfreeInterface::DeviceStatusNotification(
   bthf_network_state_t aNtkState, bthf_service_type_t aSvcType, int aSignal,
-  int aBattChg)
+  int aBattChg, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->device_status_notification(aNtkState, aSvcType, aSignal,
-                                                aBattChg);
+  bt_status_t status = mInterface->device_status_notification(
+    aNtkState, aSvcType, aSignal, aBattChg);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::DeviceStatusNotification,
+      status);
+  }
 }
 
 /* Responses */
 
-bt_status_t
-BluetoothHandsfreeInterface::CopsResponse(const char* aCops)
+void
+BluetoothHandsfreeInterface::CopsResponse(
+  const char* aCops, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->cops_response(aCops);
+  bt_status_t status = mInterface->cops_response(aCops);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::CopsResponse, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::CindResponse(int aSvc, int aNumActive,
-                                          int aNumHeld,
-                                          bthf_call_state_t aCallSetupState,
-                                          int aSignal, int aRoam, int aBattChg)
+void
+BluetoothHandsfreeInterface::CindResponse(
+  int aSvc, int aNumActive, int aNumHeld, bthf_call_state_t aCallSetupState,
+  int aSignal, int aRoam, int aBattChg, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->cind_response(aSvc, aNumActive, aNumHeld,
-                                   aCallSetupState, aSignal, aRoam,
-                                   aBattChg);
+  bt_status_t status = mInterface->cind_response(aSvc, aNumActive, aNumHeld,
+                                                 aCallSetupState, aSignal,
+                                                 aRoam, aBattChg);
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::CindResponse, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::FormattedAtResponse(const char* aRsp)
+void
+BluetoothHandsfreeInterface::FormattedAtResponse(
+  const char* aRsp, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->formatted_at_response(aRsp);
+  bt_status_t status = mInterface->formatted_at_response(aRsp);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::FormattedAtResponse, status);
+  }
 }
 
-bt_status_t
+void
 BluetoothHandsfreeInterface::AtResponse(bthf_at_response_t aResponseCode,
-                                        int aErrorCode)
+                                        int aErrorCode,
+                                        BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->at_response(aResponseCode, aErrorCode);
+  bt_status_t status = mInterface->at_response(aResponseCode, aErrorCode);
+
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::AtResponse, status);
+  }
 }
 
-bt_status_t
-BluetoothHandsfreeInterface::ClccResponse(int aIndex,
-                                          bthf_call_direction_t aDir,
-                                          bthf_call_state_t aState,
-                                          bthf_call_mode_t aMode,
-                                          bthf_call_mpty_type_t aMpty,
-                                          const char* aNumber,
-                                          bthf_call_addrtype_t aType)
+void
+BluetoothHandsfreeInterface::ClccResponse(
+  int aIndex, bthf_call_direction_t aDir, bthf_call_state_t aState,
+  bthf_call_mode_t aMode, bthf_call_mpty_type_t aMpty, const char* aNumber,
+  bthf_call_addrtype_t aType, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->clcc_response(aIndex, aDir, aState, aMode, aMpty,
-                                   aNumber, aType);
+  bt_status_t status = mInterface->clcc_response(aIndex, aDir, aState, aMode,
+                                                 aMpty, aNumber, aType);
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::ClccResponse, status);
+  }
 }
 
 /* Phone State */
 
-bt_status_t
+void
 BluetoothHandsfreeInterface::PhoneStateChange(int aNumActive, int aNumHeld,
   bthf_call_state_t aCallSetupState, const char* aNumber,
-  bthf_call_addrtype_t aType)
+  bthf_call_addrtype_t aType, BluetoothHandsfreeResultHandler* aRes)
 {
-  return mInterface->phone_state_change(aNumActive, aNumHeld, aCallSetupState,
-                                        aNumber, aType);
+  bt_status_t status = mInterface->phone_state_change(aNumActive, aNumHeld,
+                                                      aCallSetupState,
+                                                      aNumber, aType);
+  if (aRes) {
+    DispatchBluetoothHandsfreeResult(
+      aRes, &BluetoothHandsfreeResultHandler::PhoneStateChange, status);
+  }
 }
 
 //
 // Bluetooth Advanced Audio Interface
 //
 
 template<>
 struct interface_traits<BluetoothA2dpInterface>
--- a/dom/bluetooth/bluedroid/BluetoothInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothInterface.h
@@ -67,65 +67,112 @@ protected:
 private:
   const btsock_interface_t* mInterface;
 };
 
 //
 // Handsfree Interface
 //
 
+class BluetoothHandsfreeResultHandler
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BluetoothHandsfreeResultHandler)
+
+  virtual ~BluetoothHandsfreeResultHandler() { }
+
+  virtual void OnError(bt_status_t aStatus)
+  {
+    BT_WARNING("Received error code %d", (int)aStatus);
+  }
+
+  virtual void Init() { }
+  virtual void Cleanup() { }
+
+  virtual void Connect() { }
+  virtual void Disconnect() { }
+  virtual void ConnectAudio() { }
+  virtual void DisconnectAudio() { }
+
+  virtual void StartVoiceRecognition() { }
+  virtual void StopVoiceRecognition() { }
+
+  virtual void VolumeControl() { }
+
+  virtual void DeviceStatusNotification() { }
+
+  virtual void CopsResponse() { }
+  virtual void CindResponse() { }
+  virtual void FormattedAtResponse() { }
+  virtual void AtResponse() { }
+  virtual void ClccResponse() { }
+  virtual void PhoneStateChange() { }
+};
+
 class BluetoothHandsfreeInterface
 {
 public:
   friend class BluetoothInterface;
 
-  bt_status_t Init(bthf_callbacks_t* aCallbacks);
-  void        Cleanup();
+  void Init(bthf_callbacks_t* aCallbacks,
+            BluetoothHandsfreeResultHandler* aRes);
+  void Cleanup(BluetoothHandsfreeResultHandler* aRes);
 
   /* Connect / Disconnect */
 
-  bt_status_t Connect(bt_bdaddr_t* aBdAddr);
-  bt_status_t Disconnect(bt_bdaddr_t* aBdAddr);
-  bt_status_t ConnectAudio(bt_bdaddr_t* aBdAddr);
-  bt_status_t DisconnectAudio(bt_bdaddr_t* aBdAddr);
+  void Connect(bt_bdaddr_t* aBdAddr,
+               BluetoothHandsfreeResultHandler* aRes);
+  void Disconnect(bt_bdaddr_t* aBdAddr,
+                  BluetoothHandsfreeResultHandler* aRes);
+  void ConnectAudio(bt_bdaddr_t* aBdAddr,
+                    BluetoothHandsfreeResultHandler* aRes);
+  void DisconnectAudio(bt_bdaddr_t* aBdAddr,
+                       BluetoothHandsfreeResultHandler* aRes);
 
   /* Voice Recognition */
 
-  bt_status_t StartVoiceRecognition();
-  bt_status_t StopVoiceRecognition();
+  void StartVoiceRecognition(BluetoothHandsfreeResultHandler* aRes);
+  void StopVoiceRecognition(BluetoothHandsfreeResultHandler* aRes);
 
   /* Volume */
 
-  bt_status_t VolumeControl(bthf_volume_type_t aType, int aVolume);
+  void VolumeControl(bthf_volume_type_t aType, int aVolume,
+                     BluetoothHandsfreeResultHandler* aRes);
 
   /* Device status */
 
-  bt_status_t DeviceStatusNotification(bthf_network_state_t aNtkState,
-                                       bthf_service_type_t aSvcType,
-                                       int aSignal, int aBattChg);
+  void DeviceStatusNotification(bthf_network_state_t aNtkState,
+                                bthf_service_type_t aSvcType,
+                                int aSignal, int aBattChg,
+                                BluetoothHandsfreeResultHandler* aRes);
 
   /* Responses */
 
-  bt_status_t CopsResponse(const char* aCops);
-  bt_status_t CindResponse(int aSvc, int aNumActive, int aNumHeld,
-                           bthf_call_state_t aCallSetupState, int aSignal,
-                           int aRoam, int aBattChg);
-  bt_status_t FormattedAtResponse(const char* aRsp);
-  bt_status_t AtResponse(bthf_at_response_t aResponseCode, int aErrorCode);
-  bt_status_t ClccResponse(int aIndex, bthf_call_direction_t aDir,
-                           bthf_call_state_t aState, bthf_call_mode_t aMode,
-                           bthf_call_mpty_type_t aMpty, const char* aNumber,
-                           bthf_call_addrtype_t aType);
+  void CopsResponse(const char* aCops,
+                    BluetoothHandsfreeResultHandler* aRes);
+  void CindResponse(int aSvc, int aNumActive, int aNumHeld,
+                    bthf_call_state_t aCallSetupState, int aSignal,
+                    int aRoam, int aBattChg,
+                    BluetoothHandsfreeResultHandler* aRes);
+  void FormattedAtResponse(const char* aRsp,
+                           BluetoothHandsfreeResultHandler* aRes);
+  void AtResponse(bthf_at_response_t aResponseCode, int aErrorCode,
+                  BluetoothHandsfreeResultHandler* aRes);
+  void ClccResponse(int aIndex, bthf_call_direction_t aDir,
+                    bthf_call_state_t aState, bthf_call_mode_t aMode,
+                    bthf_call_mpty_type_t aMpty, const char* aNumber,
+                    bthf_call_addrtype_t aType,
+                    BluetoothHandsfreeResultHandler* aRes);
 
   /* Phone State */
 
-  bt_status_t PhoneStateChange(int aNumActive, int aNumHeld,
-                               bthf_call_state_t aCallSetupState,
-                               const char* aNumber,
-                               bthf_call_addrtype_t aType);
+  void PhoneStateChange(int aNumActive, int aNumHeld,
+                        bthf_call_state_t aCallSetupState,
+                        const char* aNumber, bthf_call_addrtype_t aType,
+                        BluetoothHandsfreeResultHandler* aRes);
 
 protected:
   BluetoothHandsfreeInterface(const bthf_interface_t* aInterface);
   ~BluetoothHandsfreeInterface();
 
 private:
   const bthf_interface_t* mInterface;
 };
--- a/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/BluetoothServiceBluedroid.cpp
@@ -150,28 +150,73 @@ public:
     if (!opp || !opp->Listen()) {
       BT_LOGR("Fail to start BluetoothOppManager listening");
     }
 
     return NS_OK;
   }
 };
 
+/* |ProfileDeinitResultHandler| collect the results of all profile
+ * result handlers and calls |Proceed| after all results handlers
+ * have been run.
+ */
+class ProfileDeinitResultHandler MOZ_FINAL
+: public BluetoothProfileResultHandler
+{
+public:
+  ProfileDeinitResultHandler(unsigned char aNumProfiles)
+  : mNumProfiles(aNumProfiles)
+  {
+    MOZ_ASSERT(mNumProfiles);
+  }
+
+  void Deinit() MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+  void OnError(nsresult aResult) MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+private:
+  void Proceed() const
+  {
+    sBtInterface->Cleanup(nullptr);
+  }
+
+  unsigned char mNumProfiles;
+};
+
 class CleanupTask MOZ_FINAL : public nsRunnable
 {
 public:
   NS_IMETHOD
   Run()
   {
+    static void (* const sDeinitManager[])(BluetoothProfileResultHandler*) = {
+      BluetoothHfpManager::DeinitHfpInterface,
+      BluetoothA2dpManager::DeinitA2dpInterface
+    };
+
     MOZ_ASSERT(NS_IsMainThread());
 
     // Cleanup bluetooth interfaces after BT state becomes BT_STATE_OFF.
-    BluetoothHfpManager::DeinitHfpInterface();
-    BluetoothA2dpManager::DeinitA2dpInterface();
-    sBtInterface->Cleanup(nullptr);
+    nsRefPtr<ProfileDeinitResultHandler> res =
+      new ProfileDeinitResultHandler(MOZ_ARRAY_LENGTH(sDeinitManager));
+
+    for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sDeinitManager); ++i) {
+      sDeinitManager[i](res);
+    }
 
     return NS_OK;
   }
 };
 
 /**
  *  Static callback functions
  */
@@ -813,29 +858,74 @@ public:
 
     nsRefPtr<nsRunnable> runnable = new BluetoothService::ToggleBtAck(false);
     if (NS_FAILED(NS_DispatchToMainThread(runnable))) {
       BT_WARNING("Failed to dispatch to main thread!");
     }
   }
 };
 
+/* |ProfileInitResultHandler| collect the results of all profile
+ * result handlers and calls |Proceed| after all results handlers
+ * have been run.
+ */
+class ProfileInitResultHandler MOZ_FINAL
+: public BluetoothProfileResultHandler
+{
+public:
+  ProfileInitResultHandler(unsigned char aNumProfiles)
+  : mNumProfiles(aNumProfiles)
+  {
+    MOZ_ASSERT(mNumProfiles);
+  }
+
+  void Init() MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+  void OnError(nsresult aResult) MOZ_OVERRIDE
+  {
+    if (!(--mNumProfiles)) {
+      Proceed();
+    }
+  }
+
+private:
+  void Proceed() const
+  {
+    sBtInterface->Enable(new EnableResultHandler());
+  }
+
+  unsigned char mNumProfiles;
+};
+
 class InitResultHandler MOZ_FINAL : public BluetoothResultHandler
 {
 public:
   void Init() MOZ_OVERRIDE
   {
+    static void (* const sInitManager[])(BluetoothProfileResultHandler*) = {
+      BluetoothHfpManager::InitHfpInterface,
+      BluetoothA2dpManager::InitA2dpInterface
+    };
+
     MOZ_ASSERT(NS_IsMainThread());
 
     // Register all the bluedroid callbacks before enable() get called
     // It is required to register a2dp callbacks before a2dp media task starts up.
     // If any interface cannot be initialized, turn on bluetooth core anyway.
-    BluetoothHfpManager::InitHfpInterface();
-    BluetoothA2dpManager::InitA2dpInterface();
-    sBtInterface->Enable(new EnableResultHandler());
+    nsRefPtr<ProfileInitResultHandler> res =
+      new ProfileInitResultHandler(MOZ_ARRAY_LENGTH(sInitManager));
+
+    for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sInitManager); ++i) {
+      sInitManager[i](res);
+    }
   }
 
   void OnError(int aStatus) MOZ_OVERRIDE
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     BT_LOGR("BluetoothInterface::Init failed: %d", aStatus);
 
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.cpp
@@ -429,35 +429,107 @@ BluetoothHfpManager::Init()
 
   nsRefPtr<GetVolumeTask> callback = new GetVolumeTask();
   rv = settingsLock->Get(AUDIO_VOLUME_BT_SCO_ID, callback);
   NS_ENSURE_SUCCESS(rv, false);
 
   return true;
 }
 
+class CleanupInitResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  CleanupInitResultHandler(BluetoothHandsfreeInterface* aInterface,
+                           BluetoothProfileResultHandler* aRes)
+  : mInterface(aInterface)
+  , mRes(aRes)
+  {
+    MOZ_ASSERT(mInterface);
+  }
+
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Init failed: %d", (int)aStatus);
+    if (mRes) {
+      mRes->OnError(NS_ERROR_FAILURE);
+    }
+  }
+
+  void Init() MOZ_OVERRIDE
+  {
+    sBluetoothHfpInterface = mInterface;
+    if (mRes) {
+      mRes->Init();
+    }
+  }
+
+  void Cleanup() MOZ_OVERRIDE
+  {
+    sBluetoothHfpInterface = nullptr;
+    /* During re-initialization, a previouly initialized
+     * |BluetoothHandsfreeInterface| has now been cleaned
+     * up, so we start initialization.
+     */
+    RunInit();
+  }
+
+  void RunInit()
+  {
+    mInterface->Init(&sBluetoothHfpCallbacks, this);
+  }
+
+private:
+  BluetoothHandsfreeInterface* mInterface;
+  nsRefPtr<BluetoothProfileResultHandler> mRes;
+};
+
+class InitResultHandlerRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  InitResultHandlerRunnable(CleanupInitResultHandler* aRes)
+  : mRes(aRes)
+  {
+    MOZ_ASSERT(mRes);
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    mRes->RunInit();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<CleanupInitResultHandler> mRes;
+};
+
 // static
 void
-BluetoothHfpManager::InitHfpInterface()
+BluetoothHfpManager::InitHfpInterface(BluetoothProfileResultHandler* aRes)
 {
   BluetoothInterface* btInf = BluetoothInterface::GetInstance();
   NS_ENSURE_TRUE_VOID(btInf);
 
-  if (sBluetoothHfpInterface) {
-    sBluetoothHfpInterface->Cleanup();
-    sBluetoothHfpInterface = nullptr;
-  }
-
   BluetoothHandsfreeInterface *interface =
     btInf->GetBluetoothHandsfreeInterface();
   NS_ENSURE_TRUE_VOID(interface);
 
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    interface->Init(&sBluetoothHfpCallbacks));
-  sBluetoothHfpInterface = interface;
+  nsRefPtr<CleanupInitResultHandler> res =
+    new CleanupInitResultHandler(interface, aRes);
+
+  if (sBluetoothHfpInterface) {
+    // Cleanup an initialized HFP before initializing again.
+    sBluetoothHfpInterface->Cleanup(res);
+  } else {
+    // If there's no HFP interface to cleanup first, we dispatch
+    // a runnable that calls the profile result handler.
+    nsRefPtr<nsRunnable> r = new InitResultHandlerRunnable(res);
+    if (NS_FAILED(NS_DispatchToMainThread(r))) {
+      BT_LOGR("Failed to dispatch HFP init runnable");
+    }
+  }
 }
 
 BluetoothHfpManager::~BluetoothHfpManager()
 {
   if (!mListener->Listen(false)) {
     BT_WARNING("Failed to stop listening RIL");
   }
   mListener = nullptr;
@@ -468,23 +540,75 @@ BluetoothHfpManager::~BluetoothHfpManage
   if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) ||
       NS_FAILED(obs->RemoveObserver(this, MOZSETTINGS_CHANGED_ID))) {
     BT_WARNING("Failed to remove observers!");
   }
 
   hal::UnregisterBatteryObserver(this);
 }
 
+class CleanupResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  CleanupResultHandler(BluetoothProfileResultHandler* aRes)
+  : mRes(aRes)
+  { }
+
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Cleanup failed: %d", (int)aStatus);
+    if (mRes) {
+      mRes->OnError(NS_ERROR_FAILURE);
+    }
+  }
+
+  void Cleanup() MOZ_OVERRIDE
+  {
+    sBluetoothHfpInterface = nullptr;
+    if (mRes) {
+      mRes->Deinit();
+    }
+  }
+
+private:
+  nsRefPtr<BluetoothProfileResultHandler> mRes;
+};
+
+class DeinitResultHandlerRunnable MOZ_FINAL : public nsRunnable
+{
+public:
+  DeinitResultHandlerRunnable(BluetoothProfileResultHandler* aRes)
+  : mRes(aRes)
+  {
+    MOZ_ASSERT(mRes);
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    mRes->Deinit();
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<BluetoothProfileResultHandler> mRes;
+};
+
 // static
 void
-BluetoothHfpManager::DeinitHfpInterface()
+BluetoothHfpManager::DeinitHfpInterface(BluetoothProfileResultHandler* aRes)
 {
   if (sBluetoothHfpInterface) {
-    sBluetoothHfpInterface->Cleanup();
-    sBluetoothHfpInterface = nullptr;
+    sBluetoothHfpInterface->Cleanup(new CleanupResultHandler(aRes));
+  } else if (aRes) {
+    // We dispatch a runnable here to make the profile resource handler
+    // behave as if HFP was initialized.
+    nsRefPtr<nsRunnable> r = new DeinitResultHandlerRunnable(aRes);
+    if (NS_FAILED(NS_DispatchToMainThread(r))) {
+      BT_LOGR("Failed to dispatch cleanup-result-handler runnable");
+    }
   }
 }
 
 //static
 BluetoothHfpManager*
 BluetoothHfpManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -668,42 +792,60 @@ BluetoothHfpManager::ProcessAtCnum()
     message.AppendLiteral(",,4");
 
     SendLine(message.get());
   }
 
   SendResponse(BTHF_AT_RESPONSE_OK);
 }
 
+class CindResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::CindResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::ProcessAtCind()
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
 
   int numActive = GetNumberOfCalls(nsITelephonyService::CALL_STATE_CONNECTED);
   int numHeld = GetNumberOfCalls(nsITelephonyService::CALL_STATE_HELD);
+  bthf_call_state_t callState = ConvertToBthfCallState(GetCallSetupState());
 
-  bt_status_t status = sBluetoothHfpInterface->CindResponse(
-                          mService,
-                          numActive,
-                          numHeld,
-                          ConvertToBthfCallState(GetCallSetupState()),
-                          mSignal,
-                          mRoam,
-                          mBattChg);
-  NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
+  sBluetoothHfpInterface->CindResponse(mService, numActive, numHeld,
+                                       callState, mSignal, mRoam, mBattChg,
+                                       new CindResponseResultHandler());
 }
 
+class CopsResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::CopsResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::ProcessAtCops()
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->CopsResponse(
-      NS_ConvertUTF16toUTF8(mOperatorName).get()));
+
+  sBluetoothHfpInterface->CopsResponse(
+    NS_ConvertUTF16toUTF8(mOperatorName).get(),
+    new CopsResponseResultHandler());
 }
 
 void
 BluetoothHfpManager::ProcessAtClcc()
 {
   uint32_t callNumbers = mCurrentCallArray.Length();
   uint32_t i;
   for (i = 1; i < callNumbers; i++) {
@@ -715,24 +857,36 @@ BluetoothHfpManager::ProcessAtClcc()
     MOZ_ASSERT(i == 2);
 
     SendCLCC(mCdmaSecondCall, 2);
   }
 
   SendResponse(BTHF_AT_RESPONSE_OK);
 }
 
+class AtResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::AtResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::ProcessUnknownAt(char *aAtString)
 {
   BT_LOGR("[%s]", aAtString);
 
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->AtResponse(BTHF_AT_RESPONSE_ERROR, 0));
+
+  sBluetoothHfpInterface->AtResponse(BTHF_AT_RESPONSE_ERROR, 0,
+                                     new AtResponseResultHandler());
 }
 
 void
 BluetoothHfpManager::ProcessKeyPressed()
 {
   bool hasActiveCall =
     (FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED) > 0);
 
@@ -833,16 +987,27 @@ BluetoothHfpManager::NotifyDialer(const 
   NS_NAMED_LITERAL_STRING(type, "bluetooth-dialer-command");
   InfallibleTArray<BluetoothNamedValue> parameters;
 
   BT_APPEND_NAMED_VALUE(parameters, "command", nsString(aCommand));
 
   BT_ENSURE_TRUE_VOID_BROADCAST_SYSMSG(type, parameters);
 }
 
+class VolumeControlResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::VolumeControl failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::HandleVolumeChanged(const nsAString& aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // The string that we're interested in will be a JSON string that looks like:
   //  {"key":"volumeup", "value":10}
   //  {"key":"volumedown", "value":2}
@@ -877,19 +1042,18 @@ BluetoothHfpManager::HandleVolumeChanged
   if (mReceiveVgsFlag) {
     mReceiveVgsFlag = false;
     return;
   }
 
   // Only send volume back when there's a connected headset
   if (IsConnected()) {
     NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-    NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-      sBluetoothHfpInterface->VolumeControl(BTHF_VOLUME_TYPE_SPK,
-                                            mCurrentVgs));
+    sBluetoothHfpInterface->VolumeControl(BTHF_VOLUME_TYPE_SPK, mCurrentVgs,
+                                          new VolumeControlResultHandler());
   }
 }
 
 void
 BluetoothHfpManager::HandleVoiceConnectionChanged(uint32_t aClientId)
 {
   nsCOMPtr<nsIMobileConnectionProvider> connection =
     do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
@@ -970,16 +1134,27 @@ BluetoothHfpManager::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   sInShutdown = true;
   Disconnect(nullptr);
   DisconnectSco();
   sBluetoothHfpManager = nullptr;
 }
 
+class ClccResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::ClccResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::SendCLCC(Call& aCall, int aIndex)
 {
   NS_ENSURE_TRUE_VOID(aCall.mState !=
                         nsITelephonyService::CALL_STATE_DISCONNECTED);
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
 
   bthf_call_state_t callState = ConvertToBthfCallState(aCall.mState);
@@ -989,43 +1164,62 @@ BluetoothHfpManager::SendCLCC(Call& aCal
                                                BTHF_CALL_STATE_ACTIVE;
   }
 
   if (callState == BTHF_CALL_STATE_INCOMING &&
       FindFirstCall(nsITelephonyService::CALL_STATE_CONNECTED)) {
     callState = BTHF_CALL_STATE_WAITING;
   }
 
-  bt_status_t status = sBluetoothHfpInterface->ClccResponse(
-                          aIndex,
-                          aCall.mDirection,
-                          callState,
-                          BTHF_CALL_TYPE_VOICE,
-                          BTHF_CALL_MPTY_TYPE_SINGLE,
-                          NS_ConvertUTF16toUTF8(aCall.mNumber).get(),
-                          aCall.mType);
-  NS_ENSURE_TRUE_VOID(status == BT_STATUS_SUCCESS);
+  sBluetoothHfpInterface->ClccResponse(
+    aIndex, aCall.mDirection, callState, BTHF_CALL_TYPE_VOICE,
+    BTHF_CALL_MPTY_TYPE_SINGLE, NS_ConvertUTF16toUTF8(aCall.mNumber).get(),
+    aCall.mType, new ClccResponseResultHandler());
 }
 
+class FormattedAtResponseResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::FormattedAtResponse failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::SendLine(const char* aMessage)
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->FormattedAtResponse(aMessage));
+
+  sBluetoothHfpInterface->FormattedAtResponse(
+    aMessage, new FormattedAtResponseResultHandler());
 }
 
 void
 BluetoothHfpManager::SendResponse(bthf_at_response_t aResponseCode)
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->AtResponse(aResponseCode, 0));
+
+  sBluetoothHfpInterface->AtResponse(
+    aResponseCode, 0, new AtResponseResultHandler());
 }
 
+class PhoneStateChangeResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::PhoneStateChange failed: %d",
+               (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::UpdatePhoneCIND(uint32_t aCallIndex)
 {
   NS_ENSURE_TRUE_VOID(sBluetoothHfpInterface);
 
   int numActive = GetNumberOfCalls(nsITelephonyService::CALL_STATE_CONNECTED);
   int numHeld = GetNumberOfCalls(nsITelephonyService::CALL_STATE_HELD);
   bthf_call_state_t callSetupState =
@@ -1033,31 +1227,42 @@ BluetoothHfpManager::UpdatePhoneCIND(uin
   nsAutoCString number =
     NS_ConvertUTF16toUTF8(mCurrentCallArray[aCallIndex].mNumber);
   bthf_call_addrtype_t type = mCurrentCallArray[aCallIndex].mType;
 
   BT_LOGR("[%d] state %d => BTHF: active[%d] held[%d] setupstate[%d]",
           aCallIndex, mCurrentCallArray[aCallIndex].mState,
           numActive, numHeld, callSetupState);
 
-  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->PhoneStateChange(
-      numActive, numHeld, callSetupState, number.get(), type));
+  sBluetoothHfpInterface->PhoneStateChange(
+    numActive, numHeld, callSetupState, number.get(), type,
+    new PhoneStateChangeResultHandler());
 }
 
+class DeviceStatusNotificationResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING(
+      "BluetoothHandsfreeInterface::DeviceStatusNotification failed: %d",
+      (int)aStatus);
+  }
+};
+
 void
 BluetoothHfpManager::UpdateDeviceCIND()
 {
   if (sBluetoothHfpInterface) {
-    NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
-      sBluetoothHfpInterface->DeviceStatusNotification(
-        (bthf_network_state_t) mService,
-        (bthf_service_type_t) mRoam,
-        mSignal,
-        mBattChg));
+    sBluetoothHfpInterface->DeviceStatusNotification(
+      (bthf_network_state_t) mService,
+      (bthf_service_type_t) mRoam,
+      mSignal,
+      mBattChg, new DeviceStatusNotificationResultHandler());
   }
 }
 
 uint32_t
 BluetoothHfpManager::FindFirstCall(uint16_t aState)
 {
   uint32_t callLength = mCurrentCallArray.Length();
 
@@ -1283,43 +1488,65 @@ BluetoothHfpManager::ToggleCalls()
   MOZ_ASSERT(mPhoneType == PhoneType::CDMA);
 
   // Toggle acitve and held calls
   mCdmaSecondCall.mState = (mCdmaSecondCall.IsActive()) ?
                              nsITelephonyService::CALL_STATE_HELD :
                              nsITelephonyService::CALL_STATE_CONNECTED;
 }
 
+class ConnectAudioResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::ConnectAudio failed: %d",
+               (int)aStatus);
+  }
+};
+
 bool
 BluetoothHfpManager::ConnectSco()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   NS_ENSURE_TRUE(!sInShutdown, false);
   NS_ENSURE_TRUE(IsConnected() && !IsScoConnected(), false);
   NS_ENSURE_TRUE(sBluetoothHfpInterface, false);
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(mDeviceAddress, &deviceBdAddress);
-  NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->ConnectAudio(&deviceBdAddress), false);
+  sBluetoothHfpInterface->ConnectAudio(&deviceBdAddress,
+                                       new ConnectAudioResultHandler());
 
   return true;
 }
 
+class DisconnectAudioResultHandler MOZ_FINAL
+: public BluetoothHandsfreeResultHandler
+{
+public:
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::DisconnectAudio failed: %d",
+               (int)aStatus);
+  }
+};
+
 bool
 BluetoothHfpManager::DisconnectSco()
 {
   NS_ENSURE_TRUE(IsScoConnected(), false);
   NS_ENSURE_TRUE(sBluetoothHfpInterface, false);
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(mDeviceAddress, &deviceBdAddress);
-  NS_ENSURE_TRUE(BT_STATUS_SUCCESS ==
-    sBluetoothHfpInterface->DisconnectAudio(&deviceBdAddress), false);
+  sBluetoothHfpInterface->DisconnectAudio(&deviceBdAddress,
+                                          new DisconnectAudioResultHandler());
 
   return true;
 }
 
 bool
 BluetoothHfpManager::IsScoConnected()
 {
   return (mAudioState == BTHF_AUDIO_STATE_CONNECTED);
@@ -1327,16 +1554,47 @@ BluetoothHfpManager::IsScoConnected()
 
 bool
 BluetoothHfpManager::IsConnected()
 {
   return (mConnectionState == BTHF_CONNECTION_STATE_SLC_CONNECTED);
 }
 
 void
+BluetoothHfpManager::OnConnectError()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
+
+  mController = nullptr;
+  mDeviceAddress.Truncate();
+}
+
+class ConnectResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  ConnectResultHandler(BluetoothHfpManager* aHfpManager)
+  : mHfpManager(aHfpManager)
+  {
+    MOZ_ASSERT(mHfpManager);
+  }
+
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Connect failed: %d",
+               (int)aStatus);
+    mHfpManager->OnConnectError();
+  }
+
+private:
+  BluetoothHfpManager* mHfpManager;
+};
+
+void
 BluetoothHfpManager::Connect(const nsAString& aDeviceAddress,
                              BluetoothProfileController* aController)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aController && !mController);
 
   if (sInShutdown) {
     aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
@@ -1347,50 +1605,71 @@ BluetoothHfpManager::Connect(const nsASt
     BT_LOGR("sBluetoothHfpInterface is null");
     aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
     return;
   }
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(aDeviceAddress, &deviceBdAddress);
 
-  bt_status_t result = sBluetoothHfpInterface->Connect(&deviceBdAddress);
-  if (BT_STATUS_SUCCESS != result) {
-    BT_LOGR("Failed to connect: %x", result);
-    aController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
-    return;
+  mDeviceAddress = aDeviceAddress;
+  mController = aController;
+
+  sBluetoothHfpInterface->Connect(&deviceBdAddress,
+                                  new ConnectResultHandler(this));
+}
+
+void
+BluetoothHfpManager::OnDisconnectError()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  mController->NotifyCompletion(NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
+}
+
+class DisconnectResultHandler MOZ_FINAL : public BluetoothHandsfreeResultHandler
+{
+public:
+  DisconnectResultHandler(BluetoothHfpManager* aHfpManager)
+  : mHfpManager(aHfpManager)
+  {
+    MOZ_ASSERT(mHfpManager);
   }
 
-  mDeviceAddress = aDeviceAddress;
-  mController = aController;
-}
+  void OnError(bt_status_t aStatus) MOZ_OVERRIDE
+  {
+    BT_WARNING("BluetoothHandsfreeInterface::Disconnect failed: %d",
+               (int)aStatus);
+    mHfpManager->OnDisconnectError();
+  }
+
+private:
+  BluetoothHfpManager* mHfpManager;
+};
+
 
 void
 BluetoothHfpManager::Disconnect(BluetoothProfileController* aController)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mController);
 
   if (!sBluetoothHfpInterface) {
     BT_LOGR("sBluetoothHfpInterface is null");
     aController->NotifyCompletion(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
     return;
   }
 
   bt_bdaddr_t deviceBdAddress;
   StringToBdAddressType(mDeviceAddress, &deviceBdAddress);
 
-  bt_status_t result = sBluetoothHfpInterface->Disconnect(&deviceBdAddress);
-  if (BT_STATUS_SUCCESS != result) {
-    BT_LOGR("Failed to disconnect: %x", result);
-    aController->NotifyCompletion(NS_LITERAL_STRING(ERR_DISCONNECTION_FAILED));
-    return;
-  }
+  mController = aController;
 
-  mController = aController;
+  sBluetoothHfpInterface->Disconnect(&deviceBdAddress,
+                                     new DisconnectResultHandler(this));
 }
 
 void
 BluetoothHfpManager::OnConnect(const nsAString& aErrorStr)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   /**
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
@@ -70,25 +70,29 @@ public:
   bthf_call_addrtype_t mType;
 };
 
 class BluetoothHfpManager : public BluetoothHfpManagerBase
                           , public BatteryObserver
 {
 public:
   BT_DECL_HFP_MGR_BASE
+
+  void OnConnectError();
+  void OnDisconnectError();
+
   virtual void GetName(nsACString& aName)
   {
     aName.AssignLiteral("HFP/HSP");
   }
 
   static BluetoothHfpManager* Get();
   virtual ~BluetoothHfpManager();
-  static void InitHfpInterface();
-  static void DeinitHfpInterface();
+  static void InitHfpInterface(BluetoothProfileResultHandler* aRes);
+  static void DeinitHfpInterface(BluetoothProfileResultHandler* aRes);
 
   bool ConnectSco();
   bool DisconnectSco();
 
   /**
    * @param aSend A boolean indicates whether we need to notify headset or not
    */
   void HandleCallStateChanged(uint32_t aCallIndex, uint16_t aCallState,
--- a/dom/browser-element/BrowserElementParent.jsm
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -621,16 +621,36 @@ BrowserElementParent.prototype = {
       extListener: null,
       onStartRequest: function(aRequest, aContext) {
         debug('DownloadListener - onStartRequest');
         let extHelperAppSvc =
           Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
           getService(Ci.nsIExternalHelperAppService);
         let channel = aRequest.QueryInterface(Ci.nsIChannel);
 
+        // First, we'll ensure the filename doesn't have any leading
+        // periods. We have to do it here to avoid ending up with a filename
+        // that's only an extension with no extension (e.g. Sending in
+        // '.jpeg' without stripping the '.' would result in a filename of
+        // 'jpeg' where we want 'jpeg.jpeg'.
+        _options.filename = _options.filename.replace(/^\.+/, "");
+
+        let ext = null;
+        let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
+        try {
+          ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
+        } catch (e) { ext = null; }
+
+        // Check if we need to add an extension to the filename.
+        if (ext && !_options.filename.endsWith(ext)) {
+          _options.filename += ext;
+        }
+        // Set the filename to use when saving to disk.
+        channel.contentDispositionFilename = _options.filename;
+
         this.extListener =
           extHelperAppSvc.doContent(
               channel.contentType,
               aRequest,
               interfaceRequestor,
               true);
         this.extListener.onStartRequest(aRequest, aContext);
       },
--- a/dom/camera/GonkCameraSource.cpp
+++ b/dom/camera/GonkCameraSource.cpp
@@ -704,17 +704,22 @@ void GonkCameraSource::dataCallbackTimes
         Mutex::Autolock autoLock(mLock);
         if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) {
             CS_LOGV("Drop frame at %lld/%lld us", timestampUs, mStartTimeUs);
             releaseOneRecordingFrame(data);
             return;
         }
 
         if (mNumFramesReceived > 0) {
-            CHECK(timestampUs > mLastFrameTimestampUs);
+            if (timestampUs <= mLastFrameTimestampUs) {
+                CS_LOGE("Drop frame at %lld us, before last at %lld us",
+                    timestampUs, mLastFrameTimestampUs);
+                releaseOneRecordingFrame(data);
+                return;
+            }
             if (timestampUs - mLastFrameTimestampUs > mGlitchDurationThresholdUs) {
                 ++mNumGlitches;
             }
         }
 
         // May need to skip frame or modify timestamp. Currently implemented
         // by the subclass CameraSourceTimeLapse.
         if (skipCurrentFrame(timestampUs)) {
--- a/dom/nfc/tests/marionette/head.js
+++ b/dom/nfc/tests/marionette/head.js
@@ -90,17 +90,17 @@ let NCI = (function() {
     MORE_NOTIFICATIONS: 2
   };
 }());
 
 let TAG = (function() {
   function setData(re, flag, tnf, type, payload) {
     let deferred = Promise.defer();
     let cmd = "nfc tag set " + re +
-              " [" + flag + "," + tnf + "," + type + "," + payload + ",]";
+              " [" + flag + "," + tnf + "," + type + ",," + payload + "]";
 
     emulator.run(cmd, function(result) {
       is(result.pop(), "OK", "set NDEF data of tag" + re);
       deferred.resolve();
     });
 
     return deferred.promise;
   };
@@ -117,23 +117,23 @@ let TAG = (function() {
 
   return {
     setData: setData,
     clearData: clearData
   };
 }());
 
 let SNEP = (function() {
-  function put(dsap, ssap, flags, tnf, type, payload, id) {
+  function put(dsap, ssap, flags, tnf, type, id, payload) {
     let deferred = Promise.defer();
     let cmd = "nfc snep put " + dsap + " " + ssap + " [" + flags + "," +
                                                            tnf + "," +
                                                            type + "," +
-                                                           payload + "," +
-                                                           id + "]";
+                                                           id + "," +
+                                                           payload + "]";
     emulator.run(cmd, function(result) {
       is(result.pop(), "OK", "send SNEP PUT");
       deferred.resolve();
     });
 
     return deferred.promise;
   };
 
--- a/dom/nfc/tests/marionette/test_nfc_manager_tech_discovered_ndef.js
+++ b/dom/nfc/tests/marionette/test_nfc_manager_tech_discovered_ndef.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let tnf = NDEF.TNF_WELL_KNOWN;
 let type = "U";
+let id = "";
 let payload = "https://www.example.com";
-let id = "";
 
 let ndef = null;
 
 function handleTechnologyDiscoveredRE0(msg) {
   log("Received 'nfc-manager-tech-discovered'");
   is(msg.type, "techDiscovered", "check for correct message type");
   is(msg.techList[0], "P2P", "check for correct tech type");
 
@@ -30,17 +30,17 @@ function handleTechnologyDiscoveredRE0(m
 
 function testReceiveNDEF() {
   log("Running 'testReceiveNDEF'");
   window.navigator.mozSetMessageHandler(
     "nfc-manager-tech-discovered", handleTechnologyDiscoveredRE0);
   toggleNFC(true)
     .then(() => NCI.activateRE(emulator.P2P_RE_INDEX_0))
     .then(() => SNEP.put(SNEP.SAP_NDEF, SNEP.SAP_NDEF, 0, tnf, btoa(type),
-                         btoa(payload), btoa(id)));
+                         btoa(id), btoa(payload)));
 }
 
 let tests = [
   testReceiveNDEF
 ];
 
 SpecialPowers.pushPermissions(
   [{'type': 'nfc', 'allow': true,
--- a/dom/webidl/GeometryUtils.webidl
+++ b/dom/webidl/GeometryUtils.webidl
@@ -18,24 +18,24 @@ dictionary BoxQuadOptions {
 
 dictionary ConvertCoordinateOptions {
   CSSBoxType fromBox = "border";
   CSSBoxType toBox = "border";
 };
 
 [NoInterfaceObject]
 interface GeometryUtils {
-  [Throws, Pref="layout.css.getBoxQuads.enabled"]
+  [Throws, Func="nsINode::HasBoxQuadsSupport"]
   sequence<DOMQuad> getBoxQuads(optional BoxQuadOptions options);
   [Throws, Pref="layout.css.convertFromNode.enabled"]
   DOMQuad convertQuadFromNode(DOMQuad quad, GeometryNode from, optional ConvertCoordinateOptions options);
   [Throws, Pref="layout.css.convertFromNode.enabled"]
   DOMQuad convertRectFromNode(DOMRectReadOnly rect, GeometryNode from, optional ConvertCoordinateOptions options);
   [Throws, Pref="layout.css.convertFromNode.enabled"]
   DOMPoint convertPointFromNode(DOMPointInit point, GeometryNode from, optional ConvertCoordinateOptions options);
 };
 
 Text implements GeometryUtils;
 Element implements GeometryUtils;
 // PseudoElement implements GeometryUtils;
 Document implements GeometryUtils;
 
-typedef (Text or Element /* or PseudoElement */ or Document) GeometryNode;
\ No newline at end of file
+typedef (Text or Element /* or PseudoElement */ or Document) GeometryNode;
--- a/editor/composer/moz.build
+++ b/editor/composer/moz.build
@@ -22,16 +22,19 @@ UNIFIED_SOURCES += [
     'nsEditingSession.cpp',
     'nsEditorSpellCheck.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 FINAL_LIBRARY = 'xul'
 RESOURCE_FILES += [
+    'res/caret_left.svg',
+    'res/caret_middle.svg',
+    'res/caret_right.svg',
     'res/EditorOverride.css',
     'res/grabber.gif',
     'res/table-add-column-after-active.gif',
     'res/table-add-column-after-hover.gif',
     'res/table-add-column-after.gif',
     'res/table-add-column-before-active.gif',
     'res/table-add-column-before-hover.gif',
     'res/table-add-column-before.gif',
@@ -42,24 +45,9 @@ RESOURCE_FILES += [
     'res/table-add-row-before-hover.gif',
     'res/table-add-row-before.gif',
     'res/table-remove-column-active.gif',
     'res/table-remove-column-hover.gif',
     'res/table-remove-column.gif',
     'res/table-remove-row-active.gif',
     'res/table-remove-row-hover.gif',
     'res/table-remove-row.gif',
-    'res/text_caret.png',
-    'res/text_caret@1.5x.png',
-    'res/text_caret@2.25x.png',
-    'res/text_caret@2x.png',
-    'res/text_caret_tilt_left.png',
-    'res/text_caret_tilt_left@1.5x.png',
-    'res/text_caret_tilt_left@2.25x.png',
-    'res/text_caret_tilt_left@2x.png',
-    'res/text_caret_tilt_right.png',
-    'res/text_caret_tilt_right@1.5x.png',
-    'res/text_caret_tilt_right@2.25x.png',
-    'res/text_caret_tilt_right@2x.png',
-    'res/text_selection_handle.png',
-    'res/text_selection_handle@1.5.png',
-    'res/text_selection_handle@2.png',
 ]
new file mode 100644
--- /dev/null
+++ b/editor/composer/res/caret_left.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+     width="29px" height="31px" viewBox="0 0 29 31" style="enable-background:new 0 0 29 31;" xml:space="preserve">
+  <!-- TODO: Enable shadow after bug 1015575 is resolved.
+  <defs>
+    <filter id="caretFilter">
+      <feOffset result="offsetOut" in="SourceAlpha" dx="1" dy="1" />
+      <feGaussianBlur result="blurOut" in="offsetOut" stdDeviation="0.5" />
+      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
+    </filter>
+  </defs>
+  <g fill="#2da9e3" filter="url(#caretFilter)">
+  -->
+  <g fill="#2da9e3">
+    <path d="M25.368,2.674c-0.049,0.104-0.09,0.209-0.134,0.314C25.304,2.893,25.347,2.786,25.368,2.674z"/>
+    <path d="M24.27,1.734c0.003-0.001,0.008-0.003,0.013-0.004C24.277,1.73,24.272,1.733,24.27,1.734z"/>
+    <path d="M24.583,8.574C24.25,6.7,24.478,4.755,25.234,2.989c0.044-0.105,0.085-0.21,0.134-0.314
+             c0.053-0.254-0.016-0.528-0.204-0.73c-0.232-0.249-0.581-0.322-0.882-0.215c-0.005,0.001-0.01,0.003-0.013,0.004
+             c-1.915,0.71-4.001,0.798-5.954,0.277C15.015,0.898,11.222,1.587,8.5,4.134c-3.947,3.691-4.155,9.882-0.464,13.828
+             c3.691,3.947,9.881,4.154,13.828,0.462C24.64,15.828,25.562,11.994,24.583,8.574z"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/editor/composer/res/caret_middle.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+     width="29px" height="31px" style="enable-background:new 0 0 29 31;" xml:space="preserve">
+  <!-- TODO: Enable shadow after bug 1015575 is resolved.
+  <defs>
+    <filter id="caretFilter">
+      <feOffset result="offsetOut" in="SourceAlpha" dx="1" dy="1" />
+      <feGaussianBlur result="blurOut" in="offsetOut" stdDeviation="0.5" />
+      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
+    </filter>
+  </defs>
+  <g fill="#2da9e3" filter="url(#caretFilter)">
+  -->
+  <g fill="#2da9e3">
+    <path d="M15.174,1.374c0.042,0.106,0.091,0.208,0.138,0.312C15.288,1.57,15.239,1.466,15.174,1.374z"/>
+    <path d="M13.735,1.534c0.002-0.003,0.004-0.009,0.006-0.013C13.739,1.525,13.737,1.531,13.735,1.534z"/>
+    <path d="M18.945,5.978c-1.596-1.038-2.861-2.532-3.634-4.292c-0.047-0.104-0.096-0.206-0.138-0.312
+             c-0.15-0.212-0.396-0.349-0.674-0.349c-0.34,0-0.631,0.204-0.759,0.497c-0.002,0.004-0.004,0.009-0.006,0.013
+             c-0.789,1.883-2.149,3.467-3.864,4.538c-3.068,1.651-5.155,4.892-5.155,8.62c0,5.404,4.379,9.784,9.783,9.784
+             c5.403,0,9.783-4.38,9.783-9.784C24.283,10.891,22.113,7.598,18.945,5.978z"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/editor/composer/res/caret_right.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+     width="29px" height="31px" viewBox="0 0 29 31" style="enable-background:new 0 0 29 31;" xml:space="preserve">
+  <!-- TODO: Enable shadow after bug 1015575 is resolved.
+  <defs>
+    <filter id="caretFilter">
+      <feOffset result="offsetOut" in="SourceAlpha" dx="1" dy="1" />
+      <feGaussianBlur result="blurOut" in="offsetOut" stdDeviation="0.5" />
+      <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
+    </filter>
+  </defs>
+  <g fill="#2da9e3" filter="url(#caretFilter)">
+  -->
+  <g fill="#2da9e3">
+    <path fill="#2da9e3" d="M27.296,2.674c-0.049,0.104-0.09,0.209-0.134,0.314C27.231,2.893,27.274,2.786,27.296,2.674z"/>
+    <path fill="#2da9e3" d="M26.197,1.734C26.2,1.733,26.205,1.73,26.21,1.729C26.205,1.73,26.2,1.733,26.197,1.734z"/>
+    <path fill="#2da9e3" d="M4.299,8.574C4.632,6.7,4.404,4.755,3.647,2.989c-0.044-0.105-0.085-0.21-0.134-0.314C3.461,2.42,3.529,2.146,3.718,1.944
+                            C3.95,1.696,4.299,1.623,4.6,1.729c0.005,0.001,0.01,0.003,0.013,0.004c1.915,0.71,4.001,0.798,5.954,0.277
+                            c3.301-1.113,7.094-0.423,9.815,2.123c3.947,3.691,4.156,9.882,0.465,13.828c-3.691,3.947-9.881,4.154-13.828,0.462
+                            C4.242,15.828,3.319,11.994,4.299,8.574z"/>
+  </g>
+</svg>
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -242,34 +242,44 @@ GLBlitHelper::InitTexQuadProgram(BlitTyp
         uniform samplerExternalOES uTexUnit;                \n\
                                                             \n\
         void main()                                         \n\
         {                                                   \n\
             gl_FragColor = texture2D(uTexUnit, vTexCoord);  \n\
         }                                                   \n\
     ";
 #endif
+    /* From Rec601:
+    [R] [1.1643835616438356, 0.0, 1.5960267857142858] [ Y - 16]
+    [G] = [1.1643835616438358, -0.3917622900949137, -0.8129676472377708] x [Cb - 128]
+    [B] [1.1643835616438356, 2.017232142857143, 8.862867620416422e-17] [Cr - 128]
+
+    For [0,1] instead of [0,255], and to 5 places:
+    [R] [1.16438, 0.00000, 1.59603] [ Y - 0.06275]
+    [G] = [1.16438, -0.39176, -0.81297] x [Cb - 0.50196]
+    [B] [1.16438, 2.01723, 0.00000] [Cr - 0.50196]
+    */
     const char kTexYUVPlanarBlit_FragShaderSource[] = "\
         varying vec2 vTexCoord;                                             \n\
         uniform sampler2D uYTexture;                                        \n\
         uniform sampler2D uCbTexture;                                       \n\
         uniform sampler2D uCrTexture;                                       \n\
         uniform vec2 uYTexScale;                                            \n\
         uniform vec2 uCbCrTexScale;                                         \n\
         void main()                                                         \n\
         {                                                                   \n\
             float y = texture2D(uYTexture, vTexCoord * uYTexScale).r;       \n\
             float cb = texture2D(uCbTexture, vTexCoord * uCbCrTexScale).r;  \n\
             float cr = texture2D(uCrTexture, vTexCoord * uCbCrTexScale).r;  \n\
-            y = (y - 0.0625) * 1.164;                                       \n\
-            cb = cb - 0.504;                                                  \n\
-            cr = cr - 0.5;                                                  \n\
-            gl_FragColor.r = floor((y + cr * 1.596) * 256.0)/256.0;         \n\
-            gl_FragColor.g = floor((y - 0.813 * cr - 0.391 * cb) * 256.0)/256.0;                   \n\
-            gl_FragColor.b = floor((y + cb * 2.018) * 256.0) /256.0;                                \n\
+            y = (y - 0.06275) * 1.16438;                                    \n\
+            cb = cb - 0.50196;                                              \n\
+            cr = cr - 0.50196;                                              \n\
+            gl_FragColor.r = y + cr * 1.59603;                              \n\
+            gl_FragColor.g = y - 0.81297 * cr - 0.39176 * cb;               \n\
+            gl_FragColor.b = y + cb * 2.01723;                              \n\
             gl_FragColor.a = 1.0;                                           \n\
         }                                                                   \n\
     ";
 
     bool success = false;
 
     GLuint *programPtr;
     GLuint *fragShaderPtr;
--- a/gfx/gl/SkiaGLGlue.cpp
+++ b/gfx/gl/SkiaGLGlue.cpp
@@ -773,16 +773,17 @@ static GrGLInterface* CreateGrGLInterfac
     if (context->IsGLES()) {
         i->fStandard = kGLES_GrGLStandard;
     } else {
         i->fStandard = kGL_GrGLStandard;
     }
 
     GrGLExtensions extensions;
     if (!extensions.init(i->fStandard, glGetString_mozilla, NULL, glGetIntegerv_mozilla)) {
+        delete i;
         return nullptr;
     }
 
     i->fExtensions.swap(&extensions);
 
     // Core GL functions required by Ganesh
     i->fFunctions.fActiveTexture = glActiveTexture_mozilla;
     i->fFunctions.fAttachShader = glAttachShader_mozilla;
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -53,21 +53,26 @@ public:
   CompositableClient* mCompositableClient;
 
   uint64_t mAsyncID;
 };
 
 void
 RemoveTextureFromCompositableTracker::ReleaseTextureClient()
 {
-  if (mTextureClient) {
+  if (mTextureClient &&
+      mTextureClient->GetAllocator() &&
+      !mTextureClient->GetAllocator()->IsImageBridgeChild())
+  {
     TextureClientReleaseTask* task = new TextureClientReleaseTask(mTextureClient);
     RefPtr<ISurfaceAllocator> allocator = mTextureClient->GetAllocator();
     mTextureClient = nullptr;
     allocator->GetMessageLoop()->PostTask(FROM_HERE, task);
+  } else {
+    mTextureClient = nullptr;
   }
 }
 
 /* static */ void
 CompositableClient::TransactionCompleteted(PCompositableChild* aActor, uint64_t aTransactionId)
 {
   CompositableChild* child = static_cast<CompositableChild*>(aActor);
   child->TransactionCompleteted(aTransactionId);
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -2,32 +2,25 @@
  * 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 "mozilla/layers/TextureClient.h"
 #include <stdint.h>                     // for uint8_t, uint32_t, etc
 #include "Layers.h"                     // for Layer, etc
 #include "gfx2DGlue.h"
-#include "gfxContext.h"                 // for gfxContext, etc
 #include "gfxPlatform.h"                // for gfxPlatform
-#include "gfxPoint.h"                   // for gfxIntSize, gfxSize
-#include "gfxReusableSurfaceWrapper.h"  // for gfxReusableSurfaceWrapper
-#include "mozilla/gfx/BaseSize.h"       // for BaseSize
 #include "mozilla/ipc/SharedMemory.h"   // for SharedMemory, etc
-#include "mozilla/layers/CompositableClient.h"  // for CompositableClient
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/ISurfaceAllocator.h"
 #include "mozilla/layers/ImageDataSerializer.h"
-#include "mozilla/layers/ShadowLayers.h"  // for ShadowLayerForwarder
-#include "mozilla/layers/SharedPlanarYCbCrImage.h"
 #include "mozilla/layers/YCbCrImageDataSerializer.h"
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_WARNING, etc
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
-#include "ImageContainer.h"             // for PlanarYCbCrImage, etc
+#include "ImageContainer.h"             // for PlanarYCbCrData, etc
 #include "mozilla/gfx/2D.h"
 #include "mozilla/layers/TextureClientOGL.h"
 #include "mozilla/layers/PTextureChild.h"
 #include "SurfaceStream.h"
 #include "GLContext.h"
 
 #ifdef XP_WIN
 #include "mozilla/layers/TextureD3D9.h"
--- a/gfx/layers/ipc/CompositableForwarder.h
+++ b/gfx/layers/ipc/CompositableForwarder.h
@@ -193,18 +193,16 @@ public:
 
   virtual int32_t GetMaxTextureSize() const MOZ_OVERRIDE
   {
     return mTextureFactoryIdentifier.mMaxTextureSize;
   }
 
   bool IsOnCompositorSide() const MOZ_OVERRIDE { return false; }
 
-  virtual bool IsImageBridgeChild() const { return false; }
-
   /**
    * Returns the type of backend that is used off the main thread.
    * We only don't allow changing the backend type at runtime so this value can
    * be queried once and will not change until Gecko is restarted.
    */
   virtual LayersBackend GetCompositorBackendType() const MOZ_OVERRIDE
   {
     return mTextureFactoryIdentifier.mParentBackend;
--- a/gfx/layers/ipc/ISurfaceAllocator.h
+++ b/gfx/layers/ipc/ISurfaceAllocator.h
@@ -160,16 +160,18 @@ public:
                           uint32_t aUsage,
                           MaybeMagicGrallocBufferHandle* aHandle);
 
   void DeallocGrallocBuffer(MaybeMagicGrallocBufferHandle* aHandle);
 
   virtual bool IPCOpen() const { return true; }
   virtual bool IsSameProcess() const = 0;
 
+  virtual bool IsImageBridgeChild() const { return false; }
+
   virtual MessageLoop * GetMessageLoop() const
   {
     return mDefaultMessageLoop;
   }
 
   // Returns true if aSurface wraps a Shmem.
   static bool IsShmem(SurfaceDescriptor* aSurface);
 
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -27,27 +27,36 @@ CurrentThreadCanAccessZone(JS::Zone *zon
 namespace gc {
 
 struct Cell;
 
 const size_t ArenaShift = 12;
 const size_t ArenaSize = size_t(1) << ArenaShift;
 const size_t ArenaMask = ArenaSize - 1;
 
+#ifdef JS_GC_SMALL_CHUNK_SIZE
+const size_t ChunkShift = 18;
+#else
 const size_t ChunkShift = 20;
+#endif
 const size_t ChunkSize = size_t(1) << ChunkShift;
 const size_t ChunkMask = ChunkSize - 1;
 
 const size_t CellShift = 3;
 const size_t CellSize = size_t(1) << CellShift;
 const size_t CellMask = CellSize - 1;
 
 /* These are magic constants derived from actual offsets in gc/Heap.h. */
+#ifdef JS_GC_SMALL_CHUNK_SIZE
+const size_t ChunkMarkBitmapOffset = 258104;
+const size_t ChunkMarkBitmapBits = 31744;
+#else
 const size_t ChunkMarkBitmapOffset = 1032352;
 const size_t ChunkMarkBitmapBits = 129024;
+#endif
 const size_t ChunkRuntimeOffset = ChunkSize - sizeof(void*);
 const size_t ChunkLocationOffset = ChunkSize - 2 * sizeof(void*) - sizeof(uint64_t);
 
 /*
  * Live objects are marked black. How many other additional colors are available
  * depends on the size of the GCThing. Objects marked gray are eligible for
  * cycle collection.
  */
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -3210,16 +3210,29 @@ MOZ_ARG_DISABLE_BOOL(exact-rooting,
 [  --disable-exact-rooting  Enable use of conservative stack scanning for GC],
     JSGC_USE_EXACT_ROOTING= ,
     JSGC_USE_EXACT_ROOTING=1 )
 if test -n "$JSGC_USE_EXACT_ROOTING"; then
     AC_DEFINE(JSGC_USE_EXACT_ROOTING)
 fi
 
 dnl ========================================================
+dnl = Use a smaller chunk size for GC chunks
+dnl ========================================================
+dnl Use large (1MB) chunks by default.  For B2G this option is used to give
+dnl smaller (currently 256K) chunks.
+MOZ_ARG_ENABLE_BOOL(small-chunk-size,
+[  --enable-small-chunk-size  Allocate memory for JS GC things in smaller chunks],
+    JS_GC_SMALL_CHUNK_SIZE=1,
+    JS_GC_SMALL_CHUNK_SIZE= )
+if test -n "$JS_GC_SMALL_CHUNK_SIZE"; then
+    AC_DEFINE(JS_GC_SMALL_CHUNK_SIZE)
+fi
+
+dnl ========================================================
 dnl = Use GC tracing
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(gc-trace,
 [  --enable-gc-trace  Enable tracing of allocation and finalization],
     JS_GC_TRACE=1,
     JS_GC_TRACE= )
 if test -n "$JS_GC_TRACE"; then
     AC_DEFINE(JS_GC_TRACE)
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -872,17 +872,17 @@ GetABICode(JSObject* obj)
 static const JSErrorFormatString ErrorFormatString[CTYPESERR_LIMIT] = {
 #define MSG_DEF(name, number, count, exception, format) \
   { format, count, exception } ,
 #include "ctypes/ctypes.msg"
 #undef MSG_DEF
 };
 
 static const JSErrorFormatString*
-GetErrorMessage(void* userRef, const char* locale, const unsigned errorNumber)
+GetErrorMessage(void* userRef, const unsigned errorNumber)
 {
   if (0 < errorNumber && errorNumber < CTYPESERR_LIMIT)
     return &ErrorFormatString[errorNumber];
   return nullptr;
 }
 
 static bool
 TypeError(JSContext* cx, const char* expected, HandleValue actual)
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -414,18 +414,16 @@ class GCRuntime
     JSRuntime             *rt;
 
     /* Embedders can use this zone however they wish. */
     JS::Zone              *systemZone;
 
     /* List of compartments and zones (protected by the GC lock). */
     js::gc::ZoneVector    zones;
 
-    js::gc::SystemPageAllocator pageAllocator;
-
 #ifdef JSGC_GENERATIONAL
     js::Nursery           nursery;
     js::gc::StoreBuffer   storeBuffer;
 #endif
 
     js::gcstats::Statistics stats;
 
     js::GCMarker          marker;
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -702,17 +702,22 @@ struct ChunkInfo
  * divide the amount of available space less the header info (not including
  * the mark bitmap which is distributed into the arena size) by the size of
  * the arena (with the mark bitmap bytes it uses).
  */
 const size_t BytesPerArenaWithHeader = ArenaSize + ArenaBitmapBytes;
 const size_t ChunkDecommitBitmapBytes = ChunkSize / ArenaSize / JS_BITS_PER_BYTE;
 const size_t ChunkBytesAvailable = ChunkSize - sizeof(ChunkInfo) - ChunkDecommitBitmapBytes;
 const size_t ArenasPerChunk = ChunkBytesAvailable / BytesPerArenaWithHeader;
+
+#ifdef JS_GC_SMALL_CHUNK_SIZE
+static_assert(ArenasPerChunk == 62, "Do not accidentally change our heap's density.");
+#else
 static_assert(ArenasPerChunk == 252, "Do not accidentally change our heap's density.");
+#endif
 
 /* A chunk bitmap contains enough mark bits for all the cells in a chunk. */
 struct ChunkBitmap
 {
     volatile uintptr_t bitmap[ArenaBitmapWords * ArenasPerChunk];
 
   public:
     ChunkBitmap() { }
--- a/js/src/gc/Memory.cpp
+++ b/js/src/gc/Memory.cpp
@@ -6,63 +6,123 @@
 
 #include "gc/Memory.h"
 
 #include "mozilla/TaggedAnonymousMemory.h"
 
 #include "js/HeapAPI.h"
 #include "vm/Runtime.h"
 
-using namespace js;
-using namespace js::gc;
+#if defined(XP_WIN)
+
+#include "jswin.h"
+#include <psapi.h>
+
+#elif defined(SOLARIS)
+
+#include <sys/mman.h>
+#include <unistd.h>
+
+#elif defined(XP_UNIX)
+
+#include <algorithm>
+#include <errno.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#endif
+
+namespace js {
+namespace gc {
+
+// The GC can only safely decommit memory when the page size of the
+// running process matches the compiled arena size.
+static size_t pageSize = 0;
 
-bool
-SystemPageAllocator::decommitEnabled()
+// The OS allocation granularity may not match the page size.
+static size_t allocGranularity = 0;
+
+#if defined(XP_UNIX)
+// The addresses handed out by mmap may grow up or down.
+static int growthDirection = 0;
+#endif
+
+// The maximum number of unalignable chunks to temporarily keep alive in
+// the last ditch allocation pass. OOM crash reports generally show <= 7
+// unaligned chunks available (bug 1005844 comment #16).
+static const int MaxLastDitchAttempts = 8;
+
+static void GetNewChunk(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize, size_t size,
+                        size_t alignment);
+static bool GetNewChunkInner(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize,
+                             size_t size, size_t alignment, bool addrsGrowDown);
+static void *MapAlignedPagesSlow(size_t size, size_t alignment);
+static void *MapAlignedPagesLastDitch(size_t size, size_t alignment);
+
+size_t
+SystemPageSize()
+{
+    return pageSize;
+}
+
+static bool
+DecommitEnabled()
 {
     return pageSize == ArenaSize;
 }
 
 /*
  * This returns the offset of address p from the nearest aligned address at
  * or below p - or alternatively, the number of unaligned bytes at the end of
  * the region starting at p (as we assert that allocation size is an integer
  * multiple of the alignment).
  */
 static inline size_t
 OffsetFromAligned(void *p, size_t alignment)
 {
     return uintptr_t(p) % alignment;
 }
 
+void *
+TestMapAlignedPagesLastDitch(size_t size, size_t alignment)
+{
+    return MapAlignedPagesLastDitch(size, alignment);
+}
+
+
 #if defined(XP_WIN)
-#include "jswin.h"
-#include <psapi.h>
 
-SystemPageAllocator::SystemPageAllocator()
+void
+InitMemorySubsystem()
 {
-    SYSTEM_INFO sysinfo;
-    GetSystemInfo(&sysinfo);
-    pageSize = sysinfo.dwPageSize;
-    allocGranularity = sysinfo.dwAllocationGranularity;
+    if (pageSize == 0) {
+        SYSTEM_INFO sysinfo;
+        GetSystemInfo(&sysinfo);
+        pageSize = sysinfo.dwPageSize;
+        allocGranularity = sysinfo.dwAllocationGranularity;
+    }
 }
 
 static inline void *
 MapMemoryAt(void *desired, size_t length, int flags, int prot = PAGE_READWRITE)
 {
     return VirtualAlloc(desired, length, flags, prot);
 }
 
 static inline void *
 MapMemory(size_t length, int flags, int prot = PAGE_READWRITE)
 {
     return VirtualAlloc(nullptr, length, flags, prot);
 }
 
 void *
-SystemPageAllocator::mapAlignedPages(size_t size, size_t alignment)
+MapAlignedPages(size_t size, size_t alignment)
 {
     MOZ_ASSERT(size >= alignment);
     MOZ_ASSERT(size % alignment == 0);
     MOZ_ASSERT(size % pageSize == 0);
     MOZ_ASSERT(alignment % allocGranularity == 0);
 
     void *p = MapMemory(size, MEM_COMMIT | MEM_RESERVE);
 
@@ -70,35 +130,35 @@ SystemPageAllocator::mapAlignedPages(siz
     if (alignment == allocGranularity)
         return p;
 
     if (OffsetFromAligned(p, alignment) == 0)
         return p;
 
     void *retainedAddr;
     size_t retainedSize;
-    getNewChunk(&p, &retainedAddr, &retainedSize, size, alignment);
+    GetNewChunk(&p, &retainedAddr, &retainedSize, size, alignment);
     if (retainedAddr)
-        unmapPages(retainedAddr, retainedSize);
+        UnmapPages(retainedAddr, retainedSize);
     if (p) {
         if (OffsetFromAligned(p, alignment) == 0)
             return p;
-        unmapPages(p, size);
+        UnmapPages(p, size);
     }
 
-    p = mapAlignedPagesSlow(size, alignment);
+    p = MapAlignedPagesSlow(size, alignment);
     if (!p)
-        return mapAlignedPagesLastDitch(size, alignment);
+        return MapAlignedPagesLastDitch(size, alignment);
 
     MOZ_ASSERT(OffsetFromAligned(p, alignment) == 0);
     return p;
 }
 
-void *
-SystemPageAllocator::mapAlignedPagesSlow(size_t size, size_t alignment)
+static void *
+MapAlignedPagesSlow(size_t size, size_t alignment)
 {
     /*
      * Windows requires that there be a 1:1 mapping between VM allocation
      * and deallocation operations.  Therefore, take care here to acquire the
      * final result via one mapping operation.  This means unmapping any
      * preliminary result that is not correctly aligned.
      */
     void *p;
@@ -111,223 +171,217 @@ SystemPageAllocator::mapAlignedPagesSlow
          * Since we're going to unmap the whole thing anyway, the first
          * mapping doesn't have to commit pages.
          */
         size_t reserveSize = size + alignment - pageSize;
         p = MapMemory(reserveSize, MEM_RESERVE);
         if (!p)
             return nullptr;
         void *chunkStart = (void *)AlignBytes(uintptr_t(p), alignment);
-        unmapPages(p, reserveSize);
+        UnmapPages(p, reserveSize);
         p = MapMemoryAt(chunkStart, size, MEM_COMMIT | MEM_RESERVE);
 
         /* Failure here indicates a race with another thread, so try again. */
     } while (!p);
 
     return p;
 }
 
 /*
  * Even though there aren't any |size + alignment - pageSize| byte chunks left,
  * the allocator may still be able to give us |size| byte chunks that are
  * either already aligned, or *can* be aligned by allocating in the nearest
  * aligned location. Since we can't tell the allocator to give us a different
  * address each time, we temporarily hold onto the unaligned part of each chunk
  * until the allocator gives us a chunk that either is, or can be aligned.
  */
-void *
-SystemPageAllocator::mapAlignedPagesLastDitch(size_t size, size_t alignment)
+static void *
+MapAlignedPagesLastDitch(size_t size, size_t alignment)
 {
     void *p = nullptr;
     void *tempMaps[MaxLastDitchAttempts];
     int attempt = 0;
     for (; attempt < MaxLastDitchAttempts; ++attempt) {
         size_t retainedSize;
-        getNewChunk(&p, tempMaps + attempt, &retainedSize, size, alignment);
+        GetNewChunk(&p, tempMaps + attempt, &retainedSize, size, alignment);
         if (OffsetFromAligned(p, alignment) == 0) {
             if (tempMaps[attempt])
-                unmapPages(tempMaps[attempt], retainedSize);
+                UnmapPages(tempMaps[attempt], retainedSize);
             break;
         }
         if (!tempMaps[attempt]) {
-            /* getNewChunk failed, but we can still try the simpler method. */
+            /* GetNewChunk failed, but we can still try the simpler method. */
             tempMaps[attempt] = p;
             p = nullptr;
         }
     }
     if (OffsetFromAligned(p, alignment)) {
-        unmapPages(p, size);
+        UnmapPages(p, size);
         p = nullptr;
     }
     while (--attempt >= 0)
-        unmapPages(tempMaps[attempt], 0);
+        UnmapPages(tempMaps[attempt], 0);
     return p;
 }
 
 /*
  * On Windows, map and unmap calls must be matched, so we deallocate the
  * unaligned chunk, then reallocate the unaligned part to block off the
  * old address and force the allocator to give us a new one.
  */
-void
-SystemPageAllocator::getNewChunk(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize,
-                                 size_t size, size_t alignment)
+static void
+GetNewChunk(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize, size_t size,
+            size_t alignment)
 {
     void *address = *aAddress;
     void *retainedAddr = nullptr;
     size_t retainedSize = 0;
     do {
         if (!address)
             address = MapMemory(size, MEM_COMMIT | MEM_RESERVE);
         size_t offset = OffsetFromAligned(address, alignment);
         if (!offset)
             break;
-        unmapPages(address, size);
+        UnmapPages(address, size);
         retainedSize = alignment - offset;
         retainedAddr = MapMemoryAt(address, retainedSize, MEM_RESERVE);
         address = MapMemory(size, MEM_COMMIT | MEM_RESERVE);
         /* If retainedAddr is null here, we raced with another thread. */
     } while (!retainedAddr);
     *aAddress = address;
     *aRetainedAddr = retainedAddr;
     *aRetainedSize = retainedSize;
 }
 
 void
-SystemPageAllocator::unmapPages(void *p, size_t size)
+UnmapPages(void *p, size_t size)
 {
     MOZ_ALWAYS_TRUE(VirtualFree(p, 0, MEM_RELEASE));
 }
 
 bool
-SystemPageAllocator::markPagesUnused(void *p, size_t size)
+MarkPagesUnused(void *p, size_t size)
 {
-    if (!decommitEnabled())
+    if (!DecommitEnabled())
         return true;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     LPVOID p2 = MapMemoryAt(p, size, MEM_RESET);
     return p2 == p;
 }
 
 bool
-SystemPageAllocator::markPagesInUse(void *p, size_t size)
+MarkPagesInUse(void *p, size_t size)
 {
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     return true;
 }
 
 size_t
-SystemPageAllocator::GetPageFaultCount()
+GetPageFaultCount()
 {
     PROCESS_MEMORY_COUNTERS pmc;
     if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc)))
         return 0;
     return pmc.PageFaultCount;
 }
 
 void *
-SystemPageAllocator::AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
+AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
 {
     // TODO: Bug 988813 - Support memory mapped array buffer for Windows platform.
     return nullptr;
 }
 
 // Deallocate mapped memory for object.
 void
-SystemPageAllocator::DeallocateMappedContent(void *p, size_t length)
+DeallocateMappedContent(void *p, size_t length)
 {
     // TODO: Bug 988813 - Support memory mapped array buffer for Windows platform.
 }
 
 #elif defined(SOLARIS)
 
-#include <sys/mman.h>
-#include <unistd.h>
-
 #ifndef MAP_NOSYNC
 # define MAP_NOSYNC 0
 #endif
 
-SystemPageAllocator::SystemPageAllocator()
+void
+InitMemorySubsystem()
 {
-    pageSize = allocGranularity = size_t(sysconf(_SC_PAGESIZE));
+    if (pageSize == 0)
+        pageSize = allocGranularity = size_t(sysconf(_SC_PAGESIZE));
 }
 
 void *
-SystemPageAllocator::mapAlignedPages(size_t size, size_t alignment)
+MapAlignedPages(size_t size, size_t alignment)
 {
     MOZ_ASSERT(size >= alignment);
     MOZ_ASSERT(size % alignment == 0);
     MOZ_ASSERT(size % pageSize == 0);
     MOZ_ASSERT(alignment % allocGranularity == 0);
 
     int prot = PROT_READ | PROT_WRITE;
     int flags = MAP_PRIVATE | MAP_ANON | MAP_ALIGN | MAP_NOSYNC;
 
     void *p = mmap((caddr_t)alignment, size, prot, flags, -1, 0);
     if (p == MAP_FAILED)
         return nullptr;
     return p;
 }
 
 void
-SystemPageAllocator::unmapPages(void *p, size_t size)
+UnmapPages(void *p, size_t size)
 {
     MOZ_ALWAYS_TRUE(0 == munmap((caddr_t)p, size));
 }
 
 bool
-SystemPageAllocator::markPagesUnused(void *p, size_t size)
+MarkPagesUnused(void *p, size_t size)
 {
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     return true;
 }
 
 bool
-SystemPageAllocator::markPagesInUse(void *p, size_t size)
+MarkPagesInUse(void *p, size_t size)
 {
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     return true;
 }
 
 size_t
-SystemPageAllocator::GetPageFaultCount()
+GetPageFaultCount()
 {
     return 0;
 }
 
 void *
-SystemPageAllocator::AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
+AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
 {
     // Not implemented.
     return nullptr;
 }
 
 // Deallocate mapped memory for object.
 void
-SystemPageAllocator::DeallocateMappedContent(void *p, size_t length)
+DeallocateMappedContent(void *p, size_t length)
 {
     // Not implemented.
 }
 
 #elif defined(XP_UNIX)
 
-#include <algorithm>
-#include <errno.h>
-#include <sys/mman.h>
-#include <sys/resource.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-SystemPageAllocator::SystemPageAllocator()
+void
+InitMemorySubsystem()
 {
-    pageSize = allocGranularity = size_t(sysconf(_SC_PAGESIZE));
-    growthDirection = 0;
+    if (pageSize == 0) {
+        pageSize = allocGranularity = size_t(sysconf(_SC_PAGESIZE));
+        growthDirection = 0;
+    }
 }
 
 static inline void *
 MapMemoryAt(void *desired, size_t length, int prot = PROT_READ | PROT_WRITE,
             int flags = MAP_PRIVATE | MAP_ANON, int fd = -1, off_t offset = 0)
 {
 #if defined(__ia64__)
     MOZ_ASSERT(0xffff800000000000ULL & (uintptr_t(desired) + length - 1) == 0);
@@ -383,17 +437,17 @@ MapMemory(size_t length, int prot = PROT
     void *region = MozTaggedAnonymousMmap(nullptr, length, prot, flags, fd, offset, "js-gc-heap");
     if (region == MAP_FAILED)
         return nullptr;
     return region;
 #endif
 }
 
 void *
-SystemPageAllocator::mapAlignedPages(size_t size, size_t alignment)
+MapAlignedPages(size_t size, size_t alignment)
 {
     MOZ_ASSERT(size >= alignment);
     MOZ_ASSERT(size % alignment == 0);
     MOZ_ASSERT(size % pageSize == 0);
     MOZ_ASSERT(alignment % allocGranularity == 0);
 
     void *p = MapMemory(size);
 
@@ -401,35 +455,35 @@ SystemPageAllocator::mapAlignedPages(siz
     if (alignment == allocGranularity)
         return p;
 
     if (OffsetFromAligned(p, alignment) == 0)
         return p;
 
     void *retainedAddr;
     size_t retainedSize;
-    getNewChunk(&p, &retainedAddr, &retainedSize, size, alignment);
+    GetNewChunk(&p, &retainedAddr, &retainedSize, size, alignment);
     if (retainedAddr)
-        unmapPages(retainedAddr, retainedSize);
+        UnmapPages(retainedAddr, retainedSize);
     if (p) {
         if (OffsetFromAligned(p, alignment) == 0)
             return p;
-        unmapPages(p, size);
+        UnmapPages(p, size);
     }
 
-    p = mapAlignedPagesSlow(size, alignment);
+    p = MapAlignedPagesSlow(size, alignment);
     if (!p)
-        return mapAlignedPagesLastDitch(size, alignment);
+        return MapAlignedPagesLastDitch(size, alignment);
 
     MOZ_ASSERT(OffsetFromAligned(p, alignment) == 0);
     return p;
 }
 
-void *
-SystemPageAllocator::mapAlignedPagesSlow(size_t size, size_t alignment)
+static void *
+MapAlignedPagesSlow(size_t size, size_t alignment)
 {
     /* Overallocate and unmap the region's edges. */
     size_t reqSize = size + alignment - pageSize;
     void *region = MapMemory(reqSize);
     if (!region)
         return nullptr;
 
     void *regionEnd = (void *)(uintptr_t(region) + reqSize);
@@ -441,102 +495,102 @@ SystemPageAllocator::mapAlignedPagesSlow
         front = (void *)(uintptr_t(end) - size);
     } else {
         size_t offset = OffsetFromAligned(region, alignment);
         front = (void *)(uintptr_t(region) + (offset ? alignment - offset : 0));
         end = (void *)(uintptr_t(front) + size);
     }
 
     if (front != region)
-        unmapPages(region, uintptr_t(front) - uintptr_t(region));
+        UnmapPages(region, uintptr_t(front) - uintptr_t(region));
     if (end != regionEnd)
-        unmapPages(end, uintptr_t(regionEnd) - uintptr_t(end));
+        UnmapPages(end, uintptr_t(regionEnd) - uintptr_t(end));
 
     return front;
 }
 
 /*
  * Even though there aren't any |size + alignment - pageSize| byte chunks left,
  * the allocator may still be able to give us |size| byte chunks that are
  * either already aligned, or *can* be aligned by allocating in the nearest
  * aligned location. Since we can't tell the allocator to give us a different
  * address each time, we temporarily hold onto the unaligned part of each chunk
  * until the allocator gives us a chunk that either is, or can be aligned.
  */
-void *
-SystemPageAllocator::mapAlignedPagesLastDitch(size_t size, size_t alignment)
+static void *
+MapAlignedPagesLastDitch(size_t size, size_t alignment)
 {
     void *p = nullptr;
     void *tempMaps[MaxLastDitchAttempts];
     size_t tempSizes[MaxLastDitchAttempts];
     int attempt = 0;
     for (; attempt < MaxLastDitchAttempts; ++attempt) {
-        getNewChunk(&p, tempMaps + attempt, tempSizes + attempt, size, alignment);
+        GetNewChunk(&p, tempMaps + attempt, tempSizes + attempt, size, alignment);
         if (OffsetFromAligned(p, alignment) == 0) {
             if (tempMaps[attempt])
-                unmapPages(tempMaps[attempt], tempSizes[attempt]);
+                UnmapPages(tempMaps[attempt], tempSizes[attempt]);
             break;
         }
         if (!tempMaps[attempt]) {
-            /* getNewChunk failed, but we can still try the simpler method. */
+            /* GetNewChunk failed, but we can still try the simpler method. */
             tempMaps[attempt] = p;
             tempSizes[attempt] = size;
             p = nullptr;
         }
     }
     if (OffsetFromAligned(p, alignment)) {
-        unmapPages(p, size);
+        UnmapPages(p, size);
         p = nullptr;
     }
     while (--attempt >= 0)
-        unmapPages(tempMaps[attempt], tempSizes[attempt]);
+        UnmapPages(tempMaps[attempt], tempSizes[attempt]);
     return p;
 }
 
 /*
  * mmap calls don't have to be matched with calls to munmap, so we can unmap
  * just the pages we don't need. However, as we don't know a priori if addresses
  * are handed out in increasing or decreasing order, we have to try both
  * directions (depending on the environment, one will always fail).
  */
-void
-SystemPageAllocator::getNewChunk(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize,
-                                 size_t size, size_t alignment)
+static void
+GetNewChunk(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize, size_t size,
+            size_t alignment)
 {
     void *address = *aAddress;
     void *retainedAddr = nullptr;
     size_t retainedSize = 0;
     do {
         bool addrsGrowDown = growthDirection <= 0;
         /* Try the direction indicated by growthDirection. */
-        if (getNewChunkInner(&address, &retainedAddr, &retainedSize, size,
+        if (GetNewChunkInner(&address, &retainedAddr, &retainedSize, size,
                              alignment, addrsGrowDown)) {
             break;
         }
         /* If that failed, try the opposite direction. */
-        if (getNewChunkInner(&address, &retainedAddr, &retainedSize, size,
+        if (GetNewChunkInner(&address, &retainedAddr, &retainedSize, size,
                              alignment, !addrsGrowDown)) {
             break;
         }
         /* If retainedAddr is non-null here, we raced with another thread. */
     } while (retainedAddr);
     *aAddress = address;
     *aRetainedAddr = retainedAddr;
     *aRetainedSize = retainedSize;
 }
 
 #define SET_OUT_PARAMS_AND_RETURN(address_, retainedAddr_, retainedSize_, toReturn_)\
     do {                                                                            \
         *aAddress = address_; *aRetainedAddr = retainedAddr_;                       \
         *aRetainedSize = retainedSize_; return toReturn_;                           \
     } while(false)
 
-bool
-SystemPageAllocator::getNewChunkInner(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize,
-                                      size_t size, size_t alignment, bool addrsGrowDown)
+static bool
+GetNewChunkInner(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize, size_t size,
+                 size_t alignment, bool addrsGrowDown)
 {
     void *initial = *aAddress;
     if (!initial)
         initial = MapMemory(size);
     if (OffsetFromAligned(initial, alignment) == 0)
         SET_OUT_PARAMS_AND_RETURN(initial, nullptr, 0, true);
     /* Set the parameters based on whether addresses grow up or down. */
     size_t offset;
@@ -550,110 +604,109 @@ SystemPageAllocator::getNewChunkInner(vo
         delta = -1;
     } else {
         offset = alignment - OffsetFromAligned(initial, alignment);
         discardedAddr = (void*)(uintptr_t(initial) + offset);
         retainedAddr = initial;
         delta = 1;
     }
     /* Keep only the |offset| unaligned bytes. */
-    unmapPages(discardedAddr, size - offset);
+    UnmapPages(discardedAddr, size - offset);
     void *address = MapMemory(size);
     if (!address) {
         /* Map the rest of the original chunk again in case we can recover. */
         address = MapMemoryAt(initial, size - offset);
         if (!address)
-            unmapPages(retainedAddr, offset);
+            UnmapPages(retainedAddr, offset);
         SET_OUT_PARAMS_AND_RETURN(address, nullptr, 0, false);
     }
     if ((addrsGrowDown && address < retainedAddr) || (!addrsGrowDown && address > retainedAddr)) {
         growthDirection += delta;
         SET_OUT_PARAMS_AND_RETURN(address, retainedAddr, offset, true);
     }
     /* If we didn't choose the right direction, reduce its score. */
     growthDirection -= delta;
     /* Accept an aligned address if growthDirection didn't just flip. */
     if (OffsetFromAligned(address, alignment) == 0 && growthDirection + delta != 0)
         SET_OUT_PARAMS_AND_RETURN(address, retainedAddr, offset, true);
-    unmapPages(address, size);
+    UnmapPages(address, size);
     /* Map the original chunk again since we chose the wrong direction. */
     address = MapMemoryAt(initial, size - offset);
     if (!address) {
         /* Return non-null retainedAddr to indicate thread-related failure. */
-        unmapPages(retainedAddr, offset);
+        UnmapPages(retainedAddr, offset);
         SET_OUT_PARAMS_AND_RETURN(nullptr, retainedAddr, 0, false);
     }
     SET_OUT_PARAMS_AND_RETURN(address, nullptr, 0, false);
 }
 
 #undef SET_OUT_PARAMS_AND_RETURN
 
 void
-SystemPageAllocator::unmapPages(void *p, size_t size)
+UnmapPages(void *p, size_t size)
 {
     if (munmap(p, size))
         MOZ_ASSERT(errno == ENOMEM);
 }
 
 bool
-SystemPageAllocator::markPagesUnused(void *p, size_t size)
+MarkPagesUnused(void *p, size_t size)
 {
-    if (!decommitEnabled())
+    if (!DecommitEnabled())
         return false;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     int result = madvise(p, size, MADV_DONTNEED);
     return result != -1;
 }
 
 bool
-SystemPageAllocator::markPagesInUse(void *p, size_t size)
+MarkPagesInUse(void *p, size_t size)
 {
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     return true;
 }
 
 size_t
-SystemPageAllocator::GetPageFaultCount()
+GetPageFaultCount()
 {
     struct rusage usage;
     int err = getrusage(RUSAGE_SELF, &usage);
     if (err)
         return 0;
     return usage.ru_majflt;
 }
 
 void *
-SystemPageAllocator::AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
+AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment)
 {
 #define NEED_PAGE_ALIGNED 0
     size_t pa_start; // Page aligned starting
     size_t pa_end; // Page aligned ending
     size_t pa_size; // Total page aligned size
-    size_t page_size = sysconf(_SC_PAGESIZE); // Page size
     struct stat st;
     uint8_t *buf;
 
     // Make sure file exists and do sanity check for offset and size.
     if (fstat(fd, &st) < 0 || offset >= (size_t) st.st_size ||
         length == 0 || length > (size_t) st.st_size - offset)
         return nullptr;
 
     // Check for minimal alignment requirement.
 #if NEED_PAGE_ALIGNED
-    alignment = std::max(alignment, page_size);
+    alignment = std::max(alignment, pageSize);
 #endif
     if (offset & (alignment - 1))
         return nullptr;
 
     // Page aligned starting of the offset.
-    pa_start = offset & ~(page_size - 1);
+    pa_start = offset & ~(pageSize - 1);
     // Calculate page aligned ending by adding one page to the page aligned
     // starting of data end position(offset + length - 1).
-    pa_end = ((offset + length - 1) & ~(page_size - 1)) + page_size;
+    pa_end = ((offset + length - 1) & ~(pageSize - 1)) + pageSize;
     pa_size = pa_end - pa_start;
 
     // Ask for a continuous memory location.
     buf = (uint8_t *) MapMemory(pa_size);
     if (!buf)
         return nullptr;
 
     buf = (uint8_t *) MapMemoryAt(buf, pa_size, PROT_READ | PROT_WRITE,
@@ -666,23 +719,25 @@ SystemPageAllocator::AllocateMappedConte
 
     // Reset the data after target file, which we don't need to see.
     memset(buf + (offset - pa_start) + length, 0, pa_end - (offset + length));
 
     return buf + (offset - pa_start);
 }
 
 void
-SystemPageAllocator::DeallocateMappedContent(void *p, size_t length)
+DeallocateMappedContent(void *p, size_t length)
 {
     void *pa_start; // Page aligned starting
-    size_t page_size = sysconf(_SC_PAGESIZE); // Page size
     size_t total_size; // Total allocated size
 
-    pa_start = (void *)(uintptr_t(p) & ~(page_size - 1));
-    total_size = ((uintptr_t(p) + length) & ~(page_size - 1)) + page_size - uintptr_t(pa_start);
+    pa_start = (void *)(uintptr_t(p) & ~(pageSize - 1));
+    total_size = ((uintptr_t(p) + length) & ~(pageSize - 1)) + pageSize - uintptr_t(pa_start);
     if (munmap(pa_start, total_size))
         MOZ_ASSERT(errno == ENOMEM);
 }
 
 #else
 #error "Memory mapping functions are not defined for your OS."
 #endif
+
+} // namespace gc
+} // namespace js
--- a/js/src/gc/Memory.h
+++ b/js/src/gc/Memory.h
@@ -9,77 +9,43 @@
 
 #include <stddef.h>
 
 struct JSRuntime;
 
 namespace js {
 namespace gc {
 
-class SystemPageAllocator
-{
-  public:
-    // Sanity check that our compiled configuration matches the currently
-    // running instance and initialize any runtime data needed for allocation.
-    SystemPageAllocator();
+// Sanity check that our compiled configuration matches the currently
+// running instance and initialize any runtime data needed for allocation.
+void InitMemorySubsystem();
 
-    size_t systemPageSize() { return pageSize; }
-    size_t systemAllocGranularity() { return allocGranularity; }
-
-    // Allocate or deallocate pages from the system with the given alignment.
-    void *mapAlignedPages(size_t size, size_t alignment);
-    void unmapPages(void *p, size_t size);
+size_t SystemPageSize();
 
-    // Tell the OS that the given pages are not in use, so they should not be
-    // written to a paging file. This may be a no-op on some platforms.
-    bool markPagesUnused(void *p, size_t size);
+// Allocate or deallocate pages from the system with the given alignment.
+void *MapAlignedPages(size_t size, size_t alignment);
+void UnmapPages(void *p, size_t size);
 
-    // Undo |MarkPagesUnused|: tell the OS that the given pages are of interest
-    // and should be paged in and out normally. This may be a no-op on some
-    // platforms.
-    bool markPagesInUse(void *p, size_t size);
-
-    // Returns #(hard faults) + #(soft faults)
-    static size_t GetPageFaultCount();
-
-    // Allocate memory mapped content.
-    // The offset must be aligned according to alignment requirement.
-    static void *AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment);
+// Tell the OS that the given pages are not in use, so they should not be
+// written to a paging file. This may be a no-op on some platforms.
+bool MarkPagesUnused(void *p, size_t size);
 
-    // Deallocate memory mapped content.
-    static void DeallocateMappedContent(void *p, size_t length);
+// Undo |MarkPagesUnused|: tell the OS that the given pages are of interest
+// and should be paged in and out normally. This may be a no-op on some
+// platforms.
+bool MarkPagesInUse(void *p, size_t size);
 
-  private:
-    bool decommitEnabled();
-    void *mapAlignedPagesSlow(size_t size, size_t alignment);
-    void *mapAlignedPagesLastDitch(size_t size, size_t alignment);
-    void getNewChunk(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize,
-                     size_t size, size_t alignment);
-    bool getNewChunkInner(void **aAddress, void **aRetainedAddr, size_t *aRetainedSize,
-                          size_t size, size_t alignment, bool addrsGrowDown);
-
-    // The GC can only safely decommit memory when the page size of the
-    // running process matches the compiled arena size.
-    size_t              pageSize;
+// Returns #(hard faults) + #(soft faults)
+size_t GetPageFaultCount();
 
-    // The OS allocation granularity may not match the page size.
-    size_t              allocGranularity;
-
-#if defined(XP_UNIX)
-    // The addresses handed out by mmap may grow up or down.
-    int                 growthDirection;
-#endif
+// Allocate memory mapped content.
+// The offset must be aligned according to alignment requirement.
+void *AllocateMappedContent(int fd, size_t offset, size_t length, size_t alignment);
 
-    // The maximum number of unalignable chunks to temporarily keep alive in
-    // the last ditch allocation pass. OOM crash reports generally show <= 7
-    // unaligned chunks available (bug 1005844 comment #16).
-    static const int    MaxLastDitchAttempts = 8;
+// Deallocate memory mapped content.
+void DeallocateMappedContent(void *p, size_t length);
 
-public:
-    void *testMapAlignedPagesLastDitch(size_t size, size_t alignment) {
-        return mapAlignedPagesLastDitch(size, alignment);
-    }
-};
+void *TestMapAlignedPagesLastDitch(size_t size, size_t alignment);
 
 } // namespace gc
 } // namespace js
 
 #endif /* gc_Memory_h */
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -56,17 +56,17 @@ js::Nursery::init(uint32_t maxNurseryByt
 
     /* If no chunks are specified then the nursery is permenantly disabled. */
     if (numNurseryChunks_ == 0)
         return true;
 
     if (!hugeSlots.init())
         return false;
 
-    void *heap = runtime()->gc.pageAllocator.mapAlignedPages(nurserySize(), Alignment);
+    void *heap = MapAlignedPages(nurserySize(), Alignment);
     if (!heap)
         return false;
 
     heapStart_ = uintptr_t(heap);
     heapEnd_ = heapStart_ + nurserySize();
     currentStart_ = start();
     numActiveChunks_ = 1;
     JS_POISON(heap, JS_FRESH_NURSERY_PATTERN, nurserySize());
@@ -81,32 +81,32 @@ js::Nursery::init(uint32_t maxNurseryByt
 
     JS_ASSERT(isEnabled());
     return true;
 }
 
 js::Nursery::~Nursery()
 {
     if (start())
-        runtime()->gc.pageAllocator.unmapPages((void *)start(), nurserySize());
+        UnmapPages((void *)start(), nurserySize());
 }
 
 void
 js::Nursery::updateDecommittedRegion()
 {
 #ifndef JS_GC_ZEAL
     if (numActiveChunks_ < numNurseryChunks_) {
         // Bug 994054: madvise on MacOS is too slow to make this
         //             optimization worthwhile.
 # ifndef XP_MACOSX
         uintptr_t decommitStart = chunk(numActiveChunks_).start();
         uintptr_t decommitSize = heapEnd() - decommitStart;
         JS_ASSERT(decommitStart == AlignBytes(decommitStart, Alignment));
         JS_ASSERT(decommitSize == AlignBytes(decommitStart, Alignment));
-        runtime()->gc.pageAllocator.markPagesUnused((void *)decommitStart, decommitSize);
+        MarkPagesUnused((void *)decommitStart, decommitSize);
 # endif
     }
 #endif
 }
 
 void
 js::Nursery::enable()
 {
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -576,17 +576,17 @@ Statistics::beginSlice(int collectedCoun
     this->collectedCount = collectedCount;
     this->zoneCount = zoneCount;
     this->compartmentCount = compartmentCount;
 
     bool first = runtime->gc.state() == gc::NO_INCREMENTAL;
     if (first)
         beginGC();
 
-    SliceData data(reason, PRMJ_Now(), SystemPageAllocator::GetPageFaultCount());
+    SliceData data(reason, PRMJ_Now(), GetPageFaultCount());
     (void) slices.append(data); /* Ignore any OOMs here. */
 
     if (JSAccumulateTelemetryDataCallback cb = runtime->telemetryCallback)
         (*cb)(JS_TELEMETRY_GC_REASON, reason);
 
     // Slice callbacks should only fire for the outermost level
     if (++gcDepth == 1) {
         bool wasFullGC = collectedCount == zoneCount;
@@ -595,17 +595,17 @@ Statistics::beginSlice(int collectedCoun
                              JS::GCDescription(!wasFullGC));
     }
 }
 
 void
 Statistics::endSlice()
 {
     slices.back().end = PRMJ_Now();
-    slices.back().endFaults = SystemPageAllocator::GetPageFaultCount();
+    slices.back().endFaults = GetPageFaultCount();
 
     if (JSAccumulateTelemetryDataCallback cb = runtime->telemetryCallback) {
         (*cb)(JS_TELEMETRY_GC_SLICE_MS, t(slices.back().end - slices.back().start));
         (*cb)(JS_TELEMETRY_GC_RESET, !!slices.back().resetReason);
     }
 
     bool last = runtime->gc.state() == gc::NO_INCREMENTAL;
     if (last)
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -1234,17 +1234,17 @@ BaselineCompiler::emit_JSOP_STRING()
 
 typedef JSObject *(*DeepCloneObjectLiteralFn)(JSContext *, HandleObject, NewObjectKind);
 static const VMFunction DeepCloneObjectLiteralInfo =
     FunctionInfo<DeepCloneObjectLiteralFn>(DeepCloneObjectLiteral);
 
 bool
 BaselineCompiler::emit_JSOP_OBJECT()
 {
-    if (JS::CompartmentOptionsRef(cx).cloneSingletons(cx)) {
+    if (JS::CompartmentOptionsRef(cx).cloneSingletons()) {
         RootedObject obj(cx, script->getObject(GET_UINT32_INDEX(pc)));
         if (!obj)
             return false;
 
         prepareVMCall();
 
         pushArg(ImmWord(js::MaybeSingletonObject));
         pushArg(ImmGCPtr(obj));
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -275,12 +275,12 @@ JitCompileOptions::JitCompileOptions()
   : cloneSingletons_(false),
     spsSlowAssertionsEnabled_(false)
 {
 }
 
 JitCompileOptions::JitCompileOptions(JSContext *cx)
 {
     JS::CompartmentOptions &options = cx->compartment()->options();
-    cloneSingletons_ = options.cloneSingletons(cx);
+    cloneSingletons_ = options.cloneSingletons();
     spsSlowAssertionsEnabled_ = cx->runtime()->spsProfiler.enabled() &&
                                 cx->runtime()->spsProfiler.slowAssertionsEnabled();
 }
--- a/js/src/jit/StupidAllocator.cpp
+++ b/js/src/jit/StupidAllocator.cpp
@@ -199,18 +199,18 @@ StupidAllocator::evictRegister(LInstruct
 {
     syncRegister(ins, index);
     registers[index].set(MISSING_ALLOCATION);
 }
 
 void
 StupidAllocator::evictAliasedRegister(LInstruction *ins, RegisterIndex index)
 {
-    for (int i = 0; i < registers[index].reg.numAliased(); i++) {
-        int aindex = registerIndex(registers[index].reg.aliased(i));
+    for (size_t i = 0; i < registers[index].reg.numAliased(); i++) {
+        uint32_t aindex = registerIndex(registers[index].reg.aliased(i));
         syncRegister(ins, aindex);
         registers[aindex].set(MISSING_ALLOCATION);
     }
 }
 
 void
 StupidAllocator::loadRegister(LInstruction *ins, uint32_t vreg, RegisterIndex index, LDefinition::Type type)
 {
--- a/js/src/jsapi-tests/testGCAllocator.cpp
+++ b/js/src/jsapi-tests/testGCAllocator.cpp
@@ -87,30 +87,28 @@ testGCAllocatorUp(const size_t PageSize)
     }
     mapMemoryAt(stagingArea, StagingSize);
     // Make sure there are no available chunks below the staging area.
     int tempChunks;
     if (!fillSpaceBeforeStagingArea(tempChunks, stagingArea, chunkPool, false))
         return false;
     // Unmap the staging area so we can set it up for testing.
     unmapPages(stagingArea, StagingSize);
-    // Reuse the same allocator so it learns the address growth direction.
-    js::gc::SystemPageAllocator GCAlloc;
     // Check that the first chunk is used if it is aligned.
-    CHECK(positionIsCorrect("xxooxxx---------", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("xxooxxx---------", stagingArea, chunkPool, tempChunks));
     // Check that the first chunk is used if it can be aligned.
-    CHECK(positionIsCorrect("x-ooxxx---------", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("x-ooxxx---------", stagingArea, chunkPool, tempChunks));
     // Check that an aligned chunk after a single unalignable chunk is used.
-    CHECK(positionIsCorrect("x--xooxxx-------", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("x--xooxxx-------", stagingArea, chunkPool, tempChunks));
     // Check that we fall back to the slow path after two unalignable chunks.
-    CHECK(positionIsCorrect("x--xx--xoo--xxx-", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("x--xx--xoo--xxx-", stagingArea, chunkPool, tempChunks));
     // Check that we also fall back after an unalignable and an alignable chunk.
-    CHECK(positionIsCorrect("x--xx---x-oo--x-", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("x--xx---x-oo--x-", stagingArea, chunkPool, tempChunks));
     // Check that the last ditch allocator works as expected.
-    CHECK(positionIsCorrect("x--xx--xx-oox---", stagingArea, chunkPool, tempChunks, GCAlloc,
+    CHECK(positionIsCorrect("x--xx--xx-oox---", stagingArea, chunkPool, tempChunks,
                             UseLastDitchAllocator));
 
     // Clean up.
     while (--tempChunks >= 0)
         unmapPages(chunkPool[tempChunks], 2 * Chunk);
     return true;
 }
 
@@ -133,30 +131,28 @@ testGCAllocatorDown(const size_t PageSiz
     }
     mapMemoryAt(stagingArea, StagingSize);
     // Make sure there are no available chunks above the staging area.
     int tempChunks;
     if (!fillSpaceBeforeStagingArea(tempChunks, stagingArea, chunkPool, true))
         return false;
     // Unmap the staging area so we can set it up for testing.
     unmapPages(stagingArea, StagingSize);
-    // Reuse the same allocator so it learns the address growth direction.
-    js::gc::SystemPageAllocator GCAlloc;
     // Check that the first chunk is used if it is aligned.
-    CHECK(positionIsCorrect("---------xxxooxx", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("---------xxxooxx", stagingArea, chunkPool, tempChunks));
     // Check that the first chunk is used if it can be aligned.
-    CHECK(positionIsCorrect("---------xxxoo-x", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("---------xxxoo-x", stagingArea, chunkPool, tempChunks));
     // Check that an aligned chunk after a single unalignable chunk is used.
-    CHECK(positionIsCorrect("-------xxxoox--x", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("-------xxxoox--x", stagingArea, chunkPool, tempChunks));
     // Check that we fall back to the slow path after two unalignable chunks.
-    CHECK(positionIsCorrect("-xxx--oox--xx--x", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("-xxx--oox--xx--x", stagingArea, chunkPool, tempChunks));
     // Check that we also fall back after an unalignable and an alignable chunk.
-    CHECK(positionIsCorrect("-x--oo-x---xx--x", stagingArea, chunkPool, tempChunks, GCAlloc));
+    CHECK(positionIsCorrect("-x--oo-x---xx--x", stagingArea, chunkPool, tempChunks));
     // Check that the last ditch allocator works as expected.
-    CHECK(positionIsCorrect("---xoo-xx--xx--x", stagingArea, chunkPool, tempChunks, GCAlloc,
+    CHECK(positionIsCorrect("---xoo-xx--xx--x", stagingArea, chunkPool, tempChunks,
                             UseLastDitchAllocator));
 
     // Clean up.
     while (--tempChunks >= 0)
         unmapPages(chunkPool[tempChunks], 2 * Chunk);
     return true;
 }
 
@@ -189,17 +185,17 @@ fillSpaceBeforeStagingArea(int &tempChun
         unmapPages(stagingArea, StagingSize);
         return false;
     }
     return true;
 }
 
 bool
 positionIsCorrect(const char *str, void *base, void **chunkPool, int tempChunks,
-                  js::gc::SystemPageAllocator& GCAlloc, AllocType allocator = UseNormalAllocator)
+                  AllocType allocator = UseNormalAllocator)
 {
     // str represents a region of memory, with each character representing a
     // region of Chunk bytes. str should contain only x, o and -, where
     // x = mapped by the test to set up the initial conditions,
     // o = mapped by the GC allocator, and
     // - = unmapped.
     // base should point to a region of contiguous free memory
     // large enough to hold strlen(str) chunks of Chunk bytes.
@@ -211,30 +207,30 @@ positionIsCorrect(const char *str, void 
     // Map the regions indicated by str.
     for (i = 0; i < len; ++i) {
         if (str[i] == 'x')
             mapMemoryAt((void *)(uintptr_t(base) +  i * Chunk), Chunk);
     }
     // Allocate using the GC's allocator.
     void *result;
     if (allocator == UseNormalAllocator)
-        result = GCAlloc.mapAlignedPages(2 * Chunk, Alignment);
+        result = js::gc::MapAlignedPages(2 * Chunk, Alignment);
     else
-        result = GCAlloc.testMapAlignedPagesLastDitch(2 * Chunk, Alignment);
+        result = js::gc::TestMapAlignedPagesLastDitch(2 * Chunk, Alignment);
     // Clean up the mapped regions.
     if (result)
-        GCAlloc.unmapPages(result, 2 * Chunk);
+        js::gc::UnmapPages(result, 2 * Chunk);
     for (--i; i >= 0; --i) {
         if (str[i] == 'x')
-            unmapPages((void *)(uintptr_t(base) +  i * Chunk), Chunk);
+            js::gc::UnmapPages((void *)(uintptr_t(base) +  i * Chunk), Chunk);
     }
     // CHECK returns, so clean up on failure.
     if (result != desired) {
         while (--tempChunks >= 0)
-            unmapPages(chunkPool[tempChunks], 2 * Chunk);
+            js::gc::UnmapPages(chunkPool[tempChunks], 2 * Chunk);
     }
     return result == desired;
 }
 
 #if defined(XP_WIN)
 
 void *
 mapMemoryAt(void *desired, size_t length)
--- a/js/src/jsapi-tests/testJSEvaluateScript.cpp
+++ b/js/src/jsapi-tests/testJSEvaluateScript.cpp
@@ -4,33 +4,33 @@
 
 #include "jsapi-tests/tests.h"
 
 BEGIN_TEST(testJSEvaluateScript)
 {
     JS::RootedObject obj(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), global));
     CHECK(obj);
 
-    CHECK(JS::ContextOptionsRef(cx).varObjFix());
+    CHECK(JS::RuntimeOptionsRef(cx).varObjFix());
 
     static const char src[] = "var x = 5;";
 
     JS::RootedValue retval(cx);
     CHECK(JS_EvaluateScript(cx, obj, src, sizeof(src) - 1, __FILE__, __LINE__, &retval));
 
     bool hasProp = true;
     CHECK(JS_AlreadyHasOwnProperty(cx, obj, "x", &hasProp));
     CHECK(!hasProp);
 
     hasProp = false;
     CHECK(JS_HasProperty(cx, global, "x", &hasProp));
     CHECK(hasProp);
 
     // Now do the same thing, but without JSOPTION_VAROBJFIX
-    JS::ContextOptionsRef(cx).setVarObjFix(false);
+    JS::RuntimeOptionsRef(cx).setVarObjFix(false);
 
     static const char src2[] = "var y = 5;";
 
     CHECK(JS_EvaluateScript(cx, obj, src2, sizeof(src2) - 1, __FILE__, __LINE__, &retval));
 
     hasProp = false;
     CHECK(JS_AlreadyHasOwnProperty(cx, obj, "y", &hasProp));
     CHECK(hasProp);
--- a/js/src/jsapi-tests/tests.h
+++ b/js/src/jsapi-tests/tests.h
@@ -293,16 +293,17 @@ class JSAPITest
         JS_SetNativeStackQuota(rt, MAX_STACK_SIZE);
     }
 
     virtual JSRuntime * createRuntime() {
         JSRuntime *rt = JS_NewRuntime(8L * 1024 * 1024);
         if (!rt)
             return nullptr;
         setNativeStackQuota(rt);
+        JS::RuntimeOptionsRef(rt).setVarObjFix(true);
         return rt;
     }
 
     virtual void destroyRuntime() {
         JS_ASSERT(!cx);
         JS_ASSERT(rt);
         JS_DestroyRuntime(rt);
     }
@@ -313,17 +314,16 @@ class JSAPITest
                 (unsigned int) report->lineno,
                 message);
     }
 
     virtual JSContext * createContext() {
         JSContext *cx = JS_NewContext(rt, 8192);
         if (!cx)
             return nullptr;
-        JS::ContextOptionsRef(cx).setVarObjFix(true);
         JS_SetErrorReporter(cx, &reportError);
         return cx;
     }
 
     virtual const JSClass * getGlobalClass() {
         return basicGlobalClass();
     }
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -2362,22 +2362,16 @@ class AutoCompartmentRooter : private JS
 
   private:
     JSCompartment *compartment;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 } /* anonymous namespace */
 
-bool
-JS::CompartmentOptions::cloneSingletons(JSContext *cx) const
-{
-    return cloneSingletonsOverride_.get(cx->options().cloneSingletons());
-}
-
 JS::CompartmentOptions &
 JS::CompartmentOptions::setZone(ZoneSpecifier spec)
 {
     zone_.spec = spec;
     return *this;
 }
 
 JS::CompartmentOptions &
@@ -4473,17 +4467,17 @@ JS::OwningCompileOptions::setIntroducerF
 JS::CompileOptions::CompileOptions(JSContext *cx, JSVersion version)
     : ReadOnlyCompileOptions(), elementRoot(cx), elementAttributeNameRoot(cx),
       introductionScriptRoot(cx)
 {
     this->version = (version != JSVERSION_UNKNOWN) ? version : cx->findVersion();
 
     compileAndGo = false;
     noScriptRval = cx->options().noScriptRval();
-    strictOption = cx->options().strictMode();
+    strictOption = cx->runtime()->options().strictMode();
     extraWarningsOption = cx->options().extraWarnings();
     werrorOption = cx->runtime()->options().werror();
     asmJSOption = cx->runtime()->options().asmJS();
 }
 
 bool
 JS::Compile(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &options,
             SourceBufferHolder &srcBuf, MutableHandleScript script)
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -728,18 +728,17 @@ typedef struct JSErrorFormatString {
     /* The number of arguments to expand in the formatted error message. */
     uint16_t argCount;
 
     /* One of the JSExnType constants above. */
     int16_t exnType;
 } JSErrorFormatString;
 
 typedef const JSErrorFormatString *
-(* JSErrorCallback)(void *userRef, const char *locale,
-                    const unsigned errorNumber);
+(* JSErrorCallback)(void *userRef, const unsigned errorNumber);
 
 typedef bool
 (* JSLocaleToUpperCase)(JSContext *cx, JS::HandleString src, JS::MutableHandleValue rval);
 
 typedef bool
 (* JSLocaleToLowerCase)(JSContext *cx, JS::HandleString src, JS::MutableHandleValue rval);
 
 typedef bool
@@ -1418,17 +1417,19 @@ namespace JS {
 
 class JS_PUBLIC_API(RuntimeOptions) {
   public:
     RuntimeOptions()
       : baseline_(false),
         ion_(false),
         asmJS_(false),
         nativeRegExp_(false),
-        werror_(false)
+        werror_(false),
+        strictMode_(false),
+        varObjFix_(false)
     {
     }
 
     bool baseline() const { return baseline_; }
     RuntimeOptions &setBaseline(bool flag) {
         baseline_ = flag;
         return *this;
     }
@@ -1468,64 +1469,73 @@ class JS_PUBLIC_API(RuntimeOptions) {
         werror_ = flag;
         return *this;
     }
     RuntimeOptions &toggleWerror() {
         werror_ = !werror_;
         return *this;
     }
 
+    bool strictMode() const { return strictMode_; }
+    RuntimeOptions &setStrictMode(bool flag) {
+        strictMode_ = flag;
+        return *this;
+    }
+    RuntimeOptions &toggleStrictMode() {
+        strictMode_ = !strictMode_;
+        return *this;
+    }
+
+    bool varObjFix() const { return varObjFix_; }
+    RuntimeOptions &setVarObjFix(bool flag) {
+        varObjFix_ = flag;
+        return *this;
+    }
+    RuntimeOptions &toggleVarObjFix() {
+        varObjFix_ = !varObjFix_;
+        return *this;
+    }
+
   private:
     bool baseline_ : 1;
     bool ion_ : 1;
     bool asmJS_ : 1;
     bool nativeRegExp_ : 1;
     bool werror_ : 1;
+    bool strictMode_ : 1;
+    bool varObjFix_ : 1;
 };
 
 JS_PUBLIC_API(RuntimeOptions &)
 RuntimeOptionsRef(JSRuntime *rt);
 
 JS_PUBLIC_API(RuntimeOptions &)
 RuntimeOptionsRef(JSContext *cx);
 
 class JS_PUBLIC_API(ContextOptions) {
   public:
     ContextOptions()
       : extraWarnings_(false),
-        varObjFix_(false),
         privateIsNSISupports_(false),
         dontReportUncaught_(false),
         noDefaultCompartmentObject_(false),
-        noScriptRval_(false),
-        strictMode_(false),
-        cloneSingletons_(false)
+        noScriptRval_(false)
     {
     }
 
     bool extraWarnings() const { return extraWarnings_; }
     ContextOptions &setExtraWarnings(bool flag) {
         extraWarnings_ = flag;
         return *this;
     }
     ContextOptions &toggleExtraWarnings() {
         extraWarnings_ = !extraWarnings_;
         return *this;
     }
 
-    bool varObjFix() const { return varObjFix_; }
-    ContextOptions &setVarObjFix(bool flag) {
-        varObjFix_ = flag;
-        return *this;
-    }
-    ContextOptions &toggleVarObjFix() {
-        varObjFix_ = !varObjFix_;
-        return *this;
-    }
-
     bool privateIsNSISupports() const { return privateIsNSISupports_; }
     ContextOptions &setPrivateIsNSISupports(bool flag) {
         privateIsNSISupports_ = flag;
         return *this;
     }
     ContextOptions &togglePrivateIsNSISupports() {
         privateIsNSISupports_ = !privateIsNSISupports_;
         return *this;
@@ -1556,45 +1566,22 @@ class JS_PUBLIC_API(ContextOptions) {
         noScriptRval_ = flag;
         return *this;
     }
     ContextOptions &toggleNoScriptRval() {
         noScriptRval_ = !noScriptRval_;
         return *this;
     }
 
-    bool strictMode() const { return strictMode_; }
-    ContextOptions &setStrictMode(bool flag) {
-        strictMode_ = flag;
-        return *this;
-    }
-    ContextOptions &toggleStrictMode() {
-        strictMode_ = !strictMode_;
-        return *this;
-    }
-
-    bool cloneSingletons() const { return cloneSingletons_; }
-    ContextOptions &setCloneSingletons(bool flag) {
-        cloneSingletons_ = flag;
-        return *this;
-    }
-    ContextOptions &toggleCloneSingletons() {
-        cloneSingletons_ = !cloneSingletons_;
-        return *this;
-    }
-
   private:
     bool extraWarnings_ : 1;
-    bool varObjFix_ : 1;
     bool privateIsNSISupports_ : 1;
     bool dontReportUncaught_ : 1;
     bool noDefaultCompartmentObject_ : 1;
     bool noScriptRval_ : 1;
-    bool strictMode_ : 1;
-    bool cloneSingletons_ : 1;
 };
 
 JS_PUBLIC_API(ContextOptions &)
 ContextOptionsRef(JSContext *cx);
 
 class JS_PUBLIC_API(AutoSaveContextOptions) {
   public:
     explicit AutoSaveContextOptions(JSContext *cx)
@@ -2567,16 +2554,17 @@ class JS_PUBLIC_API(CompartmentOptions)
         Mode mode_;
     };
 
     explicit CompartmentOptions()
       : version_(JSVERSION_UNKNOWN)
       , invisibleToDebugger_(false)
       , mergeable_(false)
       , discardSource_(false)
+      , cloneSingletons_(false)
       , traceGlobal_(nullptr)
       , singletonsAsTemplates_(true)
       , addonId_(nullptr)
     {
         zone_.spec = JS::FreshZone;
     }
 
     JSVersion version() const { return version_; }
@@ -2610,18 +2598,21 @@ class JS_PUBLIC_API(CompartmentOptions)
     // that we can discard script source entirely.
     bool discardSource() const { return discardSource_; }
     CompartmentOptions &setDiscardSource(bool flag) {
         discardSource_ = flag;
         return *this;
     }
 
 
-    bool cloneSingletons(JSContext *cx) const;
-    Override &cloneSingletonsOverride() { return cloneSingletonsOverride_; }
+    bool cloneSingletons() const { return cloneSingletons_; }
+    CompartmentOptions &setCloneSingletons(bool flag) {
+        cloneSingletons_ = flag;
+        return *this;
+    }
 
     void *zonePointer() const {
         JS_ASSERT(uintptr_t(zone_.pointer) > uintptr_t(JS::SystemZone));
         return zone_.pointer;
     }
     ZoneSpecifier zoneSpecifier() const { return zone_.spec; }
     CompartmentOptions &setZone(ZoneSpecifier spec);
     CompartmentOptions &setSameZoneAs(JSObject *obj);
@@ -2649,17 +2640,17 @@ class JS_PUBLIC_API(CompartmentOptions)
         return traceGlobal_;
     }
 
   private:
     JSVersion version_;
     bool invisibleToDebugger_;
     bool mergeable_;
     bool discardSource_;
-    Override cloneSingletonsOverride_;
+    bool cloneSingletons_;
     union {
         ZoneSpecifier spec;
         void *pointer; // js::Zone* is not exposed in the API.
     } zone_;
     JSTraceOp traceGlobal_;
 
     // To XDR singletons, we need to ensure that all singletons are all used as
     // templates, by making JSOP_OBJECT return a clone of the JSScript
@@ -4531,17 +4522,16 @@ JS_ResetDefaultLocale(JSRuntime *rt);
 /*
  * Locale specific string conversion and error message callbacks.
  */
 struct JSLocaleCallbacks {
     JSLocaleToUpperCase     localeToUpperCase;
     JSLocaleToLowerCase     localeToLowerCase;
     JSLocaleCompare         localeCompare; // not used #if EXPOSE_INTL_API
     JSLocaleToUnicode       localeToUnicode;
-    JSErrorCallback         localeGetErrorMessage;
 };
 
 /*
  * Establish locale callbacks. The pointer must persist as long as the
  * JSRuntime.  Passing nullptr restores the default behaviour.
  */
 extern JS_PUBLIC_API(void)
 JS_SetLocaleCallbacks(JSRuntime *rt, JSLocaleCallbacks *callbacks);
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -386,18 +386,17 @@ js_ReportOutOfMemory(ThreadSafeContext *
     }
 
     if (JS_IsRunning(cx)) {
         cx->setPendingException(StringValue(cx->names().outOfMemory));
         return;
     }
 
     /* Get the message for this error, but we don't expand any arguments. */
-    const JSErrorFormatString *efs =
-        js_GetLocalizedErrorMessage(cx, nullptr, nullptr, JSMSG_OUT_OF_MEMORY);
+    const JSErrorFormatString *efs = js_GetErrorMessage(nullptr, JSMSG_OUT_OF_MEMORY);
     const char *msg = efs ? efs->format : "Out of memory";
 
     /* Fill out the report, but don't do anything that requires allocation. */
     JSErrorReport report;
     PodZero(&report);
     report.flags = JSREPORT_ERROR;
     report.errorNumber = JSMSG_OUT_OF_MEMORY;
     PopulateReportBlame(cx, &report);
@@ -666,23 +665,24 @@ js_ExpandErrorArguments(ExclusiveContext
 {
     const JSErrorFormatString *efs;
     int i;
     int argCount;
     bool messageArgsPassed = !!reportp->messageArgs;
 
     *messagep = nullptr;
 
-    /* Most calls supply js_GetErrorMessage; if this is so, assume nullptr. */
-    if (!callback || callback == js_GetErrorMessage) {
-        efs = js_GetLocalizedErrorMessage(cx, userRef, nullptr, errorNumber);
-    } else {
+    if (!callback)
+        callback = js_GetErrorMessage;
+
+    {
         AutoSuppressGC suppressGC(cx);
-        efs = callback(userRef, nullptr, errorNumber);
+        efs = callback(userRef, errorNumber);
     }
+
     if (efs) {
         reportp->exnType = efs->exnType;
 
         size_t totalArgsLength = 0;
         size_t argLengths[10]; /* only {0} thru {9} supported */
         argCount = efs->argCount;
         JS_ASSERT(argCount <= 10);
         if (argCount > 0) {
@@ -999,19 +999,19 @@ js_ReportValueErrorFlags(JSContext *cx, 
 const JSErrorFormatString js_ErrorFormatString[JSErr_Limit] = {
 #define MSG_DEF(name, number, count, exception, format) \
     { format, count, exception } ,
 #include "js.msg"
 #undef MSG_DEF
 };
 
 JS_FRIEND_API(const JSErrorFormatString *)
-js_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber)
+js_GetErrorMessage(void *userRef, const unsigned errorNumber)
 {
-    if ((errorNumber > 0) && (errorNumber < JSErr_Limit))
+    if (errorNumber > 0 && errorNumber < JSErr_Limit)
         return &js_ErrorFormatString[errorNumber];
     return nullptr;
 }
 
 bool
 js::InvokeInterruptCallback(JSContext *cx)
 {
     JS_ASSERT_REQUEST_DEPTH(cx);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -271,17 +271,17 @@ struct ThreadSafeContext : ContextFriend
     StaticStrings &staticStrings() { return *runtime_->staticStrings; }
     AtomSet &permanentAtoms() { return *runtime_->permanentAtoms; }
     const JS::AsmJSCacheOps &asmJSCacheOps() { return runtime_->asmJSCacheOps; }
     PropertyName *emptyString() { return runtime_->emptyString; }
     FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
     void *runtimeAddressForJit() { return runtime_; }
     void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
     void *stackLimitAddressForJitCode(StackKind kind);
-    size_t gcSystemPageSize() { return runtime_->gc.pageAllocator.systemPageSize(); }
+    size_t gcSystemPageSize() { return gc::SystemPageSize(); }
     bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
     bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
 
     // Thread local data that may be accessed freely.
     DtoaState *dtoaState() {
         return perThreadData->dtoaState;
     }
 };
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -574,38 +574,16 @@ ErrorObject::createConstructor(JSContext
     ctor = GenericCreateConstructor<Error, 1, JSFunction::ExtendedFinalizeKind>(cx, key);
     if (!ctor)
         return nullptr;
 
     ctor->as<JSFunction>().setExtendedSlot(0, Int32Value(ExnTypeFromProtoKey(key)));
     return ctor;
 }
 
-const JSErrorFormatString*
-js_GetLocalizedErrorMessage(ExclusiveContext *cx, void *userRef, const char *locale,
-                            const unsigned errorNumber)
-{
-    const JSErrorFormatString *errorString = nullptr;
-
-    // The locale callbacks might not be thread safe, so don't call them if
-    // we're not on the main thread. When used with XPConnect,
-    // |localeGetErrorMessage| will be nullptr anyways.
-    if (cx->isJSContext() &&
-        cx->asJSContext()->runtime()->localeCallbacks &&
-        cx->asJSContext()->runtime()->localeCallbacks->localeGetErrorMessage)
-    {
-        JSLocaleCallbacks *callbacks = cx->asJSContext()->runtime()->localeCallbacks;
-        errorString = callbacks->localeGetErrorMessage(userRef, locale, errorNumber);
-    }
-
-    if (!errorString)
-        errorString = js_GetErrorMessage(userRef, locale, errorNumber);
-    return errorString;
-}
-
 JS_FRIEND_API(JSFlatString *)
 js::GetErrorTypeName(JSRuntime *rt, int16_t exnType)
 {
     /*
      * JSEXN_INTERNALERR returns null to prevent that "InternalError: "
      * is prepended before "uncaught exception: "
      */
     if (exnType <= JSEXN_NONE || exnType >= JSEXN_LIMIT ||
@@ -623,21 +601,19 @@ js_ErrorToException(JSContext *cx, const
 {
     // Tell our caller to report immediately if this report is just a warning.
     JS_ASSERT(reportp);
     if (JSREPORT_IS_WARNING(reportp->flags))
         return false;
 
     // Find the exception index associated with this error.
     JSErrNum errorNumber = static_cast<JSErrNum>(reportp->errorNumber);
-    const JSErrorFormatString *errorString;
-    if (!callback || callback == js_GetErrorMessage)
-        errorString = js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber);
-    else
-        errorString = callback(userRef, nullptr, errorNumber);
+    if (!callback)
+        callback = js_GetErrorMessage;
+    const JSErrorFormatString *errorString = callback(userRef, errorNumber);
     JSExnType exnType = errorString ? static_cast<JSExnType>(errorString->exnType) : JSEXN_NONE;
     MOZ_ASSERT(exnType < JSEXN_LIMIT);
 
     // Return false (no exception raised) if no exception is associated
     // with the given error number.
     if (exnType == JSEXN_NONE)
         return false;
 
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -71,20 +71,16 @@ js_ErrorToException(JSContext *cx, const
  * this flag.
  */
 extern bool
 js_ReportUncaughtException(JSContext *cx);
 
 extern JSErrorReport *
 js_ErrorFromException(JSContext *cx, js::HandleObject obj);
 
-extern const JSErrorFormatString *
-js_GetLocalizedErrorMessage(js::ExclusiveContext *cx, void *userRef, const char *locale,
-                            const unsigned errorNumber);
-
 /*
  * Make a copy of errobj parented to cx's compartment's global.
  *
  * errobj may be in a different compartment than cx, but it must be an Error
  * object (not a wrapper of one) and it must not be one of the standard error
  * prototype objects (errobj->getPrivate() must not be nullptr).
  */
 extern JSObject *
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1237,17 +1237,17 @@ typedef enum JSErrNum {
 #define MSG_DEF(name, number, count, exception, format) \
     name = number,
 #include "js.msg"
 #undef MSG_DEF
     JSErr_Limit
 } JSErrNum;
 
 extern JS_FRIEND_API(const JSErrorFormatString *)
-js_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber);
+js_GetErrorMessage(void *userRef, const unsigned errorNumber);
 
 namespace js {
 
 // Creates a string of the form |ErrorType: ErrorMessage| for a JSErrorReport,
 // which generally matches the toString() behavior of an ErrorObject.
 extern JS_FRIEND_API(JSString *)
 ErrorReportToString(JSContext *cx, JSErrorReport *reportp);
 
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -622,23 +622,23 @@ FinalizeArenas(FreeOp *fop,
       default:
         MOZ_CRASH("Invalid alloc kind");
     }
 }
 
 static inline Chunk *
 AllocChunk(JSRuntime *rt)
 {
-    return static_cast<Chunk *>(rt->gc.pageAllocator.mapAlignedPages(ChunkSize, ChunkSize));
+    return static_cast<Chunk *>(MapAlignedPages(ChunkSize, ChunkSize));
 }
 
 static inline void
 FreeChunk(JSRuntime *rt, Chunk *p)
 {
-    rt->gc.pageAllocator.unmapPages(static_cast<void *>(p), ChunkSize);
+    UnmapPages(static_cast<void *>(p), ChunkSize);
 }
 
 /* Must be called with the GC lock taken. */
 inline Chunk *
 ChunkPool::get(JSRuntime *rt)
 {
     Chunk *chunk = emptyChunkListHead;
     if (!chunk) {
@@ -770,17 +770,17 @@ GCRuntime::prepareToFreeChunk(ChunkInfo 
      */
     info.numArenasFreeCommitted = 0;
 #endif
 }
 
 void Chunk::decommitAllArenas(JSRuntime *rt)
 {
     decommittedArenas.clear(true);
-    rt->gc.pageAllocator.markPagesUnused(&arenas[0], ArenasPerChunk * ArenaSize);
+    MarkPagesUnused(&arenas[0], ArenasPerChunk * ArenaSize);
 
     info.freeArenasHead = nullptr;
     info.lastDecommittedArenaOffset = 0;
     info.numArenasFree = ArenasPerChunk;
     info.numArenasFreeCommitted = 0;
 }
 
 void
@@ -879,17 +879,17 @@ Chunk::fetchNextDecommittedArena()
     JS_ASSERT(info.numArenasFree > 0);
 
     unsigned offset = findDecommittedArenaOffset();
     info.lastDecommittedArenaOffset = offset + 1;
     --info.numArenasFree;
     decommittedArenas.unset(offset);
 
     Arena *arena = &arenas[offset];
-    info.trailer.runtime->gc.pageAllocator.markPagesInUse(arena, ArenaSize);
+    MarkPagesInUse(arena, ArenaSize);
     arena->aheader.setAsNotAllocated();
 
     return &arena->aheader;
 }
 
 inline void
 GCRuntime::updateOnFreeArenaAlloc(const ChunkInfo &info)
 {
@@ -907,17 +907,17 @@ Chunk::fetchNextFreeArena(JSRuntime *rt)
     info.freeArenasHead = aheader->next;
     --info.numArenasFreeCommitted;
     --info.numArenasFree;
     rt->gc.updateOnFreeArenaAlloc(info);
 
     return aheader;
 }
 
-void
+inline void
 GCRuntime::updateBytesAllocated(ptrdiff_t size)
 {
     JS_ASSERT_IF(size < 0, bytes >= size_t(-size));
     bytes += size;
 }
 
 ArenaHeader *
 Chunk::allocateArena(Zone *zone, AllocKind thingKind)
@@ -1271,16 +1271,18 @@ GCRuntime::initZeal()
 #endif
 
 /* Lifetime for type sets attached to scripts containing observed types. */
 static const int64_t JIT_SCRIPT_RELEASE_TYPES_INTERVAL = 60 * 1000 * 1000;
 
 bool
 GCRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes)
 {
+    InitMemorySubsystem();
+
 #ifdef JS_THREADSAFE
     lock = PR_NewLock();
     if (!lock)
         return false;
 #endif
 
     if (!chunkSet.init(INITIAL_CHUNK_CAPACITY))
         return false;
@@ -2501,17 +2503,17 @@ GCRuntime::decommitArenasFromAvailableLi
                 /*
                  * If the main thread waits for the decommit to finish, skip
                  * potentially expensive unlock/lock pair on the contested
                  * lock.
                  */
                 Maybe<AutoUnlockGC> maybeUnlock;
                 if (!isHeapBusy())
                     maybeUnlock.construct(rt);
-                ok = pageAllocator.markPagesUnused(aheader->getArena(), ArenaSize);
+                ok = MarkPagesUnused(aheader->getArena(), ArenaSize);
             }
 
             if (ok) {
                 ++chunk->info.numArenasFree;
                 chunk->decommittedArenas.set(arenaIndex);
             } else {
                 chunk->addArenaToFreeList(rt, aheader);
             }
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -193,17 +193,17 @@ NewContext(JSRuntime *rt);
 static void
 DestroyContext(JSContext *cx, bool withGC);
 
 static JSObject *
 NewGlobalObject(JSContext *cx, JS::CompartmentOptions &options,
                 JSPrincipals *principals);
 
 static const JSErrorFormatString *
-my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber);
+my_GetErrorMessage(void *userRef, const unsigned errorNumber);
 
 
 /*
  * A toy principals type for the shell.
  *
  * In the shell, a principal is simply a 32-bit mask: P subsumes Q if the
  * set bits in P are a superset of those in Q. Thus, the principal 0 is
  * subsumed by everything, and the principal ~0 subsumes everything.
@@ -785,17 +785,17 @@ Options(JSContext *cx, unsigned argc, js
         if (!opt)
             return false;
 
         if (strcmp(opt.ptr(), "strict") == 0)
             JS::ContextOptionsRef(cx).toggleExtraWarnings();
         else if (strcmp(opt.ptr(), "werror") == 0)
             JS::RuntimeOptionsRef(cx).toggleWerror();
         else if (strcmp(opt.ptr(), "strict_mode") == 0)
-            JS::ContextOptionsRef(cx).toggleStrictMode();
+            JS::RuntimeOptionsRef(cx).toggleStrictMode();
         else {
             JS_ReportError(cx,
                            "unknown option name '%s'."
                            " The valid names are strict,"
                            " werror, and strict_mode.",
                            opt.ptr());
             return false;
         }
@@ -806,17 +806,17 @@ Options(JSContext *cx, unsigned argc, js
     if (names && oldContextOptions.extraWarnings()) {
         names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict");
         found = true;
     }
     if (names && oldRuntimeOptions.werror()) {
         names = JS_sprintf_append(names, "%s%s", found ? "," : "", "werror");
         found = true;
     }
-    if (names && oldContextOptions.strictMode()) {
+    if (names && oldRuntimeOptions.strictMode()) {
         names = JS_sprintf_append(names, "%s%s", found ? "," : "", "strict_mode");
         found = true;
     }
     if (!names) {
         JS_ReportOutOfMemory(cx);
         return false;
     }
 
@@ -1256,17 +1256,17 @@ Evaluate(JSContext *cx, unsigned argc, j
             JS::AutoSaveContextOptions asco(cx);
             JS::ContextOptionsRef(cx).setNoScriptRval(options.noScriptRval);
             if (saveBytecode) {
                 if (!JS::CompartmentOptionsRef(cx).getSingletonsAsTemplates()) {
                     JS_ReportErrorNumber(cx, my_GetErrorMessage, nullptr,
                                          JSSMSG_CACHE_SINGLETON_FAILED);
                     return false;
                 }
-                JS::CompartmentOptionsRef(cx).cloneSingletonsOverride().set(true);
+                JS::CompartmentOptionsRef(cx).setCloneSingletons(true);
             }
 
             if (loadBytecode) {
                 script = JS_DecodeScript(cx, loadBuffer, loadLength, options.originPrincipals(cx));
             } else {
                 mozilla::Range<const jschar> chars = codeChars.twoByteRange();
                 (void) JS::Compile(cx, global, options, chars.start().get(), chars.length(), &script);
             }
@@ -5110,17 +5110,17 @@ Help(JSContext *cx, unsigned argc, jsval
 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
 #define MSG_DEF(name, number, count, exception, format) \
     { format, count, JSEXN_ERR } ,
 #include "jsshell.msg"
 #undef MSG_DEF
 };
 
 static const JSErrorFormatString *
-my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber)
+my_GetErrorMessage(void *userRef, const unsigned errorNumber)
 {
     if (errorNumber == 0 || errorNumber >= JSShellErr_Limit)
         return nullptr;
 
     return &jsShell_ErrorFormatString[errorNumber];
 }
 
 static void
--- a/js/src/vm/ArrayBufferObject.cpp
+++ b/js/src/vm/ArrayBufferObject.cpp
@@ -531,26 +531,26 @@ ArrayBufferObject::canNeuterAsmJSArrayBu
 #else
     return true;
 #endif
 }
 
 void *
 ArrayBufferObject::createMappedContents(int fd, size_t offset, size_t length)
 {
-    return SystemPageAllocator::AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
+    return AllocateMappedContent(fd, offset, length, ARRAY_BUFFER_ALIGNMENT);
 }
 
 void
 ArrayBufferObject::releaseMappedArray()
 {
     if(!isMappedArrayBuffer() || isNeutered())
         return;
 
-    SystemPageAllocator::DeallocateMappedContent(dataPointer(), byteLength());
+    DeallocateMappedContent(dataPointer(), byteLength());
 }
 
 void
 ArrayBufferObject::addView(ArrayBufferViewObject *view)
 {
     // Note that pre-barriers are not needed here because either the list was
     // previously empty, in which case no pointer is being overwritten, or the
     // list was nonempty and will be made weak during this call (and weak
@@ -1142,17 +1142,17 @@ JS_PUBLIC_API(void *)
 JS_CreateMappedArrayBufferContents(int fd, size_t offset, size_t length)
 {
     return ArrayBufferObject::createMappedContents(fd, offset, length);
 }
 
 JS_PUBLIC_API(void)
 JS_ReleaseMappedArrayBufferContents(void *contents, size_t length)
 {
-    SystemPageAllocator::DeallocateMappedContent(contents, length);
+    DeallocateMappedContent(contents, length);
 }
 
 JS_FRIEND_API(bool)
 JS_IsMappedArrayBufferObject(JSObject *obj)
 {
     obj = CheckedUnwrap(obj);
     if (!obj)
         return false;
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -633,17 +633,17 @@ js::Execute(JSContext *cx, HandleScript 
     JSObject *s = scopeChain;
     do {
         assertSameCompartment(cx, s);
         JS_ASSERT_IF(!s->enclosingScope(), s->is<GlobalObject>());
     } while ((s = s->enclosingScope()));
 #endif
 
     /* The VAROBJFIX option makes varObj == globalObj in global code. */
-    if (!cx->options().varObjFix()) {
+    if (!cx->runtime()->options().varObjFix()) {
         if (!scopeChain->setVarObj(cx))
             return false;
     }
 
     /* Use the scope chain as 'this', modulo outerization. */
     JSObject *thisObj = JSObject::thisObject(cx, scopeChain);
     if (!thisObj)
         return false;
@@ -2728,17 +2728,17 @@ CASE(JSOP_TOSTRING)
     }
 }
 END_CASE(JSOP_TOSTRING)
 
 CASE(JSOP_OBJECT)
 {
     RootedObject &ref = rootObject0;
     ref = script->getObject(REGS.pc);
-    if (JS::CompartmentOptionsRef(cx).cloneSingletons(cx)) {
+    if (JS::CompartmentOptionsRef(cx).cloneSingletons()) {
         JSObject *obj = js::DeepCloneObjectLiteral(cx, ref, js::MaybeSingletonObject);
         if (!obj)
             goto error;
         PUSH_OBJECT(*obj);
     } else {
         JS::CompartmentOptionsRef(cx).setSingletonsAsValues();
         PUSH_OBJECT(*ref);
     }
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -115,18 +115,17 @@ intrinsic_IsConstructor(JSContext *cx, u
 bool
 js::intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     JS_ASSERT(args.length() >= 1);
     uint32_t errorNumber = args[0].toInt32();
 
 #ifdef DEBUG
-    const JSErrorFormatString *efs =
-        js_GetLocalizedErrorMessage(cx, nullptr, nullptr, errorNumber);
+    const JSErrorFormatString *efs = js_GetErrorMessage(nullptr, errorNumber);
     JS_ASSERT(efs->argCount == args.length() - 1);
 #endif
 
     JSAutoByteString errorArgs[3];
     for (unsigned i = 1; i < 4 && i < args.length(); i++) {
         RootedValue val(cx, args[i]);
         if (val.isInt32()) {
             JSString *str = ToString<CanGC>(cx, val);
--- a/js/src/vm/ThreadPool.cpp
+++ b/js/src/vm/ThreadPool.cpp
@@ -497,20 +497,20 @@ ThreadPool::abortJob()
 }
 
 // We are not using the markPagesUnused() / markPagesInUse() APIs here
 // for two reasons.  One, the free list is threaded through the
 // chunks, so some pages are actually in use.  Two, the expectation is
 // that a small number of chunks will be used intensively for a short
 // while and then be abandoned at the next GC.
 //
-// It's an open question whether it's best to go directly to the
-// pageAllocator, as now, or go via the GC's chunk pool.  Either way
-// there's a need to manage a predictable chunk cache here as we don't
-// want chunks to be deallocated during a parallel section.
+// It's an open question whether it's best to map the chunk directly,
+// as now, or go via the GC's chunk pool.  Either way there's a need
+// to manage a predictable chunk cache here as we don't want chunks to
+// be deallocated during a parallel section.
 
 gc::ForkJoinNurseryChunk *
 ThreadPool::getChunk()
 {
 #ifdef JSGC_FJGENERATIONAL
     PR_Lock(chunkLock_);
     timeOfLastAllocation_ = PRMJ_Now()/1000000;
     ChunkFreeList *p = freeChunks_;
@@ -519,17 +519,17 @@ ThreadPool::getChunk()
     PR_Unlock(chunkLock_);
 
     if (p) {
         // Already poisoned.
         return reinterpret_cast<gc::ForkJoinNurseryChunk *>(p);
     }
     gc::ForkJoinNurseryChunk *c =
         reinterpret_cast<gc::ForkJoinNurseryChunk *>(
-            runtime_->gc.pageAllocator.mapAlignedPages(gc::ChunkSize, gc::ChunkSize));
+            gc::MapAlignedPages(gc::ChunkSize, gc::ChunkSize));
     if (!c)
         return c;
     poisonChunk(c);
     return c;
 #else
     return nullptr;
 #endif
 }
@@ -575,12 +575,12 @@ ThreadPool::clearChunkCache()
     PR_Lock(chunkLock_);
     ChunkFreeList *p = freeChunks_;
     freeChunks_ = nullptr;
     PR_Unlock(chunkLock_);
 
     while (p) {
         ChunkFreeList *victim = p;
         p = p->next;
-        runtime_->gc.pageAllocator.unmapPages(victim, gc::ChunkSize);
+        gc::UnmapPages(victim, gc::ChunkSize);
     }
 #endif
 }
--- a/js/xpconnect/src/XPCCallContext.cpp
+++ b/js/xpconnect/src/XPCCallContext.cpp
@@ -51,34 +51,28 @@ XPCCallContext::XPCCallContext(XPCContex
         return;
 
     mMethodIndex = 0xDEAD;
 
     mState = HAVE_OBJECT;
 
     mTearOff = nullptr;
 
-    // If the object is a security wrapper, GetWrappedNativeOfJSObject can't
-    // handle it. Do special handling here to make cross-origin Xrays work.
     JSObject *unwrapped = js::CheckedUnwrap(obj, /* stopAtOuter = */ false);
     if (!unwrapped) {
-        mWrapper = UnwrapThisIfAllowed(obj, funobj, argc);
-        if (!mWrapper) {
-            JS_ReportError(mJSContext, "Permission denied to call method on |this|");
-            mState = INIT_FAILED;
-            return;
-        }
-    } else {
-        const js::Class *clasp = js::GetObjectClass(unwrapped);
-        if (IS_WN_CLASS(clasp)) {
-            mWrapper = XPCWrappedNative::Get(unwrapped);
-        } else if (IS_TEAROFF_CLASS(clasp)) {
-            mTearOff = (XPCWrappedNativeTearOff*)js::GetObjectPrivate(unwrapped);
-            mWrapper = XPCWrappedNative::Get(js::GetObjectParent(unwrapped));
-        }
+        JS_ReportError(mJSContext, "Permission denied to call method on |this|");
+        mState = INIT_FAILED;
+        return;
+    }
+    const js::Class *clasp = js::GetObjectClass(unwrapped);
+    if (IS_WN_CLASS(clasp)) {
+        mWrapper = XPCWrappedNative::Get(unwrapped);
+    } else if (IS_TEAROFF_CLASS(clasp)) {
+        mTearOff = (XPCWrappedNativeTearOff*)js::GetObjectPrivate(unwrapped);
+        mWrapper = XPCWrappedNative::Get(js::GetObjectParent(unwrapped));
     }
     if (mWrapper) {
         if (mTearOff)
             mScriptableInfo = nullptr;
         else
             mScriptableInfo = mWrapper->GetScriptableInfo();
     }
 
@@ -306,70 +300,8 @@ XPCCallContext::GetPreviousCallContext(n
 
 NS_IMETHODIMP
 XPCCallContext::GetLanguage(uint16_t *aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = GetCallerLanguage();
   return NS_OK;
 }
-
-XPCWrappedNative*
-XPCCallContext::UnwrapThisIfAllowed(HandleObject obj, HandleObject fun, unsigned argc)
-{
-    // We should only get here for objects that aren't safe to unwrap.
-    MOZ_ASSERT(!js::CheckedUnwrap(obj));
-    MOZ_ASSERT(js::IsObjectInContextCompartment(obj, mJSContext));
-
-    // We can't do anything here without a function.
-    if (!fun)
-        return nullptr;
-
-    // Determine if we're allowed to unwrap the security wrapper to invoke the
-    // method.
-    //
-    // We have the Interface and Member that this corresponds to, but
-    // unfortunately our access checks are based on the object class name and
-    // property name. So we cheat a little bit here - we verify that the object
-    // does indeed implement the method's Interface, and then just check that we
-    // can successfully access property with method's name from the object.
-
-    // First, get the XPCWN out of the underlying object. We should have a wrapper
-    // here, potentially an outer window proxy, and then an XPCWN.
-    MOZ_ASSERT(js::IsWrapper(obj));
-    RootedObject unwrapped(mJSContext, js::UncheckedUnwrap(obj, /* stopAtOuter = */ false));
-#ifdef DEBUG
-    JS::Rooted<JSObject*> wrappedObj(mJSContext, js::Wrapper::wrappedObject(obj));
-    MOZ_ASSERT(unwrapped == JS_ObjectToInnerObject(mJSContext, wrappedObj));
-#endif
-
-    // Make sure we have an XPCWN, and grab it.
-    if (!IS_WN_REFLECTOR(unwrapped))
-        return nullptr;
-    XPCWrappedNative *wn = XPCWrappedNative::Get(unwrapped);
-
-    // Next, get the call info off the function object.
-    XPCNativeInterface *interface;
-    XPCNativeMember *member;
-    XPCNativeMember::GetCallInfo(fun, &interface, &member);
-
-    // To be extra safe, make sure that the underlying native implements the
-    // interface before unwrapping. Even if we didn't check this, we'd still
-    // theoretically fail during tearoff lookup for mismatched methods.
-    if (!wn->HasInterfaceNoQI(*interface->GetIID()))
-        return nullptr;
-
-    // See if the access is permitted.
-    //
-    // NB: This calculation of SET vs GET is a bit wonky, but that's what
-    // XPC_WN_GetterSetter does.
-    bool set = argc && argc != NO_ARGS && member->IsWritableAttribute();
-    js::Wrapper::Action act = set ? js::Wrapper::SET : js::Wrapper::GET;
-    const js::Wrapper *handler = js::Wrapper::wrapperHandler(obj);
-    bool ignored;
-    JS::Rooted<jsid> id(mJSContext, member->GetName());
-    if (!handler->enter(mJSContext, obj, id, act, &ignored))
-        return nullptr;
-
-    // Ok, this call is safe.
-    return wn;
-}
-
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -3239,17 +3239,17 @@ nsXPCComponents_Utils::Dispatch(HandleVa
     nsXPCComponents_Utils::Set## _attr(JSContext* cx, bool aValue)      \
     {                                                                   \
         RuntimeOptionsRef(cx)._setter(aValue);                          \
         return NS_OK;                                                   \
     }
 
 GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Strict, extraWarnings, setExtraWarnings)
 GENERATE_JSRUNTIMEOPTION_GETTER_SETTER(Werror, werror, setWerror)
-GENERATE_JSCONTEXTOPTION_GETTER_SETTER(Strict_mode, strictMode, setStrictMode)
+GENERATE_JSRUNTIMEOPTION_GETTER_SETTER(Strict_mode, strictMode, setStrictMode)
 GENERATE_JSRUNTIMEOPTION_GETTER_SETTER(Ion, ion, setIon)
 
 #undef GENERATE_JSCONTEXTOPTION_GETTER_SETTER
 #undef GENERATE_JSRUNTIMEOPTION_GETTER_SETTER
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::SetGCZeal(int32_t aValue, JSContext* cx)
 {
--- a/js/xpconnect/src/XPCLocale.cpp
+++ b/js/xpconnect/src/XPCLocale.cpp
@@ -38,17 +38,16 @@ struct XPCLocaleCallbacks : public JSLoc
 #endif
   {
     MOZ_COUNT_CTOR(XPCLocaleCallbacks);
 
     localeToUpperCase = LocaleToUpperCase;
     localeToLowerCase = LocaleToLowerCase;
     localeCompare = LocaleCompare;
     localeToUnicode = LocaleToUnicode;
-    localeGetErrorMessage = nullptr;
   }
 
   ~XPCLocaleCallbacks()
   {
     AssertThreadSafety();
     MOZ_COUNT_DTOR(XPCLocaleCallbacks);
   }
 
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -490,17 +490,17 @@ Options(JSContext *cx, unsigned argc, js
         if (!opt)
             return false;
 
         if (strcmp(opt.ptr(), "strict") == 0)
             ContextOptionsRef(cx).toggleExtraWarnings();
         else if (strcmp(opt.ptr(), "werror") == 0)
             RuntimeOptionsRef(cx).toggleWerror();
         else if (strcmp(opt.ptr(), "strict_mode") == 0)
-            ContextOptionsRef(cx).toggleStrictMode();
+            RuntimeOptionsRef(cx).toggleStrictMode();
         else {
             JS_ReportError(cx, "unknown option name '%s'. The valid names are "
                            "strict, werror, and strict_mode.", opt.ptr());
             return false;
         }
     }
 
     char *names = nullptr;
@@ -513,17 +513,17 @@ Options(JSContext *cx, unsigned argc, js
     }
     if (oldRuntimeOptions.werror()) {
         names = JS_sprintf_append(names, "%s%s", names ? "," : "", "werror");
         if (!names) {
             JS_ReportOutOfMemory(cx);
             return false;
         }
     }
-    if (names && oldContextOptions.strictMode()) {
+    if (names && oldRuntimeOptions.strictMode()) {
         names = JS_sprintf_append(names, "%s%s", names ? "," : "", "strict_mode");
         if (!names) {
             JS_ReportOutOfMemory(cx);
             return false;
         }
     }
 
     JSString *str = JS_NewStringCopyZ(cx, names);
@@ -876,17 +876,17 @@ typedef enum JSShellErrNum {
 static const JSErrorFormatString jsShell_ErrorFormatString[JSShellErr_Limit] = {
 #define MSG_DEF(name, number, count, exception, format) \
     { format, count } ,
 #include "jsshell.msg"
 #undef MSG_DEF
 };
 
 static const JSErrorFormatString *
-my_GetErrorMessage(void *userRef, const char *locale, const unsigned errorNumber)
+my_GetErrorMessage(void *userRef, const unsigned errorNumber)
 {
     if (errorNumber == 0 || errorNumber >= JSShellErr_Limit)
         return nullptr;
 
     return &jsShell_ErrorFormatString[errorNumber];
 }
 
 static void
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -806,19 +806,16 @@ public:
     operator JSContext*() const {return GetJSContext();}
 
 private:
 
     // no copy ctor or assignment allowed
     XPCCallContext(const XPCCallContext& r); // not implemented
     XPCCallContext& operator= (const XPCCallContext& r); // not implemented
 
-    XPCWrappedNative* UnwrapThisIfAllowed(JS::HandleObject obj, JS::HandleObject fun,
-                                          unsigned argc);
-
 private:
     // posible values for mState
     enum State {
         INIT_FAILED,
         SYSTEM_SHUTDOWN,
         HAVE_CONTEXT,
         HAVE_OBJECT,
         HAVE_NAME,
--- a/js/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/xpconnect/wrappers/AccessCheck.cpp
@@ -86,36 +86,30 @@ AccessCheck::isChrome(JSObject *obj)
 }
 
 nsIPrincipal *
 AccessCheck::getPrincipal(JSCompartment *compartment)
 {
     return GetCompartmentPrincipal(compartment);
 }
 
-// Hardcoded policy for cross origin property access. This was culled from the
-// preferences file (all.js). We don't want users to overwrite highly sensitive
-// security policies.
+// Hardcoded policy for cross origin property access. See the HTML5 Spec.
 static bool
 IsPermitted(const char *name, JSFlatString *prop, bool set)
 {
     size_t propLength = JS_GetStringLength(JS_FORGET_STRING_FLATNESS(prop));
     if (!propLength)
         return false;
 
     jschar propChar0 = JS_GetFlatStringCharAt(prop, 0);
-    switch (name[0]) {
-        case 'L':
-            if (!strcmp(name, "Location"))
-                return dom::LocationBinding::IsPermitted(prop, propChar0, set);
-        case 'W':
-            if (!strcmp(name, "Window"))
-                return dom::WindowBinding::IsPermitted(prop, propChar0, set);
-            break;
-    }
+    if (name[0] == 'L' && !strcmp(name, "Location"))
+        return dom::LocationBinding::IsPermitted(prop, propChar0, set);
+    if (name[0] == 'W' && !strcmp(name, "Window"))
+        return dom::WindowBinding::IsPermitted(prop, propChar0, set);
+
     return false;
 }
 
 static bool
 IsFrameId(JSContext *cx, JSObject *objArg, jsid idArg)
 {
     RootedObject obj(cx, objArg);
     RootedId id(cx, idArg);
--- a/layout/base/TouchCaret.cpp
+++ b/layout/base/TouchCaret.cpp
@@ -26,18 +26,26 @@
 #include "nsQueryContentEventResult.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsView.h"
 #include "nsDOMTokenList.h"
 #include <algorithm>
 
 using namespace mozilla;
 
-#define TOUCHCARET_LOG(...)
-// #define TOUCHCARET_LOG(...) printf_stderr("TouchCaret: " __VA_ARGS__)
+// To enable all the TOUCHCARET_LOG print statements, change the 0 to 1 in the
+// following #define.
+#define ENABLE_TOUCHCARET_LOG 0
+
+#if ENABLE_TOUCHCARET_LOG
+  #define TOUCHCARET_LOG(message, ...) \
+    printf_stderr("TouchCaret (%p): %s:%d : " message "\n", this, __func__, __LINE__, ##__VA_ARGS__);
+#else
+  #define TOUCHCARET_LOG(message, ...)
+#endif
 
 // Click on the boundary of input/textarea will place the caret at the
 // front/end of the content. To advoid this, we need to deflate the content
 // boundary by 61 app units (1 pixel + 1 app unit).
 static const int32_t kBoundaryAppUnits = 61;
 
 NS_IMPL_ISUPPORTS(TouchCaret, nsISelectionListener)
 
@@ -45,16 +53,17 @@ NS_IMPL_ISUPPORTS(TouchCaret, nsISelecti
 /*static*/ int32_t TouchCaret::sTouchCaretExpirationTime = 0;
 
 TouchCaret::TouchCaret(nsIPresShell* aPresShell)
   : mState(TOUCHCARET_NONE),
     mActiveTouchId(-1),
     mCaretCenterToDownPointOffsetY(0),
     mVisible(false)
 {
+  TOUCHCARET_LOG("Constructor");
   MOZ_ASSERT(NS_IsMainThread());
 
   static bool addedTouchCaretPref = false;
   if (!addedTouchCaretPref) {
     Preferences::AddIntVarCache(&sTouchCaretMaxDistance,
                                 "touchcaret.distance.threshold");
     Preferences::AddIntVarCache(&sTouchCaretExpirationTime,
                                 "touchcaret.expiration.time");
@@ -63,16 +72,17 @@ TouchCaret::TouchCaret(nsIPresShell* aPr
 
   // The presshell owns us, so no addref.
   mPresShell = do_GetWeakReference(aPresShell);
   MOZ_ASSERT(mPresShell, "Hey, pres shell should support weak refs");
 }
 
 TouchCaret::~TouchCaret()
 {
+  TOUCHCARET_LOG("Destructor");
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mTouchCaretExpirationTimer) {
     mTouchCaretExpirationTimer->Cancel();
     mTouchCaretExpirationTimer = nullptr;
   }
 }
 
@@ -85,34 +95,38 @@ TouchCaret::GetCanvasFrame()
   }
   return presShell->GetCanvasFrame();
 }
 
 void
 TouchCaret::SetVisibility(bool aVisible)
 {
   if (mVisible == aVisible) {
+    TOUCHCARET_LOG("Visibility not changed");
     return;
   }
+
   mVisible = aVisible;
 
   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
   if (!presShell) {
     return;
   }
   mozilla::dom::Element* touchCaretElement = presShell->GetTouchCaretElement();
   if (!touchCaretElement) {
     return;
   }
 
   // Set touch caret visibility.
   ErrorResult err;
   touchCaretElement->ClassList()->Toggle(NS_LITERAL_STRING("hidden"),
                                          dom::Optional<bool>(!mVisible),
                                          err);
+  TOUCHCARET_LOG("Visibility %s", (mVisible ? "shown" : "hidden"));
+
   // Set touch caret expiration time.
   mVisible ? LaunchExpirationTimer() : CancelExpirationTimer();
 
   // We must call SetHasTouchCaret() in order to get APZC to wait until the
   // event has been round-tripped and check whether it has been handled,
   // otherwise B2G will end up panning the document when the user tries to drag
   // touch caret.
   presShell->SetMayHaveTouchCaret(mVisible);
@@ -225,16 +239,18 @@ TouchCaret::SetTouchFramePos(const nsPoi
 
   nsAutoString styleStr;
   styleStr.AppendLiteral("left: ");
   styleStr.AppendInt(x);
   styleStr.AppendLiteral("px; top: ");
   styleStr.AppendInt(y);
   styleStr.AppendLiteral("px;");
 
+  TOUCHCARET_LOG("Set style: %s", NS_ConvertUTF16toUTF8(styleStr).get());
+
   touchCaretElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
                              styleStr, true);
 }
 
 void
 TouchCaret::MoveCaret(const nsPoint& movePoint)
 {
   nsCOMPtr<nsIPresShell> presShell = do_QueryReferent(mPresShell);
@@ -362,25 +378,27 @@ TouchCaret::UpdateTouchCaret(bool aVisib
     SetVisibility(false);
     return;
   }
 
   // Hide touch caret while caret is not visible.
   bool caretVisible = false;
   caret->GetCaretVisible(&caretVisible);
   if (!caretVisible) {
+    TOUCHCARET_LOG("Caret is not visible");
     SetVisibility(false);
     return;
   }
 
   // Caret is visible and shown, update touch caret.
   nsISelection* caretSelection = caret->GetCaretDOMSelection();
   nsRect focusRect;
   nsIFrame* focusFrame = caret->GetGeometry(caretSelection, &focusRect);
   if (!focusFrame || focusRect.IsEmpty()) {
+    TOUCHCARET_LOG("Focus frame not valid");
     SetVisibility(false);
     return;
   }
 
   // Position of the touch caret relative to focusFrame.
   nsPoint pos = nsPoint(focusRect.x + (focusRect.width / 2),
                         focusRect.y + focusRect.height);
 
@@ -487,16 +505,17 @@ TouchCaret::HandleEvent(WidgetEvent* aEv
       SetState(TOUCHCARET_NONE);
       LaunchExpirationTimer();
       break;
     case NS_KEY_UP:
     case NS_KEY_DOWN:
     case NS_KEY_PRESS:
     case NS_WHEEL_EVENT_START:
       // Disable touch caret while key/wheel event is received.
+      TOUCHCARET_LOG("Receive key/wheel event");
       SetVisibility(false);
       break;
     default:
       break;
   }
 
   return status;
 }
@@ -527,17 +546,17 @@ TouchCaret::GetEventPosition(WidgetMouse
   return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent,
                                                       mouseIntPoint,
                                                       canvasFrame);
 }
 
 nsEventStatus
 TouchCaret::HandleMouseMoveEvent(WidgetMouseEvent* aEvent)
 {
-  TOUCHCARET_LOG("%p got a mouse-move in state %d\n", this, mState);
+  TOUCHCARET_LOG("Got a mouse-move in state %d", mState);
   nsEventStatus status = nsEventStatus_eIgnore;
 
   switch (mState) {
     case TOUCHCARET_NONE:
       break;
 
     case TOUCHCARET_MOUSEDRAG_ACTIVE:
       {
@@ -559,17 +578,17 @@ TouchCaret::HandleMouseMoveEvent(WidgetM
   }
 
   return status;
 }
 
 nsEventStatus
 TouchCaret::HandleTouchMoveEvent(WidgetTouchEvent* aEvent)
 {
-  TOUCHCARET_LOG("%p got a touch-move in state %d\n", this, mState);
+  TOUCHCARET_LOG("Got a touch-move in state %d", mState);
   nsEventStatus status = nsEventStatus_eIgnore;
 
   switch (mState) {
     case TOUCHCARET_NONE:
       break;
 
     case TOUCHCARET_MOUSEDRAG_ACTIVE:
       // Consume touch event in mouse sequence.
@@ -595,17 +614,17 @@ TouchCaret::HandleTouchMoveEvent(WidgetT
   }
 
   return status;
 }
 
 nsEventStatus
 TouchCaret::HandleMouseUpEvent(WidgetMouseEvent* aEvent)
 {
-  TOUCHCARET_LOG("%p got a mouse-up in state %d\n", this, mState);
+  TOUCHCARET_LOG("Got a mouse-up in state %d", mState);
   nsEventStatus status = nsEventStatus_eIgnore;
 
   switch (mState) {
     case TOUCHCARET_NONE:
       break;
 
     case TOUCHCARET_MOUSEDRAG_ACTIVE:
       if (aEvent->button == WidgetMouseEvent::eLeftButton) {
@@ -623,17 +642,17 @@ TouchCaret::HandleMouseUpEvent(WidgetMou
   }
 
   return status;
 }
 
 nsEventStatus
 TouchCaret::HandleTouchUpEvent(WidgetTouchEvent* aEvent)
 {
-  TOUCHCARET_LOG("%p got a touch-end in state %d\n", this, mState);
+  TOUCHCARET_LOG("Got a touch-end in state %d", mState);
   // Remove touches from cache if the stroke is gone in TOUCHDRAG states.
   if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE ||
       mState == TOUCHCARET_TOUCHDRAG_INACTIVE) {
     for (size_t i = 0; i < aEvent->touches.Length(); i++) {
       nsTArray<int32_t>::index_type index =
         mTouchesId.IndexOf(aEvent->touches[i]->mIdentifier);
       MOZ_ASSERT(index != nsTArray<int32_t>::NoIndex);
       mTouchesId.RemoveElementAt(index);
@@ -680,17 +699,17 @@ TouchCaret::HandleTouchUpEvent(WidgetTou
   }
 
   return status;
 }
 
 nsEventStatus
 TouchCaret::HandleMouseDownEvent(WidgetMouseEvent* aEvent)
 {
-  TOUCHCARET_LOG("%p got a mouse-down in state %d\n", this, mState);
+  TOUCHCARET_LOG("Got a mouse-down in state %d", mState);
   if (!GetVisibility()) {
     // If touch caret is invisible, bypass event.
     return nsEventStatus_eIgnore;
   }
 
   nsEventStatus status = nsEventStatus_eIgnore;
 
   switch (mState) {
@@ -729,17 +748,17 @@ TouchCaret::HandleMouseDownEvent(WidgetM
   }
 
   return status;
 }
 
 nsEventStatus
 TouchCaret::HandleTouchDownEvent(WidgetTouchEvent* aEvent)
 {
-  TOUCHCARET_LOG("%p got a touch-start in state %d\n", this, mState);
+  TOUCHCARET_LOG("Got a touch-start in state %d", mState);
 
   nsEventStatus status = nsEventStatus_eIgnore;
 
   switch (mState) {
     case TOUCHCARET_NONE:
       if (!GetVisibility()) {
         // If touch caret is invisible, bypass event.
         status = nsEventStatus_eIgnore;
@@ -788,17 +807,17 @@ TouchCaret::HandleTouchDownEvent(WidgetT
   }
 
   return status;
 }
 
 void
 TouchCaret::SetState(TouchCaretState aState)
 {
-  TOUCHCARET_LOG("%p state changed from %d to %d\n", this, mState, aState);
+  TOUCHCARET_LOG("state changed from %d to %d", mState, aState);
   if (mState == TOUCHCARET_NONE) {
     MOZ_ASSERT(aState != TOUCHCARET_TOUCHDRAG_INACTIVE,
                "mState: NONE => TOUCHDRAG_INACTIVE isn't allowed!");
   }
 
   if (mState == TOUCHCARET_TOUCHDRAG_ACTIVE) {
     MOZ_ASSERT(aState != TOUCHCARET_MOUSEDRAG_ACTIVE,
                "mState: TOUCHDRAG_ACTIVE => MOUSEDRAG_ACTIVE isn't allowed!");
--- a/layout/base/nsCaret.h
+++ b/layout/base/nsCaret.h
@@ -41,35 +41,35 @@ class nsCaret : public nsISelectionListe
     void    Terminate();
 
     nsISelection*    GetCaretDOMSelection();
     nsresult    SetCaretDOMSelection(nsISelection *inDOMSel);
 
     /** GetCaretVisible will get the visibility of the caret
      *  This function is virtual so that it can be used by nsCaretAccessible
      *  without linking
-     *  @param inMakeVisible true it is shown, false it is hidden
-     *  @return false if and only if inMakeVisible is null, otherwise true 
+     *  @param outMakeVisible true if it is shown, false if it is hidden
+     *  @return NS_OK
      */
     virtual nsresult    GetCaretVisible(bool *outMakeVisible);
 
     /** SetCaretVisible will set the visibility of the caret
      *  @param inMakeVisible true to show the caret, false to hide it
      */
     void    SetCaretVisible(bool intMakeVisible);
 
     /** SetCaretReadOnly set the appearance of the caret
      *  @param inMakeReadonly true to show the caret in a 'read only' state,
-     *	    false to show the caret in normal, editing state
+     *         false to show the caret in normal, editing state
      */
     void    SetCaretReadOnly(bool inMakeReadonly);
 
     /** GetCaretReadOnly get the appearance of the caret
-     *	@return true if the caret is in 'read only' state, otherwise,
-     *	    returns false
+     *  @return true if the caret is in 'read only' state, otherwise,
+     *          returns false
      */
     bool GetCaretReadOnly()
     {
       return mReadOnly;
     }
 
     /**
      * Gets the position and size of the caret that would be drawn for
@@ -227,23 +227,23 @@ protected:
 
     nsCOMPtr<nsITimer>    mBlinkTimer;
 
     // XXX these fields should go away and the values be acquired as needed,
     // probably by ComputeMetrics.
     uint32_t              mBlinkRate;         // time for one cyle (on then off), in milliseconds
     nscoord               mCaretWidthCSSPx;   // caret width in CSS pixels
     float                 mCaretAspectRatio;  // caret width/height aspect ratio
-    
+
     bool                  mVisible;           // is the caret blinking
 
     bool                  mDrawn;             // Denotes when the caret is physically drawn on the screen.
     bool                  mPendingDraw;       // True when the last on-state draw was suppressed.
 
-    bool                  mReadOnly;          // it the caret in readonly state (draws differently)      
+    bool                  mReadOnly;          // it the caret in readonly state (draws differently)
     bool                  mShowDuringSelection; // show when text is selected
 
     bool                  mIgnoreUserModify;
 
     bool                  mKeyboardRTL;       // is the keyboard language right-to-left
     bool                  mBidiUI;            // is bidi UI turned on
     nsRect                mHookRect;          // directional hook on the caret
     uint8_t               mLastBidiLevel;     // saved bidi level of the last draw request, to use when we erase
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -48,26 +48,26 @@
   -moz-backface-visibility: inherit;
   clip: inherit;
 }
 
 *|*::-moz-table-row {
   display: table-row !important;
 }
 
-/* The ::-moz-table-column pseudo-element is for extra columns at the end 
+/* The ::-moz-table-column pseudo-element is for extra columns at the end
    of a table. */
 *|*::-moz-table-column {
   display: table-column !important;
 }
 
 *|*::-moz-table-column-group {
   display: table-column-group !important;
 }
- 
+
 *|*::-moz-table-row-group {
   display: table-row-group !important;
 }
 
 *|*::-moz-table-cell {
   display: table-cell !important;
   white-space: inherit;
 }
@@ -162,17 +162,17 @@
 
 *|*::-moz-viewport-scroll {
   overflow: auto;
 %ifdef XP_WIN
   resize: both;
 %endif
 }
 
-*|*::-moz-column-content { 
+*|*::-moz-column-content {
   /* the column boxes inside a column-flowed block */
   /* make unicode-bidi inherit, otherwise it has no effect on column boxes */
   unicode-bidi: inherit;
   text-overflow: inherit;
   /* inherit the outer frame's display, otherwise we turn into an inline */
   display: inherit !important;
   /* Carry through our parent's height so that %-height children get
   their heights set */
@@ -283,112 +283,36 @@ parsererror|sourcetext {
   font-family: -moz-fixed;
   margin-top: 2em;
   margin-bottom: 1em;
   color: red;
   font-weight: bold;
   font-size: 12pt;
 }
 
-div[\_moz_anonclass="mozTouchCaret"].moz-touchcaret {
-  background-image: url("resource://gre/res/text_selection_handle.png");
-  position: absolute;
-  width: 19px;
-  height: 24px;
-  margin-left: -10px;
-  background-position: center center;
-  z-index: 2147483647;
-}
-
+div[\_moz_anonclass="mozTouchCaret"].moz-touchcaret,
 div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left,
 div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right {
-  background-image: url("resource://gre/res/text_caret.png");
+  background-image: url("resource://gre/res/caret_middle.svg");
   position: absolute;
-  width: 21px;
-  height: 26px;
-  margin-left: -11px;
+  width: 29px;
+  height: 31px;
+  margin-left: -15px;
   background-position: center center;
   background-size: 100% 100%;
   z-index: 2147483647;
 }
 
 div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left.tilt {
-  background-image: url("resource://gre/res/text_caret_tilt_left.png");
-  margin-left: -22px;
-  width: 22px;
+  background-image: url("resource://gre/res/caret_left.svg");
+  margin-left: -29px;
 }
 
 div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right.tilt {
-  background-image: url("resource://gre/res/text_caret_tilt_right.png");
+  background-image: url("resource://gre/res/caret_right.svg");
   margin-left: 0px;
-  width: 22px;
-}
-
-@media (min-resolution: 1.5dppx) {
-  div[\_moz_anonclass="mozTouchCaret"].moz-touchcaret {
-    background-image: url("resource://gre/res/text_selection_handle@1.5.png");
-    position: absolute;
-    width: 29px;
-    height: 36px;
-    margin-left: -15px;
-    background-position: center center;
-    z-index: 2147483647;
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left,
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right {
-    background-image: url("resource://gre/res/text_caret@1.5x.png");
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left.tilt {
-    background-image: url("resource://gre/res/text_caret_tilt_left@1.5x.png");
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right.tilt {
-    background-image: url("resource://gre/res/text_caret_tilt_right@1.5x.png");
-  }
-}
-
-@media (min-resolution: 2dppx) {
-  div[\_moz_anonclass="mozTouchCaret"].moz-touchcaret {
-    background-image: url("resource://gre/res/text_selection_handle@2.png");
-    position: absolute;
-    width: 38px;
-    height: 48px;
-    margin-left: -19px;
-    background-position: center center;
-    z-index: 2147483647;
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left,
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right {
-    background-image: url("resource://gre/res/text_caret@2x.png");
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left.tilt {
-    background-image: url("resource://gre/res/text_caret_tilt_left@2x.png");
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right.tilt {
-    background-image: url("resource://gre/res/text_caret_tilt_right@2x.png");
-  }
-}
-
-@media (min-resolution: 2.25dppx) {
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right {
-    background-image: url("resource://gre/res/text_caret@2.25x.png");
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left.tilt {
-    background-image: url("resource://gre/res/text_caret_tilt_left@2.25x.png");
-  }
-
-  div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right.tilt {
-    background-image: url("resource://gre/res/text_caret_tilt_right@2.25x.png");
-  }
 }
 
 div[\_moz_anonclass="mozTouchCaret"].moz-touchcaret.hidden,
 div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-left.hidden,
 div[\_moz_anonclass="mozTouchCaret"].moz-selectioncaret-right.hidden {
   visibility: hidden;
 }
--- a/layout/tools/reftest/remotereftest.py
+++ b/layout/tools/reftest/remotereftest.py
@@ -100,17 +100,17 @@ class RemoteOptions(ReftestOptions):
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options):
         if options.runTestsInParallel:
             self.error("Cannot run parallel tests here")
 
         # Ensure our defaults are set properly for everything we can infer
         if not options.remoteTestRoot:
-            options.remoteTestRoot = self.automation._devicemanager.getDeviceRoot() + '/reftest'
+            options.remoteTestRoot = self.automation._devicemanager.deviceRoot + '/reftest'
         options.remoteProfile = options.remoteTestRoot + "/profile"
 
         # Verify that our remotewebserver is set properly
         if (options.remoteWebServer == None or
             options.remoteWebServer == '127.0.0.1'):
             print "ERROR: Either you specified the loopback for the remote webserver or ",
             print "your local IP cannot be detected.  Please provide the local ip in --remote-webserver"
             return None
@@ -397,17 +397,17 @@ class RemoteReftest(RefTest):
         return path
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
                 logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
                 print ''.join(logcat)
             print "Device info: %s" % self._devicemanager.getInfo()
-            print "Test root: %s" % self._devicemanager.getDeviceRoot()
+            print "Test root: %s" % self._devicemanager.deviceRoot
         except devicemanager.DMError:
             print "WARNING: Error getting device information"
 
     def cleanup(self, profileDir):
         # Pull results back from device
         if self.remoteLogFile and \
                 self._devicemanager.fileExists(self.remoteLogFile):
             self._devicemanager.getFile(self.remoteLogFile, self.localLogName)
--- a/layout/tools/reftest/runreftestb2g.py
+++ b/layout/tools/reftest/runreftestb2g.py
@@ -135,17 +135,17 @@ class B2GOptions(ReftestOptions):
 
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options, auto):
         if options.runTestsInParallel:
             self.error("Cannot run parallel tests here")
 
         if not options.remoteTestRoot:
-            options.remoteTestRoot = auto._devicemanager.getDeviceRoot() + "/reftest"
+            options.remoteTestRoot = auto._devicemanager.deviceRoot + "/reftest"
 
         options.remoteProfile = options.remoteTestRoot + "/profile"
 
         productRoot = options.remoteTestRoot + "/" + auto._product
         if options.utilityPath == auto.DIST_BIN:
             options.utilityPath = productRoot + "/bin"
 
         if options.remoteWebServer == None:
--- a/media/libyuv/include/libyuv/row.h
+++ b/media/libyuv/include/libyuv/row.h
@@ -33,17 +33,18 @@ extern "C" {
   uint8* var = (uint8*)(((intptr_t)(var##_mem) + 63) & ~63)       /* NOLINT */
 #endif
 
 #define free_aligned_buffer_64(var) \
   free(var##_mem);  \
   var = 0
 
 #if defined(__pnacl__) || defined(__CLR_VER) || defined(COVERAGE_ENABLED) || \
-    defined(TARGET_IPHONE_SIMULATOR)
+    defined(TARGET_IPHONE_SIMULATOR) || \
+    (defined(_MSC_VER) && defined(__clang__))
 #define LIBYUV_DISABLE_X86
 #endif
 // True if compiling for SSSE3 as a requirement.
 #if defined(__SSSE3__) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 3))
 #define LIBYUV_SSSE3_ONLY
 #endif
 
 // Enable for NaCL pepper 33 for bundle and AVX2 support.
--- a/media/libyuv/include/libyuv/video_common.h
+++ b/media/libyuv/include/libyuv/video_common.h
@@ -110,17 +110,17 @@ enum FourCC {
   FOURCC_L555 = FOURCC('L', '5', '5', '5'),  // Alias for RGBO.
   FOURCC_L565 = FOURCC('L', '5', '6', '5'),  // Alias for RGBP.
   FOURCC_5551 = FOURCC('5', '5', '5', '1'),  // Alias for RGBO.
 
   // 1 Auxiliary compressed YUV format set aside for capturer.
   FOURCC_H264 = FOURCC('H', '2', '6', '4'),
 
   // Match any fourcc.
-  FOURCC_ANY  = 0xFFFFFFFF,
+  FOURCC_ANY = -1,
 };
 
 enum FourCCBpp {
   // Canonical fourcc codes used in our code.
   FOURCC_BPP_I420 = 12,
   FOURCC_BPP_I422 = 16,
   FOURCC_BPP_I444 = 24,
   FOURCC_BPP_I411 = 12,
--- a/media/libyuv/source/cpu_id.cc
+++ b/media/libyuv/source/cpu_id.cc
@@ -5,17 +5,17 @@
  *  that can be found in the LICENSE file in the root of the source
  *  tree. An additional intellectual property rights grant can be found
  *  in the file PATENTS. All contributing project authors may
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
 #include "libyuv/cpu_id.h"
 
-#ifdef _MSC_VER
+#if defined(_MSC_VER) && !defined(__clang__)
 #include <intrin.h>  // For __cpuidex()
 #endif
 #if !defined(__pnacl__) && !defined(__CLR_VER) && \
     !defined(__native_client__) && defined(_M_X64) && \
     defined(_MSC_VER) && (_MSC_FULL_VER >= 160040219)
 #include <immintrin.h>  // For _xgetbv()
 #endif
 
@@ -43,17 +43,17 @@ extern "C" {
 #endif
 
 // Low level cpuid for X86. Returns zeros on other CPUs.
 #if !defined(__pnacl__) && !defined(__CLR_VER) && \
     (defined(_M_IX86) || defined(_M_X64) || \
     defined(__i386__) || defined(__x86_64__))
 LIBYUV_API
 void CpuId(uint32 info_eax, uint32 info_ecx, uint32* cpu_info) {
-#if defined(_MSC_VER)
+#if defined(_MSC_VER) && !defined(__clang__)
 #if (_MSC_FULL_VER >= 160040219)
   __cpuidex((int*)(cpu_info), info_eax, info_ecx);
 #elif defined(_M_IX86)
   __asm {
     mov        eax, info_eax
     mov        ecx, info_ecx
     mov        edi, cpu_info
     cpuid
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -91,16 +91,17 @@ static int nr_ice_pre_answer_request_des
 
     par = *parp;
     *parp = 0;
 
     nr_stun_message_destroy(&par->req.request);
     nr_stun_message_destroy(&par->req.response);
 
     RFREE(par->username);
+    RFREE(par);
 
     return(0);
   }
 
 int nr_ice_component_create(nr_ice_media_stream *stream, int component_id, nr_ice_component **componentp)
   {
     int _status;
     nr_ice_component *comp=0;
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
@@ -516,16 +516,20 @@ int nr_stun_client_process_response(nr_s
       else {
         if (r=nr_stun_compute_lt_message_integrity_password(username, ctx->realm,
                                                             password, &hmac_key))
           ABORT(r);
       }
       password = &hmac_key;
     }
 
+    if (ctx->response) {
+      nr_stun_message_destroy(&ctx->response);
+    }
+
     if ((r=nr_stun_message_create2(&ctx->response, msg, len)))
         ABORT(r);
 
     if ((r=nr_stun_decode_message(ctx->response, nr_stun_client_get_password, password))) {
         r_log(NR_LOG_STUN,LOG_WARNING,"STUN-CLIENT(%s): error decoding response",ctx->label);
         ABORT(r);
     }
 
new file mode 100644
--- /dev/null
+++ b/mfbt/LinuxSignal.h
@@ -0,0 +1,48 @@
+/* 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_LinuxSignal_h
+#define mozilla_LinuxSignal_h
+
+namespace mozilla {
+
+#if defined(__arm__)
+
+// Some (old) Linux kernels on ARM have a bug where a signal handler
+// can be called without clearing the IT bits in CPSR first. The result
+// is that the first few instructions of the handler could be skipped,
+// ultimately resulting in crashes. To workaround this bug, the handler
+// on ARM is a trampoline that starts with enough NOP instructions, so
+// that even if the IT bits are not cleared, only the NOP instructions
+// will be skipped over.
+
+template <void (*H)(int, siginfo_t*, void*)>
+__attribute__((naked)) void
+SignalTrampoline(int aSignal, siginfo_t* aInfo, void* aContext)
+{
+  asm volatile (
+    "nop; nop; nop; nop"
+    : : : "memory");
+
+  // Because the assembler may generate additional insturctions below, we
+  // need to ensure NOPs are inserted first by separating them out above.
+
+  asm volatile (
+    "bx %0"
+    :
+    : "r"(H), "l"(aSignal), "l"(aInfo), "l"(aContext)
+    : "memory");
+}
+
+# define MOZ_SIGNAL_TRAMPOLINE(h) (mozilla::SignalTrampoline<h>)
+
+#else // __arm__
+
+# define MOZ_SIGNAL_TRAMPOLINE(h) (h)
+
+#endif // __arm__
+
+} // namespace mozilla
+
+#endif // mozilla_LinuxSignal_h
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -73,16 +73,20 @@ EXPORTS.mozilla = [
     'Vector.h',
     'WeakPtr.h',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS.mozilla += [
         'WindowsVersion.h',
     ]
+elif CONFIG['OS_ARCH'] == 'Linux':
+    EXPORTS.mozilla += [
+        'LinuxSignal.h',
+    ]
 
 UNIFIED_SOURCES = [
     'double-conversion/bignum-dtoa.cc',
     'double-conversion/bignum.cc',
     'double-conversion/cached-powers.cc',
     'double-conversion/diy-fp.cc',
     'double-conversion/double-conversion.cc',
     'double-conversion/fast-dtoa.cc',
--- a/netwerk/protocol/http/nsIHttpChannel.idl
+++ b/netwerk/protocol/http/nsIHttpChannel.idl
@@ -19,18 +19,18 @@ interface nsIHttpChannel : nsIChannel
 {
     /**************************************************************************
      * REQUEST CONFIGURATION
      *
      * Modifying request parameters after asyncOpen has been called is an error.
      */
 
     /**
-     * Set/get the HTTP request method (default is "GET").  Setter is case
-     * insensitive; getter returns an uppercase string.
+     * Set/get the HTTP request method (default is "GET").  Both setter and
+     * getter are case sensitive.
      *
      * This attribute may only be set before the channel is opened.
      *
      * NOTE: The data for a "POST" or "PUT" request can be configured via
      * nsIUploadChannel; however, after setting the upload data, it may be
      * necessary to set the request method explicitly.  The documentation
      * for nsIUploadChannel has further details.
      *
--- a/security/pkix/test/gtest/moz.build
+++ b/security/pkix/test/gtest/moz.build
@@ -15,16 +15,17 @@ SOURCES += [
 
     # The naming conventions are described in ./README.txt.
 
     'pkixder_input_tests.cpp',
     'pkixder_pki_types_tests.cpp',
     'pkixder_universal_types_tests.cpp',
     'pkixgtest.cpp',
     'pkixocsp_CreateEncodedOCSPRequest_tests.cpp',
+    'pkixocsp_VerifyEncodedOCSPResponse.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../../include',
     '../../lib',
     '../lib',
 ]
 
new file mode 100644
--- /dev/null
+++ b/security/pkix/test/gtest/pkixocsp_VerifyEncodedOCSPResponse.cpp
@@ -0,0 +1,885 @@
+/* -*- 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 code is made available to you under your choice of the following sets
+ * of licensing terms:
+ */
+/* 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/.
+ */
+/* Copyright 2014 Mozilla Contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "nss.h"
+#include "nssgtest.h"
+#include "pkix/pkix.h"
+#include "pkixtestutil.h"
+#include "prinit.h"
+#include "secerr.h"
+
+using namespace mozilla::pkix;
+using namespace mozilla::pkix::test;
+
+const uint16_t END_ENTITY_MAX_LIFETIME_IN_DAYS = 10;
+
+class OCSPTestTrustDomain : public TrustDomain
+{
+public:
+  OCSPTestTrustDomain()
+  {
+  }
+
+  virtual SECStatus GetCertTrust(EndEntityOrCA endEntityOrCA,
+                                 const CertPolicyId&,
+                                 const SECItem& candidateCert,
+                         /*out*/ TrustLevel* trustLevel)
+  {
+    EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
+    EXPECT_TRUE(trustLevel);
+    *trustLevel = TrustLevel::InheritsTrust;
+    return SECSuccess;
+  }
+
+  virtual SECStatus FindIssuer(const SECItem&, IssuerChecker&, PRTime)
+  {
+    ADD_FAILURE();
+    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+    return SECFailure;
+  }
+
+  virtual SECStatus CheckRevocation(EndEntityOrCA endEntityOrCA, const CertID&,
+                                    PRTime time,
+                                    /*optional*/ const SECItem*,
+                                    /*optional*/ const SECItem*)
+  {
+    // TODO: I guess mozilla::pkix should support revocation of designated
+    // OCSP responder eventually, but we don't now, so this function should
+    // never get called.
+    ADD_FAILURE();
+    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+    return SECFailure;
+  }
+
+  virtual SECStatus IsChainValid(const DERArray&)
+  {
+    ADD_FAILURE();
+    PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0);
+    return SECFailure;
+  }
+
+  virtual SECStatus VerifySignedData(const SignedDataWithSignature& signedData,
+                                     const SECItem& subjectPublicKeyInfo)
+  {
+    return ::mozilla::pkix::VerifySignedData(signedData, subjectPublicKeyInfo,
+                                             nullptr);
+  }
+
+  virtual SECStatus DigestBuf(const SECItem& item, /*out*/ uint8_t* digestBuf,
+                              size_t digestBufLen)
+  {
+    return ::mozilla::pkix::DigestBuf(item, digestBuf, digestBufLen);
+  }
+
+private:
+  OCSPTestTrustDomain(const OCSPTestTrustDomain&) /*delete*/;
+  void operator=(const OCSPTestTrustDomain&) /*delete*/;
+};
+
+namespace {
+char const* const rootName = "CN=Test CA 1";
+void deleteCertID(CertID* certID) { delete certID; }
+} // unnamed namespace
+
+class pkixocsp_VerifyEncodedResponse : public NSSTest
+{
+public:
+  static bool SetUpTestCaseInner()
+  {
+    ScopedSECKEYPublicKey rootPublicKey;
+    if (GenerateKeyPair(rootPublicKey, rootPrivateKey) != SECSuccess) {
+      return false;
+    }
+    rootSPKI = SECKEY_EncodeDERSubjectPublicKeyInfo(rootPublicKey.get());
+    if (!rootSPKI) {
+      return false;
+    }
+
+    return true;
+  }
+
+  static void SetUpTestCase()
+  {
+    NSSTest::SetUpTestCase();
+    if (!SetUpTestCaseInner()) {
+      PR_Abort();
+    }
+  }
+
+  void SetUp()
+  {
+    NSSTest::SetUp();
+
+    const SECItem* rootNameDER = ASCIIToDERName(arena.get(), rootName);
+    if (!rootNameDER) {
+      PR_Abort();
+    }
+    const SECItem*
+      endEntitySerialNumber(CreateEncodedSerialNumber(arena.get(),
+                                                      ++rootIssuedCount));
+    if (!endEntitySerialNumber) {
+      PR_Abort();
+    }
+    endEntityCertID = new (std::nothrow) CertID(*rootNameDER, *rootSPKI,
+                                                *endEntitySerialNumber);
+    if (!endEntityCertID) {
+      PR_Abort();
+    }
+  }
+
+  static ScopedSECKEYPrivateKey rootPrivateKey;
+  static ScopedSECItem rootSPKI;
+  static long rootIssuedCount;
+
+  OCSPTestTrustDomain trustDomain;
+  // endEntityCertID references items owned by arena and rootSPKI.
+  ScopedPtr<CertID, deleteCertID> endEntityCertID;
+};
+
+/*static*/ ScopedSECKEYPrivateKey
+              pkixocsp_VerifyEncodedResponse::rootPrivateKey;
+/*static*/ ScopedSECItem pkixocsp_VerifyEncodedResponse::rootSPKI;
+/*static*/ long pkixocsp_VerifyEncodedResponse::rootIssuedCount = 0;
+
+///////////////////////////////////////////////////////////////////////////////
+// responseStatus
+
+struct WithoutResponseBytes
+{
+  uint8_t responseStatus;
+  PRErrorCode expectedError;
+};
+
+static const WithoutResponseBytes WITHOUT_RESPONSEBYTES[] = {
+  { OCSPResponseContext::successful, SEC_ERROR_OCSP_MALFORMED_RESPONSE },
+  { OCSPResponseContext::malformedRequest, SEC_ERROR_OCSP_MALFORMED_REQUEST },
+  { OCSPResponseContext::internalError, SEC_ERROR_OCSP_SERVER_ERROR },
+  { OCSPResponseContext::tryLater, SEC_ERROR_OCSP_TRY_SERVER_LATER },
+  { 4/*unused*/, SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS },
+  { OCSPResponseContext::sigRequired, SEC_ERROR_OCSP_REQUEST_NEEDS_SIG },
+  { OCSPResponseContext::unauthorized, SEC_ERROR_OCSP_UNAUTHORIZED_REQUEST },
+  { OCSPResponseContext::unauthorized + 1,
+    SEC_ERROR_OCSP_UNKNOWN_RESPONSE_STATUS
+  },
+};
+
+class pkixocsp_VerifyEncodedResponse_WithoutResponseBytes
+  : public pkixocsp_VerifyEncodedResponse
+  , public ::testing::WithParamInterface<WithoutResponseBytes>
+{
+protected:
+  SECItem* CreateEncodedOCSPErrorResponse(uint8_t status)
+  {
+    static const SECItem EMPTY = { siBuffer, nullptr, 0 };
+    OCSPResponseContext context(arena.get(),
+                                CertID(EMPTY, EMPTY, EMPTY),
+                                oneDayBeforeNow);
+    context.responseStatus = status;
+    context.skipResponseBytes = true;
+    return CreateEncodedOCSPResponse(context);
+  }
+};
+
+TEST_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes, CorrectErrorCode)
+{
+  SECItem* response(CreateEncodedOCSPErrorResponse(
+                      GetParam().responseStatus));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(GetParam().expectedError,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+}
+
+INSTANTIATE_TEST_CASE_P(pkixocsp_VerifyEncodedResponse_WithoutResponseBytes,
+                        pkixocsp_VerifyEncodedResponse_WithoutResponseBytes,
+                        testing::ValuesIn(WITHOUT_RESPONSEBYTES));
+
+///////////////////////////////////////////////////////////////////////////////
+// "successful" responses
+
+namespace {
+
+// Alias for nullptr to aid readability in the code below.
+static const char* byKey = nullptr;
+
+} // unnamed namespcae
+
+class pkixocsp_VerifyEncodedResponse_successful
+  : public pkixocsp_VerifyEncodedResponse
+{
+public:
+  void SetUp()
+  {
+    pkixocsp_VerifyEncodedResponse::SetUp();
+  }
+
+  static void SetUpTestCase()
+  {
+    pkixocsp_VerifyEncodedResponse::SetUpTestCase();
+  }
+
+  SECItem* CreateEncodedOCSPSuccessfulResponse(
+                    OCSPResponseContext::CertStatus certStatus,
+                    const CertID& certID,
+                    /*optional*/ const char* signerName,
+                    const ScopedSECKEYPrivateKey& signerPrivateKey,
+                    PRTime producedAt, PRTime thisUpdate,
+                    /*optional*/ const PRTime* nextUpdate,
+                    /*optional*/ SECItem const* const* certs = nullptr)
+  {
+    OCSPResponseContext context(arena.get(), certID, producedAt);
+    if (signerName) {
+      context.signerNameDER = ASCIIToDERName(arena.get(), signerName);
+      if (!context.signerNameDER) {
+        return nullptr;
+      }
+    }
+    context.signerPrivateKey = SECKEY_CopyPrivateKey(signerPrivateKey.get());
+    if (!context.signerPrivateKey) {
+      return nullptr;
+    }
+    context.responseStatus = OCSPResponseContext::successful;
+    context.producedAt = producedAt;
+    context.certs = certs;
+
+    context.certIDHashAlg = SEC_OID_SHA1;
+    context.certStatus = certStatus;
+    context.thisUpdate = thisUpdate;
+    context.nextUpdate = nextUpdate ? *nextUpdate : 0;
+    context.includeNextUpdate = nextUpdate != nullptr;
+
+    return CreateEncodedOCSPResponse(context);
+  }
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey)
+{
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, byKey,
+                      rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      &oneDayAfterNow));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byName)
+{
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, rootName,
+                      rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      &oneDayAfterNow));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, good_byKey_without_nextUpdate)
+{
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, byKey,
+                      rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      nullptr));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, revoked)
+{
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::revoked, *endEntityCertID, byKey,
+                      rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      &oneDayAfterNow));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_REVOKED_CERTIFICATE,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_successful, unknown)
+{
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::unknown, *endEntityCertID, byKey,
+                      rootPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      &oneDayAfterNow));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_UNKNOWN_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// indirect responses (signed by a delegated OCSP responder cert)
+
+class pkixocsp_VerifyEncodedResponse_DelegatedResponder
+  : public pkixocsp_VerifyEncodedResponse_successful
+{
+protected:
+  // certSubjectName should be unique for each call. This way, we avoid any
+  // issues with NSS caching the certificates internally. For the same reason,
+  // we generate a new keypair on each call. Either one of these should be
+  // sufficient to avoid issues with the NSS cache, but we do both to be
+  // cautious.
+  //
+  // signerName should be byKey to use the byKey ResponderID construction, or
+  // another value (usually equal to certSubjectName) to use the byName
+  // ResponderID construction.
+  //
+  // If signerEKU is omitted, then the certificate will have the
+  // id-kp-OCSPSigning EKU. If signerEKU is SEC_OID_UNKNOWN then it will not
+  // have any EKU extension. Otherwise, the certificate will have the given
+  // EKU.
+  //
+  // signerDEROut is owned by the arena
+  SECItem* CreateEncodedIndirectOCSPSuccessfulResponse(
+              const char* certSubjectName,
+              OCSPResponseContext::CertStatus certStatus,
+              const char* signerName,
+              SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER,
+              /*optional, out*/ const SECItem** signerDEROut = nullptr)
+  {
+    PR_ASSERT(certSubjectName);
+
+    const SECItem* extensions[] = {
+      signerEKU != SEC_OID_UNKNOWN
+        ? CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
+                                    ExtensionCriticality::NotCritical)
+        : nullptr,
+      nullptr
+    };
+    ScopedSECKEYPrivateKey signerPrivateKey;
+    SECItem* signerDER(CreateEncodedCertificate(
+                          arena.get(), ++rootIssuedCount, rootName,
+                          oneDayBeforeNow, oneDayAfterNow, certSubjectName,
+                          signerEKU != SEC_OID_UNKNOWN ? extensions : nullptr,
+                          rootPrivateKey.get(), signerPrivateKey));
+    EXPECT_TRUE(signerDER);
+    if (!signerDER) {
+      return nullptr;
+    }
+
+    const SECItem* signerNameDER = nullptr;
+    if (signerName) {
+      signerNameDER = ASCIIToDERName(arena.get(), signerName);
+      if (!signerNameDER) {
+        return nullptr;
+      }
+    }
+    if (signerDEROut) {
+      *signerDEROut = signerDER;
+    }
+    SECItem const* const certs[] = { signerDER, nullptr };
+    return CreateEncodedOCSPSuccessfulResponse(certStatus, *endEntityCertID,
+                                               signerName, signerPrivateKey,
+                                               oneDayBeforeNow, oneDayBeforeNow,
+                                               &oneDayAfterNow, certs);
+  }
+
+  static SECItem* CreateEncodedCertificate(PLArenaPool* arena,
+                                           uint32_t serialNumber,
+                                           const char* issuer,
+                                           PRTime notBefore,
+                                           PRTime notAfter,
+                                           const char* subject,
+                              /*optional*/ SECItem const* const* extensions,
+                              /*optional*/ SECKEYPrivateKey* signerKey,
+                                   /*out*/ ScopedSECKEYPrivateKey& privateKey)
+  {
+    const SECItem* serialNumberDER(CreateEncodedSerialNumber(arena,
+                                                             serialNumber));
+    if (!serialNumberDER) {
+      return nullptr;
+    }
+    const SECItem* issuerDER(ASCIIToDERName(arena, issuer));
+    if (!issuerDER) {
+      return nullptr;
+    }
+    const SECItem* subjectDER(ASCIIToDERName(arena, subject));
+    if (!subjectDER) {
+      return nullptr;
+    }
+    return ::mozilla::pkix::test::CreateEncodedCertificate(
+                                    arena, v3,
+                                    SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION,
+                                    serialNumberDER, issuerDER, notBefore,
+                                    notAfter, subjectDER, extensions,
+                                    signerKey, SEC_OID_SHA256, privateKey);
+  }
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byKey)
+{
+  SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                      "CN=good_indirect_byKey", OCSPResponseContext::good,
+                      byKey));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_byName)
+{
+  SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                      "CN=good_indirect_byName", OCSPResponseContext::good,
+                      "CN=good_indirect_byName"));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+       good_byKey_missing_signer)
+{
+  ScopedSECKEYPublicKey missingSignerPublicKey;
+  ScopedSECKEYPrivateKey missingSignerPrivateKey;
+  ASSERT_SECSuccess(GenerateKeyPair(missingSignerPublicKey,
+                                    missingSignerPrivateKey));
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, byKey,
+                      missingSignerPrivateKey, oneDayBeforeNow,
+                      oneDayBeforeNow, nullptr));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+       good_byName_missing_signer)
+{
+  ScopedSECKEYPublicKey missingSignerPublicKey;
+  ScopedSECKEYPrivateKey missingSignerPrivateKey;
+  ASSERT_SECSuccess(GenerateKeyPair(missingSignerPublicKey,
+                                    missingSignerPrivateKey));
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, "CN=missing",
+                      missingSignerPrivateKey, oneDayBeforeNow,
+                      oneDayBeforeNow, nullptr));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_expired)
+{
+  static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
+  static const char* signerName = "CN=good_indirect_expired";
+
+  const SECItem* extensions[] = {
+    CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
+                              ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey signerPrivateKey;
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                              rootName,
+                                              now - (10 * ONE_DAY),
+                                              now - (2 * ONE_DAY),
+                                              signerName, extensions,
+                                              rootPrivateKey.get(),
+                                              signerPrivateKey));
+  ASSERT_TRUE(signerDER);
+
+  SECItem const* const certs[] = { signerDER, nullptr };
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID, signerName,
+                      signerPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                      &oneDayAfterNow,
+                      certs));
+  ASSERT_TRUE(response);
+
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_future)
+{
+  static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
+  static const char* signerName = "CN=good_indirect_future";
+
+  const SECItem* extensions[] = {
+    CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
+                              ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey signerPrivateKey;
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                              rootName,
+                                              now + (2 * ONE_DAY),
+                                              now + (10 * ONE_DAY),
+                                              signerName, extensions,
+                                              rootPrivateKey.get(),
+                                              signerPrivateKey));
+  ASSERT_TRUE(signerDER);
+
+  SECItem const* const certs[] = { signerDER, nullptr };
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                      OCSPResponseContext::good, *endEntityCertID,
+                      signerName, signerPrivateKey, oneDayBeforeNow,
+                      oneDayBeforeNow, &oneDayAfterNow, certs));
+  ASSERT_TRUE(response);
+
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_no_eku)
+{
+  SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                      "CN=good_indirect_wrong_eku", OCSPResponseContext::good,
+                      byKey, SEC_OID_UNKNOWN));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+       good_indirect_wrong_eku)
+{
+  SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                      "CN=good_indirect_wrong_eku", OCSPResponseContext::good,
+                      byKey, SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
+  ASSERT_TRUE(response);
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+// Test that signature of OCSP response signer cert is verified
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_tampered_eku)
+{
+  SECItem* response(CreateEncodedIndirectOCSPSuccessfulResponse(
+                      "CN=good_indirect_tampered_eku",
+                      OCSPResponseContext::good, byKey,
+                      SEC_OID_EXT_KEY_USAGE_SERVER_AUTH));
+  ASSERT_TRUE(response);
+
+#define EKU_PREFIX \
+  0x06, 8, /* OBJECT IDENTIFIER, 8 bytes */ \
+  0x2B, 6, 1, 5, 5, 7, /* id-pkix */ \
+  0x03 /* id-kp */
+  static const uint8_t EKU_SERVER_AUTH[] = { EKU_PREFIX, 0x01 }; // serverAuth
+  static const uint8_t EKU_OCSP_SIGNER[] = { EKU_PREFIX, 0x09 }; // OCSPSigning
+#undef EKU_PREFIX
+  ASSERT_SECSuccess(TamperOnce(*response,
+                               EKU_SERVER_AUTH, PR_ARRAY_SIZE(EKU_SERVER_AUTH),
+                               EKU_OCSP_SIGNER, PR_ARRAY_SIZE(EKU_OCSP_SIGNER)));
+
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder, good_unknown_issuer)
+{
+  static const char* subCAName = "CN=good_indirect_unknown_issuer sub-CA";
+  static const char* signerName = "CN=good_indirect_unknown_issuer OCSP signer";
+
+  // unknown issuer
+  ScopedSECKEYPublicKey unknownPublicKey;
+  ScopedSECKEYPrivateKey unknownPrivateKey;
+  ASSERT_SECSuccess(GenerateKeyPair(unknownPublicKey, unknownPrivateKey));
+
+  // Delegated responder cert signed by unknown issuer
+  static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
+  const SECItem* extensions[] = {
+    CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
+                              ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey signerPrivateKey;
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1,
+                        subCAName, oneDayBeforeNow, oneDayAfterNow,
+                        signerName, extensions, unknownPrivateKey.get(),
+                        signerPrivateKey));
+  ASSERT_TRUE(signerDER);
+
+  // OCSP response signed by that delegated responder
+  SECItem const* const certs[] = { signerDER, nullptr };
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                        OCSPResponseContext::good, *endEntityCertID,
+                        signerName, signerPrivateKey, oneDayBeforeNow,
+                        oneDayBeforeNow, &oneDayAfterNow, certs));
+  ASSERT_TRUE(response);
+
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+// The CA that issued the OCSP responder cert is a sub-CA of the issuer of
+// the certificate that the OCSP response is for. That sub-CA cert is included
+// in the OCSP response before the OCSP responder cert.
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+       good_indirect_subca_1_first)
+{
+  static const char* subCAName = "CN=good_indirect_subca_1_first sub-CA";
+  static const char* signerName = "CN=good_indirect_subca_1_first OCSP signer";
+
+  // sub-CA of root (root is the direct issuer of endEntity)
+  const SECItem* subCAExtensions[] = {
+    CreateEncodedBasicConstraints(arena.get(), true, 0,
+                                  ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey subCAPrivateKey;
+  SECItem* subCADER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                             rootName,
+                                             oneDayBeforeNow, oneDayAfterNow,
+                                             subCAName, subCAExtensions,
+                                             rootPrivateKey.get(),
+                                             subCAPrivateKey));
+  ASSERT_TRUE(subCADER);
+
+  // Delegated responder cert signed by that sub-CA
+  static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
+  const SECItem* extensions[] = {
+    CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
+                              ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey signerPrivateKey;
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1, subCAName,
+                                              oneDayBeforeNow, oneDayAfterNow,
+                                              signerName, extensions,
+                                              subCAPrivateKey.get(),
+                                              signerPrivateKey));
+  ASSERT_TRUE(signerDER);
+
+  // OCSP response signed by the delegated responder issued by the sub-CA
+  // that is trying to impersonate the root.
+  SECItem const* const certs[] = { subCADER, signerDER, nullptr };
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                        OCSPResponseContext::good, *endEntityCertID, signerName,
+                        signerPrivateKey, oneDayBeforeNow, oneDayBeforeNow,
+                        &oneDayAfterNow,
+                        certs));
+  ASSERT_TRUE(response);
+
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+// The CA that issued the OCSP responder cert is a sub-CA of the issuer of
+// the certificate that the OCSP response is for. That sub-CA cert is included
+// in the OCSP response after the OCSP responder cert.
+TEST_F(pkixocsp_VerifyEncodedResponse_DelegatedResponder,
+       good_indirect_subca_1_second)
+{
+  static const char* subCAName = "CN=good_indirect_subca_1_second sub-CA";
+  static const char* signerName = "CN=good_indirect_subca_1_second OCSP signer";
+
+  // sub-CA of root (root is the direct issuer of endEntity)
+  const SECItem* subCAExtensions[] = {
+    CreateEncodedBasicConstraints(arena.get(), true, 0,
+                                  ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey subCAPrivateKey;
+  SECItem* subCADER(CreateEncodedCertificate(arena.get(), ++rootIssuedCount,
+                                             rootName,
+                                             oneDayBeforeNow, oneDayAfterNow,
+                                             subCAName, subCAExtensions,
+                                             rootPrivateKey.get(),
+                                             subCAPrivateKey));
+  ASSERT_TRUE(subCADER);
+
+  // Delegated responder cert signed by that sub-CA
+  static const SECOidTag signerEKU = SEC_OID_OCSP_RESPONDER;
+  const SECItem* extensions[] = {
+    CreateEncodedEKUExtension(arena.get(), &signerEKU, 1,
+                              ExtensionCriticality::NotCritical),
+    nullptr
+  };
+  ScopedSECKEYPrivateKey signerPrivateKey;
+  SECItem* signerDER(CreateEncodedCertificate(arena.get(), 1, subCAName,
+                                              oneDayBeforeNow, oneDayAfterNow,
+                                              signerName, extensions,
+                                              subCAPrivateKey.get(),
+                                              signerPrivateKey));
+  ASSERT_TRUE(signerDER);
+
+  // OCSP response signed by the delegated responder issued by the sub-CA
+  // that is trying to impersonate the root.
+  SECItem const* const certs[] = { signerDER, subCADER, nullptr };
+  SECItem* response(CreateEncodedOCSPSuccessfulResponse(
+                        OCSPResponseContext::good, *endEntityCertID,
+                        signerName, signerPrivateKey, oneDayBeforeNow,
+                        oneDayBeforeNow, &oneDayAfterNow, certs));
+  ASSERT_TRUE(response);
+
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+class pkixocsp_VerifyEncodedResponse_GetCertTrust
+  : public pkixocsp_VerifyEncodedResponse_DelegatedResponder {
+public:
+  pkixocsp_VerifyEncodedResponse_GetCertTrust()
+    : signerCertDER(nullptr)
+    , response(nullptr)
+  {
+  }
+
+  void SetUp()
+  {
+    pkixocsp_VerifyEncodedResponse_DelegatedResponder::SetUp();
+    response = CreateEncodedIndirectOCSPSuccessfulResponse(
+                          "CN=OCSPGetCertTrustTest Signer",
+                          OCSPResponseContext::good, byKey,
+                          SEC_OID_OCSP_RESPONDER, &signerCertDER);
+    if (!response || !signerCertDER) {
+      PR_Abort();
+    }
+  }
+
+  class TrustDomain : public OCSPTestTrustDomain
+  {
+  public:
+    TrustDomain()
+      : certTrustLevel(TrustLevel::InheritsTrust)
+    {
+    }
+
+    bool SetCertTrust(const SECItem* certDER, TrustLevel certTrustLevel)
+    {
+      this->certDER = certDER;
+      this->certTrustLevel = certTrustLevel;
+      return true;
+    }
+  private:
+    virtual SECStatus GetCertTrust(EndEntityOrCA endEntityOrCA,
+                                   const CertPolicyId&,
+                                   const SECItem& candidateCert,
+                           /*out*/ TrustLevel* trustLevel)
+    {
+      EXPECT_EQ(endEntityOrCA, EndEntityOrCA::MustBeEndEntity);
+      EXPECT_TRUE(trustLevel);
+      EXPECT_TRUE(certDER);
+      EXPECT_TRUE(SECITEM_ItemsAreEqual(certDER, &candidateCert));
+      *trustLevel = certTrustLevel;
+      return SECSuccess;
+    }
+
+    const SECItem* certDER; // weak pointer
+    TrustLevel certTrustLevel;
+  };
+
+  TrustDomain trustDomain;
+  const SECItem* signerCertDER; // owned by arena
+  SECItem* response; // owned by arena
+};
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, InheritTrust)
+{
+  ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+                                       TrustLevel::InheritsTrust));
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, TrustAnchor)
+{
+  ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+                                       TrustLevel::TrustAnchor));
+  bool expired;
+  ASSERT_SECSuccess(VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
+
+TEST_F(pkixocsp_VerifyEncodedResponse_GetCertTrust, ActivelyDistrusted)
+{
+  ASSERT_TRUE(trustDomain.SetCertTrust(signerCertDER,
+                                       TrustLevel::ActivelyDistrusted));
+  bool expired;
+  ASSERT_SECFailure(SEC_ERROR_OCSP_INVALID_SIGNING_CERT,
+                    VerifyEncodedOCSPResponse(trustDomain, *endEntityCertID, now,
+                                              END_ENTITY_MAX_LIFETIME_IN_DAYS,
+                                              *response, expired));
+  ASSERT_FALSE(expired);
+}
--- a/security/pkix/test/lib/pkixtestutil.cpp
+++ b/security/pkix/test/lib/pkixtestutil.cpp
@@ -88,16 +88,69 @@ OpenFile(const char* dir, const char* fi
   if (!file) {
     // TODO: map errno to NSPR error code
     PR_SetError(PR_FILE_NOT_FOUND_ERROR, errno);
   }
 #endif
   return file.release();
 }
 
+SECStatus
+TamperOnce(SECItem& item,
+           const uint8_t* from, size_t fromLen,
+           const uint8_t* to, size_t toLen)
+{
+  if (!item.data || !from || !to || fromLen != toLen) {
+    PR_NOT_REACHED("invalid args to TamperOnce");
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return SECFailure;
+  }
+
+  if (fromLen < 8) {
+    PR_NOT_REACHED("invalid parameter to TamperOnce; fromLen must be at least 8");
+    PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+    return SECFailure;
+  }
+
+  uint8_t* p = item.data;
+  size_t remaining = item.len;
+  bool alreadyFoundMatch = false;
+  for (;;) {
+    uint8_t* foundFirstByte = static_cast<uint8_t*>(memchr(p, from[0],
+                                                           remaining));
+    if (!foundFirstByte) {
+      if (alreadyFoundMatch) {
+        return SECSuccess;
+      }
+      PR_SetError(SEC_ERROR_BAD_DATA, 0);
+      return SECFailure;
+    }
+    remaining -= (foundFirstByte - p);
+    if (remaining < fromLen) {
+      if (alreadyFoundMatch) {
+        return SECSuccess;
+      }
+      PR_SetError(SEC_ERROR_BAD_DATA, 0);
+      return SECFailure;
+    }
+    if (!memcmp(foundFirstByte, from, fromLen)) {
+      if (alreadyFoundMatch) {
+        PR_SetError(SEC_ERROR_BAD_DATA, 0);
+        return SECFailure;
+      }
+      alreadyFoundMatch = true;
+      memmove(foundFirstByte, to, toLen);
+      p = foundFirstByte + toLen;
+    } else {
+      p = foundFirstByte + 1;
+      --remaining;
+    }
+  }
+}
+
 class Output
 {
 public:
   Output()
     : numItems(0)
     , length(0)
   {
   }
--- a/security/pkix/test/lib/pkixtestutil.h
+++ b/security/pkix/test/lib/pkixtestutil.h
@@ -70,16 +70,26 @@ PRTime YMDHMS(int16_t year, int16_t mont
               int16_t hour, int16_t minutes, int16_t seconds);
 
 SECStatus GenerateKeyPair(/*out*/ ScopedSECKEYPublicKey& publicKey,
                           /*out*/ ScopedSECKEYPrivateKey& privateKey);
 
 // The result will be owned by the arena
 const SECItem* ASCIIToDERName(PLArenaPool* arena, const char* cn);
 
+// Replace one substring in item with another of the same length, but only if
+// the substring was found exactly once. The "only once" restriction is helpful
+// for avoiding making multiple changes at once.
+//
+// The string to search for must be 8 or more bytes long so that it is
+// extremely unlikely that there will ever be any false positive matches
+// in digital signatures, keys, hashes, etc.
+SECStatus TamperOnce(SECItem& item, const uint8_t* from, size_t fromLen,
+                     const uint8_t* to, size_t toLen);
+
 ///////////////////////////////////////////////////////////////////////////////
 // Encode Certificates
 
 enum Version { v1 = 0, v2 = 1, v3 = 2 };
 
 // serialNumber is assumed to be the DER encoding of an INTEGER.
 //
 // If extensions is null, then no extensions will be encoded. Otherwise,
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -298,17 +298,22 @@ function FxAccountsInternal() {
   // able to abort all work on the first sign-in process.  The currentTimer and
   // currentAccountState are used for this purpose.
   // (XXX - should the timer be directly on the currentAccountState?)
   this.currentTimer = null;
   this.currentAccountState = new AccountState(this);
 
   // We don't reference |profileDir| in the top-level module scope
   // as we may be imported before we know where it is.
+  // We only want the fancy new LoginManagerStorage on desktop.
+#if defined(MOZ_B2G)
   this.signedInUserStorage = new JSONStorage({
+#else
+  this.signedInUserStorage = new LoginManagerStorage({
+#endif
     filename: DEFAULT_STORAGE_FILENAME,
     baseDir: OS.Constants.Path.profileDir,
   });
 }
 
 /**
  * The internal API's prototype.
  */
@@ -896,16 +901,204 @@ JSONStorage.prototype = {
       .then(CommonUtils.writeJSON.bind(null, contents, this.path));
   },
 
   get: function() {
     return CommonUtils.readJSON(this.path);
   }
 };
 
+/**
+ * LoginManagerStorage constructor that creates instances that may set/get
+ * from a combination of a clear-text JSON file and stored securely in
+ * the nsILoginManager.
+ *
+ * @param options {
+ *                  filename: of the plain-text file to write to
+ *                  baseDir: directory where the file resides
+ *                }
+ * @return instance
+ */
+
+function LoginManagerStorage(options) {
+  // we reuse the JSONStorage for writing the plain-text stuff.
+  this.jsonStorage = new JSONStorage(options);
+}
+
+LoginManagerStorage.prototype = {
+  // The fields in the credentials JSON object that are stored in plain-text
+  // in the profile directory.  All other fields are stored in the login manager,
+  // and thus are only available when the master-password is unlocked.
+
+  // a hook point for testing.
+  get _isLoggedIn() {
+    return Services.logins.isLoggedIn;
+  },
+
+  // Clear any data from the login manager.  Returns true if the login manager
+  // was unlocked (even if no existing logins existed) or false if it was
+  // locked (meaning we don't even know if it existed or not.)
+  _clearLoginMgrData: Task.async(function* () {
+    try { // Services.logins might be third-party and broken...
+      yield Services.logins.initializationPromise;
+      if (!this._isLoggedIn) {
+        return false;
+      }
+      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
+      for (let login of logins) {
+        Services.logins.removeLogin(login);
+      }
+      return true;
+    } catch (ex) {
+      log.error("Failed to clear login data: ${}", ex);
+      return false;
+    }
+  }),
+
+  set: Task.async(function* (contents) {
+    if (!contents) {
+      // User is signing out - write the null to the json file.
+      yield this.jsonStorage.set(contents);
+
+      // And nuke it from the login manager.
+      let cleared = yield this._clearLoginMgrData();
+      if (!cleared) {
+        // just log a message - we verify that the email address matches when
+        // we reload it, so having a stale entry doesn't really hurt.
+        log.info("not removing credentials from login manager - not logged in");
+      }
+      return;
+    }
+
+    // We are saving actual data.
+    // Split the data into 2 chunks - one to go to the plain-text, and the
+    // other to write to the login manager.
+    let toWriteJSON = {version: contents.version};
+    let accountDataJSON = toWriteJSON.accountData = {};
+    let toWriteLoginMgr = {version: contents.version};
+    let accountDataLoginMgr = toWriteLoginMgr.accountData = {};
+    for (let [name, value] of Iterator(contents.accountData)) {
+      if (FXA_PWDMGR_PLAINTEXT_FIELDS.indexOf(name) >= 0) {
+        accountDataJSON[name] = value;
+      } else {
+        accountDataLoginMgr[name] = value;
+      }
+    }
+    yield this.jsonStorage.set(toWriteJSON);
+
+    try { // Services.logins might be third-party and broken...
+      // and the stuff into the login manager.
+      yield Services.logins.initializationPromise;
+      // If MP is locked we silently fail - the user may need to re-auth
+      // next startup.
+      if (!this._isLoggedIn) {
+        log.info("not saving credentials to login manager - not logged in");
+        return;
+      }
+      // write the rest of the data to the login manager.
+      let loginInfo = new Components.Constructor(
+         "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo, "init");
+      let login = new loginInfo(FXA_PWDMGR_HOST,
+                                null, // aFormSubmitURL,
+                                FXA_PWDMGR_REALM, // aHttpRealm,
+                                contents.accountData.email, // aUsername
+                                JSON.stringify(toWriteLoginMgr), // aPassword
+                                "", // aUsernameField
+                                "");// aPasswordField
+
+      let existingLogins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null,
+                                                      FXA_PWDMGR_REALM);
+      if (existingLogins.length) {
+        Services.logins.modifyLogin(existingLogins[0], login);
+      } else {
+        Services.logins.addLogin(login);
+      }
+    } catch (ex) {
+      log.error("Failed to save data to the login manager: ${}", ex);
+    }
+  }),
+
+  get: Task.async(function* () {
+    // we need to suck some data from the .json file in the profile dir and
+    // some other from the login manager.
+    let data = yield this.jsonStorage.get();
+    if (!data) {
+      // no user logged in, nuke the storage data incase we couldn't remove
+      // it previously and then we are done.
+      yield this._clearLoginMgrData();
+      return null;
+    }
+
+    // if we have encryption keys it must have been saved before we
+    // used the login manager, so re-save it.
+    if (data.accountData.kA || data.accountData.kB || data.keyFetchToken) {
+      // We need to migrate, but the MP might be locked (eg, on the first run
+      // with this enabled, we will get here very soon after startup, so will
+      // certainly be locked.)  This means we can't actually store the data in
+      // the login manager (and thus might lose it if we migrated now)
+      // So if the MP is locked, we *don't* migrate, but still just return
+      // the subset of data we now store in the JSON.
+      // This will cause sync to notice the lack of keys, force an unlock then
+      // re-fetch the account data to see if the keys are there.  At *that*
+      // point we will end up back here, but because the MP is now unlocked
+      // we can actually perform the migration.
+      if (!this._isLoggedIn) {
+        // return the "safe" subset but leave the storage alone.
+        log.info("account data needs migration to the login manager but the MP is locked.");
+        let result = {
+          version: data.version,
+          accountData: {},
+        };
+        for (let fieldName of FXA_PWDMGR_PLAINTEXT_FIELDS) {
+          result.accountData[fieldName] = data.accountData[fieldName];
+        }
+        return result;
+      }
+      // actually migrate - just calling .set() will split everything up.
+      log.info("account data is being migrated to the login manager.");
+      yield this.set(data);
+    }
+
+    try { // Services.logins might be third-party and broken...
+      // read the data from the login manager and merge it for return.
+      yield Services.logins.initializationPromise;
+
+      if (!this._isLoggedIn) {
+        log.info("returning partial account data as the login manager is locked.");
+        return data;
+      }
+
+      let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
+      if (logins.length == 0) {
+        // This could happen if the MP was locked when we wrote the data.
+        log.info("Can't find the rest of the credentials in the login manager");
+        return data;
+      }
+      let login = logins[0];
+      if (login.username == data.accountData.email) {
+        let lmData = JSON.parse(login.password);
+        if (lmData.version == data.version) {
+          // Merge the login manager data
+          copyObjectProperties(lmData.accountData, data.accountData);
+        } else {
+          log.info("version field in the login manager doesn't match - ignoring it");
+          yield this._clearLoginMgrData();
+        }
+      } else {
+        log.info("username in the login manager doesn't match - ignoring it");
+        yield this._clearLoginMgrData();
+      }
+    } catch (ex) {
+      log.error("Failed to get data from the login manager: ${}", ex);
+    }
+    return data;
+  }),
+
+}
+
 // A getter for the instance to export
 XPCOMUtils.defineLazyGetter(this, "fxAccounts", function() {
   let a = new FxAccounts();
 
   // XXX Bug 947061 - We need a strategy for resuming email verification after
   // browser restart
   a.loadAndPoll();
 
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -173,10 +173,23 @@ SERVER_ERRNO_TO_ERROR[ERRNO_INVALID_AUTH
 SERVER_ERRNO_TO_ERROR[ERRNO_ENDPOINT_NO_LONGER_SUPPORTED]   = ERROR_ENDPOINT_NO_LONGER_SUPPORTED;
 SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_LOGIN_METHOD]         = ERROR_INCORRECT_LOGIN_METHOD;
 SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_KEY_RETRIEVAL_METHOD] = ERROR_INCORRECT_KEY_RETRIEVAL_METHOD;
 SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_API_VERSION]          = ERROR_INCORRECT_API_VERSION;
 SERVER_ERRNO_TO_ERROR[ERRNO_INCORRECT_EMAIL_CASE]           = ERROR_INCORRECT_EMAIL_CASE;
 SERVER_ERRNO_TO_ERROR[ERRNO_SERVICE_TEMP_UNAVAILABLE]       = ERROR_SERVICE_TEMP_UNAVAILABLE;
 SERVER_ERRNO_TO_ERROR[ERRNO_UNKNOWN_ERROR]                  = ERROR_UNKNOWN;
 
+// FxAccounts has the ability to "split" the credentials between a plain-text
+// JSON file in the profile dir and in the login manager.
+// These constants relate to that.
+
+// The fields we save in the plaintext JSON.
+// See bug 1013064 comments 23-25 for why the sessionToken is "safe"
+this.FXA_PWDMGR_PLAINTEXT_FIELDS = ["email", "verified", "authAt",
+                                    "sessionToken", "uid"];
+// The pseudo-host we use in the login manager
+this.FXA_PWDMGR_HOST = "chrome://FirefoxAccounts";
+// The realm we use in the login manager.
+this.FXA_PWDMGR_REALM = "Firefox Accounts credentials";
+
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = Object.keys(this);
--- a/services/fxaccounts/moz.build
+++ b/services/fxaccounts/moz.build
@@ -5,16 +5,19 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 PARALLEL_DIRS += ['interfaces']
 
 TEST_DIRS += ['tests']
 
 EXTRA_JS_MODULES += [
   'Credentials.jsm',
-  'FxAccounts.jsm',
   'FxAccountsClient.jsm',
   'FxAccountsCommon.js'
 ]
 
+EXTRA_PP_JS_MODULES += [
+  'FxAccounts.jsm',
+]
+
 # For now, we will only be using the FxA manager in B2G.
 if CONFIG['MOZ_B2G']:
   EXTRA_JS_MODULES += ['FxAccountsManager.jsm']
new file mode 100644
--- /dev/null
+++ b/services/fxaccounts/tests/xpcshell/test_loginmgr_storage.js
@@ -0,0 +1,309 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests for FxAccounts, storage and the master password.
+
+// Stop us hitting the real auth server.
+Services.prefs.setCharPref("identity.fxaccounts.auth.uri", "http://localhost");
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FxAccounts.jsm");
+Cu.import("resource://gre/modules/FxAccountsClient.jsm");
+Cu.import("resource://gre/modules/FxAccountsCommon.js");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://gre/modules/FxAccountsCommon.js");
+
+initTestLogging("Trace");
+// See verbose logging from FxAccounts.jsm
+Services.prefs.setCharPref("identity.fxaccounts.loglevel", "DEBUG");
+
+function run_test() {
+  run_next_test();
+}
+
+function getLoginMgrData() {
+  let logins = Services.logins.findLogins({}, FXA_PWDMGR_HOST, null, FXA_PWDMGR_REALM);
+  if (logins.length == 0) {
+    return null;
+  }
+  Assert.equal(logins.length, 1, "only 1 login available");
+  return logins[0];
+}
+
+add_task(function test_simple() {
+  let fxa = new FxAccounts({});
+
+  let creds = {
+    email: "test@example.com",
+    sessionToken: "sessionToken",
+    kA: "the kA value",
+    kB: "the kB value",
+    verified: true
+  };
+  yield fxa.setSignedInUser(creds);
+
+  // This should have stored stuff in both the .json file in the profile
+  // dir, and the login dir.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
+  let data = yield CommonUtils.readJSON(path);
+
+  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
+  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
+  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
+
+  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
+  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
+
+  let login = getLoginMgrData();
+  Assert.strictEqual(login.username, creds.email, "email matches");
+  let loginData = JSON.parse(login.password);
+  Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
+  Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
+  Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
+
+  Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
+  Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
+  Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
+
+  yield fxa.signOut(/* localOnly = */ true);
+  Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
+});
+
+add_task(function test_MPLocked() {
+  let fxa = new FxAccounts({});
+
+  let creds = {
+    email: "test@example.com",
+    sessionToken: "sessionToken",
+    kA: "the kA value",
+    kB: "the kB value",
+    verified: true
+  };
+
+  // tell the storage that the MP is locked.
+  fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
+  yield fxa.setSignedInUser(creds);
+
+  // This should have stored stuff in the .json, and the login manager stuff
+  // will not exist.
+  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
+  let data = yield CommonUtils.readJSON(path);
+
+  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
+  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
+  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
+
+  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
+  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
+
+  Assert.strictEqual(getLoginMgrData(), null, "login mgr data doesn't exist");
+  yield fxa.signOut(/* localOnly = */ true)
+});
+
+add_task(function test_migrationMPUnlocked() {
+  // first manually save a signedInUser.json to simulate a first-run with
+  // pre-migrated data.
+  let fxa = new FxAccounts({});
+
+  let creds = {
+    email: "test@example.com",
+    sessionToken: "sessionToken",
+    kA: "the kA value",
+    kB: "the kB value",
+    verified: true
+  };
+  let toWrite = {
+    version: fxa.version,
+    accountData: creds,
+  }
+
+  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
+  yield CommonUtils.writeJSON(toWrite, path);
+
+  // now load it - it should migrate.
+  let data = yield fxa.getSignedInUser();
+  Assert.deepEqual(data, creds, "we got all the data back");
+
+  // and verify it was actually migrated - re-read signedInUser back.
+  let data = yield CommonUtils.readJSON(path);
+
+  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
+  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
+  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
+
+  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
+  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
+
+  let login = getLoginMgrData();
+  Assert.strictEqual(login.username, creds.email, "email matches");
+  let loginData = JSON.parse(login.password);
+  Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
+  Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
+  Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
+
+  Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
+  Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
+  Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
+
+  yield fxa.signOut(/* localOnly = */ true);
+  Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
+});
+
+add_task(function test_migrationMPLocked() {
+  // first manually save a signedInUser.json to simulate a first-run with
+  // pre-migrated data.
+  let fxa = new FxAccounts({});
+
+  let creds = {
+    email: "test@example.com",
+    sessionToken: "sessionToken",
+    kA: "the kA value",
+    kB: "the kB value",
+    verified: true
+  };
+  let toWrite = {
+    version: fxa.version,
+    accountData: creds,
+  }
+
+  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
+  yield CommonUtils.writeJSON(toWrite, path);
+
+  // pretend the MP is locked.
+  fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
+
+  // now load it - it should *not* migrate, but should only give the JSON-safe
+  // data back.
+  let data = yield fxa.getSignedInUser();
+  Assert.ok(!data.kA);
+  Assert.ok(!data.kB);
+
+  // and verify the data on disk wan't migrated.
+  data = yield CommonUtils.readJSON(path);
+  Assert.deepEqual(data, toWrite);
+
+  // Now "unlock" and re-ask for the signedInUser - it should migrate.
+  fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() true);
+  data = yield fxa.getSignedInUser();
+  // this time we should have got all the data, not just the JSON-safe fields.
+  Assert.strictEqual(data.kA, creds.kA);
+  Assert.strictEqual(data.kB, creds.kB);
+
+  // And verify the data in the JSON was migrated
+  data = yield CommonUtils.readJSON(path);
+  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
+  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
+  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
+
+  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
+  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
+
+  let login = getLoginMgrData();
+  Assert.strictEqual(login.username, creds.email, "email matches");
+  let loginData = JSON.parse(login.password);
+  Assert.strictEqual(loginData.version, data.version, "same version flag in both places");
+  Assert.strictEqual(loginData.accountData.kA, creds.kA, "correct kA in the login mgr");
+  Assert.strictEqual(loginData.accountData.kB, creds.kB, "correct kB in the login mgr");
+
+  Assert.ok(!("email" in loginData), "email not stored in the login mgr json");
+  Assert.ok(!("sessionToken" in loginData), "sessionToken not stored in the login mgr json");
+  Assert.ok(!("verified" in loginData), "verified not stored in the login mgr json");
+
+  yield fxa.signOut(/* localOnly = */ true);
+  Assert.strictEqual(getLoginMgrData(), null, "login mgr data deleted on logout");
+});
+
+add_task(function test_consistentWithMPEdgeCases() {
+  let fxa = new FxAccounts({});
+
+  let creds1 = {
+    email: "test@example.com",
+    sessionToken: "sessionToken",
+    kA: "the kA value",
+    kB: "the kB value",
+    verified: true
+  };
+
+  let creds2 = {
+    email: "test2@example.com",
+    sessionToken: "sessionToken2",
+    kA: "the kA value2",
+    kB: "the kB value2",
+    verified: false,
+  };
+
+  // Log a user in while MP is unlocked.
+  yield fxa.setSignedInUser(creds1);
+
+  // tell the storage that the MP is locked - this will prevent logout from
+  // being able to clear the data.
+  fxa.internal.signedInUserStorage.__defineGetter__("_isLoggedIn", function() false);
+
+  // now set the second credentials.
+  yield fxa.setSignedInUser(creds2);
+
+  // We should still have creds1 data in the login manager.
+  let login = getLoginMgrData();
+  Assert.strictEqual(login.username, creds1.email);
+  // and that we do have the first kA in the login manager.
+  Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds1.kA,
+                     "stale data still in login mgr");
+
+  // Make a new FxA instance (otherwise the values in memory will be used.)
+  // Because we haven't overridden _isLoggedIn for this new instance it will
+  // treat the MP as unlocked.
+  let fxa = new FxAccounts({});
+
+  let accountData = yield fxa.getSignedInUser();
+  Assert.strictEqual(accountData.email, creds2.email);
+  // we should have no kA at all.
+  Assert.strictEqual(accountData.kA, undefined, "stale kA wasn't used");
+  yield fxa.signOut(/* localOnly = */ true)
+});
+
+add_task(function test_migration() {
+  // manually write out the full creds data to the JSON - this will look like
+  // old data that needs migration.
+  let creds = {
+    email: "test@example.com",
+    sessionToken: "sessionToken",
+    kA: "the kA value",
+    kB: "the kB value",
+    verified: true
+  };
+  let toWrite = {
+    version: 1,
+    accountData: creds,
+  };
+
+  let path = OS.Path.join(OS.Constants.Path.profileDir, "signedInUser.json");
+  let data = yield CommonUtils.writeJSON(toWrite, path);
+
+  // Create an FxA object - and tell it to load the data.
+  let fxa = new FxAccounts({});
+  data = yield fxa.getSignedInUser();
+
+  Assert.deepEqual(data, creds, "we should have everything available");
+
+  // now sniff the data on disk - it should have been magically migrated.
+  data = yield CommonUtils.readJSON(path);
+
+  Assert.strictEqual(data.accountData.email, creds.email, "correct email in the clear text");
+  Assert.strictEqual(data.accountData.sessionToken, creds.sessionToken, "correct sessionToken in the clear text");
+  Assert.strictEqual(data.accountData.verified, creds.verified, "correct verified flag");
+
+  Assert.ok(!("kA" in data.accountData), "kA not stored in clear text");
+  Assert.ok(!("kB" in data.accountData), "kB not stored in clear text");
+
+  // and it should magically be in the login manager.
+  let login = getLoginMgrData();
+  Assert.strictEqual(login.username, creds.email);
+  // and that we do have the first kA in the login manager.
+  Assert.strictEqual(JSON.parse(login.password).accountData.kA, creds.kA,
+                     "kA was migrated");
+
+  yield fxa.signOut(/* localOnly = */ true)
+});
--- a/services/fxaccounts/tests/xpcshell/xpcshell.ini
+++ b/services/fxaccounts/tests/xpcshell/xpcshell.ini
@@ -1,10 +1,12 @@
 [DEFAULT]
 head = head.js ../../../common/tests/unit/head_helpers.js ../../../common/tests/unit/head_http.js
 tail =
 
 [test_accounts.js]
 [test_client.js]
 [test_credentials.js]
+[test_loginmgr_storage.js]
+skip-if = appname == 'b2g' # login manager storage only used on desktop.
 [test_manager.js]
 run-if = appname == 'b2g'
 reason = FxAccountsManager is only available for B2G for now
--- a/services/sync/Weave.js
+++ b/services/sync/Weave.js
@@ -104,26 +104,16 @@ WeaveService.prototype = {
       let username = Services.prefs.getCharPref(SYNC_PREFS_BRANCH + "username");
       return !username || username.contains('@');
     } catch (_) {
       return true; // No username == only allow FxA to be configured.
     }
   },
 
   /**
-   * Returns whether the password engine is allowed. We explicitly disallow
-   * the password engine when a master password is used to ensure those can't
-   * be accessed without the master key.
-   */
-  get allowPasswordsEngine() {
-    // This doesn't apply to old-style sync, it's only an issue for FxA.
-    return !this.fxAccountsEnabled || !Utils.mpEnabled();
-  },
-
-  /**
    * Whether Sync appears to be enabled.
    *
    * This returns true if all the Sync preferences for storing account
    * and server configuration are populated.
    *
    * It does *not* perform a robust check to see if the client is working.
    * For that, you'll want to check Weave.Status.checkSetup().
    */
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -394,41 +394,83 @@ this.BrowserIDManager.prototype = {
     this._syncKeyUpdated = true;
     this._shouldHaveSyncKeyBundle = false;
   },
 
   /**
    * The current state of the auth credentials.
    *
    * This essentially validates that enough credentials are available to use
-   * Sync.
+   * Sync, although it effectively ignores the state of the master-password -
+   * if that's locked and that's the only problem we can see, say everything
+   * is OK - unlockAndVerifyAuthState will be used to perform the unlock
+   * and re-verification if necessary.
    */
   get currentAuthState() {
     if (this._authFailureReason) {
       this._log.info("currentAuthState returning " + this._authFailureReason +
                      " due to previous failure");
       return this._authFailureReason;
     }
     // TODO: need to revisit this. Currently this isn't ready to go until
     // both the username and syncKeyBundle are both configured and having no
     // username seems to make things fail fast so that's good.
     if (!this.username) {
       return LOGIN_FAILED_NO_USERNAME;
     }
 
     // No need to check this.syncKey as our getter for that attribute
     // uses this.syncKeyBundle
-    // If bundle creation started, but failed.
-    if (this._shouldHaveSyncKeyBundle && !this.syncKeyBundle) {
-      return LOGIN_FAILED_NO_PASSPHRASE;
+    // If bundle creation started, but failed due to any reason other than
+    // the MP being locked...
+    if (this._shouldHaveSyncKeyBundle && !this.syncKeyBundle && !Utils.mpLocked()) {
+      // Return a state that says a re-auth is necessary so we can get keys.
+      return LOGIN_FAILED_LOGIN_REJECTED;
     }
 
     return STATUS_OK;
   },
 
+  // Do we currently have keys, or do we have enough that we should be able
+  // to successfully fetch them?
+  _canFetchKeys: function() {
+    let userData = this._signedInUser;
+    // a keyFetchToken means we can almost certainly grab them.
+    // kA and kB means we already have them.
+    return userData && (userData.keyFetchToken || (userData.kA && userData.kB));
+  },
+
+  /**
+   * Verify the current auth state, unlocking the master-password if necessary.
+   *
+   * Returns a promise that resolves with the current auth state after
+   * attempting to unlock.
+   */
+  unlockAndVerifyAuthState: function() {
+    if (this._canFetchKeys()) {
+      return Promise.resolve(STATUS_OK);
+    }
+    // so no keys - ensure MP unlocked.
+    if (!Utils.ensureMPUnlocked()) {
+      // user declined to unlock, so we don't know if they are stored there.
+      return Promise.resolve(MASTER_PASSWORD_LOCKED);
+    }
+    // now we are unlocked we must re-fetch the user data as we may now have
+    // the details that were previously locked away.
+    return this._fxaService.getSignedInUser().then(
+      accountData => {
+        this._updateSignedInUser(accountData);
+        // If we still can't get keys it probably means the user authenticated
+        // without unlocking the MP or cleared the saved logins, so we've now
+        // lost them - the user will need to reauth before continuing.
+        return this._canFetchKeys() ? STATUS_OK : LOGIN_FAILED_LOGIN_REJECTED;
+      }
+    );
+  },
+
   /**
    * Do we have a non-null, not yet expired token for the user currently
    * signed in?
    */
   hasValidToken: function() {
     if (!this._token) {
       return false;
     }
@@ -444,16 +486,24 @@ this.BrowserIDManager.prototype = {
     if (tokenServerURI.endsWith("/")) { // trailing slashes cause problems...
       tokenServerURI = tokenServerURI.slice(0, -1);
     }
     let log = this._log;
     let client = this._tokenServerClient;
     let fxa = this._fxaService;
     let userData = this._signedInUser;
 
+    // We need kA and kB for things to work.  If we don't have them, just
+    // return null for the token - sync calling unlockAndVerifyAuthState()
+    // before actually syncing will setup the error states if necessary.
+    if (!this._canFetchKeys()) {
+      log.info("_fetchTokenForUser has no keys to use.");
+      return null;
+    }
+
     log.info("Fetching assertion and token from: " + tokenServerURI);
 
     let maybeFetchKeys = () => {
       // This is called at login time and every time we need a new token - in
       // the latter case we already have kA and kB, so optimise that case.
       if (userData.kA && userData.kB) {
         return;
       }
@@ -519,17 +569,18 @@ this.BrowserIDManager.prototype = {
         // TODO: write tests to make sure that different auth error cases are handled here
         // properly: auth error getting assertion, auth error getting token (invalid generation
         // and client-state error)
         if (err instanceof AuthenticationError) {
           this._log.error("Authentication error in _fetchTokenForUser: " + err);
           // set it to the "fatal" LOGIN_FAILED_LOGIN_REJECTED reason.
           this._authFailureReason = LOGIN_FAILED_LOGIN_REJECTED;
         } else {
-          this._log.error("Non-authentication error in _fetchTokenForUser: " + err.message);
+          this._log.error("Non-authentication error in _fetchTokenForUser: "
+                          + (err.message || err));
           // for now assume it is just a transient network related problem.
           this._authFailureReason = LOGIN_FAILED_NETWORK_ERROR;
         }
         // Drop the sync key bundle, but still expect to have one.
         // This will arrange for us to be in the right 'currentAuthState'
         // such that UI will show the right error.
         this._shouldHaveSyncKeyBundle = true;
         Weave.Status.login = this._authFailureReason;
--- a/services/sync/modules/engines/passwords.js
+++ b/services/sync/modules/engines/passwords.js
@@ -31,38 +31,16 @@ this.PasswordEngine = function PasswordE
 }
 PasswordEngine.prototype = {
   __proto__: SyncEngine.prototype,
   _storeObj: PasswordStore,
   _trackerObj: PasswordTracker,
   _recordObj: LoginRec,
   applyIncomingBatchSize: PASSWORDS_STORE_BATCH_SIZE,
 
-  get isAllowed() {
-    return Cc["@mozilla.org/weave/service;1"]
-             .getService(Ci.nsISupports)
-             .wrappedJSObject
-             .allowPasswordsEngine;
-  },
-
-  get enabled() {
-    // If we are disabled due to !isAllowed(), we must take care to ensure the
-    // engine has actually had the enabled setter called which reflects this state.
-    let prefVal = SyncEngine.prototype.__lookupGetter__("enabled").call(this);
-    let newVal = this.isAllowed && prefVal;
-    if (newVal != prefVal) {
-      this.enabled = newVal;
-    }
-    return newVal;
-  },
-
-  set enabled(val) {
-    SyncEngine.prototype.__lookupSetter__("enabled").call(this, this.isAllowed && val);
-  },
-
   _syncFinish: function _syncFinish() {
     SyncEngine.prototype._syncFinish.call(this);
 
     // Delete the weave credentials from the server once
     if (!Svc.Prefs.get("deletePwdFxA", false)) {
       try {
         let ids = [];
         for (let host of Utils.getSyncCredentialsHosts()) {
--- a/services/sync/modules/identity.js
+++ b/services/sync/modules/identity.js
@@ -373,16 +373,35 @@ IdentityManager.prototype = {
     if (!this.syncKeyBundle) {
       return LOGIN_FAILED_INVALID_PASSPHRASE;
     }
 
     return STATUS_OK;
   },
 
   /**
+   * Verify the current auth state, unlocking the master-password if necessary.
+   *
+   * Returns a promise that resolves with the current auth state after
+   * attempting to unlock.
+   */
+  unlockAndVerifyAuthState: function() {
+    // Try to fetch the passphrase - this will prompt for MP unlock as a
+    // side-effect...
+    try {
+      this.syncKey;
+    } catch (ex) {
+      this._log.debug("Fetching passphrase threw " + ex +
+                      "; assuming master password locked.");
+      return Promise.resolve(MASTER_PASSWORD_LOCKED);
+    }
+    return Promise.resolve(STATUS_OK);
+  },
+
+  /**
    * Persist credentials to password store.
    *
    * When credentials are updated, they are changed in memory only. This will
    * need to be called to save them to the underlying password store.
    *
    * If the password store is locked (e.g. if the master password hasn't been
    * entered), this could throw an exception.
    */
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -679,27 +679,31 @@ Sync11Service.prototype = {
     }
 
     if (!this.identity.username) {
       this._log.warn("No username in verifyLogin.");
       this.status.login = LOGIN_FAILED_NO_USERNAME;
       return false;
     }
 
-    // Unlock master password, or return.
     // Attaching auth credentials to a request requires access to
     // passwords, which means that Resource.get can throw MP-related
     // exceptions!
-    // Try to fetch the passphrase first, while we still have control.
-    try {
-      this.identity.syncKey;
-    } catch (ex) {
-      this._log.debug("Fetching passphrase threw " + ex +
-                      "; assuming master password locked.");
-      this.status.login = MASTER_PASSWORD_LOCKED;
+    // So we ask the identity to verify the login state after unlocking the
+    // master password (ie, this call is expected to prompt for MP unlock
+    // if necessary) while we still have control.
+    let cb = Async.makeSpinningCallback();
+    this.identity.unlockAndVerifyAuthState().then(
+      result => cb(null, result),
+      cb
+    );
+    let unlockedState = cb.wait();
+    this._log.debug("Fetching unlocked auth state returned " + unlockedState);
+    if (unlockedState != STATUS_OK) {
+      this.status.login = unlockedState;
       return false;
     }
 
     try {
       // Make sure we have a cluster to verify against.
       // This is a little weird, if we don't get a node we pretend
       // to succeed, since that probably means we just don't have storage.
       if (this.clusterURL == "" && !this._clusterManager.setCluster()) {
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -14,16 +14,23 @@ Cu.import("resource://services-common/as
 Cu.import("resource://services-crypto/utils.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm", this);
 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm", this);
 
+// FxAccountsCommon.js doesn't use a "namespace", so create one here.
+XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function() {
+  let FxAccountsCommon = {};
+  Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
+  return FxAccountsCommon;
+});
+
 /*
  * Utility functions
  */
 
 this.Utils = {
   // Alias in functions from CommonUtils. These previously were defined here.
   // In the ideal world, references to these would be removed.
   nextTick: CommonUtils.nextTick,
@@ -589,18 +596,21 @@ this.Utils = {
    * reset when we drop sync credentials, etc.
    */
   getSyncCredentialsHosts: function() {
     // This is somewhat expensive and the result static, so we cache the result.
     if (this._syncCredentialsHosts) {
       return this._syncCredentialsHosts;
     }
     let result = new Set();
-    // the legacy sync host.
+    // the legacy sync host
     result.add(PWDMGR_HOST);
+    // the FxA host
+    result.add(FxAccountsCommon.FXA_PWDMGR_HOST);
+    //
     // The FxA hosts - these almost certainly all have the same hostname, but
     // better safe than sorry...
     for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
                           "identity.fxaccounts.remote.signup.uri",
                           "identity.fxaccounts.remote.signin.uri",
                           "identity.fxaccounts.settings.uri"]) {
       let prefVal;
       try {
--- a/services/sync/tests/unit/test_browserid_identity.js
+++ b/services/sync/tests/unit/test_browserid_identity.js
@@ -78,19 +78,33 @@ add_task(function test_initialializeWith
     browseridManager.initializeWithCurrentIdentity();
     yield browseridManager.whenReadyToAuthenticate.promise;
     do_check_true(!!browseridManager._token);
     do_check_true(browseridManager.hasValidToken());
     do_check_eq(browseridManager.account, identityConfig.fxaccount.user.email);
   }
 );
 
+add_task(function test_initialializeWithNoKeys() {
+    _("Verify start after initializeWithCurrentIdentity without kA, kB or keyFetchToken");
+    let identityConfig = makeIdentityConfig();
+    delete identityConfig.fxaccount.user.kA;
+    delete identityConfig.fxaccount.user.kB;
+    // there's no keyFetchToken by default, so the initialize should fail.
+    configureFxAccountIdentity(browseridManager, identityConfig);
+
+    yield browseridManager.initializeWithCurrentIdentity();
+    yield browseridManager.whenReadyToAuthenticate.promise;
+    do_check_eq(Status.login, LOGIN_SUCCEEDED, "login succeeded even without keys");
+    do_check_false(browseridManager._canFetchKeys(), "_canFetchKeys reflects lack of keys");
+});
 
 add_test(function test_getResourceAuthenticator() {
     _("BrowserIDManager supplies a Resource Authenticator callback which returns a Hawk header.");
+    configureFxAccountIdentity(browseridManager);
     let authenticator = browseridManager.getResourceAuthenticator();
     do_check_true(!!authenticator);
     let req = {uri: CommonUtils.makeURI(
       "https://example.net/somewhere/over/the/rainbow"),
                method: 'GET'};
     let output = authenticator(req, 'GET');
     do_check_true('headers' in output);
     do_check_true('authorization' in output.headers);
@@ -235,16 +249,17 @@ add_test(function test_RESTResourceAuthe
       (getTimestampDelta(authHeader, now) - 12 * HOUR_MS) < 2 * MINUTE_MS);
 
   run_next_test();
 });
 
 add_task(function test_ensureLoggedIn() {
   configureFxAccountIdentity(browseridManager);
   yield browseridManager.initializeWithCurrentIdentity();
+  yield browseridManager.whenReadyToAuthenticate.promise;
   Assert.equal(Status.login, LOGIN_SUCCEEDED, "original initialize worked");
   yield browseridManager.ensureLoggedIn();
   Assert.equal(Status.login, LOGIN_SUCCEEDED, "original ensureLoggedIn worked");
   Assert.ok(browseridManager._shouldHaveSyncKeyBundle,
             "_shouldHaveSyncKeyBundle should always be true after ensureLogin completes.");
 
   // arrange for no logged in user.
   let fxa = browseridManager._fxaService
deleted file mode 100644
--- a/services/sync/tests/unit/test_password_mpenabled.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-Cu.import("resource://gre/modules/Log.jsm");
-Cu.import("resource://services-sync/stages/enginesync.js");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://services-sync/engines/passwords.js");
-Cu.import("resource://services-sync/service.js");
-Cu.import("resource://testing-common/services/sync/utils.js");
-
-function run_test() {
-  initTestLogging("Trace");
-  run_next_test();
-}
-
-add_test(function test_simple() {
-  ensureLegacyIdentityManager();
-  // Stub fxAccountsEnabled
-  let xpcs = Cc["@mozilla.org/weave/service;1"]
-             .getService(Components.interfaces.nsISupports)
-             .wrappedJSObject;
-  let fxaEnabledGetter = xpcs.__lookupGetter__("fxAccountsEnabled");
-  xpcs.__defineGetter__("fxAccountsEnabled", () => true);
-
-  // Stub mpEnabled.
-  let mpEnabledF = Utils.mpEnabled;
-  let mpEnabled = false;
-  Utils.mpEnabled = function() mpEnabled;
-
-  let manager = Service.engineManager;
-
-  Service.engineManager.register(PasswordEngine);
-  let engine = Service.engineManager.get("passwords");
-  let wipeCount = 0;
-  let engineWipeServerF = engine.wipeServer;
-  engine.wipeServer = function() {
-    ++wipeCount;
-  }
-
-  // A server for the metadata.
-  let server  = new SyncServer();
-  let johndoe = server.registerUser("johndoe", "password");
-  johndoe.createContents({
-    meta: {global: {engines: {passwords: {version: engine.version,
-                                          syncID: engine.syncID}}}},
-    crypto: {},
-    clients: {}
-  });
-  server.start();
-  setBasicCredentials("johndoe", "password", "abcdeabcdeabcdeabcdeabcdea");
-  Service.serverURL = server.baseURI;
-  Service.clusterURL = server.baseURI;
-
-  let engineSync = new EngineSynchronizer(Service);
-  engineSync._log.level = Log.Level.Trace;
-
-  function assertEnabled(expected, message) {
-    Assert.strictEqual(engine.enabled, expected, message);
-    // The preference *must* reflect the actual state.
-    Assert.strictEqual(Svc.Prefs.get("engine." + engine.prefName), expected,
-                       message + " (pref should match enabled state)");
-  }
-
-  try {
-    assertEnabled(true, "password engine should be enabled by default")
-    let engineMeta = Service.recordManager.get(engine.metaURL);
-    // This engine should be in the meta/global
-    Assert.notStrictEqual(engineMeta.payload.engines[engine.name], undefined,
-                          "The engine should appear in the metadata");
-    Assert.ok(!engineMeta.changed, "the metadata for the password engine hasn't changed");
-
-    // (pretend to) enable a master-password
-    mpEnabled = true;
-    // The password engine should be locally disabled...
-    assertEnabled(false, "if mp is locked the engine should be disabled");
-    // ...but not declined.
-    Assert.ok(!manager.isDeclined("passwords"), "password engine is not declined");
-    // Next time a sync would happen, we call _updateEnabledEngines(), which
-    // would remove the engine from the metadata - call that now.
-    engineSync._updateEnabledEngines();
-    // The global meta should no longer list the engine.
-    engineMeta = Service.recordManager.get(engine.metaURL);
-    Assert.strictEqual(engineMeta.payload.engines[engine.name], undefined,
-                       "The engine should have vanished");
-    // And we should have wiped the server data.
-    Assert.strictEqual(wipeCount, 1, "wipeServer should have been called");
-
-    // Now simulate an incoming meta/global indicating the engine should be
-    // enabled.  We should fail to actually enable it - the pref should remain
-    // false and we wipe the server for anything another device might have
-    // stored.
-    let meta = {
-      payload: {
-        engines: {
-          "passwords": {"version":1,"syncID":"yfBi2v7PpFO2"},
-        },
-      },
-    };
-    engineSync._updateEnabledFromMeta(meta, 3, manager);
-    Assert.strictEqual(wipeCount, 2, "wipeServer should have been called");
-    Assert.ok(!manager.isDeclined("passwords"), "password engine is not declined");
-    assertEnabled(false, "engine still not enabled locally");
-
-    // Let's turn the MP off - but *not* re-enable it locally.
-    mpEnabled = false;
-    // Just disabling the MP isn't enough to force it back to enabled.
-    assertEnabled(false, "engine still not enabled locally");
-    // Another incoming metadata record with the engine enabled should cause
-    // it to be enabled locally.
-    meta = {
-      payload: {
-        engines: {
-          "passwords": 1,
-        },
-      },
-    };
-    engineSync._updateEnabledFromMeta(meta, 3, manager);
-    Assert.strictEqual(wipeCount, 2, "wipeServer should *not* have been called again");
-    Assert.ok(!manager.isDeclined("passwords"), "password engine is not declined");
-    // It should be enabled locally.
-    assertEnabled(true, "engine now enabled locally");
-    // Next time a sync starts it should magically re-appear in our meta/global
-    engine._syncStartup();
-    //engineSync._updateEnabledEngines();
-    engineMeta = Service.recordManager.get(engine.metaURL);
-    Assert.equal(engineMeta.payload.engines[engine.name].version, engine.version,
-                 "The engine should re-appear in the metadata");
-  } finally {
-    // restore the damage we did above...
-    engine.wipeServer = engineWipeServerF;
-    engine._store.wipe();
-    // Un-stub mpEnabled and fxAccountsEnabled
-    Utils.mpEnabled = mpEnabledF;
-    xpcs.__defineGetter__("fxAccountsEnabled", fxaEnabledGetter);
-    server.stop(run_next_test);
-  }
-});
--- a/services/sync/tests/unit/xpcshell.ini
+++ b/services/sync/tests/unit/xpcshell.ini
@@ -164,10 +164,8 @@ skip-if = debug
 [test_prefs_store.js]
 [test_prefs_tracker.js]
 [test_tab_engine.js]
 [test_tab_store.js]
 [test_tab_tracker.js]
 
 [test_healthreport.js]
 skip-if = ! healthreport
-
-[test_password_mpenabled.js]
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -118,17 +118,17 @@ class RemoteOptions(MochitestOptions):
         defaults["testPath"] = ""
         defaults["app"] = None
         defaults["utilityPath"] = None
 
         self.set_defaults(**defaults)
 
     def verifyRemoteOptions(self, options, automation):
         if not options.remoteTestRoot:
-            options.remoteTestRoot = automation._devicemanager.getDeviceRoot()
+            options.remoteTestRoot = automation._devicemanager.deviceRoot
 
         if options.remoteWebServer == None:
             if os.name != "nt":
                 options.remoteWebServer = moznetwork.get_ip()
             else:
                 log.error("you must specify a --remote-webserver=<ip address>")
                 return None
 
@@ -495,22 +495,22 @@ class MochiRemote(Mochitest):
         log.info("SCREENSHOT: TOTAL PRINTED: [%s]", printed)
 
     def printDeviceInfo(self, printLogcat=False):
         try:
             if printLogcat:
                 logcat = self._dm.getLogcat(filterOutRegexps=fennecLogcatFilters)
                 log.info('\n'+(''.join(logcat)))
             log.info("Device info: %s", self._dm.getInfo())
-            log.info("Test root: %s", self._dm.getDeviceRoot())
+            log.info("Test root: %s", self._dm.deviceRoot)
         except devicemanager.DMError:
             log.warn("Error getting device information")
 
     def buildRobotiumConfig(self, options, browserEnv):
-        deviceRoot = self._dm.getDeviceRoot()
+        deviceRoot = self._dm.deviceRoot
         fHandle = tempfile.NamedTemporaryFile(suffix='.config',
                                               prefix='robotium-',
                                               dir=os.getcwd(),
                                               delete=False)
         fHandle.write("profile=%s\n" % (self.remoteProfile))
         fHandle.write("logfile=%s\n" % (options.remoteLogFile))
         fHandle.write("host=http://mochi.test:8888/tests\n")
         fHandle.write("rawhost=http://%s:%s/tests\n" % (options.remoteWebServer, options.httpPort))
@@ -592,17 +592,17 @@ def main():
     mochitest.printDeviceInfo()
 
     # Add Android version (SDK level) to mozinfo so that manifest entries
     # can be conditional on android_version.
     androidVersion = dm.shellCheckOutput(['getprop', 'ro.build.version.sdk'])
     log.info("Android sdk version '%s'; will use this to filter manifests" % str(androidVersion))
     mozinfo.info['android_version'] = androidVersion
 
-    deviceRoot = dm.getDeviceRoot()
+    deviceRoot = dm.deviceRoot
     if options.dmdPath:
         dmdLibrary = "libdmd.so"
         dmdPathOnDevice = os.path.join(deviceRoot, dmdLibrary)
         dm.removeFile(dmdPathOnDevice)
         dm.pushFile(os.path.join(options.dmdPath, dmdLibrary), dmdPathOnDevice)
         options.dmdPath = deviceRoot
 
     options.dumpOutputDirectory = deviceRoot
--- a/testing/mozbase/docs/mozdevice.rst
+++ b/testing/mozbase/docs/mozdevice.rst
@@ -42,33 +42,32 @@ Informational methods
 .. automethod:: DeviceManager.getCurrentTime(self)
 .. automethod:: DeviceManager.getIP
 .. automethod:: DeviceManager.saveScreenshot
 .. automethod:: DeviceManager.recordLogcat
 .. automethod:: DeviceManager.getLogcat
 
 File management methods
 ```````````````````````
+.. autoattribute:: DeviceManager.deviceRoot
+.. automethod:: DeviceManager.getDeviceRoot(self)
 .. automethod:: DeviceManager.pushFile(self, localFilename, remoteFilename, retryLimit=1)
 .. automethod:: DeviceManager.pushDir(self, localDirname, remoteDirname, retryLimit=1)
 .. automethod:: DeviceManager.pullFile(self, remoteFilename)
 .. automethod:: DeviceManager.getFile(self, remoteFilename, localFilename)
 .. automethod:: DeviceManager.getDirectory(self, remoteDirname, localDirname, checkDir=True)
 .. automethod:: DeviceManager.validateFile(self, remoteFilename, localFilename)
 .. automethod:: DeviceManager.mkDir(self, remoteDirname)
 .. automethod:: DeviceManager.mkDirs(self, filename)
 .. automethod:: DeviceManager.dirExists(self, dirpath)
 .. automethod:: DeviceManager.fileExists(self, filepath)
 .. automethod:: DeviceManager.listFiles(self, rootdir)
 .. automethod:: DeviceManager.removeFile(self, filename)
 .. automethod:: DeviceManager.removeDir(self, remoteDirname)
 .. automethod:: DeviceManager.chmodDir(self, remoteDirname, mask="777")
-.. automethod:: DeviceManager.getDeviceRoot(self)
-.. automethod:: DeviceManager.getAppRoot(self, packageName=None)
-.. automethod:: DeviceManager.getTestRoot(self, harnessName)
 .. automethod:: DeviceManager.getTempDir(self)
 
 Process management methods
 ``````````````````````````
 .. automethod:: DeviceManager.shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False)
 .. automethod:: DeviceManager.shellCheckOutput(self, cmd, env=None, cwd=None, timeout=None, root=False)
 .. automethod:: DeviceManager.getProcessList(self)
 .. automethod:: DeviceManager.processExist(self, processName)
@@ -120,16 +119,17 @@ Android extensions
 For Android, we provide two variants of the `DeviceManager` interface
 with extensions useful for that platform. These classes are called
 DroidADB and DroidSUT. They inherit all methods from DeviceManagerADB
 and DeviceManagerSUT. Here is the interface for DroidADB:
 
 .. automethod:: mozdevice.DroidADB.launchApplication
 .. automethod:: mozdevice.DroidADB.launchFennec
 .. automethod:: mozdevice.DroidADB.getInstalledApps
+.. automethod:: mozdevice.DroidADB.getAppRoot
 
 These methods are also found in the DroidSUT class.
 
 ADB Interface
 -------------
 
 The following classes provide a basic interface to interact with the
 Android Debug Tool (adb) and Android-based devices.  It is intended to
--- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
@@ -41,21 +41,36 @@ class DeviceManager(object):
     applications from the device.
 
     Never instantiate this class directly! Instead, instantiate an
     implementation of it like DeviceManagerADB or DeviceManagerSUT.
     """
 
     _logcatNeedsRoot = True
 
-    def __init__(self, logLevel=mozlog.ERROR):
-        self._logger = mozlog.getLogger("DeviceManager")
+    def __init__(self, logLevel=mozlog.ERROR, deviceRoot=None):
+        try:
+            self._logger = mozlog.structured.structuredlog.get_default_logger(component="DeviceManager")
+            if not self._logger: # no global structured logger, fall back to reg logging
+                self._logger = mozlog.getLogger("DeviceManager")
+                self._logger.setLevel(logLevel)
+        except AttributeError:
+            # Structured logging doesn't work on Python 2.6
+            self._logger = None
         self._logLevel = logLevel
-        self._logger.setLevel(logLevel)
         self._remoteIsWin = None
+        self._isDeviceRootSetup = False
+        self._deviceRoot = deviceRoot
+
+    def _log(self, data):
+        """
+        This helper function is called by ProcessHandler to log
+        the output produced by processes
+        """
+        self._logger.debug(data)
 
     @property
     def remoteIsWin(self):
         if self._remoteIsWin is None:
             self._remoteIsWin = self.getInfo("os")["os"][0] == "windows"
         return self._remoteIsWin
 
     @property
@@ -157,17 +172,17 @@ class DeviceManager(object):
         """
         screencap = '/system/bin/screencap'
         if not self.fileExists(screencap):
             raise DMError("Unable to capture screenshot on device: no screencap utility")
 
         with open(filename, 'w') as pngfile:
             # newer versions of screencap can write directly to a png, but some
             # older versions can't
-            tempScreenshotFile = self.getDeviceRoot() + "/ss-dm.tmp"
+            tempScreenshotFile = self.deviceRoot + "/ss-dm.tmp"
             self.shellCheckOutput(["sh", "-c", "%s > %s" %
                                    (screencap, tempScreenshotFile)],
                                   root=True)
             buf = self.pullFile(tempScreenshotFile)
             width = int(struct.unpack("I", buf[0:4])[0])
             height = int(struct.unpack("I", buf[4:8])[0])
             with open(filename, 'w') as pngfile:
                 pngfile.write(self._writePNG(buf[12:], width, height))
@@ -306,65 +321,44 @@ class DeviceManager(object):
          """
 
     @abstractmethod
     def chmodDir(self, remoteDirname, mask="777"):
         """
         Recursively changes file permissions in a directory.
         """
 
-    @abstractmethod
-    def getDeviceRoot(self):
+    @property
+    def deviceRoot(self):
         """
-        Gets the device root for the testing area on the device.
-
-        For all devices we will use / type slashes and depend on the device-agent
-        to sort those out.  The agent will return us the device location where we
-        should store things, we will then create our /tests structure relative to
-        that returned path.
+        The device root on the device filesystem for putting temporary
+        testing files.
+        """
+        # derive deviceroot value if not set
+        if not self._deviceRoot or not self._isDeviceRootSetup:
+            self._deviceRoot = self._setupDeviceRoot(self._deviceRoot)
+            self._isDeviceRootSetup = True
 
-        Structure on the device is as follows:
-
-        ::
-
-          /tests
-              /<fennec>|<firefox>  --> approot
-              /profile
-              /xpcshell
-              /reftest
-              /mochitest
-        """
+        return self._deviceRoot
 
     @abstractmethod
-    def getAppRoot(self, packageName=None):
-        """
-        Returns the app root directory.
-
-        E.g /tests/fennec or /tests/firefox
+    def _setupDeviceRoot(self):
         """
-        # TODO Support org.mozilla.firefox and B2G
-
-    def getTestRoot(self, harnessName):
-        """
-        Gets the directory location on the device for a specific test type.
-
-        :param harnessName: one of: "xpcshell", "reftest", "mochitest"
+        Sets up and returns a device root location that can be written to by tests.
         """
 
-        devroot = self.getDeviceRoot()
-        if (devroot == None):
-            return None
+    def getDeviceRoot(self):
+        """
+        Get the device root on the device filesystem for putting temporary
+        testing files.
 
-        if (re.search('xpcshell', harnessName, re.I)):
-            self.testRoot = devroot + '/xpcshell'
-        elif (re.search('?(i)reftest', harnessName)):
-            self.testRoot = devroot + '/reftest'
-        elif (re.search('?(i)mochitest', harnessName)):
-            self.testRoot = devroot + '/mochitest'
-        return self.testRoot
+        .. deprecated:: 0.38
+          Use the :py:attr:`deviceRoot` property instead.
+        """
+        return self.deviceRoot
 
     @abstractmethod
     def getTempDir(self):
         """
         Returns a temporary directory we can use on this device, ensuring
         also that it exists.
         """
 
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerADB.py
@@ -1,20 +1,20 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import subprocess
 import re
 import os
 import shutil
 import tempfile
 import time
 
-from devicemanager import DeviceManager, DMError, _pop_last_line
+from devicemanager import DeviceManager, DMError
+from mozprocess import ProcessHandler
 import mozfile
 import mozlog
 
 
 class DeviceManagerADB(DeviceManager):
     """
     Implementation of DeviceManager interface that uses the Android "adb"
     utility to communicate with the device. Normally used to communicate
@@ -30,21 +30,21 @@ class DeviceManagerADB(DeviceManager):
     _packageName = None
     _tempDir = None
     connected = False
     default_timeout = 300
 
     def __init__(self, host=None, port=5555, retryLimit=5, packageName='fennec',
                  adbPath='adb', deviceSerial=None, deviceRoot=None,
                  logLevel=mozlog.ERROR, autoconnect=True, **kwargs):
-        DeviceManager.__init__(self, logLevel)
+        DeviceManager.__init__(self, logLevel=logLevel,
+                               deviceRoot=deviceRoot)
         self.host = host
         self.port = port
         self.retryLimit = retryLimit
-        self.deviceRoot = deviceRoot
 
         # the path to adb, or 'adb' to assume that it's on the PATH
         self._adbPath = adbPath
 
         # The serial number of the device to use with adb, used in cases
         # where multiple devices are being managed by the same adb instance.
         self._deviceSerial = deviceSerial
 
@@ -66,19 +66,16 @@ class DeviceManagerADB(DeviceManager):
         if not self.connected:
             # try to connect to the device over tcp/ip if we have a hostname
             if self.host:
                 self._connectRemoteADB()
 
             # verify that we can connect to the device. can't continue
             self._verifyDevice()
 
-            # set up device root
-            self._setupDeviceRoot()
-
             # Some commands require root to work properly, even with ADB (e.g.
             # grabbing APKs out of /data). For these cases, we check whether
             # we're running as root. If that isn't true, check for the
             # existence of an su binary
             self._checkForRoot()
 
             # can we use zip to speed up some file operations? (currently not
             # required)
@@ -119,47 +116,42 @@ class DeviceManagerADB(DeviceManager):
             cmdline = envstr + "; " + cmdline
 
         # all output should be in stdout
         args=[self._adbPath]
         if self._deviceSerial:
             args.extend(['-s', self._deviceSerial])
         args.extend(["shell", cmdline])
 
-        procOut = tempfile.SpooledTemporaryFile()
-        procErr = tempfile.SpooledTemporaryFile()
-        proc = subprocess.Popen(args, stdout=procOut, stderr=procErr)
+        def _raise():
+            raise DMError("Timeout exceeded for shell call")
+
+        self._logger.debug("shell - command: %s" % ' '.join(args))
+        proc = ProcessHandler(args, processOutputLine=self._log, onTimeout=_raise)
 
         if not timeout:
             # We are asserting that all commands will complete in this time unless otherwise specified
             timeout = self.default_timeout
 
         timeout = int(timeout)
-        start_time = time.time()
-        ret_code = proc.poll()
-        while ((time.time() - start_time) <= timeout) and ret_code == None:
-            time.sleep(self._pollingInterval)
-            ret_code = proc.poll()
-        if ret_code == None:
-            proc.kill()
-            raise DMError("Timeout exceeded for shell call")
+        proc.run(timeout)
+        proc.wait()
+        output = proc.output
 
-        procOut.seek(0)
-        outputfile.write(procOut.read().rstrip('\n'))
-        procOut.close()
-        procErr.close()
-
-        lastline = _pop_last_line(outputfile)
-        if lastline:
-            m = re.search('([0-9]+)', lastline)
-            if m:
-                return_code = m.group(1)
-                outputfile.seek(-2, 2)
-                outputfile.truncate() # truncate off the return code
-                return int(return_code)
+        if output:
+            lastline = output[-1]
+            if lastline:
+                m = re.search('([0-9]+)', lastline)
+                if m:
+                    return_code = m.group(1)
+                    for line in output:
+                        outputfile.write(line + '\n')
+                    outputfile.seek(-2, 2)
+                    outputfile.truncate() # truncate off the return code
+                    return int(return_code)
 
         return None
 
     def forward(self, local, remote):
         """
         Forward socket connections.
 
         Forward specs are one of:
@@ -174,19 +166,19 @@ class DeviceManagerADB(DeviceManager):
 
     def remount(self):
         "Remounts the /system partition on the device read-write."
         return self._checkCmd(['remount'])
 
     def devices(self):
         "Return a list of connected devices as (serial, status) tuples."
         proc = self._runCmd(['devices'])
-        proc.stdout.readline() # ignore first line of output
+        proc.output.pop(0) # ignore first line of output
         devices = []
-        for line in iter(proc.stdout.readline, ''):
+        for line in proc.output:
             result = re.match('(.*?)\t(.*)', line)
             if result:
                 devices.append((result.group(1), result.group(2)))
         return devices
 
     def _connectRemoteADB(self):
         self._checkCmd(["connect", self.host + ":" + str(self.port)])
 
@@ -204,38 +196,38 @@ class DeviceManagerADB(DeviceManager):
                           (localname, destname))
         if not os.access(localname, os.F_OK):
             raise DMError("File not found: %s" % localname)
 
         self._checkCmd(["push", os.path.realpath(localname), destname],
                        retryLimit=retryLimit)
 
     def mkDir(self, name):
-        result = self._runCmd(["shell", "mkdir", name]).stdout.read()
+        result = str(self._runCmd(["shell", "mkdir", name]).output)
         if 'read-only file system' in result.lower():
             raise DMError("Error creating directory: read only file system")
 
     def pushDir(self, localDir, remoteDir, retryLimit=None, timeout=None):
         # adb "push" accepts a directory as an argument, but if the directory
         # contains symbolic links, the links are pushed, rather than the linked
         # files; we either zip/unzip or re-copy the directory into a temporary
         # one to get around this limitation
         retryLimit = retryLimit or self.retryLimit
         if not self.dirExists(remoteDir):
             self.mkDirs(remoteDir+"/x")
         if self._useZip:
             try:
                 localZip = tempfile.mktemp() + ".zip"
                 remoteZip = remoteDir + "/adbdmtmp.zip"
-                subprocess.Popen(["zip", "-r", localZip, '.'], cwd=localDir,
-                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+                ProcessHandler(["zip", "-r", localZip, '.'], cwd=localDir,
+                        processOutputLine=self._log).run().wait()
                 self.pushFile(localZip, remoteZip, retryLimit=retryLimit, createDir=False)
                 mozfile.remove(localZip)
                 data = self._runCmd(["shell", "unzip", "-o", remoteZip,
-                                     "-d", remoteDir]).stdout.read()
+                                     "-d", remoteDir]).output
                 self._checkCmd(["shell", "rm", remoteZip],
                                retryLimit=retryLimit, timeout=timeout)
                 if re.search("unzip: exiting", data) or re.search("Operation not permitted", data):
                     raise Exception("unzip failed, or permissions error")
             except:
                 self._logger.info("zip/unzip failure: falling back to normal push")
                 self._useZip = False
                 self.pushDir(localDir, remoteDir, retryLimit=retryLimit)
@@ -243,28 +235,26 @@ class DeviceManagerADB(DeviceManager):
             tmpDir = tempfile.mkdtemp()
             # copytree's target dir must not already exist, so create a subdir
             tmpDirTarget = os.path.join(tmpDir, "tmp")
             shutil.copytree(localDir, tmpDirTarget)
             self._checkCmd(["push", tmpDirTarget, remoteDir], retryLimit=retryLimit)
             mozfile.remove(tmpDir)
 
     def dirExists(self, remotePath):
-        p = self._runCmd(["shell", "ls", "-a", remotePath + '/'])
+        data = self._runCmd(["shell", "ls", "-a", remotePath + '/']).output
 
-        data = p.stdout.readlines()
         if len(data) == 1:
             res = data[0]
             if "Not a directory" in res or "No such file or directory" in res:
                 return False
         return True
 
     def fileExists(self, filepath):
-        p = self._runCmd(["shell", "ls", "-a", filepath])
-        data = p.stdout.readlines()
+        data = self._runCmd(["shell", "ls", "-a", filepath]).output
         if len(data) == 1:
             foundpath = data[0].decode('utf-8').rstrip()
             if foundpath == filepath:
                 return True
         return False
 
     def removeFile(self, filename):
         if self.fileExists(filename):
@@ -278,46 +268,44 @@ class DeviceManagerADB(DeviceManager):
 
     def moveTree(self, source, destination):
         self._checkCmd(["shell", "mv", source, destination])
 
     def copyTree(self, source, destination):
         self._checkCmd(["shell", "dd", "if=%s" % source, "of=%s" % destination])
 
     def listFiles(self, rootdir):
-        p = self._runCmd(["shell", "ls", "-a", rootdir])
-        data = p.stdout.readlines()
+        data = self._runCmd(["shell", "ls", "-a", rootdir]).output
         data[:] = [item.rstrip('\r\n') for item in data]
         if (len(data) == 1):
             if (data[0] == rootdir):
                 return []
             if (data[0].find("No such file or directory") != -1):
                 return []
             if (data[0].find("Not a directory") != -1):
                 return []
             if (data[0].find("Permission denied") != -1):
                 return []
             if (data[0].find("opendir failed") != -1):
                 return []
         return data
 
     def getProcessList(self):
         p = self._runCmd(["shell", "ps"])
-            # first line is the headers
-        p.stdout.readline()
-        proc = p.stdout.readline()
+        # first line is the headers
+        p.output.pop(0)
         ret = []
-        while (proc):
+        for proc in p.output:
             els = proc.split()
-            # we need to figure out if this is "user pid name" or "pid user vsz stat command"
+            # We need to figure out if this is "user pid name" or
+            # "pid user vsz stat command"
             if els[1].isdigit():
                 ret.append(list([int(els[1]), els[len(els) - 1], els[0]]))
             else:
                 ret.append(list([int(els[0]), els[len(els) - 1], els[1]]))
-            proc =  p.stdout.readline()
         return ret
 
     def fireProcess(self, appname, failIfRunning=False):
         """
         Starts a process
 
         returns: pid
 
@@ -380,27 +368,26 @@ class DeviceManagerADB(DeviceManager):
         procs = self.getProcessList()
         for (pid, name, user) in procs:
             if name == appname:
                 args = ["shell", "kill"]
                 if sig:
                     args.append("-%d" % sig)
                 args.append(str(pid))
                 p = self._runCmd(args)
-                p.communicate()
                 if p.returncode != 0:
                     raise DMError("Error killing process "
-                                  "'%s': %s" % (appname, p.stdout.read()))
+                                  "'%s': %s" % (appname, p.output))
 
     def _runPull(self, remoteFile, localFile):
         """
         Pulls remoteFile from device to host
         """
         try:
-            self._runCmd(["pull",  remoteFile, localFile]).communicate()
+            self._runCmd(["pull",  remoteFile, localFile])
         except (OSError, ValueError):
             raise DMError("Error pulling remote file '%s' to '%s'" % (remoteFile, localFile))
 
     def pullFile(self, remoteFile, offset=None, length=None):
         # TODO: add debug flags and allow for printing stdout
         localFile = tempfile.mkstemp()[1]
         self._runPull(remoteFile, localFile)
 
@@ -444,168 +431,146 @@ class DeviceManagerADB(DeviceManager):
         if localFile is None:
             return None
 
         md5 = self._getLocalHash(localFile)
         mozfile.remove(localFile)
 
         return md5
 
-    def _setupDeviceRoot(self):
-        """
-        setup the device root and cache its value
-        """
-        # if self.deviceRoot is already set, create it if necessary, and use it
-        if self.deviceRoot:
-            if not self.dirExists(self.deviceRoot):
-                try:
-                    self.mkDir(self.deviceRoot)
-                except:
-                    self._logger.error("Unable to create device root %s" % self.deviceRoot)
-                    raise
-            return
+    def _setupDeviceRoot(self, deviceRoot):
+        # user-specified device root, create it and return it
+        if deviceRoot:
+            self.mkDir(deviceRoot)
+            return deviceRoot
 
+        # we must determine the device root ourselves
         paths = [('/storage/sdcard0', 'tests'),
                  ('/storage/sdcard1', 'tests'),
                  ('/sdcard', 'tests'),
                  ('/mnt/sdcard', 'tests'),
                  ('/data/local', 'tests')]
         for (basePath, subPath) in paths:
             if self.dirExists(basePath):
-                testRoot = os.path.join(basePath, subPath)
+                root = os.path.join(basePath, subPath)
                 try:
-                    self.mkDir(testRoot)
-                    self.deviceRoot = testRoot
-                    return
+                    self.mkDir(root)
+                    return root
                 except:
                     pass
 
         raise DMError("Unable to set up device root using paths: [%s]"
                         % ", ".join(["'%s'" % os.path.join(b, s) for b, s in paths]))
 
-    def getDeviceRoot(self):
-        return self.deviceRoot
-
     def getTempDir(self):
         # Cache result to speed up operations depending
         # on the temporary directory.
         if not self._tempDir:
-            self._tempDir = self.getDeviceRoot() + "/tmp"
+            self._tempDir = "%s/tmp" % self.deviceRoot
             self.mkDir(self._tempDir)
 
         return self._tempDir
 
-    def getAppRoot(self, packageName):
-        devroot = self.getDeviceRoot()
-        if (devroot == None):
-            return None
-
-        if (packageName and self.dirExists('/data/data/' + packageName)):
-            self._packageName = packageName
-            return '/data/data/' + packageName
-        elif (self._packageName and self.dirExists('/data/data/' + self._packageName)):
-            return '/data/data/' + self._packageName
-
-        # Failure (either not installed or not a recognized platform)
-        raise DMError("Failed to get application root for: %s" % packageName)
-
     def reboot(self, wait = False, **kwargs):
         self._checkCmd(["reboot"])
         if wait:
             self._checkCmd(["wait-for-device", "shell", "ls", "/sbin"])
 
     def updateApp(self, appBundlePath, **kwargs):
-        return self._runCmd(["install", "-r", appBundlePath]).stdout.read()
+        return self._runCmd(["install", "-r", appBundlePath]).output
 
     def getCurrentTime(self):
-        timestr = self._runCmd(["shell", "date", "+%s"]).stdout.read().strip()
+        timestr = str(self._runCmd(["shell", "date", "+%s"]).output[0])
         if (not timestr or not timestr.isdigit()):
             raise DMError("Unable to get current time using date (got: '%s')" % timestr)
         return int(timestr)*1000
 
     def getInfo(self, directive=None):
         ret = {}
         if (directive == "id" or directive == "all"):
-            ret["id"] = self._runCmd(["get-serialno"]).stdout.read()
+            ret["id"] = self._runCmd(["get-serialno"]).output
         if (directive == "os" or directive == "all"):
-            ret["os"] = self._runCmd(["shell", "getprop", "ro.build.display.id"]).stdout.read()
+            ret["os"] = self._runCmd(["shell", "getprop", "ro.build.display.id"]).output
         if (directive == "uptime" or directive == "all"):
-            utime = self._runCmd(["shell", "uptime"]).stdout.read()
+            utime = self._runCmd(["shell", "uptime"]).output[0]
             if (not utime):
                 raise DMError("error getting uptime")
             utime = utime[9:]
             hours = utime[0:utime.find(":")]
             utime = utime[utime[1:].find(":") + 2:]
             minutes = utime[0:utime.find(":")]
             utime = utime[utime[1:].find(":") +  2:]
             seconds = utime[0:utime.find(",")]
             ret["uptime"] = ["0 days " + hours + " hours " + minutes + " minutes " + seconds + " seconds"]
         if (directive == "process" or directive == "all"):
-            ret["process"] = self._runCmd(["shell", "ps"]).stdout.read()
+            ret["process"] = self._runCmd(["shell", "ps"]).output
         if (directive == "systime" or directive == "all"):
-            ret["systime"] = self._runCmd(["shell", "date"]).stdout.read()
+            ret["systime"] = self._runCmd(["shell", "date"]).output
         self._logger.info(ret)
         return ret
 
     def uninstallApp(self, appName, installPath=None):
-        data = self._runCmd(["uninstall", appName]).stdout.read().strip()
+        data = self._runCmd(["uninstall", appName]).output.strip()
         status = data.split('\n')[0].strip()
         if status != 'Success':
             raise DMError("uninstall failed for %s. Got: %s" % (appName, status))
 
     def uninstallAppAndReboot(self, appName, installPath=None):
         self.uninstallApp(appName)
         self.reboot()
 
     def _runCmd(self, args):
         """
         Runs a command using adb
 
-        returns: returncode from subprocess.Popen
+        returns: instance of ProcessHandler
         """
         finalArgs = [self._adbPath]
         if self._deviceSerial:
             finalArgs.extend(['-s', self._deviceSerial])
         finalArgs.extend(args)
-        return subprocess.Popen(finalArgs, stdout=subprocess.PIPE,
-                                stderr=subprocess.STDOUT)
+        self._logger.debug("_runCmd - command: %s" % ' '.join(finalArgs))
+        proc = ProcessHandler(finalArgs, storeOutput=True,
+                processOutputLine=self._log)
+        proc.run()
+        proc.returncode = proc.wait()
+        return proc
 
     # timeout is specified in seconds, and if no timeout is given,
     # we will run until we hit the default_timeout specified in the __init__
     def _checkCmd(self, args, timeout=None, retryLimit=None):
         """
         Runs a command using adb and waits for the command to finish.
         If timeout is specified, the process is killed after <timeout> seconds.
 
-        returns: returncode from subprocess.Popen
+        returns: returncode from process
         """
         retryLimit = retryLimit or self.retryLimit
         finalArgs = [self._adbPath]
         if self._deviceSerial:
             finalArgs.extend(['-s', self._deviceSerial])
         finalArgs.extend(args)
+        self._logger.debug("_checkCmd - command: %s" % ' '.join(finalArgs))
         if not timeout:
-            # We are asserting that all commands will complete in this time unless otherwise specified
+            # We are asserting that all commands will complete in this
+            # time unless otherwise specified
             timeout = self.default_timeout
 
         timeout = int(timeout)
         retries = 0
-        with tempfile.SpooledTemporaryFile() as procOut:
-            while retries < retryLimit:
-                proc = subprocess.Popen(finalArgs, stdout=procOut, stderr=subprocess.STDOUT)
-                start_time = time.time()
-                ret_code = proc.poll()
-                while ((time.time() - start_time) <= timeout) and ret_code == None:
-                    time.sleep(self._pollingInterval)
-                    ret_code = proc.poll()
-                if ret_code == None:
-                    proc.kill()
-                    retries += 1
-                    continue
+        while retries < retryLimit:
+            proc = ProcessHandler(finalArgs, processOutputLine=self._log)
+            proc.run(timeout=timeout)
+            ret_code = proc.wait()
+            if ret_code == None:
+                proc.kill()
+                retries += 1
+            else:
                 return ret_code
+
         raise DMError("Timeout exceeded for _checkCmd call after %d retries." % retries)
 
     def chmodDir(self, remoteDir, mask="777"):
         if (self.dirExists(remoteDir)):
             files = self.listFiles(remoteDir.strip())
             for f in files:
                 remoteEntry = remoteDir.strip() + "/" + f.strip()
                 if (self.dirExists(remoteEntry)):
@@ -626,51 +591,40 @@ class DeviceManagerADB(DeviceManager):
         if self._adbPath != 'adb':
             if not os.access(self._adbPath, os.X_OK):
                 raise DMError("invalid adb path, or adb not executable: %s" % self._adbPath)
 
         try:
             self._checkCmd(["version"])
         except os.error, err:
             raise DMError("unable to execute ADB (%s): ensure Android SDK is installed and adb is in your $PATH" % err)
-        except subprocess.CalledProcessError:
-            raise DMError("unable to execute ADB: ensure Android SDK is installed and adb is in your $PATH")
 
     def _verifyDevice(self):
         # If there is a device serial number, see if adb is connected to it
         if self._deviceSerial:
             deviceStatus = None
-            proc = subprocess.Popen([self._adbPath, "devices"],
-                                    stdout=subprocess.PIPE,
-                                    stderr=subprocess.STDOUT)
-            for line in proc.stdout:
+            for line in self._runCmd(["devices"]).output:
                 m = re.match('(.+)?\s+(.+)$', line)
                 if m:
                     if self._deviceSerial == m.group(1):
                         deviceStatus = m.group(2)
             if deviceStatus == None:
                 raise DMError("device not found: %s" % self._deviceSerial)
             elif deviceStatus != "device":
                 raise DMError("bad status for device %s: %s" % (self._deviceSerial, deviceStatus))
 
         # Check to see if we can connect to device and run a simple command
-        ret = None
-        try:
-            ret = self._checkCmd(["shell", "echo"])
-        except subprocess.CalledProcessError:
-            raise DMError("unable to connect to device: is it plugged in?")
-        if ret:
+        if self._checkCmd(["shell", "echo"]) is None:
             raise DMError("unable to connect to device")
 
     def _checkForRoot(self):
         # Check whether we _are_ root by default (some development boards work
         # this way, this is also the result of some relatively rare rooting
         # techniques)
-        proc = self._runCmd(["shell", "id"])
-        data = proc.stdout.read()
+        data = self._runCmd(["shell", "id"]).output[0]
         if data.find('uid=0(root)') >= 0:
             self._haveRootShell = True
             # if this returns true, we don't care about su
             return
 
         # if root shell is not available, check if 'su' can be used to gain
         # root
         proc = self._runCmd(["shell", "su", "-c", "id"])
@@ -679,30 +633,30 @@ class DeviceManagerADB(DeviceManager):
         # password or triggers the Android SuperUser prompt
         start_time = time.time()
         retcode = None
         while (time.time() - start_time) <= 15 and retcode is None:
             retcode = proc.poll()
         if retcode is None: # still not terminated, kill
             proc.kill()
 
-        data = proc.stdout.read()
+        data = proc.output
         if data.find('uid=0(root)') >= 0:
             self._haveSu = True
 
     def _isUnzipAvailable(self):
-        data = self._runCmd(["shell", "unzip"]).stdout.read()
-        if (re.search('Usage', data)):
-            return True
-        else:
-            return False
+        data = self._runCmd(["shell", "unzip"]).output
+        for line in data:
+            if (re.search('Usage', line)):
+                return True
+        return False
 
     def _isLocalZipAvailable(self):
         try:
-            subprocess.check_call(["zip", "-?"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+            self._checkCmd(["zip", "-?"])
         except:
             return False
         return True
 
     def _verifyZip(self):
         # If "zip" can be run locally, and "unzip" can be run remotely, then pushDir
         # can use these to push just one file per directory -- a significant
         # optimization for large directories.
--- a/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanagerSUT.py
@@ -32,26 +32,23 @@ class DeviceManagerSUT(DeviceManager):
     _agentErrorRE = re.compile('^##AGENT-WARNING##\ ?(.*)')
     default_timeout = 300
 
     reboot_timeout = 600
     reboot_settling_time = 60
 
     def __init__(self, host, port = 20701, retryLimit = 5,
             deviceRoot = None, logLevel = mozlog.ERROR, **kwargs):
-        DeviceManager.__init__(self, logLevel)
+        DeviceManager.__init__(self, logLevel = logLevel,
+                               deviceRoot = deviceRoot)
         self.host = host
         self.port = port
         self.retryLimit = retryLimit
         self._sock = None
         self._everConnected = False
-        self.deviceRoot = deviceRoot
-
-        # Initialize device root
-        self.getDeviceRoot()
 
         # Get version
         verstring = self._runCmds([{ 'cmd': 'ver' }])
         ver_re = re.match('(\S+) Version (\S+)', verstring)
         self.agentProductName = ver_re.group(1)
         self.agentVersion = ver_re.group(2)
 
     def _cmdNeedsResponse(self, cmd):
@@ -293,16 +290,24 @@ class DeviceManagerSUT(DeviceManager):
         if shouldCloseSocket:
             try:
                 self._sock.close()
                 self._sock = None
             except:
                 self._sock = None
                 raise DMError("Automation Error: Error closing socket")
 
+    def _setupDeviceRoot(self, deviceRoot):
+        if not deviceRoot:
+            deviceRoot = "%s/tests" % self._runCmds(
+                [{ 'cmd': 'testroot' }]).strip()
+        self.mkDir(deviceRoot)
+
+        return deviceRoot
+
     def shell(self, cmd, outputfile, env=None, cwd=None, timeout=None, root=False):
         cmdline = self._escapedCommandLine(cmd)
         if env:
             cmdline = '%s %s' % (self._formatEnvString(env), cmdline)
 
         # execcwd/execcwdsu currently unsupported in Negatus; see bug 824127.
         if cwd and self.agentProductName == 'SUTAgentNegatus':
             raise DMError("Negatus does not support execcwd/execcwdsu")
@@ -508,24 +513,21 @@ class DeviceManagerSUT(DeviceManager):
 
         DEPRECATED: Use shell() or launchApplication() for new code
         """
         if not cmd:
             self._logger.warn("launchProcess called without command to run")
             return None
 
         if cmd[0] == 'am' and hasattr(self, '_getExtraAmStartArgs'):
-            cmd = cmd[:2] + self._getExtraAmStartArgs() + cmd[2:] 
+            cmd = cmd[:2] + self._getExtraAmStartArgs() + cmd[2:]
 
         cmdline = subprocess.list2cmdline(cmd)
-        if (outputFile == "process.txt" or outputFile == None):
-            outputFile = self.getDeviceRoot();
-            if outputFile is None:
-                return None
-            outputFile += "/process.txt"
+        if outputFile == "process.txt" or outputFile is None:
+            outputFile += "%s/process.txt" % self.deviceRoot
             cmdline += " > " + outputFile
 
         # Prepend our env to the command
         cmdline = '%s %s' % (self._formatEnvString(env), cmdline)
 
         # fireProcess may trigger an exception, but we won't handle it
         if cmd[0] == "am":
             # Robocop tests spawn "am instrument". sutAgent's exec ensures that
@@ -710,41 +712,22 @@ class DeviceManagerSUT(DeviceManager):
 
         return False
 
     def _getRemoteHash(self, filename):
         data = self._runCmds([{ 'cmd': 'hash ' + filename }]).strip()
         self._logger.debug("remote hash returned: '%s'" % data)
         return data
 
-    def getDeviceRoot(self):
-        if not self.deviceRoot:
-            data = self._runCmds([{ 'cmd': 'testroot' }])
-            self.deviceRoot = data.strip() + '/tests'
-
-        if not self.dirExists(self.deviceRoot):
-            self.mkDir(self.deviceRoot)
-
-        return self.deviceRoot
-
-    def getAppRoot(self, packageName):
-        data = self._runCmds([{ 'cmd': 'getapproot ' + packageName }])
-
-        return data.strip()
-
     def unpackFile(self, filePath, destDir=None):
         """
         Unzips a bundle to a location on the device
 
         If destDir is not specified, the bundle is extracted in the same directory
         """
-        devroot = self.getDeviceRoot()
-        if (devroot == None):
-            return None
-
         # if no destDir is passed in just set it to filePath's folder
         if not destDir:
             destDir = posixpath.dirname(filePath)
 
         if destDir[-1] != '/':
             destDir += '/'
 
         self._runCmds([{ 'cmd': 'unzp %s %s' % (filePath, destDir)}])
--- a/testing/mozbase/mozdevice/mozdevice/dmcli.py
+++ b/testing/mozbase/mozdevice/mozdevice/dmcli.py
@@ -148,17 +148,17 @@ class DMCli(object):
         if ret is None:
             ret = 0
 
         sys.exit(ret)
 
     def add_options(self, parser):
         parser.add_argument("-v", "--verbose", action="store_true",
                             help="Verbose output from DeviceManager",
-                            default=False)
+                            default=bool(os.environ.get('VERBOSE')))
         parser.add_argument("--host", action="store",
                             help="Device hostname (only if using TCP/IP, " \
                                 "defaults to TEST_DEVICE environment " \
                                 "variable if present)",
                             default=os.environ.get('TEST_DEVICE'))
         parser.add_argument("-p", "--port", action="store",
                             type=int,
                             help="Custom device port (if using SUTAgent or "
@@ -239,17 +239,17 @@ class DMCli(object):
             dest = posixpath.basename(src)
         if self.dm.dirExists(src):
             self.dm.getDirectory(src, dest)
         else:
             self.dm.getFile(src, dest)
 
     def install(self, args):
         basename = os.path.basename(args.file)
-        app_path_on_device = posixpath.join(self.dm.getDeviceRoot(),
+        app_path_on_device = posixpath.join(self.dm.deviceRoot,
                                             basename)
         self.dm.pushFile(args.file, app_path_on_device)
         self.dm.installApp(app_path_on_device)
 
     def uninstall(self, args):
         self.dm.uninstallApp(args.packagename)
 
     def launchapp(self, args):
--- a/testing/mozbase/mozdevice/mozdevice/droid.py
+++ b/testing/mozbase/mozdevice/mozdevice/droid.py
@@ -171,16 +171,23 @@ class DroidADB(DeviceManagerADB, DroidMi
             # Extract package name: string of non-whitespace ending in forward slash
             m = re.search('(\S+)/$', line)
             if m:
                 package = m.group(1)
         if not package:
             raise DMError("unable to find focused app")
         return package
 
+    def getAppRoot(self, packageName):
+        """
+        Returns the root directory for the specified android application
+        """
+        # relying on convention
+        return '/data/data/%s' % packageName
+
 class DroidSUT(DeviceManagerSUT, DroidMixin):
 
     def _getExtraAmStartArgs(self):
         # in versions of android in jellybean and beyond, the agent may run as
         # a different process than the one that started the app. In this case,
         # we need to get back the original user serial number and then pass
         # that to the 'am start' command line
         if not hasattr(self, '_userSerial'):
@@ -200,16 +207,19 @@ class DroidSUT(DeviceManagerSUT, DroidMi
         if self._userSerial is not None:
             return [ "--user", self._userSerial ]
 
         return []
 
     def getTopActivity(self):
         return self._runCmds([{ 'cmd': "activity" }]).strip()
 
+    def getAppRoot(self, packageName):
+        return self._runCmds([{ 'cmd': 'getapproot %s' % packageName }]).strip()
+
 def DroidConnectByHWID(hwid, timeout=30, **kwargs):
     """Try to connect to the given device by waiting for it to show up using mDNS with the given timeout."""
     zc = Zeroconf(moznetwork.get_ip())
 
     evt = threading.Event()
     listener = ZeroconfListener(hwid, evt)
     sb = ServiceBrowser(zc, "_sutagent._tcp.local.", listener)
     foundIP = None
--- a/testing/mozbase/mozdevice/setup.py
+++ b/testing/mozbase/mozdevice/setup.py
@@ -4,17 +4,18 @@
 
 from setuptools import setup
 
 PACKAGE_NAME = 'mozdevice'
 PACKAGE_VERSION = '0.37'
 
 deps = ['mozfile >= 1.0',
         'mozlog',
-        'moznetwork >= 0.24'
+        'moznetwork >= 0.24',
+        'mozprocess >= 0.19',
        ]
 
 setup(name=PACKAGE_NAME,
       version=PACKAGE_VERSION,
       description="Mozilla-authored device management",
       long_description="see http://mozbase.readthedocs.org/",
       classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
       keywords='',
--- a/testing/mozbase/mozdevice/sut_tests/test_exec.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_exec.py
@@ -7,17 +7,17 @@ from StringIO import StringIO
 
 from dmunit import DeviceManagerTestCase
 
 class ExecTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """Simple exec test, does not use env vars."""
         out = StringIO()
-        filename = posixpath.join(self.dm.getDeviceRoot(), 'test_exec_file')
+        filename = posixpath.join(self.dm.deviceRoot, 'test_exec_file')
         # Make sure the file was not already there
         self.dm.removeFile(filename)
         self.dm.shell(['dd', 'if=/dev/zero', 'of=%s' % filename, 'bs=1024',
                        'count=1'], out)
         # Check that the file has been created
         self.assertTrue(self.dm.fileExists(filename))
         # Clean up
         self.dm.removeFile(filename)
--- a/testing/mozbase/mozdevice/sut_tests/test_exec_env.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_exec_env.py
@@ -9,17 +9,17 @@ from StringIO import StringIO
 from dmunit import DeviceManagerTestCase
 
 class ExecEnvTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """Exec test with env vars."""
         # Push the file
         localfile = os.path.join('test-files', 'test_script.sh')
-        remotefile = posixpath.join(self.dm.getDeviceRoot(), 'test_script.sh')
+        remotefile = posixpath.join(self.dm.deviceRoot, 'test_script.sh')
         self.dm.pushFile(localfile, remotefile)
 
         # Run the cmd
         out = StringIO()
         self.dm.shell(['sh', remotefile], out, env={'THE_ANSWER': 42})
 
         # Rewind the output file
         out.seek(0)
--- a/testing/mozbase/mozdevice/sut_tests/test_fileExists.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_fileExists.py
@@ -13,25 +13,25 @@ class FileExistsTestCase(DeviceManagerTe
 
     def testOnRoot(self):
         self.assertTrue(self.dm.fileExists('/'))
 
     def testOnNonexistent(self):
         self.assertFalse(self.dm.fileExists('/doesNotExist'))
 
     def testOnRegularFile(self):
-        remote_path = posixpath.join(self.dm.getDeviceRoot(), 'testFile')
+        remote_path = posixpath.join(self.dm.deviceRoot, 'testFile')
         self.assertFalse(self.dm.fileExists(remote_path))
         with tempfile.NamedTemporaryFile() as f:
             self.dm.pushFile(f.name, remote_path)
         self.assertTrue(self.dm.fileExists(remote_path))
         self.dm.removeFile(remote_path)
 
     def testOnDirectory(self):
-        remote_path = posixpath.join(self.dm.getDeviceRoot(), 'testDir')
+        remote_path = posixpath.join(self.dm.deviceRoot, 'testDir')
         remote_path_file = posixpath.join(remote_path, 'testFile')
         self.assertFalse(self.dm.fileExists(remote_path))
         with tempfile.NamedTemporaryFile() as f:
             self.dm.pushFile(f.name, remote_path_file)
         self.assertTrue(self.dm.fileExists(remote_path))
         self.dm.removeFile(remote_path_file)
         self.dm.removeDir(remote_path)
 
--- a/testing/mozbase/mozdevice/sut_tests/test_getdir.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_getdir.py
@@ -24,17 +24,17 @@ class GetDirectoryTestCase(DeviceManager
 
     def tearDown(self):
         shutil.rmtree(self.localsrcdir)
         shutil.rmtree(self.localdestdir)
 
     def runTest(self):
         """This tests the getDirectory() function.
         """
-        testroot = posixpath.join(self.dm.getDeviceRoot(), 'infratest')
+        testroot = posixpath.join(self.dm.deviceRoot, 'infratest')
         self.dm.removeDir(testroot)
         self.dm.mkDir(testroot)
         self.dm.pushDir(
             os.path.join(self.localsrcdir, 'push1'),
             posixpath.join(testroot, 'push1'))
         # pushDir doesn't copy over empty directories, but we want to make sure
         # that they are retrieved correctly.
         self.dm.mkDir(posixpath.join(testroot, 'push1', 'emptysub'))
--- a/testing/mozbase/mozdevice/sut_tests/test_pull.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_pull.py
@@ -14,17 +14,17 @@ class PullTestCase(DeviceManagerTestCase
     def runTest(self):
         """Tests the "pull" command with a binary file.
         """
         orig = hashlib.md5()
         new = hashlib.md5()
         local_test_file = os.path.join('test-files', 'mybinary.zip')
         orig.update(file(local_test_file, 'r').read())
 
-        testroot = self.dm.getDeviceRoot()
+        testroot = self.dm.deviceRoot
         remote_test_file = posixpath.join(testroot, 'mybinary.zip')
         self.dm.removeFile(remote_test_file)
         self.dm.pushFile(local_test_file, remote_test_file)
         new.update(self.dm.pullFile(remote_test_file))
         # Use hexdigest() instead of digest() since values are printed
         # if assert fails
         self.assertEqual(orig.hexdigest(), new.hexdigest())
 
--- a/testing/mozbase/mozdevice/sut_tests/test_push1.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_push1.py
@@ -7,17 +7,17 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class Push1TestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a directory structure to the device.
         """
-        dvroot = self.dm.getDeviceRoot()
+        dvroot = self.dm.deviceRoot
         dvpath = posixpath.join(dvroot, 'infratest')
         self.dm.removeDir(dvpath)
         self.dm.mkDir(dvpath)
 
         p1 = os.path.join('test-files', 'push1')
         # Set up local stuff
         try:
             os.rmdir(p1)
--- a/testing/mozbase/mozdevice/sut_tests/test_push2.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_push2.py
@@ -7,17 +7,17 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class Push2TestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a directory structure with files to the device.
         """
-        testroot = posixpath.join(self.dm.getDeviceRoot(), 'infratest')
+        testroot = posixpath.join(self.dm.deviceRoot, 'infratest')
         self.dm.removeDir(testroot)
         self.dm.mkDir(testroot)
         path = posixpath.join(testroot, 'push2')
         self.dm.pushDir(os.path.join('test-files', 'push2'), path)
 
         # Let's walk the tree and make sure everything is there
         # though it's kind of cheesy, we'll use the validate file to compare
         # hashes - we use the client side hashing when testing the cat command
--- a/testing/mozbase/mozdevice/sut_tests/test_pushbinary.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_pushbinary.py
@@ -7,12 +7,12 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class PushBinaryTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a binary file.
         """
-        testroot = self.dm.getDeviceRoot()
+        testroot = self.dm.deviceRoot
         self.dm.removeFile(posixpath.join(testroot, 'mybinary.zip'))
         self.dm.pushFile(os.path.join('test-files', 'mybinary.zip'),
                          posixpath.join(testroot, 'mybinary.zip'))
--- a/testing/mozbase/mozdevice/sut_tests/test_pushsmalltext.py
+++ b/testing/mozbase/mozdevice/sut_tests/test_pushsmalltext.py
@@ -7,12 +7,12 @@ import posixpath
 
 from dmunit import DeviceManagerTestCase
 
 class PushSmallTextTestCase(DeviceManagerTestCase):
 
     def runTest(self):
         """This tests copying a small text file.
         """
-        testroot = self.dm.getDeviceRoot()
+        testroot = self.dm.deviceRoot
         self.dm.removeFile(posixpath.join(testroot, 'smalltext.txt'))
         self.dm.pushFile(os.path.join('test-files', 'smalltext.txt'),
                          posixpath.join(testroot, 'smalltext.txt'))
--- a/testing/mozbase/mozdevice/tests/sut.py
+++ b/testing/mozbase/mozdevice/tests/sut.py
@@ -13,19 +13,17 @@ class MockAgent(object):
 
     MAX_WAIT_TIME_SECONDS = 10
     SOCKET_TIMEOUT_SECONDS = 5
 
     def __init__(self, tester, start_commands = None, commands = []):
         if start_commands:
             self.commands = start_commands
         else:
-            self.commands = [("testroot", "/mnt/sdcard"),
-                             ("isdir /mnt/sdcard/tests", "TRUE"),
-                             ("ver", "SUTAgentAndroid Version 1.14")]
+            self.commands = [("ver", "SUTAgentAndroid Version 1.14")]
         self.commands = self.commands + commands
 
         self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self._sock.bind(("127.0.0.1", 0))
         self._sock.listen(1)
 
         self.tester = tester
 
--- a/testing/mozbase/mozdevice/tests/sut_basic.py
+++ b/testing/mozbase/mozdevice/tests/sut_basic.py
@@ -10,23 +10,21 @@ class BasicTest(unittest.TestCase):
         a = MockAgent(self)
 
         mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=mozlog.DEBUG)
         # all testing done in device's constructor
         a.wait()
 
     def test_init_err(self):
         """Tests error handling during initialization."""
-        cmds = [("testroot", "/mnt/sdcard"),
-                ("isdir /mnt/sdcard/tests", "/mnt/sdcard/tests: No such file or directory\n"),
-                ("isdir /mnt/sdcard/tests", "/mnt/sdcard/tests: No such file or directory\n"),
-                ("mkdr /mnt/sdcard/tests", "/mnt/sdcard/tests successfully created"),
-                ("ver", "SUTAgentAndroid Version 1.14")]
-        a = MockAgent(self, start_commands = cmds)
-        mozdevice.DroidSUT("127.0.0.1", port=a.port, logLevel=mozlog.DEBUG)
+        a = MockAgent(self, start_commands=[("ver", "##AGENT-WARNING## No version")])
+        self.assertRaises(mozdevice.DMError,
+                          lambda: mozdevice.DroidSUT("127.0.0.1",
+                                                     port=a.port,
+                                                     logLevel=mozlog.DEBUG))
         a.wait()
 
     def test_timeout_normal(self):
         """Tests DeviceManager timeout, normal case."""
         a = MockAgent(self, commands = [("isdir /mnt/sdcard/tests", "TRUE"),
                                         ("cd /mnt/sdcard/tests", ""),
                                         ("ls", "test.txt"),
                                         ("rm /mnt/sdcard/tests/test.txt",
--- a/testing/mozbase/mozdevice/tests/sut_unpackfile.py
+++ b/testing/mozbase/mozdevice/tests/sut_unpackfile.py
@@ -5,18 +5,17 @@ import mozlog
 import unittest
 from sut import MockAgent
 
 
 class TestUnpack(unittest.TestCase):
 
     def test_unpackFile(self):
 
-        commands = [("isdir /mnt/sdcard/tests", "TRUE"),
-                    ("unzp /data/test/sample.zip /data/test/",
+        commands = [("unzp /data/test/sample.zip /data/test/",
                      "Checksum:          653400271\n"
                      "1 of 1 successfully extracted\n")]
         m = MockAgent(self, commands=commands)
         d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=mozlog.DEBUG)
         # No error being thrown imples all is well
         self.assertEqual(None, d.unpackFile("/data/test/sample.zip",
                                             "/data/test/"))
 
--- a/testing/mozbase/mozlog/mozlog/__init__.py
+++ b/testing/mozbase/mozlog/mozlog/__init__.py
@@ -15,10 +15,12 @@ from logger import *
 from loglistener import LogMessageServer
 from loggingmixin import LoggingMixin
 
 try:
     import structured
 except ImportError:
     # Structured logging doesn't work on python 2.6 which is still used on some
     # legacy test machines; https://bugzilla.mozilla.org/show_bug.cgi?id=864866
+    # Once we move away from Python 2.6, please cleanup devicemanager.py's
+    # exception block
     pass
 
--- a/testing/mozbase/mozprocess/mozprocess/processhandler.py
+++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py
@@ -816,17 +816,18 @@ falling back to not using job objects fo
             # wake up once a second in case a keyboard interrupt is sent
             count = 0
             while self.outThread.isAlive():
                 self.outThread.join(timeout=1)
                 count += 1
                 if timeout and count > timeout:
                     return None
 
-        return self.proc.wait()
+        self.returncode = self.proc.wait()
+        return self.returncode
 
     # TODO Remove this method when consumers have been fixed
     def waitForFinish(self, timeout=None):
         print >> sys.stderr, "MOZPROCESS WARNING: ProcessHandler.waitForFinish() is deprecated, " \
                              "use ProcessHandler.wait() instead"
         return self.wait(timeout=timeout)
 
 
@@ -946,17 +947,17 @@ class ProcessHandler(ProcessHandlerMixin
 
     If storeOutput==True, the output produced by the process will be saved
     as self.output.
 
     If logfile is not None, the output produced by the process will be
     appended to the given file.
     """
 
-    def __init__(self, cmd, logfile=None, stream=None,  storeOutput=True, **kwargs):
+    def __init__(self, cmd, logfile=None, stream=None, storeOutput=True, **kwargs):
         kwargs.setdefault('processOutputLine', [])
         if callable(kwargs['processOutputLine']):
             kwargs['processOutputLine'] = [kwargs['processOutputLine']]