Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 13 Nov 2014 16:27:00 +0100
changeset 215614 ba71bc4d69948a97b665fd10beb1f5d8f6be6620
parent 215613 a4d2e72afc42595a2f65468ff4d0e96914a007db (current diff)
parent 215518 ae27ae77e32f5c76f925daff1b29be21ec77f5e6 (diff)
child 215615 be28aa12810c108418b7ee24f5fa32802b1445ba
push id51796
push userryanvm@gmail.com
push dateThu, 13 Nov 2014 20:47:14 +0000
treeherdermozilla-inbound@a05b5362429f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to fx-team
netwerk/sctp/src/user_sctp_timer_iterate.c
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="83760d213fb3bec7b4117d266fcfbf6fe2ba14ab"/>
   <project name="device/common" path="device/common" revision="6a2995683de147791e516aae2ccb31fdfbe2ad30"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <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/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="f92a936f2aa97526d4593386754bdbf02db07a12"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="6e47ff2790f5656b5b074407829ceecf3e6188c4"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="1950e4760fa14688b83cdbb5acaa1af9f82ef434"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="ac6eb97a37035c09fb5ede0852f0881e9aadf9ad"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="737f591c5f95477148d26602c7be56cbea0cdeb9"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="51da9b1981be481b92a59a826d4d78dc73d0989a"/>
   <project name="device/common" path="device/common" revision="798a3664597e6041985feab9aef42e98d458bc3d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,23 +14,23 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
   <project name="platform/bionic" path="bionic" revision="c72b8f6359de7ed17c11ddc9dfdde3f615d188a9"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="425f8b5fadf5889834c5acd27d23c9e0b2129c28"/>
   <project name="device/common" path="device/common" revision="42b808b7e93d0619286ae8e59110b176b7732389"/>
   <project name="device/sample" path="device/sample" revision="237bd668d0f114d801a8d6455ef5e02cc3577587"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="52a1a862a8bac319652b8f82d9541ba40bfa45ce"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,25 +10,25 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <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="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/i686-linux-glibc2.7-4.6" revision="95bb5b66b3ec5769c3de8d3f25d681787418e7d2"/>
   <project groups="linux" name="platform/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" path="prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.7-4.6" revision="ebdad82e61c16772f6cd47e9f11936bf6ebe9aa0"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-eabi-4.7" revision="8b880805d454664b3eed11d0f053cdeafa1ff06e"/>
   <project groups="linux,arm" name="platform/prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" path="prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.7" revision="a1e239a0bb5cd1d69680bf1075883aa9a7bf2429"/>
   <project groups="linux,x86" name="platform/prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" path="prebuilts/gcc/linux-x86/x86/i686-linux-android-4.7" revision="c7931763d41be602407ed9d71e2c0292c6597e00"/>
   <project groups="linux,x86" name="platform/prebuilts/python/linux-x86/2.7.5" path="prebuilts/python/linux-x86/2.7.5" revision="a32003194f707f66a2d8cdb913ed1869f1926c5d"/>
   <project name="device/common" path="device/common" revision="96d4d2006c4fcb2f19a3fa47ab10cb409faa017b"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <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": "2e0f2f070a2265b537e6a2ff4e0f2e1f2aca49c6", 
+    "revision": "c1bed74af46cb81a7092d6e80624134bae5d1bf0", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,21 +12,21 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
   <project name="platform/development" path="development" revision="2460485184bc8535440bb63876d4e63ec1b4770c"/>
   <project name="device/common" path="device/common" revision="0dcc1e03659db33b77392529466f9eb685cdd3c7"/>
   <project name="device/sample" path="device/sample" revision="68b1cb978a20806176123b959cb05d4fa8adaea4"/>
   <project name="platform_external_apriori" path="external/apriori" remote="b2g" revision="11816ad0406744f963537b23d68ed9c2afb412bd"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,20 +12,20 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <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="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="be8b0151d2f9a4c41fc63952128e0b723cd1161d"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="a222358a6210c0bb94e53e036ec8c73dc2e3d4d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
-  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="dfb7845803f3afcdcf157c5babec357bf9ce74eb"/>
+  <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="e0a9ac010df3afaa47ba107192c05ac8b5516435"/>
   <project name="platform/development" path="development" revision="a384622f5fcb1d2bebb9102591ff7ae91fe8ed2d"/>
   <project name="device/common" path="device/common" revision="7c65ea240157763b8ded6154a17d3c033167afb7"/>
   <project name="device/sample" path="device/sample" revision="c328f3d4409db801628861baa8d279fb8855892f"/>
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -214,23 +214,18 @@ var gPrivacyPane = {
       // select the remember history option if needed
       let rememberHistoryCheckbox = document.getElementById("rememberHistory");
       if (!rememberHistoryCheckbox.checked)
         rememberHistoryCheckbox.checked = true;
 
       // select the remember forms history option
       document.getElementById("browser.formfill.enable").value = true;
 
-#ifdef RELEASE_BUILD
       // select the allow cookies option
       document.getElementById("network.cookie.cookieBehavior").value = 0;
-#else
-      // select the limit cookies option
-      document.getElementById("network.cookie.cookieBehavior").value = 3;
-#endif
       // select the cookie lifetime policy option
       document.getElementById("network.cookie.lifetimePolicy").value = 0;
 
       // select the clear on close option
       document.getElementById("privacy.sanitize.sanitizeOnShutdown").value = false;
       break;
     case "dontremember":
       if (!pref.value)
@@ -416,29 +411,21 @@ var gPrivacyPane = {
    * Enables/disables the "keep until" label and menulist in response to the
    * "accept cookies" checkbox being checked or unchecked.
    */
   writeAcceptCookies: function ()
   {
     var accept = document.getElementById("acceptCookies");
     var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
 
-#ifdef RELEASE_BUILD
     // if we're enabling cookies, automatically select 'accept third party always'
     if (accept.checked)
       acceptThirdPartyMenu.selectedIndex = 0;
 
     return accept.checked ? 0 : 2;
-#else
-    // if we're enabling cookies, automatically select 'accept third party from visited'
-    if (accept.checked)
-      acceptThirdPartyMenu.selectedIndex = 1;
-
-    return accept.checked ? 3 : 2;
-#endif
   },
   
   /**
    * Converts between network.cookie.cookieBehavior and the third-party cookie UI
    */
   readAcceptThirdPartyCookies: function ()
   {
     var pref = document.getElementById("network.cookie.cookieBehavior");
--- a/browser/components/preferences/in-content/tests/browser_privacypane_4.js
+++ b/browser/components/preferences/in-content/tests/browser_privacypane_4.js
@@ -12,24 +12,20 @@ function test() {
   }
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
   let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 
   run_test_subset(Array.concat([
     test_custom_retention("acceptCookies", "remember"),
     test_custom_retention("acceptCookies", "custom")
   ],
-    (runtime.isReleaseBuild ? [
+    [
     test_custom_retention("acceptThirdPartyMenu", "remember", "visited"),
     test_custom_retention("acceptThirdPartyMenu", "custom", "always")
-    ]
-    : [
-    test_custom_retention("acceptThirdPartyMenu", "remember", "always"),
-    test_custom_retention("acceptThirdPartyMenu", "custom", "visited")
-    ]), [
+    ], [
     test_custom_retention("keepCookiesUntil", "remember", 1),
     test_custom_retention("keepCookiesUntil", "custom", 2),
     test_custom_retention("keepCookiesUntil", "custom", 0),
     test_custom_retention("alwaysClear", "remember"),
     test_custom_retention("alwaysClear", "custom"),
     test_historymode_retention("remember", "remember"),
 
     // reset all preferences to their default values once we're done
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -180,23 +180,18 @@ var gPrivacyPane = {
       // select the remember history option if needed
       let rememberHistoryCheckbox = document.getElementById("rememberHistory");
       if (!rememberHistoryCheckbox.checked)
         rememberHistoryCheckbox.checked = true;
 
       // select the remember forms history option
       document.getElementById("browser.formfill.enable").value = true;
 
-#ifdef RELEASE_BUILD
       // select the accept cookies option
       document.getElementById("network.cookie.cookieBehavior").value = 0;
-#else
-      // select the limit cookies option
-      document.getElementById("network.cookie.cookieBehavior").value = 3;
-#endif
       // select the cookie lifetime policy option
       document.getElementById("network.cookie.lifetimePolicy").value = 0;
 
       // select the clear on close option
       document.getElementById("privacy.sanitize.sanitizeOnShutdown").value = false;
       break;
     case "dontremember":
       if (!pref.value)
@@ -383,29 +378,21 @@ var gPrivacyPane = {
    * Enables/disables the "keep until" label and menulist in response to the
    * "accept cookies" checkbox being checked or unchecked.
    */
   writeAcceptCookies: function ()
   {
     var accept = document.getElementById("acceptCookies");
     var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
 
-#ifdef RELEASE_BUILD
     // if we're enabling cookies, automatically select 'accept third party always'
     if (accept.checked)
       acceptThirdPartyMenu.selectedIndex = 0;
 
     return accept.checked ? 0 : 2;
-#else
-    // if we're enabling cookies, automatically select 'accept third party from visited'
-    if (accept.checked)
-      acceptThirdPartyMenu.selectedIndex = 1;
-
-    return accept.checked ? 3 : 2;
-#endif
   },
 
   /**
    * Converts between network.cookie.cookieBehavior and the third-party cookie UI
    */
   readAcceptThirdPartyCookies: function ()
   {
     var pref = document.getElementById("network.cookie.cookieBehavior");
--- a/browser/components/preferences/tests/browser_privacypane_4.js
+++ b/browser/components/preferences/tests/browser_privacypane_4.js
@@ -13,24 +13,20 @@ function test() {
   }
   loader.loadSubScript(rootDir + "privacypane_tests_perwindow.js", this);
   let runtime = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime);
 
   run_test_subset(Array.concat([
     test_custom_retention("acceptCookies", "remember"),
     test_custom_retention("acceptCookies", "custom")
     ],
-    (runtime.isReleaseBuild ? [
+    [
     test_custom_retention("acceptThirdPartyMenu", "remember", "visited"),
     test_custom_retention("acceptThirdPartyMenu", "custom", "always")
-    ]
-    : [
-    test_custom_retention("acceptThirdPartyMenu", "remember", "always"),
-    test_custom_retention("acceptThirdPartyMenu", "custom", "visited")
-    ]), [
+    ], [
     test_custom_retention("keepCookiesUntil", "remember", 1),
     test_custom_retention("keepCookiesUntil", "custom", 2),
     test_custom_retention("keepCookiesUntil", "custom", 0),
     test_custom_retention("alwaysClear", "remember"),
     test_custom_retention("alwaysClear", "custom"),
     test_historymode_retention("remember", "remember"),
 
     // reset all preferences to their default values once we're done
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -14763,16 +14763,35 @@ class CGEventMethod(CGNativeMember):
                         sequenceCopy = "e->%s.AppendElements(%s);\n"
                         if m.type.nullable():
                             sequenceCopy = CGIfWrapper(
                                 CGGeneric(sequenceCopy),
                                 "!%s.IsNull()" % source).define()
                             target += ".SetValue()"
                             source += ".Value()"
                         members += sequenceCopy % (target, source)
+                    elif m.type.isSpiderMonkeyInterface():
+                        srcname = "%s.%s" % (self.args[1].name, name)
+                        if m.type.nullable():
+                            members += fill(
+                                """
+                                if (${srcname}.IsNull()) {
+                                  e->${varname} = nullptr;
+                                } else {
+                                  e->${varname} = ${srcname}.Value().Obj();
+                                }
+                                """,
+                            varname=name,
+                            srcname=srcname);
+                        else:
+                            members += fill(
+                                """
+                                e->${varname}.set(${srcname}.Obj());
+                                """,
+                            varname=name, srcname=srcname);
                     else:
                         members += "e->%s = %s.%s;\n" % (name, self.args[1].name, name)
                     if m.type.isAny() or m.type.isObject() or m.type.isSpiderMonkeyInterface():
                         holdJS = "mozilla::HoldJSObjects(e.get());\n"
             iface = iface.parent
 
         self.body = fill(
             """
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1352,16 +1352,35 @@ CanvasRenderingContext2D::ClearTarget()
   mDSPathBuilder = nullptr;
 
   ContextState *state = mStyleStack.AppendElement();
   state->globalAlpha = 1.0;
 
   state->colorStyles[Style::FILL] = NS_RGB(0,0,0);
   state->colorStyles[Style::STROKE] = NS_RGB(0,0,0);
   state->shadowColor = NS_RGBA(0,0,0,0);
+
+  // For vertical writing-mode, unless text-orientation is sideways,
+  // we'll modify the initial value of textBaseline to 'middle'.
+  nsRefPtr<nsStyleContext> canvasStyle;
+  if (mCanvasElement && mCanvasElement->IsInDoc()) {
+    nsCOMPtr<nsIPresShell> presShell = GetPresShell();
+    if (presShell) {
+      canvasStyle =
+        nsComputedDOMStyle::GetStyleContextForElement(mCanvasElement,
+                                                      nullptr,
+                                                      presShell);
+      if (canvasStyle) {
+        WritingMode wm(canvasStyle);
+        if (wm.IsVertical() && !wm.IsSideways()) {
+          state->textBaseline = TextBaseline::MIDDLE;
+        }
+      }
+    }
+  }
 }
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::InitializeWithSurface(nsIDocShell *shell,
                                                 gfxASurface *surface,
                                                 int32_t width,
                                                 int32_t height)
 {
@@ -3144,16 +3163,17 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
     return NSToCoordRound(textRunMetrics.mAdvanceWidth);
   }
 
   virtual void DrawText(nscoord xOffset, nscoord width)
   {
     gfxPoint point = mPt;
     bool rtl = mTextRun->IsRightToLeft();
     bool verticalRun = mTextRun->IsVertical();
+    bool centerBaseline = mTextRun->UseCenterBaseline();
 
     gfxFloat& inlineCoord = verticalRun ? point.y : point.x;
     inlineCoord += xOffset;
 
     // offset is given in terms of left side of string
     if (rtl) {
       // Bug 581092 - don't use rounded pixel width to advance to
       // right-hand end of run, because this will cause different
@@ -3212,30 +3232,37 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
         // This can occur when something switched DirectWrite off.
         return;
       }
 
       AutoRestoreTransform sidewaysRestore;
       if (runs[c].mOrientation ==
           gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT) {
         sidewaysRestore.Init(mCtx->mTarget);
-        // TODO: The baseline adjustment here is kinda ad-hoc; eventually
-        // perhaps we should check for horizontal and vertical baseline data
-        // in the font, and adjust accordingly.
-        // (The same will be true for HTML text layout.)
         const gfxFont::Metrics& metrics = mTextRun->GetFontGroup()->
           GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
-        mCtx->mTarget->SetTransform(mCtx->mTarget->GetTransform().Copy().
+
+        gfx::Matrix mat = mCtx->mTarget->GetTransform().Copy().
           PreTranslate(baselineOrigin).      // translate origin for rotation
           PreRotate(gfx::Float(M_PI / 2.0)). // turn 90deg clockwise
-          PreTranslate(-baselineOrigin).     // undo the translation
-          PreTranslate(Point(0, (metrics.emAscent - metrics.emDescent) / 2)));
-                              // and offset the (alphabetic) baseline of the
+          PreTranslate(-baselineOrigin);     // undo the translation
+
+        if (centerBaseline) {
+          // TODO: The baseline adjustment here is kinda ad hoc; eventually
+          // perhaps we should check for horizontal and vertical baseline data
+          // in the font, and adjust accordingly.
+          // (The same will be true for HTML text layout.)
+          float offset = (metrics.emAscent - metrics.emDescent) / 2;
+          mat = mat.PreTranslate(Point(0, offset));
+                              // offset the (alphabetic) baseline of the
                               // horizontally-shaped text from the (centered)
                               // default baseline used for vertical
+        }
+
+        mCtx->mTarget->SetTransform(mat);
       }
 
       RefPtr<GlyphRenderingOptions> renderingOptions = font->GetGlyphRenderingOptions();
 
       GlyphBuffer buffer;
 
       std::vector<Glyph> glyphBuf;
 
@@ -3517,49 +3544,55 @@ CanvasRenderingContext2D::DrawOrMeasureT
             (isRTL && state.textAlign == TextAlign::END)) {
     anchorX = 0;
   } else {
     anchorX = 1;
   }
 
   processor.mPt.x -= anchorX * totalWidth;
 
-  // offset pt.y based on text baseline
+  // offset pt.y (or pt.x, for vertical text) based on text baseline
   processor.mFontgrp->UpdateUserFonts(); // ensure user font generation is current
   const gfxFont::Metrics& fontMetrics =
-    processor.mFontgrp->GetFirstValidFont()->GetMetrics(
-      ((processor.mTextRunFlags & gfxTextRunFactory::TEXT_ORIENT_MASK) ==
-        gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL)
-      ? gfxFont::eHorizontal : gfxFont::eVertical);
-
-  gfxFloat anchorY;
+    processor.mFontgrp->GetFirstValidFont()->GetMetrics(gfxFont::eHorizontal);
+
+  gfxFloat baselineAnchor;
 
   switch (state.textBaseline)
   {
   case TextBaseline::HANGING:
       // fall through; best we can do with the information available
   case TextBaseline::TOP:
-    anchorY = fontMetrics.emAscent;
+    baselineAnchor = fontMetrics.emAscent;
     break;
   case TextBaseline::MIDDLE:
-    anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
+    baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
     break;
   case TextBaseline::IDEOGRAPHIC:
     // fall through; best we can do with the information available
   case TextBaseline::ALPHABETIC:
-    anchorY = 0;
+    baselineAnchor = 0;
     break;
   case TextBaseline::BOTTOM:
-    anchorY = -fontMetrics.emDescent;
+    baselineAnchor = -fontMetrics.emDescent;
     break;
   default:
     MOZ_CRASH("unexpected TextBaseline");
   }
 
-  processor.mPt.y += anchorY;
+  if (processor.mTextRun->IsVertical()) {
+    if (processor.mTextRun->UseCenterBaseline()) {
+      // Adjust to account for mTextRun being shaped using center baseline
+      // rather than alphabetic.
+      baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
+    }
+    processor.mPt.x -= baselineAnchor;
+  } else {
+    processor.mPt.y += baselineAnchor;
+  }
 
   // correct bounding box to get it to be the correct size/position
   processor.mBoundingBox.width = totalWidth;
   processor.mBoundingBox.MoveBy(processor.mPt);
 
   processor.mPt.x *= processor.mAppUnitsPerDevPixel;
   processor.mPt.y *= processor.mAppUnitsPerDevPixel;
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -1788,38 +1788,41 @@ HTMLMediaElement::CaptureStreamInternal(
     return nullptr;
   }
 #ifdef MOZ_EME
   if (ContainsRestrictedContent()) {
     return nullptr;
   }
 #endif
   OutputMediaStream* out = mOutputStreams.AppendElement();
+  uint8_t hints = 0;
+  if (mReadyState >= nsIDOMHTMLMediaElement::HAVE_METADATA) {
+    hints = (mHasAudio? DOMMediaStream::HINT_CONTENTS_AUDIO : 0) |
+            (mHasVideo? DOMMediaStream::HINT_CONTENTS_VIDEO : 0);
+  } else {
 #ifdef DEBUG
-  // Estimate hints based on the type of the media element
-  // under the preference media.capturestream_hints for the
-  // debug builds only. This allows WebRTC Peer Connection
-  // to behave appropriately when media streams generated
-  // via mozCaptureStream*() are added to the Peer Connection.
-  // This functionality is planned to be used as part of Audio
-  // Quality Performance testing for WebRTC.
-  // Bug932845: Revisit this once hints mechanism is dealt with
-  // holistically.
-  uint8_t hints = 0;
-  if (Preferences::GetBool("media.capturestream_hints.enabled")) {
-    if (IsVideo() && GetVideoFrameContainer()) {
-      hints = DOMMediaStream::HINT_CONTENTS_VIDEO | DOMMediaStream::HINT_CONTENTS_AUDIO;
-    } else {
-      hints = DOMMediaStream::HINT_CONTENTS_AUDIO;
+    // Estimate hints based on the type of the media element
+    // under the preference media.capturestream_hints for the
+    // debug builds only. This allows WebRTC Peer Connection
+    // to behave appropriately when media streams generated
+    // via mozCaptureStream*() are added to the Peer Connection.
+    // This functionality is planned to be used as part of Audio
+    // Quality Performance testing for WebRTC.
+    // Bug932845: Revisit this once hints mechanism is dealt with
+    // holistically.
+    if (Preferences::GetBool("media.capturestream_hints.enabled")) {
+      if (IsVideo() && GetVideoFrameContainer()) {
+        hints = DOMMediaStream::HINT_CONTENTS_VIDEO | DOMMediaStream::HINT_CONTENTS_AUDIO;
+      } else {
+        hints = DOMMediaStream::HINT_CONTENTS_AUDIO;
+      }
     }
+#endif
   }
   out->mStream = DOMMediaStream::CreateTrackUnionStream(window, hints);
-#else
-  out->mStream = DOMMediaStream::CreateTrackUnionStream(window);
-#endif
   nsRefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   out->mStream->CombineWithPrincipal(principal);
   out->mFinishWhenEnded = aFinishWhenEnded;
 
   mAudioCaptured = true;
   // Block the output stream initially.
   // Decoders are responsible for removing the block while they are playing
   // back into the output stream.
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -8729,54 +8729,39 @@ FileManager::Init(nsIFile* aDirectory,
 
 nsresult
 FileManager::Invalidate()
 {
   class MOZ_STACK_CLASS Helper MOZ_FINAL
   {
   public:
     static PLDHashOperator
-    CopyToTArray(const uint64_t& aKey, FileInfo* aValue, void* aUserArg)
+    ClearDBRefs(const uint64_t& aKey, FileInfo*& aValue, void* aUserArg)
     {
       MOZ_ASSERT(aValue);
 
-      auto* array = static_cast<FallibleTArray<FileInfo*>*>(aUserArg);
-      MOZ_ASSERT(array);
-
-      MOZ_ALWAYS_TRUE(array->AppendElement(aValue));
-
-      return PL_DHASH_NEXT;
+      if (aValue->LockedClearDBRefs()) {
+        return PL_DHASH_NEXT;
+      }
+
+      return PL_DHASH_REMOVE;
     }
   };
 
   if (IndexedDatabaseManager::IsClosed()) {
     MOZ_ASSERT(false, "Shouldn't be called after shutdown!");
     return NS_ERROR_UNEXPECTED;
   }
 
-  FallibleTArray<FileInfo*> fileInfos;
-  {
-    MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
-
-    MOZ_ASSERT(!mInvalidated);
-    mInvalidated = true;
-
-    if (NS_WARN_IF(!fileInfos.SetCapacity(mFileInfos.Count()))) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
-
-    mFileInfos.EnumerateRead(Helper::CopyToTArray, &fileInfos);
-  }
-
-  for (uint32_t count = fileInfos.Length(), index = 0; index < count; index++) {
-    FileInfo* fileInfo = fileInfos[index];
-    MOZ_ASSERT(fileInfo);
-
-    fileInfo->ClearDBRefs();
-  }
+  MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
+
+  MOZ_ASSERT(!mInvalidated);
+  mInvalidated = true;
+
+  mFileInfos.Enumerate(Helper::ClearDBRefs, nullptr);
 
   return NS_OK;
 }
 
 already_AddRefed<nsIFile>
 FileManager::GetDirectory()
 {
   return GetFileForPath(mDirectoryPath);
--- a/dom/indexedDB/FileInfo.cpp
+++ b/dom/indexedDB/FileInfo.cpp
@@ -125,25 +125,23 @@ FileInfo::GetReferences(int32_t* aRefCnt
 
   if (aSliceRefCnt) {
     *aSliceRefCnt = mSliceRefCnt;
   }
 }
 
 void
 FileInfo::UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
-                           int32_t aDelta,
-                           bool aClear)
+                           int32_t aDelta)
 {
   // XXX This can go away once DOM objects no longer hold FileInfo objects...
   //     Looking at you, IDBMutableFile...
   if (IndexedDatabaseManager::IsClosed()) {
     MOZ_ASSERT(&aRefCount == &mRefCnt);
     MOZ_ASSERT(aDelta == 1 || aDelta == -1);
-    MOZ_ASSERT(!aClear);
 
     if (aDelta > 0) {
       ++aRefCount;
     } else {
       nsrefcnt count = --aRefCount;
       if (!count) {
         mRefCnt = 1;
         delete this;
@@ -153,17 +151,17 @@ FileInfo::UpdateReferences(ThreadSafeAut
   }
 
   MOZ_ASSERT(!IndexedDatabaseManager::IsClosed());
 
   bool needsCleanup;
   {
     MutexAutoLock lock(IndexedDatabaseManager::FileMutex());
 
-    aRefCount = aClear ? 0 : aRefCount + aDelta;
+    aRefCount = aRefCount + aDelta;
 
     if (mRefCnt + mDBRefCnt + mSliceRefCnt > 0) {
       return;
     }
 
     mFileManager->mFileInfos.Remove(Id());
 
     needsCleanup = !mFileManager->Invalidated();
@@ -171,16 +169,39 @@ FileInfo::UpdateReferences(ThreadSafeAut
 
   if (needsCleanup) {
     Cleanup();
   }
 
   delete this;
 }
 
+bool
+FileInfo::LockedClearDBRefs()
+{
+  MOZ_ASSERT(!IndexedDatabaseManager::IsClosed());
+
+  IndexedDatabaseManager::FileMutex().AssertCurrentThreadOwns();
+
+  mDBRefCnt = 0;
+
+  if (mRefCnt || mSliceRefCnt) {
+    return true;
+  }
+
+  // In this case, we are not responsible for removing the file info from the
+  // hashtable. It's up to FileManager which is the only caller of this method.
+
+  MOZ_ASSERT(mFileManager->Invalidated());
+
+  delete this;
+
+  return false;
+}
+
 void
 FileInfo::Cleanup()
 {
   int64_t id = Id();
 
   // IndexedDatabaseManager is main-thread only.
   if (!NS_IsMainThread()) {
     nsRefPtr<CleanupFileRunnable> cleaner =
--- a/dom/indexedDB/FileInfo.h
+++ b/dom/indexedDB/FileInfo.h
@@ -46,22 +46,16 @@ public:
 
   void
   UpdateDBRefs(int32_t aDelta)
   {
     UpdateReferences(mDBRefCnt, aDelta);
   }
 
   void
-  ClearDBRefs()
-  {
-    UpdateReferences(mDBRefCnt, 0, true);
-  }
-
-  void
   UpdateSliceRefs(int32_t aDelta)
   {
     UpdateReferences(mSliceRefCnt, aDelta);
   }
 
   void
   GetReferences(int32_t* aRefCnt, int32_t* aDBRefCnt, int32_t* aSliceRefCnt);
 
@@ -75,18 +69,20 @@ public:
   Id() const = 0;
 
 protected:
   virtual ~FileInfo();
 
 private:
   void
   UpdateReferences(ThreadSafeAutoRefCnt& aRefCount,
-                   int32_t aDelta,
-                   bool aClear = false);
+                   int32_t aDelta);
+
+  bool
+  LockedClearDBRefs();
 
   void
   Cleanup();
 };
 
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/encoder/OpusTrackEncoder.cpp
+++ b/dom/media/encoder/OpusTrackEncoder.cpp
@@ -153,16 +153,20 @@ OpusTrackEncoder::Init(int aChannels, in
   NS_ENSURE_TRUE((aChannels <= MAX_SUPPORTED_AUDIO_CHANNELS) && (aChannels > 0),
                  NS_ERROR_FAILURE);
 
   // This version of encoder API only support 1 or 2 channels,
   // So set the mChannels less or equal 2 and
   // let InterleaveTrackData downmix pcm data.
   mChannels = aChannels > MAX_CHANNELS ? MAX_CHANNELS : aChannels;
 
+  // Reject non-audio sample rates.
+  NS_ENSURE_TRUE(aSamplingRate >= 8000, NS_ERROR_INVALID_ARG);
+  NS_ENSURE_TRUE(aSamplingRate <= 192000, NS_ERROR_INVALID_ARG);
+
   // According to www.opus-codec.org, creating an opus encoder requires the
   // sampling rate of source signal be one of 8000, 12000, 16000, 24000, or
   // 48000. If this constraint is not satisfied, we resample the input to 48kHz.
   nsTArray<int> supportedSamplingRates;
   supportedSamplingRates.AppendElements(kOpusSupportedInputSamplingRates,
                          ArrayLength(kOpusSupportedInputSamplingRates));
   if (!supportedSamplingRates.Contains(aSamplingRate)) {
     int error;
--- a/dom/media/encoder/VorbisTrackEncoder.cpp
+++ b/dom/media/encoder/VorbisTrackEncoder.cpp
@@ -44,20 +44,20 @@ VorbisTrackEncoder::~VorbisTrackEncoder(
     vorbis_dsp_clear(&mVorbisDsp);
     vorbis_info_clear(&mVorbisInfo);
   }
 }
 
 nsresult
 VorbisTrackEncoder::Init(int aChannels, int aSamplingRate)
 {
-  if (aChannels <= 0 || aChannels > 8) {
-    VORBISLOG("aChannels <= 0 || aChannels > 8");
-    return NS_ERROR_INVALID_ARG;
-  }
+  NS_ENSURE_TRUE(aChannels > 0, NS_ERROR_INVALID_ARG);
+  NS_ENSURE_TRUE(aChannels <= 8, NS_ERROR_INVALID_ARG);
+  NS_ENSURE_TRUE(aSamplingRate >= 8000, NS_ERROR_INVALID_ARG);
+  NS_ENSURE_TRUE(aSamplingRate <= 192000, NS_ERROR_INVALID_ARG);
 
   // This monitor is used to wake up other methods that are waiting for encoder
   // to be completely initialized.
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
   mChannels = aChannels;
   mSamplingRate = aSamplingRate;
 
   int ret = 0;
--- a/dom/media/fmp4/apple/AppleATDecoder.cpp
+++ b/dom/media/fmp4/apple/AppleATDecoder.cpp
@@ -4,17 +4,16 @@
  * 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 <AudioToolbox/AudioToolbox.h>
 #include "AppleUtils.h"
 #include "MP4Reader.h"
 #include "MP4Decoder.h"
 #include "mozilla/RefPtr.h"
-#include "mozilla/ReentrantMonitor.h"
 #include "mp4_demuxer/Adts.h"
 #include "mp4_demuxer/DecoderData.h"
 #include "nsIThread.h"
 #include "AppleATDecoder.h"
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 PRLogModuleInfo* GetAppleMediaLog();
@@ -28,20 +27,21 @@ namespace mozilla {
 AppleATDecoder::AppleATDecoder(const mp4_demuxer::AudioDecoderConfig& aConfig,
                                MediaTaskQueue* aAudioTaskQueue,
                                MediaDataDecoderCallback* aCallback)
   : mConfig(aConfig)
   , mTaskQueue(aAudioTaskQueue)
   , mCallback(aCallback)
   , mConverter(nullptr)
   , mStream(nullptr)
-  , mCurrentAudioTimestamp(0)
+  , mCurrentAudioTimestamp(-1)
+  , mNextAudioTimestamp(-1)
   , mSamplePosition(0)
-  , mHaveOutput(false)
-  , mFlushed(false)
+  , mSizeDecoded(0)
+  , mLastError(noErr)
 {
   MOZ_COUNT_CTOR(AppleATDecoder);
   LOG("Creating Apple AudioToolbox decoder");
   LOG("Audio Decoder configuration: %s %d Hz %d channels %d bits per channel",
       mConfig.mime_type,
       mConfig.samples_per_second,
       mConfig.channel_count,
       mConfig.bits_per_sample);
@@ -233,96 +233,74 @@ void
 AppleATDecoder::SampleCallback(uint32_t aNumBytes,
                                uint32_t aNumPackets,
                                const void* aData,
                                AudioStreamPacketDescription* aPackets)
 {
   // Pick a multiple of the frame size close to a power of two
   // for efficient allocation.
   const uint32_t MAX_AUDIO_FRAMES = 128;
-  const uint32_t decodedSize = MAX_AUDIO_FRAMES * mConfig.channel_count *
-    sizeof(AudioDataValue);
+  const uint32_t maxDecodedSamples = MAX_AUDIO_FRAMES * mConfig.channel_count;
 
   // Descriptions for _decompressed_ audio packets. ignored.
   nsAutoArrayPtr<AudioStreamPacketDescription>
       packets(new AudioStreamPacketDescription[MAX_AUDIO_FRAMES]);
 
   // This API insists on having packets spoon-fed to it from a callback.
   // This structure exists only to pass our state and the result of the
   // parser on to the callback above.
   PassthroughUserData userData =
       { this, aNumPackets, aNumBytes, aData, aPackets, false };
 
+  // Decompressed audio buffer
+  nsAutoArrayPtr<AudioDataValue> decoded(new AudioDataValue[maxDecodedSamples]);
+
   do {
-    // Decompressed audio buffer
-    nsAutoArrayPtr<uint8_t> decoded(new uint8_t[decodedSize]);
-
     AudioBufferList decBuffer;
     decBuffer.mNumberBuffers = 1;
     decBuffer.mBuffers[0].mNumberChannels = mOutputFormat.mChannelsPerFrame;
-    decBuffer.mBuffers[0].mDataByteSize = decodedSize;
+    decBuffer.mBuffers[0].mDataByteSize =
+      maxDecodedSamples * sizeof(AudioDataValue);
     decBuffer.mBuffers[0].mData = decoded.get();
 
     // in: the max number of packets we can handle from the decoder.
     // out: the number of packets the decoder is actually returning.
     UInt32 numFrames = MAX_AUDIO_FRAMES;
 
     OSStatus rv = AudioConverterFillComplexBuffer(mConverter,
                                                   _PassthroughInputDataCallback,
                                                   &userData,
                                                   &numFrames /* in/out */,
                                                   &decBuffer,
                                                   packets.get());
 
     if (rv && rv != kNeedMoreData) {
       LOG("Error decoding audio stream: %d\n", rv);
-      mCallback->Error();
-      break;
-    }
-    LOG("%d frames decoded", numFrames);
-
-    // If we decoded zero frames then AudioConverterFillComplexBuffer is out
-    // of data to provide.  We drained its internal buffer completely on the
-    // last pass.
-    if (numFrames == 0 && rv == kNeedMoreData) {
-      LOG("FillComplexBuffer out of data exactly\n");
-      mCallback->InputExhausted();
+      mLastError = rv;
       break;
     }
 
-    const int rate = mOutputFormat.mSampleRate;
-    const int channels = mOutputFormat.mChannelsPerFrame;
-
-    int64_t time = mCurrentAudioTimestamp;
-    int64_t duration = FramesToUsecs(numFrames, rate).value();
-
-    LOG("pushed audio at time %lfs; duration %lfs\n",
-        (double)time / USECS_PER_S, (double)duration / USECS_PER_S);
-
-    AudioData* audio = new AudioData(mSamplePosition,
-                                     time, duration, numFrames,
-                                     reinterpret_cast<AudioDataValue*>(decoded.forget()),
-                                     channels, rate);
-    mCallback->Output(audio);
-    mHaveOutput = true;
+    mOutputData.AppendElements(decoded.get(),
+                               numFrames * mConfig.channel_count);
 
     if (rv == kNeedMoreData) {
       // No error; we just need more data.
       LOG("FillComplexBuffer out of data\n");
-      mCallback->InputExhausted();
       break;
     }
+    LOG("%d frames decoded", numFrames);
   } while (true);
+
+  mSizeDecoded += aNumBytes;
 }
 
 void
 AppleATDecoder::SetupDecoder()
 {
   LOG("Setting up Apple AudioToolbox decoder.");
-  mHaveOutput = false;
 
   AudioStreamBasicDescription inputFormat;
   nsresult rv = AppleUtils::GetRichestDecodableFormat(mStream, inputFormat);
   if (NS_FAILED(rv)) {
     mCallback->Error();
     return;
   }
 
@@ -363,36 +341,99 @@ AppleATDecoder::SubmitSample(nsAutoPtr<m
                                                mConfig.aac_profile,
                                                aSample);
     if (!rv) {
       NS_ERROR("Failed to apply ADTS header");
       mCallback->Error();
       return;
     }
   }
-  // Push the sample to the AudioFileStream for parsing.
-  mSamplePosition = aSample->byte_offset;
-  mCurrentAudioTimestamp = aSample->composition_timestamp;
-  uint32_t flags = mFlushed ? kAudioFileStreamParseFlag_Discontinuity : 0;
+
+  const Microseconds fuzz = 5;
+  CheckedInt<Microseconds> upperFuzz = mNextAudioTimestamp + fuzz;
+  CheckedInt<Microseconds> lowerFuzz = mNextAudioTimestamp - fuzz;
+  bool discontinuity =
+    !mNextAudioTimestamp.isValid() || mNextAudioTimestamp.value() < 0 ||
+    !upperFuzz.isValid() || lowerFuzz.value() < 0 ||
+    upperFuzz.value() < aSample->composition_timestamp ||
+    lowerFuzz.value() > aSample->composition_timestamp;
+
+  if (discontinuity) {
+    LOG("Discontinuity detected, expected %lld got %lld\n",
+        mNextAudioTimestamp.value(), aSample->composition_timestamp);
+    mCurrentAudioTimestamp = aSample->composition_timestamp;
+    mSamplePosition = aSample->byte_offset;
+  }
+
+  uint32_t flags = discontinuity ? kAudioFileStreamParseFlag_Discontinuity : 0;
+
   OSStatus rv = AudioFileStreamParseBytes(mStream,
                                           aSample->size,
                                           aSample->data,
                                           flags);
+
+  if (!mOutputData.IsEmpty()) {
+    int rate = mOutputFormat.mSampleRate;
+    int channels = mOutputFormat.mChannelsPerFrame;
+    size_t numFrames = mOutputData.Length() / channels;
+    CheckedInt<Microseconds> duration = FramesToUsecs(numFrames, rate);
+    if (!duration.isValid()) {
+      NS_ERROR("Invalid count of accumulated audio samples");
+      mCallback->Error();
+      return;
+    }
+
+    LOG("pushed audio at time %lfs; duration %lfs\n",
+        (double)mCurrentAudioTimestamp.value() / USECS_PER_S,
+        (double)duration.value() / USECS_PER_S);
+
+    nsAutoArrayPtr<AudioDataValue>
+      data(new AudioDataValue[mOutputData.Length()]);
+    PodCopy(data.get(), &mOutputData[0], mOutputData.Length());
+    mOutputData.Clear();
+    AudioData* audio = new AudioData(mSamplePosition,
+                                     mCurrentAudioTimestamp.value(),
+                                     duration.value(),
+                                     numFrames,
+                                     data.forget(),
+                                     channels,
+                                     rate);
+    mCallback->Output(audio);
+    mCurrentAudioTimestamp += duration.value();
+    if (!mCurrentAudioTimestamp.isValid()) {
+      NS_ERROR("Invalid count of accumulated audio samples");
+      mCallback->Error();
+      return;
+    }
+    mSamplePosition += mSizeDecoded;
+    mSizeDecoded = 0;
+  }
+
+  // This is the timestamp of the next sample we should be receiving
+  mNextAudioTimestamp =
+    CheckedInt<Microseconds>(aSample->composition_timestamp) + aSample->duration;
+
   if (rv != noErr) {
     LOG("Error %d parsing audio data", rv);
     mCallback->Error();
+    return;
+  }
+  if (mLastError != noErr) {
+    LOG("Error %d during decoding", mLastError);
+    mCallback->Error();
+    mLastError = noErr;
+    return;
   }
 
-  // Sometimes we need multiple input samples before AudioToolbox
-  // starts decoding. If we haven't seen any output yet, ask for
-  // more data here.
-  if (!mHaveOutput) {
+  if (mTaskQueue->IsEmpty()) {
     mCallback->InputExhausted();
   }
 }
 
 void
 AppleATDecoder::SignalFlush()
 {
-  mFlushed = true;
+  mOutputData.Clear();
+  mNextAudioTimestamp = -1;
+  mSizeDecoded = 0;
 }
 
 } // namespace mozilla
--- a/dom/media/fmp4/apple/AppleATDecoder.h
+++ b/dom/media/fmp4/apple/AppleATDecoder.h
@@ -44,22 +44,30 @@ public:
   // Callbacks also need access to the config.
   const mp4_demuxer::AudioDecoderConfig& mConfig;
 
 private:
   RefPtr<MediaTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
   AudioConverterRef mConverter;
   AudioFileStreamID mStream;
-  Microseconds mCurrentAudioTimestamp;
+  // Timestamp of the next audio frame going to be output by the decoder.
+  CheckedInt<Microseconds> mCurrentAudioTimestamp;
+  // Estimated timestamp of the next compressed audio packet to be supplied by
+  // the MP4 demuxer.
+  CheckedInt<Microseconds> mNextAudioTimestamp;
   int64_t mSamplePosition;
-  bool mHaveOutput;
-  bool mFlushed;
+  // Compressed data size that has been processed by the decoder since the last
+  // output.
+  int64_t mSizeDecoded;
   AudioStreamBasicDescription mOutputFormat;
   AudioFileTypeID mFileType;
+  // Array containing the queued decoded audio frames, about to be output.
+  nsTArray<AudioDataValue> mOutputData;
+  OSStatus mLastError;
 
   void SetupDecoder();
   void SubmitSample(nsAutoPtr<mp4_demuxer::MP4Sample> aSample);
   void SignalFlush();
 };
 
 } // namespace mozilla
 
--- a/dom/media/fmp4/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/fmp4/ffmpeg/FFmpegDataDecoder.cpp
@@ -13,16 +13,17 @@
 #include "FFmpegLog.h"
 #include "FFmpegDataDecoder.h"
 #include "prsystem.h"
 
 namespace mozilla
 {
 
 bool FFmpegDataDecoder<LIBAV_VER>::sFFmpegInitDone = false;
+StaticMutex FFmpegDataDecoder<LIBAV_VER>::sMonitor;
 
 FFmpegDataDecoder<LIBAV_VER>::FFmpegDataDecoder(MediaTaskQueue* aTaskQueue,
                                                 AVCodecID aCodecID)
   : mTaskQueue(aTaskQueue)
   , mCodecContext(nullptr)
   , mFrame(NULL)
   , mCodecID(aCodecID)
 {
@@ -53,16 +54,18 @@ ChoosePixelFormat(AVCodecContext* aCodec
 
   NS_WARNING("FFmpeg does not share any supported pixel formats.");
   return PIX_FMT_NONE;
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Init()
 {
+  StaticMutexAutoLock mon(sMonitor);
+
   FFMPEG_LOG("Initialising FFmpeg decoder.");
 
   if (!sFFmpegInitDone) {
     av_register_all();
 #ifdef DEBUG
     av_log_set_level(AV_LOG_DEBUG);
 #endif
     sFFmpegInitDone = true;
@@ -125,16 +128,18 @@ FFmpegDataDecoder<LIBAV_VER>::Flush()
   mTaskQueue->Flush();
   avcodec_flush_buffers(mCodecContext);
   return NS_OK;
 }
 
 nsresult
 FFmpegDataDecoder<LIBAV_VER>::Shutdown()
 {
+  StaticMutexAutoLock mon(sMonitor);
+
   if (sFFmpegInitDone) {
     avcodec_close(mCodecContext);
     av_freep(&mCodecContext);
 #if LIBAVCODEC_VERSION_MAJOR >= 55
     av_frame_free(&mFrame);
 #elif LIBAVCODEC_VERSION_MAJOR == 54
     avcodec_free_frame(&mFrame);
 #else
--- a/dom/media/fmp4/ffmpeg/FFmpegDataDecoder.h
+++ b/dom/media/fmp4/ffmpeg/FFmpegDataDecoder.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __FFmpegDataDecoder_h__
 #define __FFmpegDataDecoder_h__
 
 #include "PlatformDecoderModule.h"
 #include "FFmpegLibs.h"
 #include "mozilla/Vector.h"
+#include "mozilla/StaticMutex.h"
 
 namespace mozilla
 {
 
 template <int V>
 class FFmpegDataDecoder : public MediaDataDecoder
 {
 };
@@ -39,15 +40,16 @@ protected:
 
   MediaTaskQueue* mTaskQueue;
   AVCodecContext* mCodecContext;
   AVFrame*        mFrame;
   Vector<uint8_t> mExtraData;
 
 private:
   static bool sFFmpegInitDone;
+  static StaticMutex sMonitor;
 
   AVCodecID mCodecID;
 };
 
 } // namespace mozilla
 
 #endif // __FFmpegDataDecoder_h__
--- a/dom/media/gtest/TestTrackEncoder.cpp
+++ b/dom/media/gtest/TestTrackEncoder.cpp
@@ -61,16 +61,25 @@ TEST(Media, OpusEncoder_Init)
   // Should accept channels within valid range.
   for (int i = 1; i <= 8; i++) {
     EXPECT_TRUE(TestOpusInit(i, 16000));
   }
 
   // Expect false with 0 or negative sampling rate of input signal.
   EXPECT_FALSE(TestOpusInit(1, 0));
   EXPECT_FALSE(TestOpusInit(1, -1));
+
+  // Verify sample rate bounds checking.
+  EXPECT_FALSE(TestOpusInit(2, 2000));
+  EXPECT_FALSE(TestOpusInit(2, 4000));
+  EXPECT_FALSE(TestOpusInit(2, 7999));
+  EXPECT_TRUE(TestOpusInit(2, 8000));
+  EXPECT_TRUE(TestOpusInit(2, 192000));
+  EXPECT_FALSE(TestOpusInit(2, 192001));
+  EXPECT_FALSE(TestOpusInit(2, 200000));
 }
 
 TEST(Media, OpusEncoder_Resample)
 {
   // Sampling rates of data to be fed to Opus encoder, should remain unchanged
   // if it is one of Opus supported rates (8000, 12000, 16000, 24000 and 48000
   // (kHz)) at initialization.
   EXPECT_TRUE(TestOpusResampler(1, 8000) == 8000);
--- a/dom/media/gtest/TestVorbisTrackEncoder.cpp
+++ b/dom/media/gtest/TestVorbisTrackEncoder.cpp
@@ -125,24 +125,29 @@ TEST(VorbisTrackEncoder, Init)
   // Expect false with 0 or negative channels of input signal.
   EXPECT_FALSE(TestVorbisInit(0, 16000));
   EXPECT_FALSE(TestVorbisInit(-1, 16000));
   EXPECT_FALSE(TestVorbisInit(8 + 1, 16000));
 
   // Sample rate and channel range test.
   for (int i = 1; i <= 8; i++) {
     EXPECT_FALSE(TestVorbisInit(i, -1));
+    EXPECT_FALSE(TestVorbisInit(i, 2000));
+    EXPECT_FALSE(TestVorbisInit(i, 4000));
+    EXPECT_FALSE(TestVorbisInit(i, 7999));
     EXPECT_TRUE(TestVorbisInit(i, 8000));
     EXPECT_TRUE(TestVorbisInit(i, 11000));
     EXPECT_TRUE(TestVorbisInit(i, 16000));
     EXPECT_TRUE(TestVorbisInit(i, 22050));
     EXPECT_TRUE(TestVorbisInit(i, 32000));
     EXPECT_TRUE(TestVorbisInit(i, 44100));
     EXPECT_TRUE(TestVorbisInit(i, 48000));
     EXPECT_TRUE(TestVorbisInit(i, 96000));
+    EXPECT_TRUE(TestVorbisInit(i, 192000));
+    EXPECT_FALSE(TestVorbisInit(i, 192001));
     EXPECT_FALSE(TestVorbisInit(i, 200000 + 1));
   }
 }
 
 // Test metadata
 TEST(VorbisTrackEncoder, Metadata)
 {
   // Initiate vorbis encoder.
--- a/dom/media/mediasource/test/test_LoadedMetadataFired.html
+++ b/dom/media/mediasource/test/test_LoadedMetadataFired.html
@@ -13,16 +13,18 @@
 SimpleTest.waitForExplicitFinish();
 
 runWithMSE(function (ms, v) {
   ms.addEventListener("sourceopen", function () {
     var sb = ms.addSourceBuffer("video/webm");
 
     v.addEventListener("loadedmetadata", function () {
       ok(true, "Got loadedmetadata event");
+      is(v.videoWidth, 320, "videoWidth has correct initial value");
+      is(v.videoHeight, 240, "videoHeight has correct initial value");
       SimpleTest.finish();
     });
 
     fetchWithXHR("seek.webm", function (arrayBuffer) {
       sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 318));
       v.play();
     });
   });
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -84,16 +84,18 @@ skip-if = toolkit == 'gonk' # b2g(Bug 96
 [test_peerConnection_bug834153.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_bug835370.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_bug1013809.html]
 skip-if = toolkit == 'gonk' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_peerConnection_bug1042791.html]
 skip-if = buildapp == 'b2g' || os == 'android' # bug 1043403
+[test_peerConnection_capturedVideo.html]
+skip-if = toolkit == 'gonk' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_peerConnection_close.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_errorCallbacks.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_noTrickleAnswer.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
 [test_peerConnection_noTrickleOffer.html]
 skip-if = toolkit == 'gonk' # b2g (Bug 1059867)
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -2349,20 +2349,21 @@ PeerConnectionWrapper.prototype = {
         ok(self.onAddStreamFired, self + " checkMediaTracks() timed out waiting for onaddstream event to fire");
         if (!self.onAddStreamFired) {
           onSuccess();
         }
       }, 60000);
     }
   },
 
-  verifySdp : function PCW_verifySdp(desc, expectedType, constraints,
-      offerOptions, trickleIceCallback) {
+  verifySdp : function PCW_verifySdp(desc, expectedType, offerConstraintsList,
+      answerConstraintsList, offerOptions, trickleIceCallback) {
     info("Examining this SessionDescription: " + JSON.stringify(desc));
-    info("constraints: " + JSON.stringify(constraints));
+    info("offerConstraintsList: " + JSON.stringify(offerConstraintsList));
+    info("answerConstraintsList: " + JSON.stringify(answerConstraintsList));
     info("offerOptions: " + JSON.stringify(offerOptions));
     ok(desc, "SessionDescription is not null");
     is(desc.type, expectedType, "SessionDescription type is " + expectedType);
     ok(desc.sdp.length > 10, "SessionDescription body length is plausible");
     ok(desc.sdp.contains("a=ice-ufrag"), "ICE username is present in SDP");
     ok(desc.sdp.contains("a=ice-pwd"), "ICE password is present in SDP");
     ok(desc.sdp.contains("a=fingerprint"), "ICE fingerprint is present in SDP");
     //TODO: update this for loopback support bug 1027350
@@ -2371,38 +2372,38 @@ PeerConnectionWrapper.prototype = {
       ok(true, "at least one ICE candidate is present in SDP");
       trickleIceCallback(false);
     } else {
       info("No ICE candidate in SDP -> requiring trickle ICE");
       trickleIceCallback(true);
     }
     //TODO: how can we check for absence/presence of m=application?
 
-    //TODO: how to handle media contraints + offer options
-    var audioTracks = this.countAudioTracksInMediaConstraint(constraints);
-    if (constraints.length === 0) {
-      audioTracks = this.audioInOfferOptions(offerOptions);
-    }
+    var audioTracks =
+      Math.max(this.countAudioTracksInMediaConstraint(offerConstraintsList),
+               this.countAudioTracksInMediaConstraint(answerConstraintsList)) ||
+      this.audioInOfferOptions(offerOptions);
+
     info("expected audio tracks: " + audioTracks);
     if (audioTracks == 0) {
       ok(!desc.sdp.contains("m=audio"), "audio m-line is absent from SDP");
     } else {
       ok(desc.sdp.contains("m=audio"), "audio m-line is present in SDP");
       ok(desc.sdp.contains("a=rtpmap:109 opus/48000/2"), "OPUS codec is present in SDP");
       //TODO: ideally the rtcp-mux should be for the m=audio, and not just
       //      anywhere in the SDP (JS SDP parser bug 1045429)
       ok(desc.sdp.contains("a=rtcp-mux"), "RTCP Mux is offered in SDP");
 
     }
 
-    //TODO: how to handle media contraints + offer options
-    var videoTracks = this.countVideoTracksInMediaConstraint(constraints);
-    if (constraints.length === 0) {
-      videoTracks = this.videoInOfferOptions(offerOptions);
-    }
+    var videoTracks =
+      Math.max(this.countVideoTracksInMediaConstraint(offerConstraintsList),
+               this.countVideoTracksInMediaConstraint(answerConstraintsList)) ||
+      this.videoInOfferOptions(offerOptions);
+
     info("expected video tracks: " + videoTracks);
     if (videoTracks == 0) {
       ok(!desc.sdp.contains("m=video"), "video m-line is absent from SDP");
     } else {
       ok(desc.sdp.contains("m=video"), "video m-line is present in SDP");
       if (this.h264) {
         ok(desc.sdp.contains("a=rtpmap:126 H264/90000"), "H.264 codec is present in SDP");
       } else {
--- a/dom/media/tests/mochitest/templates.js
+++ b/dom/media/tests/mochitest/templates.js
@@ -215,28 +215,28 @@ var commandsPeerConnection = [
         test.next();
       });
     }
   ],
   [
     'PC_LOCAL_SANE_LOCAL_SDP',
     function (test) {
       test.pcLocal.verifySdp(test._local_offer, "offer",
-        test._offer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function(trickle) {
           test.pcLocal.localRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_REMOTE_SANE_REMOTE_SDP',
     function (test) {
       test.pcRemote.verifySdp(test._local_offer, "offer",
-        test._offer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function (trickle) {
           test.pcRemote.remoteRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_REMOTE_CREATE_ANSWER',
@@ -339,28 +339,28 @@ var commandsPeerConnection = [
         }
       );
     }
   ],
   [
     'PC_REMOTE_SANE_LOCAL_SDP',
     function (test) {
       test.pcRemote.verifySdp(test._remote_answer, "answer",
-        test._answer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function (trickle) {
           test.pcRemote.localRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_LOCAL_SANE_REMOTE_SDP',
     function (test) {
       test.pcLocal.verifySdp(test._remote_answer, "answer",
-        test._answer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function (trickle) {
           test.pcLocal.remoteRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_LOCAL_WAIT_FOR_ICE_CONNECTED',
@@ -832,28 +832,28 @@ var commandsDataChannel = [
         test.next();
       });
     }
   ],
   [
     'PC_LOCAL_SANE_LOCAL_SDP',
     function (test) {
       test.pcLocal.verifySdp(test._local_offer, "offer",
-        test._offer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function(trickle) {
           test.pcLocal.localRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_REMOTE_SANE_REMOTE_SDP',
     function (test) {
       test.pcRemote.verifySdp(test._local_offer, "offer",
-        test._offer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function (trickle) {
           test.pcRemote.remoteRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_REMOTE_CREATE_ANSWER',
@@ -936,28 +936,28 @@ var commandsDataChannel = [
         }
       );
     }
   ],
   [
     'PC_REMOTE_SANE_LOCAL_SDP',
     function (test) {
       test.pcRemote.verifySdp(test._remote_answer, "answer",
-        test._answer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function (trickle) {
           test.pcRemote.localRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_LOCAL_SANE_REMOTE_SDP',
     function (test) {
       test.pcLocal.verifySdp(test._remote_answer, "answer",
-        test._answer_constraints, test._offer_options,
+        test._offer_constraints, test._answer_constraints, test._offer_options,
         function (trickle) {
           test.pcLocal.remoteRequiresTrickleIce = trickle;
         });
       test.next();
     }
   ],
   [
     'PC_LOCAL_WAIT_FOR_ICE_CONNECTED',
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_peerConnection_capturedVideo.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="head.js"></script>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+  <script type="application/javascript" src="pc.js"></script>
+  <script type="application/javascript" src="templates.js"></script>
+  <script type="application/javascript" src="turnConfig.js"></script>
+</head>
+<body>
+<video id="v1" src="../../test/vp9cake.webm" height="120" width="160" autoplay muted></video>
+<pre id="test">
+<script type="application/javascript;version=1.8">
+  createHTML({
+    bug: "1081409",
+    title: "Captured video-only over peer connection",
+    visible: true
+  });
+
+  var domLoaded = new Promise(r => addEventListener("DOMContentLoaded", e => r()));
+  var test;
+  var stream;
+  var waitUntil = func => new Promise(resolve => {
+    var ival = setInterval(() => func() && resolve(clearInterval(ival)), 200);
+  });
+
+  runNetworkTest(function() {
+    test = new PeerConnectionTest();
+    test.setOfferOptions({ offerToReceiveVideo: false,
+                           offerToReceiveAudio: false });
+    test.chain.insertAfter("PC_LOCAL_GUM", [["PC_LOCAL_CAPTUREVIDEO", function (test) {
+      domLoaded
+      .then(() => waitUntil(() => v1.videoWidth > 0)) // TODO: Bug 1096723
+      .then(function() {
+        stream = v1.mozCaptureStreamUntilEnded();
+        is(stream.getTracks().length, 2, "Captured stream has 2 tracks");
+        stream.getTracks().forEach(tr => test.pcLocal._pc.addTrack(tr, stream));
+        test.pcLocal.constraints = [{ video: true, audio:true }]; // fool tests
+        test.next();
+      })
+      .catch(function(reason) {
+        ok(false, "unexpected failure: " + reason);
+        SimpleTest.finish();
+      });
+    }
+    ]]);
+    test.chain.removeAfter("PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT");
+    test.run();
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -222,18 +222,24 @@ InternalSetAudioRoutesICS(SwitchState aS
     AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
                                           AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
     sHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADSET;
   } else if (aState == SWITCH_STATE_HEADPHONE) {
     AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
                                           AUDIO_POLICY_DEVICE_STATE_AVAILABLE, "");
     sHeadsetState |= AUDIO_DEVICE_OUT_WIRED_HEADPHONE;
   } else if (aState == SWITCH_STATE_OFF) {
-    AudioSystem::setDeviceConnectionState(static_cast<audio_devices_t>(sHeadsetState),
-                                          AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
+    if (sHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADSET) {
+      AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADSET,
+                                            AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
+    }
+    if (sHeadsetState & AUDIO_DEVICE_OUT_WIRED_HEADPHONE) {
+      AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_WIRED_HEADPHONE,
+                                            AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
+    }
     sHeadsetState = 0;
   }
 }
 
 static void
 InternalSetAudioRoutes(SwitchState aState)
 {
   if (static_cast<
--- a/dom/telephony/test/marionette/head.js
+++ b/dom/telephony/test/marionette/head.js
@@ -31,69 +31,69 @@ let emulator = (function() {
     throw "Use emulator.runCmdWithCallback(cmd, callback) instead of runEmulatorCmd";
   };
 
   // Overwritten it so people could not call this function directly.
   runEmulatorShell = function() {
     throw "Use emulator.runShellCmd(cmd, callback) instead of runEmulatorShell";
   };
 
+  /**
+   * @return Promise
+   */
   function runCmd(cmd) {
-    let deferred = Promise.defer();
-
-    pendingCmdCount++;
-    originalRunEmulatorCmd(cmd, function(result) {
-      pendingCmdCount--;
-      if (result[result.length - 1] === "OK") {
-        deferred.resolve(result);
-      } else {
-        is(result[result.length - 1], "OK", "emulator command result.");
-        deferred.reject();
-      }
+    return new Promise(function(resolve, reject) {
+      pendingCmdCount++;
+      originalRunEmulatorCmd(cmd, function(result) {
+        pendingCmdCount--;
+        if (result[result.length - 1] === "OK") {
+          resolve(result);
+        } else {
+          is(result[result.length - 1], "OK", "emulator command result.");
+          reject();
+        }
+      });
     });
-
-    return deferred.promise;
   }
 
+  /**
+   * @return Promise
+   */
   function runCmdWithCallback(cmd, callback) {
-    runCmd(cmd).then(result => {
+    return runCmd(cmd).then(result => {
       if (callback && typeof callback === "function") {
         callback(result);
       }
     });
   }
 
   /**
    * @return Promise
    */
   function runShellCmd(aCommands) {
-    let deferred = Promise.defer();
-
-    ++pendingShellCount;
-    originalRunEmulatorShell(aCommands, function(aResult) {
-      --pendingShellCount;
-      deferred.resolve(aResult);
+    return new Promise(function(resolve, reject) {
+      ++pendingShellCount;
+      originalRunEmulatorShell(aCommands, function(aResult) {
+        --pendingShellCount;
+        resolve(aResult);
+      });
     });
-
-    return deferred.promise;
   }
 
   /**
    * @return Promise
    */
   function waitFinish() {
-    let deferred = Promise.defer();
-
-    waitFor(function() {
-      deferred.resolve();
-    }, function() {
-      return pendingCmdCount === 0 && pendingShellCount === 0;
+    return new Promise(function(resolve, reject) {
+      waitFor(function() {
+        resolve();
+      }, function() {
+        return pendingCmdCount === 0 && pendingShellCount === 0;
+      });
     });
-
-    return deferred.promise;
   }
 
   return {
     runCmd: runCmd,
     runCmdWithCallback: runCmdWithCallback,
     runShellCmd: runShellCmd,
     waitFinish: waitFinish
   };
@@ -102,42 +102,140 @@ let emulator = (function() {
 /**
  * Telephony related helper functions.
  */
 (function() {
   /**
    * @return Promise
    */
   function delay(ms) {
-    let deferred = Promise.defer();
+    return new Promise(function(resolve, reject) {
+      let startTime = Date.now();
+      waitFor(function() {
+        resolve();
+      },function() {
+        let duration = Date.now() - startTime;
+        return (duration >= ms);
+      });
+    });
+  }
+
+  /**
+   * Wait for one named event.
+   *
+   * @param aTarget
+   *        A event target.
+   * @param aEventName
+   *        A string event name.
+   * @param aPredicate [optional]
+   *        A predicate function, resolve the promise if aPredicate(event)
+   *        return true
+   * @return Promise<DOMEvent>
+   */
+  function waitForEvent(aTarget, aEventName, aPredicate) {
+    return new Promise(function(resolve, reject) {
+      aTarget.addEventListener(aEventName, function onevent(aEvent) {
+        if (aPredicate === undefined || aPredicate(aEvent)) {
+          aTarget.removeEventListener(aEventName, onevent);
+
+          let label = "X";
+          if (aTarget instanceof TelephonyCall) {
+            label = "Call (" + aTarget.id.number + ")";
+          } else if (aTarget instanceof TelephonyCallGroup) {
+            label = "Conference";
+          } else if (aTarget instanceof Telephony) {
+            label = "Telephony";
+          }
+
+          log(label + " received event '" + aEventName + "'");
+          resolve(aEvent);
+        }
+      });
+    });
+  }
 
-    let startTime = Date.now();
-    waitFor(function() {
-      deferred.resolve();
-    },function() {
-      let duration = Date.now() - startTime;
-      return (duration >= ms);
-    });
+  /**
+   * Wait for callschanged event with event.call == aExpectedCall
+   *
+   * @param aTarget
+   *        A event target.
+   * @param aExpectedCall
+   *        Expected call for event.call
+   * @return Promise<DOMEvent>
+   */
+  function waitForCallsChangedEvent(aTarget, aExpectedCall) {
+    return waitForEvent(aTarget, "callschanged",
+                        event => event.call == aExpectedCall);
+  }
 
-    return deferred.promise;
+  /**
+   * Wait for call state event, e.g., "connected", "disconnected", ...
+   *
+   * @param aTarget
+   *        A event target.
+   * @param aState
+   *        State name
+   * @return Promise<TelephonyCall>
+   */
+  function waitForNamedStateEvent(aTarget, aState) {
+    return waitForEvent(aTarget, aState)
+      .then(event => {
+        if (aTarget instanceof TelephonyCall) {
+          is(aTarget, event.call, "event.call");
+        }
+        is(aTarget.state, aState, "check state");
+        return aTarget;
+      });
+  }
+
+  /**
+   * Wait for groupchange event.
+   *
+   * @param aCall
+   *        A TelephonyCall object.
+   * @param aGroup
+   *        The new group
+   * @return Promise<TelephonyCall>
+   */
+  function waitForGroupChangeEvent(aCall, aGroup) {
+    return waitForEvent(aCall, "groupchange")
+      .then(() => {
+        is(aCall.group, aGroup, "call group");
+        return aCall;
+      });
+  }
+
+  /**
+   * Wait for statechange event.
+   *
+   * @param aTarget
+   *        A event target.
+   * @param aState
+   *        The desired new state. Check it.
+   * @return Promise<DOMEvent>
+   */
+  function waitForStateChangeEvent(aTarget, aState) {
+    return waitForEvent(aTarget, "statechange")
+      .then(() => {
+        is(aTarget.state, aState);
+        return aTarget;
+      });
   }
 
   /**
    * @return Promise
    */
   function waitForNoCall() {
-    let deferred = Promise.defer();
-
-    waitFor(function() {
-      deferred.resolve();
-    }, function() {
-      return telephony.calls.length === 0;
+    return new Promise(function(resolve, reject) {
+      waitFor(function() {
+        resolve();
+      }, function() {
+        return telephony.calls.length === 0;
+      });
     });
-
-    return deferred.promise;
   }
 
   /**
    * @return Promise
    */
   function clearCalls() {
     log("Clear existing calls.");
 
@@ -209,164 +307,33 @@ let emulator = (function() {
 
   /**
    * Check utility functions.
    */
 
   function checkInitialState() {
     log("Verify initial state.");
     ok(telephony.calls, 'telephony.call');
-    checkTelephonyActiveAndCalls(null, []);
     ok(conference.calls, 'conference.calls');
-    checkConferenceStateAndCalls('', []);
-  }
-
-  /**
-   * Convenient helper to compare a TelephonyCall and a received call event.
-   */
-  function checkEventCallState(event, call, state) {
-    is(call, event.call, "event.call");
-    is(call.state, state, "call state");
-  }
-
-  /**
-   * Convenient helper to compare two call lists. Size should be the same and
-   * order is not important.
-   */
-  function checkCalls(actualCalls, expectedCalls) {
-    if (actualCalls.length == expectedCalls.length) {
-      let expectedSet = new Set(expectedCalls);
-      for (let i = 0; i < actualCalls.length; ++i) {
-        ok(expectedSet.has(actualCalls[i]), "should contain the call");
-      }
-    }
-  }
-
-  /**
-   * Convenient helper to check mozTelephony.active and mozTelephony.calls.
-   */
-  function checkTelephonyActiveAndCalls(active, calls) {
-    is(telephony.active, active, "telephony.active");
-    is(telephony.calls.length, calls.length, "telephony.calls");
-    checkCalls(telephony.calls, calls);
-  }
-
-  /**
-   * Convenient helper to check mozTelephony.conferenceGroup.state and
-   * .conferenceGroup.calls.
-   */
-  function checkConferenceStateAndCalls(state, calls) {
-    is(conference.state, state, "conference.state");
-    is(conference.calls.length, calls.length, "conference.calls");
-    checkCalls(conference.calls, calls);
+    checkState(null, [], "", []);
   }
 
   /**
-   * Convenient helper to handle *.oncallschanged event.
-   *
-   * @param container
-   *        Representation of "mozTelephony" or "mozTelephony.conferenceGroup."
-   * @param containerName
-   *        Name of container. Could be an arbitrary string, used for debug
-   *        messages only.
-   * @param expectedCalls
-   *        An array of calls.
-   * @param callback
-   *        A callback function.
+   * Convenient helper to compare two call lists (order is not important).
    */
-  function check_oncallschanged(container, containerName, expectedCalls,
-                                callback) {
-    container.oncallschanged = function(event) {
-      log("Received 'callschanged' event for the " + containerName);
-
-      ok(event.call);
-
-      let index = expectedCalls.indexOf(event.call);
-      ok(index != -1);
-      expectedCalls.splice(index, 1);
-
-      if (expectedCalls.length === 0) {
-        container.oncallschanged = null;
-        callback();
-      }
-    };
-  }
-
-  /**
-   * Convenient helper to handle *.ongroupchange event.
-   *
-   * @param call
-   *        A TelephonyCall object.
-   * @param callName
-   *        Name of a call. Could be an arbitrary string, used for debug messages
-   *        only.
-   * @param group
-   *        Representation of mozTelephony.conferenceGroup.
-   * @param callback
-   *        A callback function.
-   */
-  function check_ongroupchange(call, callName, group, callback) {
-    call.ongroupchange = function(event) {
-      log("Received 'groupchange' event for the " + callName);
-      call.ongroupchange = null;
+  function checkCalls(actualCalls, expectedCalls) {
+    if (actualCalls.length != expectedCalls.length) {
+      ok(false, "check calls.length");
+      return;
+    }
 
-      is(call.group, group);
-      callback();
-    };
-  }
-
-  /**
-   * Convenient helper to handle *.onstatechange event.
-   *
-   * @param container
-   *        Representation of a TelephonyCall or mozTelephony.conferenceGroup.
-   * @param containerName
-   *        Name of container. Could be an arbitrary string, used for debug messages
-   *        only.
-   * @param state
-   *        A string.
-   * @param callback
-   *        A callback function.
-   */
-  function check_onstatechange(container, containerName, state, callback) {
-    container.onstatechange = function(event) {
-      log("Received 'statechange' event for the " + containerName);
-      container.onstatechange = null;
-
-      is(container.state, state);
-      callback();
-    };
-  }
-
-  /**
-   * Convenient helper to check the sequence of call state and event handlers.
-   *
-   * @param state
-   *        A string of the expected call state.
-   * @param previousEvent
-   *        A string of the event that should come before the expected state.
-   */
-  function StateEventChecker(state, previousEvent) {
-    let event = 'on' + state;
-
-    return function(call, callName, callback) {
-      call[event] = function() {
-        log("Received '" + state + "' event for the " + callName);
-        call[event] = null;
-
-        if (previousEvent) {
-          // We always clear the event handler when the event is received.
-          // Therefore, if the corresponding handler is not existed, the expected
-          // previous event has been already received.
-          ok(!call[previousEvent]);
-        }
-        is(call.state, state);
-        callback();
-      };
-    };
+    let expectedSet = new Set(expectedCalls);
+    for (let i = 0; i < actualCalls.length; ++i) {
+      ok(expectedSet.has(actualCalls[i]), "should contain the call");
+    }
   }
 
   /**
    * Convenient helper to check the expected call number and name.
    *
    * @param number
    *        A string sent to modem.
    * @param numberPresentation
@@ -396,17 +363,17 @@ let emulator = (function() {
     is(receivedName, expectedName, "check name per number/namePresentation");
   }
 
   /**
    * Convenient helper to check the call list existing in the emulator.
    *
    * @param expectedCallList
    *        An array of call info with the format of "callStrPool()[state]".
-   * @return A deferred promise.
+   * @return Promise
    */
   function checkEmulatorCallList(expectedCallList) {
     return emulator.runCmd("gsm list").then(result => {
       log("Call list is now: " + result);
       for (let i = 0; i < expectedCallList.length; ++i) {
         is(result[i], expectedCallList[i], "emulator calllist");
       }
     });
@@ -423,18 +390,20 @@ let emulator = (function() {
    *        mozTelephony.calls.
    * @param conferenceState
    *        A string. Should be the expected conference state.
    * @param conferenceCalls
    *        An array of TelephonyCall objects. Should be the expected list of
    *        mozTelephony.conferenceGroup.calls.
    */
   function checkState(active, calls, conferenceState, conferenceCalls) {
-    checkTelephonyActiveAndCalls(active, calls);
-    checkConferenceStateAndCalls(conferenceState, conferenceCalls);
+    is(telephony.active, active, "telephony.active");
+    checkCalls(telephony.calls, calls);
+    is(conference.state, conferenceState, "conference.state");
+    checkCalls(conference.calls, conferenceCalls);
   }
 
   /**
    * Super convenient helper to check calls and state of mozTelephony and
    * mozTelephony.conferenceGroup as well as the calls existing in the emulator.
    *
    * @param active
    *        A TelephonyCall object. Should be the expected active call.
@@ -443,715 +412,483 @@ let emulator = (function() {
    *        mozTelephony.calls.
    * @param conferenceState
    *        A string. Should be the expected conference state.
    * @param conferenceCalls
    *        An array of TelephonyCall objects. Should be the expected list of
    *        mozTelephony.conferenceGroup.calls.
    * @param callList
    *        An array of call info with the format of "callStrPool()[state]".
-   * @return A deferred promise.
+   * @return Promise
    */
   function checkAll(active, calls, conferenceState, conferenceCalls, callList) {
     checkState(active, calls, conferenceState, conferenceCalls);
     return checkEmulatorCallList(callList);
   }
 
   /**
    * Request utility functions.
    */
 
   /**
-   * Make sure there's no pending event before we jump to the next action.
-   *
-   * @param received
-   *        A string of the received event.
-   * @param pending
-   *        An array of the pending events.
-   * @param nextAction
-   *        A callback function that is called when there's no pending event.
-   */
-  function receivedPending(received, pending, nextAction) {
-    let index = pending.indexOf(received);
-    if (index != -1) {
-      pending.splice(index, 1);
-    }
-    if (pending.length === 0) {
-      nextAction();
-    }
-  }
-
-  /**
    * Make an outgoing call.
    *
    * @param number
    *        A string.
    * @param serviceId [optional]
    *        Identification of a service. 0 is set as default.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function dial(number, serviceId) {
     serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
     log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
 
-    let deferred = Promise.defer();
-
-    telephony.dial(number, serviceId).then(call => {
-      ok(call);
-      is(call.id.number, number);
-      is(call.state, "dialing");
-      is(call.serviceId, serviceId);
+    return telephony.dial(number, serviceId)
+      .then(call => {
+        ok(call);
+        is(call.id.number, number);
+        is(call.state, "dialing");
+        is(call.serviceId, serviceId);
 
-      call.onalerting = function onalerting(event) {
-        call.onalerting = null;
-        log("Received 'onalerting' call event.");
-        checkEventCallState(event, call, "alerting");
-        deferred.resolve(call);
-      };
-    }, cause => {
-      deferred.reject(cause);
-    });
-
-    return deferred.promise;
+        return waitForNamedStateEvent(call, "alerting");
+      });
   }
 
   /**
    * Make an outgoing emergency call.
    *
    * @param number
    *        A string.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function dialEmergency(number) {
     log("Make an outgoing emergency call: " + number);
 
-    let deferred = Promise.defer();
-
-    telephony.dialEmergency(number).then(call => {
-      ok(call);
-      is(call.id.number, number);
-      is(call.state, "dialing");
+    return telephony.dialEmergency(number)
+      .then(call => {
+        ok(call);
+        is(call.id.number, number);
+        is(call.state, "dialing");
 
-      call.onalerting = function onalerting(event) {
-        call.onalerting = null;
-        log("Received 'onalerting' call event.");
-        checkEventCallState(event, call, "alerting");
-        deferred.resolve(call);
-      };
-    }, cause => {
-      deferred.reject(cause);
-    });
-
-    return deferred.promise;
+        return waitForNamedStateEvent(call, "alerting");
+      });
   }
 
   /**
    * Answer an incoming call.
    *
    * @param call
    *        An incoming TelephonyCall object.
    * @param conferenceStateChangeCallback [optional]
    *        A callback function which is called if answering an incoming call
    *        triggers conference state change.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function answer(call, conferenceStateChangeCallback) {
     log("Answering the incoming call.");
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve(call);
-    };
+    let promises = [];
 
-    let pending = ["call.onconnected"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
+    let promise = waitForNamedStateEvent(call, "connecting")
+      .then(() => waitForNamedStateEvent(call, "connected"));
+
+    promises.push(promise);
 
-    // When there's already a connected conference call, answering a new incoming
-    // call triggers conference state change. We should wait for
-    // |conference.onstatechange| before checking the state of the conference call.
+    // incoming call triggers conference state change. We should wait for
+    // |conference.onstatechange| before checking the state of the conference
+    // call.
     if (conference.state === "connected") {
-      pending.push("conference.onstatechange");
-      check_onstatechange(conference, "conference", "held", function() {
-        if (typeof conferenceStateChangeCallback === "function") {
-          conferenceStateChangeCallback();
-        }
-        receive("conference.onstatechange");
-      });
+      let promise = waitForStateChangeEvent(conference, "held")
+        .then(() => {
+          if (typeof conferenceStateChangeCallback === "function") {
+            conferenceStateChangeCallback();
+          }
+        });
+
+      promises.push(promise);
     }
 
-    call.onconnecting = function onconnectingIn(event) {
-      log("Received 'connecting' call event for incoming call.");
-      call.onconnecting = null;
-      checkEventCallState(event, call, "connecting");
-    };
-
-    call.onconnected = function onconnectedIn(event) {
-      log("Received 'connected' call event for incoming call.");
-      call.onconnected = null;
-      checkEventCallState(event, call, "connected");
-      ok(!call.onconnecting);
-      receive("call.onconnected");
-    };
     call.answer();
 
-    return deferred.promise;
+    return Promise.all(promises).then(() => call);
   }
 
   /**
    * Hold a call.
    *
    * @param call
    *        A TelephonyCall object.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function hold(call) {
     log("Putting the call on hold.");
 
-    let deferred = Promise.defer();
+    let promise = waitForNamedStateEvent(call, "holding")
+      .then(() => waitForNamedStateEvent(call, "held"));
 
-    let gotHolding = false;
-    call.onholding = function onholding(event) {
-      log("Received 'holding' call event");
-      call.onholding = null;
-      checkEventCallState(event, call, "holding");
-      gotHolding = true;
-    };
-
-    call.onheld = function onheld(event) {
-      log("Received 'held' call event");
-      call.onheld = null;
-      checkEventCallState(event, call, "held");
-      ok(gotHolding);
-      deferred.resolve(call);
-    };
     call.hold();
 
-    return deferred.promise;
+    return promise;
   }
 
   /**
    * Resume a call.
    *
    * @param call
    *        A TelephonyCall object.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function resume(call) {
     log("Resuming the held call.");
 
-    let deferred = Promise.defer();
+    let promise = waitForNamedStateEvent(call, "resuming")
+      .then(() => waitForNamedStateEvent(call, "connected"));
 
-    let gotResuming = false;
-    call.onresuming = function onresuming(event) {
-      log("Received 'resuming' call event");
-      call.onresuming = null;
-      checkEventCallState(event, call, "resuming");
-      gotResuming = true;
-    };
-
-    call.onconnected = function onconnected(event) {
-      log("Received 'connected' call event");
-      call.onconnected = null;
-      checkEventCallState(event, call, "connected");
-      ok(gotResuming);
-      deferred.resolve(call);
-    };
     call.resume();
 
-    return deferred.promise;
+    return promise;
   }
 
   /**
    * Locally hang up a call.
    *
    * @param call
    *        A TelephonyCall object.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function hangUp(call) {
-    let deferred = Promise.defer();
+    log("Local hanging up the call: " + call.id.number);
 
-    call.ondisconnected = function(event) {
-      log("Received 'disconnected' call event");
-      call.ondisconnected = null;
-      checkEventCallState(event, call, "disconnected");
-      deferred.resolve(call);
-    };
+    let promise = waitForNamedStateEvent(call, "disconnecting")
+      .then(() => waitForNamedStateEvent(call, "disconnected"));
+
     call.hangUp();
 
-    return deferred.promise;
+    return promise;
   }
 
   /**
    * Simulate an incoming call.
    *
    * @param number
    *        A string.
    * @param numberPresentation [optional]
    *        An unsigned short integer.
    * @param name [optional]
    *        A string.
    * @param namePresentation [optional]
    *        An unsigned short integer.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function remoteDial(number, numberPresentation, name, namePresentation) {
     log("Simulating an incoming call.");
 
-    let deferred = Promise.defer();
-
-    telephony.onincoming = function onincoming(event) {
-      log("Received 'incoming' call event.");
-      telephony.onincoming = null;
-
-      let call = event.call;
-
-      ok(call);
-      is(call.state, "incoming");
-      checkCallId(number, numberPresentation, name, namePresentation,
-                  call.id.number, call.id.name);
-      deferred.resolve(call);
-    };
-
     numberPresentation = numberPresentation || "";
     name = name || "";
     namePresentation = namePresentation || "";
     emulator.runCmd("gsm call " + number + "," + numberPresentation + "," + name +
                  "," + namePresentation);
-    return deferred.promise;
+
+    return waitForEvent(telephony, "incoming")
+      .then(event => {
+        let call = event.call;
+
+        ok(call);
+        is(call.state, "incoming");
+        checkCallId(number, numberPresentation, name, namePresentation,
+                    call.id.number, call.id.name);
+
+        return call;
+      });
   }
 
   /**
    * Remote party answers the call.
    *
    * @param call
    *        A TelephonyCall object.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function remoteAnswer(call) {
-    log("Remote answering the call.");
-
-    let deferred = Promise.defer();
+    log("Remote answering the call: " + call.id.number);
 
-    call.onconnected = function onconnected(event) {
-      log("Received 'connected' call event.");
-      call.onconnected = null;
-      checkEventCallState(event, call, "connected");
-      deferred.resolve(call);
-    };
     emulator.runCmd("gsm accept " + call.id.number);
 
-    return deferred.promise;
+    return waitForNamedStateEvent(call, "connected");
   }
 
   /**
    * Remote party hangs up the call.
    *
    * @param call
    *        A TelephonyCall object.
-   * @return A deferred promise.
+   * @return Promise<TelephonyCall>
    */
   function remoteHangUp(call) {
-    log("Remote hanging up the call.");
-
-    let deferred = Promise.defer();
+    log("Remote hanging up the call: " + call.id.number);
 
-    call.ondisconnected = function ondisconnected(event) {
-      log("Received 'disconnected' call event.");
-      call.ondisconnected = null;
-      checkEventCallState(event, call, "disconnected");
-      deferred.resolve(call);
-    };
     emulator.runCmd("gsm cancel " + call.id.number);
 
-    return deferred.promise;
+    return waitForNamedStateEvent(call, "disconnected");
   }
 
   /**
    * Remote party hangs up all the calls.
    *
    * @param calls
    *        An array of TelephonyCall objects.
-   * @return A deferred promise.
+   * @return Promise
    */
   function remoteHangUpCalls(calls) {
-    let promise = Promise.resolve();
-
-    for (let call of calls) {
-      promise = promise.then(remoteHangUp.bind(null, call));
-    }
-
-    return promise;
+    let promises = calls.map(remoteHangUp);
+    return Promise.all(promises);
   }
 
   /**
    * Add calls to conference.
    *
    * @param callsToAdd
    *        An array of TelephonyCall objects to be added into conference. The
    *        length of the array should be 1 or 2.
    * @param connectedCallback [optional]
    *        A callback function which is called when conference state becomes
    *        connected.
    * @param twice [optional]
    *        To send conference request twice. It is only used for special test.
-   * @return A deferred promise.
+   * @return Promise<[TelephonyCall ...]>
    */
   function addCallsToConference(callsToAdd, connectedCallback, twice) {
     log("Add " + callsToAdd.length + " calls into conference.");
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve();
-    };
-
-    let pending = ["conference.oncallschanged", "conference.onconnected"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
-
-    let check_onconnected  = StateEventChecker('connected', 'onresuming');
+    let promises = [];
 
     for (let call of callsToAdd) {
-      let callName = "callToAdd (" + call.id.number + ')';
-
-      let ongroupchange = callName + ".ongroupchange";
-      pending.push(ongroupchange);
-      check_ongroupchange(call, callName, conference,
-                          receive.bind(null, ongroupchange));
-
-      let onstatechange = callName + ".onstatechange";
-      pending.push(onstatechange);
-      check_onstatechange(call, callName, 'connected',
-                          receive.bind(null, onstatechange));
+      promises.push(waitForCallsChangedEvent(conference, call));
+      promises.push(waitForGroupChangeEvent(call, conference));
+      promises.push(waitForNamedStateEvent(call, "connected"));
+      promises.push(waitForStateChangeEvent(call, "connected"));
     }
 
-    check_oncallschanged(conference, 'conference', callsToAdd,
-                         receive.bind(null, "conference.oncallschanged"));
-
-    check_onconnected(conference, "conference", function() {
-      ok(!conference.oncallschanged);
-      if (typeof connectedCallback === 'function') {
-        connectedCallback();
-      }
-      receive("conference.onconnected");
-    });
+    let promise = waitForNamedStateEvent(conference, "connected")
+      .then(() => {
+        if (typeof connectedCallback === "function") {
+          connectedCallback();
+        }
+      });
+    promises.push(promise);
 
     // Cannot use apply() through webidl, so just separate the cases to handle.
     let requestCount = twice ? 2 : 1;
     for (let i = 0; i < requestCount; ++i) {
       if (callsToAdd.length == 2) {
         conference.add(callsToAdd[0], callsToAdd[1]);
       } else {
         conference.add(callsToAdd[0]);
       }
     }
 
-    return deferred.promise;
+    return Promise.all(promises).then(() => conference.calls);
   }
 
   /**
    * Hold the conference.
    *
-   * @param calls
+   * @param callsInConference
    *        An array of TelephonyCall objects existing in conference.
    * @param heldCallback [optional]
    *        A callback function which is called when conference state becomes
    *        held.
-   * @return A deferred promise.
+   * @return Promise<[TelephonyCall ...]>
    */
-  function holdConference(calls, heldCallback) {
+  function holdConference(callsInConference, heldCallback) {
     log("Holding the conference call.");
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve();
-    };
-
-    let pending = ["conference.onholding", "conference.onheld"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
+    let promises = [];
 
-    let check_onholding = StateEventChecker('holding', null);
-    let check_onheld = StateEventChecker('held', 'onholding');
-
-    for (let call of calls) {
-      let callName = "call (" + call.id.number + ')';
-
-      let onholding = callName + ".onholding";
-      pending.push(onholding);
-      check_onholding(call, callName, receive.bind(null, onholding));
-
-      let onheld = callName + ".onheld";
-      pending.push(onheld);
-      check_onheld(call, callName, receive.bind(null, onheld));
+    for (let call of callsInConference) {
+      let promise = waitForNamedStateEvent(call, "holding")
+        .then(() => waitForNamedStateEvent(call, "held"));
+      promises.push(promise);
     }
 
-    check_onholding(conference, "conference",
-                    receive.bind(null, "conference.onholding"));
-
-    check_onheld(conference, "conference", function() {
-      if (typeof heldCallback === 'function') {
-        heldCallback();
-      }
-      receive("conference.onheld");
-    });
+    let promise = waitForNamedStateEvent(conference, "holding")
+      .then(() => waitForNamedStateEvent(conference, "held"))
+      .then(() => {
+        if (typeof heldCallback === "function") {
+          heldCallback();
+        }
+      });
+    promises.push(promise);
 
     conference.hold();
 
-    return deferred.promise;
+    return Promise.all(promises).then(() => conference.calls);
   }
 
   /**
    * Resume the conference.
    *
-   * @param calls
+   * @param callsInConference
    *        An array of TelephonyCall objects existing in conference.
    * @param connectedCallback [optional]
    *        A callback function which is called when conference state becomes
    *        connected.
-   * @return A deferred promise.
+   * @return Promise<[TelephonyCall ...]>
    */
-  function resumeConference(calls, connectedCallback) {
+  function resumeConference(callsInConference, connectedCallback) {
     log("Resuming the held conference call.");
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve();
-    };
-
-    let pending = ["conference.onresuming", "conference.onconnected"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
+    let promises = [];
 
-    let check_onresuming   = StateEventChecker('resuming', null);
-    let check_onconnected  = StateEventChecker('connected', 'onresuming');
-
-    for (let call of calls) {
-      let callName = "call (" + call.id.number + ')';
-
-      let onresuming = callName + ".onresuming";
-      pending.push(onresuming);
-      check_onresuming(call, callName, receive.bind(null, onresuming));
-
-      let onconnected = callName + ".onconnected";
-      pending.push(onconnected);
-      check_onconnected(call, callName, receive.bind(null, onconnected));
+    for (let call of callsInConference) {
+      let promise = waitForNamedStateEvent(call, "resuming")
+        .then(() => waitForNamedStateEvent(call, "connected"));
+      promises.push(promise);
     }
 
-    check_onresuming(conference, "conference",
-                     receive.bind(null, "conference.onresuming"));
-
-    check_onconnected(conference, "conference", function() {
-      if (typeof connectedCallback === 'function') {
-        connectedCallback();
-      }
-      receive("conference.onconnected");
-    });
+    let promise = waitForNamedStateEvent(conference, "resuming")
+      .then(() => waitForNamedStateEvent(conference, "connected"))
+      .then(() => {
+        if (typeof connectedCallback === "function") {
+          connectedCallback();
+        }
+      });
+    promises.push(promise);
 
     conference.resume();
 
-    return deferred.promise;
+    return Promise.all(promises).then(() => conference.calls);
   }
 
   /**
    * Remove a call out of conference.
    *
    * @param callToRemove
    *        A TelephonyCall object existing in conference.
    * @param autoRemovedCalls
    *        An array of TelephonyCall objects which is going to be automatically
    *        removed. The length of the array should be 0 or 1.
    * @param remainedCalls
    *        An array of TelephonyCall objects which remain in conference.
    * @param stateChangeCallback [optional]
    *        A callback function which is called when conference state changes.
-   * @return A deferred promise.
+   * @return Promise<[TelephonyCall ...]>
    */
   function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls,
                                   statechangeCallback) {
     log("Removing a participant from the conference call.");
 
     is(conference.state, 'connected');
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve();
-    };
+    let promises = [];
 
-    let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
-                   "conference.oncallschanged", "conference.onstatechange"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
-
-    // Remained call in conference will be held.
-    for (let call of remainedCalls) {
-      let callName = "remainedCall (" + call.id.number + ')';
-
-      let onstatechange = callName + ".onstatechange";
-      pending.push(onstatechange);
-      check_onstatechange(call, callName, 'held',
-                          receive.bind(null, onstatechange));
-    }
+    // callToRemove.
+    promises.push(waitForCallsChangedEvent(telephony, callToRemove));
+    promises.push(waitForCallsChangedEvent(conference, callToRemove));
+    promises.push(waitForGroupChangeEvent(callToRemove, null).then(() => {
+      is(callToRemove.state, 'connected');
+    }));
 
     // When a call is removed from conference with 2 calls, another one will be
     // automatically removed from group and be put on hold.
     for (let call of autoRemovedCalls) {
-      let callName = "autoRemovedCall (" + call.id.number + ')';
-
-      let ongroupchange = callName + ".ongroupchange";
-      pending.push(ongroupchange);
-      check_ongroupchange(call, callName, null,
-                          receive.bind(null, ongroupchange));
-
-      let onstatechange = callName + ".onstatechange";
-      pending.push(onstatechange);
-      check_onstatechange(call, callName, 'held',
-                          receive.bind(null, onstatechange));
+      promises.push(waitForCallsChangedEvent(telephony, call));
+      promises.push(waitForCallsChangedEvent(conference, call));
+      promises.push(waitForGroupChangeEvent(call, null));
+      promises.push(waitForStateChangeEvent(call, "held"));
     }
 
-    check_ongroupchange(callToRemove, "callToRemove", null, function() {
-      is(callToRemove.state, 'connected');
-      receive("callToRemove.ongroupchange");
-    });
-
-    check_oncallschanged(telephony, 'telephony',
-                         autoRemovedCalls.concat(callToRemove),
-                         receive.bind(null, "telephony.oncallschanged"));
+    // Remained call in conference will be held.
+    for (let call of remainedCalls) {
+      promises.push(waitForStateChangeEvent(call, "held"));
+    }
 
-    check_oncallschanged(conference, 'conference',
-                         autoRemovedCalls.concat(callToRemove), function() {
-      is(conference.calls.length, remainedCalls.length);
-      receive("conference.oncallschanged");
-    });
-
-    check_onstatechange(conference, 'conference',
-                        (remainedCalls.length ? 'held' : ''), function() {
-      ok(!conference.oncallschanged);
-      if (typeof statechangeCallback === 'function') {
-        statechangeCallback();
-      }
-      receive("conference.onstatechange");
-    });
+    let finalConferenceState = remainedCalls.length ? "held" : "";
+    let promise = waitForStateChangeEvent(conference, finalConferenceState)
+      .then(() => {
+        if (typeof statechangeCallback === 'function') {
+          statechangeCallback();
+        }
+      });
+    promises.push(promise);
 
     conference.remove(callToRemove);
 
-    return deferred.promise;
+    return Promise.all(promises)
+      .then(() => checkCalls(conference.calls, remainedCalls))
+      .then(() => conference.calls);
   }
 
   /**
    * Hangup a call in conference.
    *
    * @param callToHangUp
    *        A TelephonyCall object existing in conference.
    * @param autoRemovedCalls
    *        An array of TelephonyCall objects which is going to be automatically
    *        removed. The length of the array should be 0 or 1.
    * @param remainedCalls
    *        An array of TelephonyCall objects which remain in conference.
    * @param stateChangeCallback [optional]
    *        A callback function which is called when conference state changes.
-   * @return A deferred promise.
+   * @return Promise<[TelephonyCall ...]>
    */
   function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls,
                                   statechangeCallback) {
     log("Release one call in conference.");
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve();
-    };
+    let promises = [];
 
-    let pending = ["conference.oncallschanged", "remoteHangUp"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
+    // callToHangUp.
+    promises.push(waitForCallsChangedEvent(conference, callToHangUp));
 
-    // When a call is hang up from conference with 2 calls, another one will be
+    // When a call is removed from conference with 2 calls, another one will be
     // automatically removed from group.
     for (let call of autoRemovedCalls) {
-      let callName = "autoRemovedCall (" + call.id.number + ')';
-
-      let ongroupchange = callName + ".ongroupchange";
-      pending.push(ongroupchange);
-      check_ongroupchange(call, callName, null,
-                          receive.bind(null, ongroupchange));
+      promises.push(waitForCallsChangedEvent(telephony, call));
+      promises.push(waitForCallsChangedEvent(conference, call));
+      promises.push(waitForGroupChangeEvent(call, null));
     }
 
-    if (autoRemovedCalls.length) {
-      pending.push("telephony.oncallschanged");
-      check_oncallschanged(telephony, 'telephony',
-                           autoRemovedCalls,
-                           receive.bind(null, "telephony.oncallschanged"));
+    if (remainedCalls.length === 0) {
+      let promise = waitForStateChangeEvent(conference, "")
+        .then(() => {
+          if (typeof statechangeCallback === 'function') {
+            statechangeCallback();
+          }
+        });
+      promises.push(promise);
     }
 
-    check_oncallschanged(conference, 'conference',
-                         autoRemovedCalls.concat(callToHangUp), function() {
-      is(conference.calls.length, remainedCalls.length);
-      receive("conference.oncallschanged");
-    });
+    promises.push(remoteHangUp(callToHangUp));
 
-    if (remainedCalls.length === 0) {
-      pending.push("conference.onstatechange");
-      check_onstatechange(conference, 'conference', '', function() {
-        ok(!conference.oncallschanged);
-        if (typeof statechangeCallback === 'function') {
-          statechangeCallback();
-        }
-        receive("conference.onstatechange");
-      });
-    }
-
-    remoteHangUp(callToHangUp)
-      .then(receive.bind(null, "remoteHangUp"));
-
-    return deferred.promise;
+    return Promise.all(promises)
+      .then(() => checkCalls(conference.calls, remainedCalls))
+      .then(() => conference.calls);
   }
 
   /**
    * Hangup conference.
    *
-   * @return A deferred promise.
+   * @return Promise
    */
   function hangUpConference() {
     log("Hangup conference.");
 
-    let deferred = Promise.defer();
-    let done = function() {
-      deferred.resolve();
-    };
+    let promises = [];
 
-    let pending = ["conference.hangUp", "conference.onstatechange"];
-    let receive = function(name) {
-      receivedPending(name, pending, done);
-    };
+    promises.push(waitForStateChangeEvent(conference, ""));
 
     for (let call of conference.calls) {
-      let callName = "Call (" + call.id.number + ')';
-
-      let onstatechange = callName + ".onstatechange";
-      pending.push(onstatechange);
-      check_onstatechange(call, callName, 'disconnected',
-                          receive.bind(null, onstatechange));
+      promises.push(waitForNamedStateEvent(call, "disconnected"));
     }
 
-    check_onstatechange(conference, 'conference', '', function() {
-      receive("conference.onstatechange");
+    return conference.hangUp().then(() => {
+      return Promise.all(promises);
     });
-
-    conference.hangUp().then(() => {
-      receive("conference.hangUp");
-    });
-
-    return deferred.promise;
   }
 
   /**
    * Create a conference with an outgoing call and an incoming call.
    *
    * @param outNumber
    *        Number of an outgoing call.
    * @param inNumber
@@ -1282,16 +1019,17 @@ let emulator = (function() {
     });
   }
 
   /**
    * Public members.
    */
 
   this.gDelay = delay;
+  this.gWaitForEvent = waitForEvent;
   this.gCheckInitialState = checkInitialState;
   this.gClearCalls = clearCalls;
   this.gOutCallStrPool = outCallStrPool;
   this.gInCallStrPool = inCallStrPool;
   this.gCheckState = checkState;
   this.gCheckAll = checkAll;
   this.gSendMMI = sendMMI;
   this.gDial = dial;
@@ -1306,17 +1044,16 @@ let emulator = (function() {
   this.gRemoteHangUpCalls = remoteHangUpCalls;
   this.gAddCallsToConference = addCallsToConference;
   this.gHoldConference = holdConference;
   this.gResumeConference = resumeConference;
   this.gRemoveCallInConference = removeCallInConference;
   this.gHangUpCallInConference = hangUpCallInConference;
   this.gHangUpConference = hangUpConference;
   this.gSetupConference = setupConference;
-  this.gReceivedPending = receivedPending;
 }());
 
 function _startTest(permissions, test) {
   function permissionSetUp() {
     SpecialPowers.setBoolPref("dom.mozSettings.enabled", true);
     for (let per of permissions) {
       SpecialPowers.addPermission(per, true, document);
     }
@@ -1366,28 +1103,21 @@ function _startTest(permissions, test) {
         .then(function() {
           originalFinish.apply(this, arguments);
         });
     }
 
     return tearDown.bind(this);
   }());
 
-  function mainTest() {
-    setUp()
-      .then(function onSuccess() {
-        log("== Test Start ==");
-        test();
-      }, function onError(error) {
-        SpecialPowers.Cu.reportError(error);
-        ok(false, "SetUp error");
-      });
-  }
-
-  mainTest();
+  setUp().then(() => {
+    log("== Test Start ==");
+    test();
+  })
+  .catch(error => ok(false, error));
 }
 
 function startTest(test) {
   _startTest(["telephony"], test);
 }
 
 function startTestWithPermissions(permissions, test) {
   _startTest(permissions.concat("telephony"), test);
--- a/dom/telephony/test/marionette/test_audiomanager_phonestate.js
+++ b/dom/telephony/test/marionette/test_audiomanager_phonestate.js
@@ -84,13 +84,11 @@ startTest(function() {
     // Conference
     .then(() => gAddCallsToConference([outCall, inCall]))
     .then(() => check(PHONE_STATE_IN_CALL))
     // Hang up all
     .then(() => gRemoteHangUpCalls([outCall, inCall]))
     .then(() => check(PHONE_STATE_NORMAL))
 
     // End
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_add_error.js
+++ b/dom/telephony/test/marionette/test_conference_add_error.js
@@ -59,13 +59,11 @@ function testConferenceAddError() {
     .then(() => handleConferenceAddError(inCall5))
     .then(() => gRemoteHangUpCalls([outCall, inCall, inCall2, inCall3, inCall4,
                                     inCall5]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceAddError()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_add_twice_error.js
+++ b/dom/telephony/test/marionette/test_conference_add_twice_error.js
@@ -36,13 +36,11 @@ function testConferenceTwoCallsTwice() {
     .then(() => gCheckAll(conference, [], 'connected', [outCall, inCall],
                           [outInfo.active, inInfo.active]))
     .then(() => gRemoteHangUpCalls([outCall, inCall]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceTwoCallsTwice()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_remove_error.js
+++ b/dom/telephony/test/marionette/test_conference_remove_error.js
@@ -56,13 +56,11 @@ function testConferenceRemoveError() {
     // calls.
     .then(() => handleConferenceRemoveError(outCall))
     .then(() => gRemoteHangUpCalls([outCall, inCall, inCall2]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceRemoveError()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_three_hangup_one.js
+++ b/dom/telephony/test/marionette/test_conference_three_hangup_one.js
@@ -25,13 +25,11 @@ function testConferenceThreeAndHangupOne
     .then(() => gCheckAll(conference, [], 'connected', [inCall, inCall2],
                           [inInfo.active, inInfo2.active]))
     .then(() => gRemoteHangUpCalls([inCall, inCall2]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceThreeAndHangupOne()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_three_remove_one.js
+++ b/dom/telephony/test/marionette/test_conference_three_remove_one.js
@@ -29,13 +29,11 @@ function testConferenceThreeAndRemoveOne
     .then(() => gCheckAll(outCall, [outCall], 'held', [inCall, inCall2],
                           [outInfo.active, inInfo.held, inInfo2.held]))
     .then(() => gRemoteHangUpCalls([outCall, inCall, inCall2]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceThreeAndRemoveOne()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_two_calls.js
+++ b/dom/telephony/test/marionette/test_conference_two_calls.js
@@ -18,13 +18,11 @@ function testConferenceTwoCalls() {
       [outCall, inCall] = calls;
     })
     .then(() => gRemoteHangUpCalls([outCall, inCall]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceTwoCalls()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_two_hangup_all.js
+++ b/dom/telephony/test/marionette/test_conference_two_hangup_all.js
@@ -44,13 +44,11 @@ function testConferenceHangUpBackground(
     .then(() => gHangUpConference())
     .then(() => gCheckAll(null, [], '', [], []));
 }
 
 // Start the test
 startTest(function() {
   testConferenceHangUpForeground()
     .then(() => testConferenceHangUpBackground())
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_two_hangup_one.js
+++ b/dom/telephony/test/marionette/test_conference_two_hangup_one.js
@@ -23,13 +23,11 @@ function testConferenceTwoAndHangupOne()
     }))
     .then(() => gCheckAll(inCall, [inCall], '', [], [inInfo.active]))
     .then(() => gRemoteHangUpCalls([inCall]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceTwoAndHangupOne()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_two_hold_resume.js
+++ b/dom/telephony/test/marionette/test_conference_two_hold_resume.js
@@ -30,13 +30,11 @@ function testConferenceHoldAndResume() {
     .then(() => gCheckAll(conference, [], 'connected', [outCall, inCall],
                           [outInfo.active, inInfo.active]))
     .then(() => gRemoteHangUpCalls([outCall, inCall]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceHoldAndResume()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_conference_two_remove_one.js
+++ b/dom/telephony/test/marionette/test_conference_two_remove_one.js
@@ -25,13 +25,11 @@ function testConferenceTwoAndRemoveOne()
     .then(() => gCheckAll(outCall, [outCall, inCall], '', [],
                           [outInfo.active, inInfo.held]))
     .then(() => gRemoteHangUpCalls([outCall, inCall]));
 }
 
 // Start the test
 startTest(function() {
   testConferenceTwoAndRemoveOne()
-    .then(null, error => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_dsds_connection_conflict.js
+++ b/dom/telephony/test/marionette/test_dsds_connection_conflict.js
@@ -41,13 +41,11 @@ function testNewCallWhenOtherConnectionI
     })
     .then(() => gRemoteHangUp(outCall));
 }
 
 startDSDSTest(function() {
   testNewCallWhenOtherConnectionInUse(0, 1)
     .then(() => testNewCallWhenOtherConnectionInUse(1, 0))
     .then(() => muxModem(0))
-    .then(null, () => {
-      ok(false, "promise rejects during test.");
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_dsds_normal_call.js
+++ b/dom/telephony/test/marionette/test_dsds_normal_call.js
@@ -68,13 +68,11 @@ function testIncomingCall() {
     .then(() => muxModem(1))
     .then(() => testIncomingCallForServiceId("0912345001", 1))
     .then(() => muxModem(0));
 }
 
 startDSDSTest(function() {
   testOutgoingCall()
     .then(testIncomingCall)
-    .then(null, () => {
-      ok(false, "promise rejects during test.");
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_emergency_label.js
+++ b/dom/telephony/test/marionette/test_emergency_label.js
@@ -73,13 +73,11 @@ startTest(function() {
     .then(() => {
       eccList = "777,119";
       return setEccListProperty(eccList);
     })
     .then(() => testEmergencyLabel("777", eccList))
     .then(() => testEmergencyLabel("119", eccList))
     .then(() => testEmergencyLabel("112", eccList))
     .then(() => setEccListProperty(origEccList))
-    .then(null, error => {
-      ok(false, 'promise rejects during test: ' + error);
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_mmi.js
+++ b/dom/telephony/test/marionette/test_mmi.js
@@ -16,13 +16,11 @@ function testGettingIMEI() {
     is(aResult.statusMessage, "000000000000000", "Emulator IMEI");
     is(aResult.additionalInformation, undefined, "No additional information");
   });
 }
 
 // Start test
 startTest(function() {
   testGettingIMEI()
-    .then(null, cause => {
-      ok(false, 'promise rejects during test: ' + cause);
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_mmi_call_forwarding.js
+++ b/dom/telephony/test/marionette/test_mmi_call_forwarding.js
@@ -180,13 +180,11 @@ startTestWithPermissions(['mobileconnect
   for (let i = 0; i < TEST_DATA.length; i++) {
     let data = TEST_DATA[i];
     promise = promise.then(() => testSetCallForwarding(data))
                      .then(() => testGetCallForwarding(data));
   }
 
   // reset call forwarding settings.
   return promise.then(() => clearAllCallForwardingSettings())
-    .then(null, cause => {
-      ok(false, 'promise rejects during test: ' + cause);
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_mmi_change_pin.js
+++ b/dom/telephony/test/marionette/test_mmi_change_pin.js
@@ -103,13 +103,11 @@ startTest(function() {
     let data = TEST_DATA[i];
     promise = promise.then(() => testChangePin(data.pin,
                                                data.newPin,
                                                data.newPinAgain,
                                                data.expectedError));
   }
 
   return promise
-    .then(null, cause => {
-      ok(false, 'promise rejects during test: ' + cause);
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_outgoing_answer_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_answer_radio_off.js
@@ -93,13 +93,11 @@ function testOutgoingCallRadioOff() {
     .then(call => remoteAnswer(call))
     .then(call => disableRadioAndWaitForCallEvent(call))
     .then(() => setRadioEnabled(true));
 }
 
 // Start test
 startTestWithPermissions(['mobileconnection'], function() {
   testOutgoingCallRadioOff()
-    .then(null, () => {
-      ok(false, "promise rejects during test.");
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_outgoing_auto_hold.js
+++ b/dom/telephony/test/marionette/test_outgoing_auto_hold.js
@@ -40,13 +40,11 @@ function testAutoHoldConferenceCall() {
       is(conference.state, "held");
     })
     .then(() => gRemoteHangUpCalls([subCall1, subCall2, outCall]));
 }
 
 startTest(function() {
   testAutoHoldCall()
     .then(() => testAutoHoldConferenceCall())
-    .then(null, () => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
+++ b/dom/telephony/test/marionette/test_outgoing_emergency_in_airplane_mode.js
@@ -31,13 +31,11 @@ function setRadioEnabled(enabled) {
 startTestWithPermissions(['mobileconnection'], function() {
   let outCall;
   setRadioEnabled(false)
     .then(() => gDial("112"))
     .then(call => { outCall = call; })
     .then(() => gRemoteAnswer(outCall))
     .then(() => gDelay(1000))  // See Bug 1018051 for the purpose of the delay.
     .then(() => gRemoteHangUp(outCall))
-    .then(null, () => {
-      ok(false, "promise rejects during test.");
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_outgoing_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_radio_off.js
@@ -1,70 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
 MARIONETTE_HEAD_JS = 'head.js';
 
 let connection;
 
-function setRadioEnabled(enabled, callback) {
-  let request  = connection.setRadioEnabled(enabled);
+function setRadioEnabled(enabled) {
   let desiredRadioState = enabled ? 'enabled' : 'disabled';
-
-  let pending = ['onradiostatechange', 'onsuccess'];
-  let done = callback;
-
-  connection.onradiostatechange = function() {
-    let state = connection.radioState;
-    log("Received 'radiostatechange' event, radioState: " + state);
+  log("Set radio: " + desiredRadioState);
 
-    if (state == desiredRadioState) {
-      gReceivedPending('onradiostatechange', pending, done);
-    }
-  };
-
-  request.onsuccess = function onsuccess() {
-    gReceivedPending('onsuccess', pending, done);
-  };
-
-  request.onerror = function onerror() {
-    ok(false, "setRadioEnabled should be ok");
-  };
-}
+  let promises = [];
 
-function dial(number) {
-  // Verify initial state before dial.
-  ok(telephony);
-  is(telephony.active, null);
-  ok(telephony.calls);
-  is(telephony.calls.length, 0);
-
-  log("Make an outgoing call.");
-
-  telephony.dial(number).then(null, cause => {
-    log("Received promise 'reject'");
+  let promise = gWaitForEvent(connection, "radiostatechange", event => {
+    let state = connection.radioState;
+    log("current radioState: " + state);
+    return state == desiredRadioState;
+  });
+  promises.push(promise);
 
-    is(telephony.active, null);
-    is(telephony.calls.length, 0);
-    is(cause, "RadioNotAvailable");
-
-    emulator.runCmdWithCallback("gsm list", function(result) {
-      log("Initial call list: " + result);
+  promises.push(connection.setRadioEnabled(enabled));
 
-      setRadioEnabled(true, cleanUp);
-    });
-  });
-}
-
-function cleanUp() {
-  finish();
+  return Promise.all(promises);
 }
 
 startTestWithPermissions(['mobileconnection'], function() {
   connection = navigator.mozMobileConnections[0];
   ok(connection instanceof MozMobileConnection,
      "connection is instanceof " + connection.constructor);
 
-  setRadioEnabled(false, function() {
-    dial("0912345678");
-  });
+  setRadioEnabled(false)
+    .then(() => gDial("0912345678"))
+    .catch(cause => {
+      is(telephony.active, null);
+      is(telephony.calls.length, 0);
+      is(cause, "RadioNotAvailable");
+    })
+    .then(() => setRadioEnabled(true))
+    .catch(error => ok(false, "Promise reject: " + error))
+    .then(finish);
 });
--- a/dom/telephony/test/marionette/test_outgoing_when_two_calls_on_line.js
+++ b/dom/telephony/test/marionette/test_outgoing_when_two_calls_on_line.js
@@ -22,13 +22,11 @@ function testReject3rdCall() {
       log("Reject 3rd call, cuase: " + cause);
       is(cause, "InvalidStateError");
     })
     .then(() => gRemoteHangUpCalls([outCall1, outCall2]));
 }
 
 startTest(function() {
   testReject3rdCall()
-    .then(null, () => {
-      ok(false, 'promise rejects during test.');
-    })
+    .catch(error => ok(false, "Promise reject: " + error))
     .then(finish);
 });
--- a/dom/telephony/test/marionette/test_temporary_clir.js
+++ b/dom/telephony/test/marionette/test_temporary_clir.js
@@ -24,14 +24,11 @@ startTest(function() {
       log("Received 'onalerting' call event.");
       is(call.id.number, number);
       deferred.resolve(call);
     };
 
     return deferred.promise;
   })
   .then(() => gRemoteHangUp(outCall))
-  // End
-  .then(null, error => {
-    ok(false, 'promise rejects during test.');
-  })
+  .catch(error => ok(false, "Promise reject: " + error))
   .then(finish);
 });
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1897,32 +1897,38 @@ gfxFont::Draw(gfxTextRun *aTextRun, uint
     bool sideways = false;
     gfxPoint origPt = *aPt;
     if (aRunParams.isVerticalRun && !fontParams.isVerticalFont) {
         sideways = true;
         aRunParams.context->Save();
         gfxPoint p(aPt->x * aRunParams.devPerApp,
                    aPt->y * aRunParams.devPerApp);
         const Metrics& metrics = GetMetrics(eHorizontal);
-        // Adjust the matrix to draw the (horizontally-shaped) textrun with
-        // 90-degree CW rotation, and adjust position so that the rotated
-        // horizontal text (which uses a standard alphabetic baseline) will
+        // Get a matrix we can use to draw the (horizontally-shaped) textrun
+        // with 90-degree CW rotation.
+        gfxMatrix mat = aRunParams.context->CurrentMatrix().
+            Translate(p).       // translate origin for rotation
+            Rotate(M_PI / 2.0). // turn 90deg clockwise
+            Translate(-p);      // undo the translation
+
+        // If we're drawing rotated horizontal text for an element styled
+        // text-orientation:mixed, the dominant baseline will be vertical-
+        // centered. So in this case, we need to adjust the position so that
+        // the rotated horizontal text (which uses an alphabetic baseline) will
         // look OK when juxtaposed with upright glyphs (rendered on a centered
         // vertical baseline). The adjustment here is somewhat ad hoc; we
         // should eventually look for baseline tables[1] in the fonts and use
         // those if available.
-        // [1] http://www.microsoft.com/typography/otspec/base.htm
-        aRunParams.context->SetMatrix(aRunParams.context->CurrentMatrix().
-            Translate(p).       // translate origin for rotation
-            Rotate(M_PI / 2.0). // turn 90deg clockwise
-            Translate(-p).      // undo the translation
-            Translate(gfxPoint(0, (metrics.emAscent - metrics.emDescent) / 2)));
-                                // and offset the (alphabetic) baseline of the
-                                // horizontally-shaped text from the (centered)
-                                // default baseline used for vertical
+        // [1] See http://www.microsoft.com/typography/otspec/base.htm
+        if (aTextRun->UseCenterBaseline()) {
+            gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
+            mat.Translate(baseAdj);
+        }
+
+        aRunParams.context->SetMatrix(mat);
     }
 
     nsAutoPtr<gfxTextContextPaint> contextPaint;
     if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
         // If no pattern is specified for fill, use the current pattern
         NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
                      "no pattern supplied for stroking text");
         nsRefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
@@ -2133,25 +2139,43 @@ gfxFont::Measure(gfxTextRun *aTextRun,
                                        aRefContext, aSpacing, aOrientation);
         }
     }
 
     const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
     // Current position in appunits
     gfxFont::Orientation orientation =
         aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT
-        ? gfxFont::eVertical : gfxFont::eHorizontal;
+        ? eVertical : eHorizontal;
     const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
 
+    gfxFloat baselineOffset = 0;
+    if (aTextRun->UseCenterBaseline() && orientation == eHorizontal) {
+        // For a horizontal font being used in vertical writing mode with
+        // text-orientation:mixed, the overall metrics we're accumulating
+        // will be aimed at a center baseline. But this font's metrics were
+        // based on the alphabetic baseline. So we compute a baseline offset
+        // that will be applied to ascent/descent values and glyph rects
+        // to effectively shift them relative to the baseline.
+        // XXX Eventually we should probably use the BASE table, if present.
+        // But it usually isn't, so we need an ad hoc adjustment for now.
+        baselineOffset = appUnitsPerDevUnit *
+            (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
+    }
+
     RunMetrics metrics;
-    metrics.mAscent = fontMetrics.maxAscent*appUnitsPerDevUnit;
-    metrics.mDescent = fontMetrics.maxDescent*appUnitsPerDevUnit;
+    metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
+    metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
+
     if (aStart == aEnd) {
         // exit now before we look at aSpacing[0], which is undefined
-        metrics.mBoundingBox = gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent);
+        metrics.mAscent -= baselineOffset;
+        metrics.mDescent += baselineOffset;
+        metrics.mBoundingBox = gfxRect(0, -metrics.mAscent,
+                                       0, metrics.mAscent + metrics.mDescent);
         return metrics;
     }
 
     gfxFloat advanceMin = 0, advanceMax = 0;
     const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
     bool isRTL = aTextRun->IsRightToLeft();
     double direction = aTextRun->GetDirection();
     bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
@@ -2242,16 +2266,22 @@ gfxFont::Measure(gfxTextRun *aTextRun,
         gfxRect fontBox(advanceMin, -metrics.mAscent,
                         advanceMax - advanceMin, metrics.mAscent + metrics.mDescent);
         metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
     }
     if (isRTL) {
         metrics.mBoundingBox -= gfxPoint(x, 0);
     }
 
+    if (baselineOffset != 0) {
+        metrics.mAscent -= baselineOffset;
+        metrics.mDescent += baselineOffset;
+        metrics.mBoundingBox.y += baselineOffset;
+    }
+
     metrics.mAdvanceWidth = x*direction;
     return metrics;
 }
 
 static PLDHashOperator
 NotifyGlyphChangeObservers(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey,
                            void* aClosure)
 {
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -940,16 +940,22 @@ public:
         return mFlags;
     }
 
     bool IsVertical() const {
         return (GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK) !=
                 gfxTextRunFactory::TEXT_ORIENT_HORIZONTAL;
     }
 
+    bool UseCenterBaseline() const {
+        uint32_t orient = GetFlags() & gfxTextRunFactory::TEXT_ORIENT_MASK;
+        return orient == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED ||
+               orient == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+    }
+
     bool IsRightToLeft() const {
         return (GetFlags() & gfxTextRunFactory::TEXT_IS_RTL) != 0;
     }
 
     gfxFloat GetDirection() const {
         return IsRightToLeft() ? -1.0f : 1.0f;
     }
 
--- a/hal/Hal.cpp
+++ b/hal/Hal.cpp
@@ -1067,29 +1067,27 @@ EnableFMRadio(const FMRadioSettings& aIn
 void
 DisableFMRadio() {
   AssertMainThread();
   PROXY_IF_SANDBOXED(DisableFMRadio());
 }
 
 void
 FMRadioSeek(const FMRadioSeekDirection& aDirection) {
-  AssertMainThread();
   PROXY_IF_SANDBOXED(FMRadioSeek(aDirection));
 }
 
 void
 GetFMRadioSettings(FMRadioSettings* aInfo) {
   AssertMainThread();
   PROXY_IF_SANDBOXED(GetFMRadioSettings(aInfo));
 }
 
 void
 SetFMRadioFrequency(const uint32_t aFrequency) {
-  AssertMainThread();
   PROXY_IF_SANDBOXED(SetFMRadioFrequency(aFrequency));
 }
 
 uint32_t
 GetFMRadioFrequency() {
   AssertMainThread();
   RETURN_PROXY_IF_SANDBOXED(GetFMRadioFrequency(), 0);
 }
--- a/js/src/jit/shared/Assembler-x86-shared.cpp
+++ b/js/src/jit/shared/Assembler-x86-shared.cpp
@@ -156,17 +156,21 @@ CPUInfo::SetSSEVersion()
          "movl $0x1, %%eax;"
          "cpuid;"
          : "=c" (flagsECX), "=d" (flagsEDX)
          :
          : "%eax", "%ebx"
          );
 # else
     // On x86, preserve ebx. The compiler needs it for PIC mode.
+    // Some older processors don't fill the ecx register with cpuid, so clobber
+    // it before calling cpuid, so that there's no risk of picking random bits
+    // indicating SSE3/SSE4 are present.
     asm (
+         "xor %%ecx, %%ecx;"
          "movl $0x1, %%eax;"
          "pushl %%ebx;"
          "cpuid;"
          "popl %%ebx;"
          : "=c" (flagsECX), "=d" (flagsEDX)
          :
          : "%eax"
          );
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -1448,19 +1448,22 @@ RestyleManager::RebuildAllStyleData(nsCh
   MOZ_ASSERT(!mIsProcessingRestyles, "Nesting calls to processing restyles");
 #ifdef DEBUG
   mIsProcessingRestyles = true;
 #endif
 
   // Until we get rid of these phases in bug 960465, we need to skip
   // animation restyles during the non-animation phase, and post
   // animation restyles so that we restyle those elements again in the
-  // animation phase.
+  // animation phase.  Furthermore, we need to add
+  // eRestyle_ChangeAnimationPhaseDescendants so that we actually honor
+  // these booleans in all cases.
   mSkipAnimationRules = true;
   mPostAnimationRestyles = true;
+  aRestyleHint |= eRestyle_ChangeAnimationPhaseDescendants;
 
   DoRebuildAllStyleData(mPendingRestyles, aExtraHint, aRestyleHint);
 
   mPostAnimationRestyles = false;
   mSkipAnimationRules = false;
 #ifdef DEBUG
   mIsProcessingRestyles = false;
 #endif
@@ -1490,18 +1493,22 @@ RestyleManager::DoRebuildAllStyleData(Re
       (aRestyleHint & ~(eRestyle_Force | eRestyle_ForceDescendants))) {
     // We want this hint to apply to the root node's primary frame
     // rather than the root frame, since it's the primary frame that has
     // the styles for the root element (rather than the ancestors of the
     // primary frame whose mContent is the root node but which have
     // different styles).  If we use up the hint for one of the
     // ancestors that we hit first, then we'll fail to do the restyling
     // we need to do.
-    aRestyleTracker.AddPendingRestyle(mPresContext->Document()->GetRootElement(),
-                                      aRestyleHint, nsChangeHint(0));
+    Element* root = mPresContext->Document()->GetRootElement();
+    if (root) {
+      // If the root element is gone, dropping the hint on the floor
+      // should be fine.
+      aRestyleTracker.AddPendingRestyle(root, aRestyleHint, nsChangeHint(0));
+    }
     aRestyleHint = nsRestyleHint(0);
   }
 
   // Recalculate all of the style contexts for the document
   // Note that we can ignore the return value of ComputeStyleChangeFor
   // because we never need to reframe the root frame
   // XXX This could be made faster by not rerunning rule matching
   // (but note that nsPresShell::SetPreferenceStyleRules currently depends
@@ -2510,21 +2517,23 @@ ElementRestyler::Restyle(nsRestyleHint a
         mChangeList->AppendChange(mFrame, mContent, restyleData->mChangeHint);
       }
       hintToRestore = restyleData->mRestyleHint;
       aRestyleHint = nsRestyleHint(aRestyleHint | restyleData->mRestyleHint);
       descendants.SwapElements(restyleData->mDescendants);
     }
   }
 
-  // If we are restyling this frame with eRestyle_Self, we restyle
-  // children with nsRestyleHint(0).  But we pass the eRestyle_ForceDescendants
-  // flag down too.
+  // If we are restyling this frame with eRestyle_Self or weaker hints,
+  // we restyle children with nsRestyleHint(0).  But we pass the
+  // eRestyle_ChangeAnimationPhaseDescendants and eRestyle_ForceDescendants
+  // flags down too.
   nsRestyleHint childRestyleHint =
     nsRestyleHint(aRestyleHint & (eRestyle_Subtree |
+                                  eRestyle_ChangeAnimationPhaseDescendants |
                                   eRestyle_ForceDescendants));
 
   nsRefPtr<nsStyleContext> oldContext = mFrame->StyleContext();
 
   // TEMPORARY (until bug 918064):  Call RestyleSelf for each
   // continuation or block-in-inline sibling.
 
   // We must make a single decision on how to process this frame and
@@ -3731,17 +3740,19 @@ RestyleManager::StructsToLog()
 #ifdef DEBUG
 /* static */ nsCString
 RestyleManager::RestyleHintToString(nsRestyleHint aHint)
 {
   nsCString result;
   bool any = false;
   const char* names[] = { "Self", "Subtree", "LaterSiblings", "CSSTransitions",
                           "CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
-                          "ChangeAnimationPhase", "Force", "ForceDescendants" };
+                          "ChangeAnimationPhase",
+                          "ChangeAnimationPhaseDescendants",
+                          "Force", "ForceDescendants" };
   uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
   uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
   for (uint32_t i = 0; i < ArrayLength(names); i++) {
     if (hint & (1 << i)) {
       if (any) {
         result.AppendLiteral(" | ");
       }
       result.AppendPrintf("eRestyle_%s", names[i]);
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -4157,18 +4157,22 @@ nsCSSRendering::PaintDecorationLine(nsIF
         drawOptions.mAntialiasMode = AntialiasMode::NONE;
       }
       break;
     default:
       NS_ERROR("Invalid style value!");
       return;
   }
 
-  // The y position should be set to the middle of the line.
-  rect.y += lineThickness / 2;
+  // The block-direction position should be set to the middle of the line.
+  if (aVertical) {
+    rect.x -= lineThickness / 2;
+  } else {
+    rect.y += lineThickness / 2;
+  }
 
   switch (aStyle) {
     case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
     case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
     case NS_STYLE_TEXT_DECORATION_STYLE_DASHED: {
       Point p1 = rect.TopLeft();
       Point p2 = aVertical ? rect.BottomLeft() : rect.TopRight();
       aDrawTarget.StrokeLine(p1, p2, color, strokeOptions, drawOptions);
@@ -4355,22 +4359,28 @@ nsCSSRendering::DecorationLineToPath(nsI
 
   if (aStyle != NS_STYLE_TEXT_DECORATION_STYLE_SOLID) {
     // For the moment, we support only solid text decorations.
     return;
   }
 
   gfxFloat lineThickness = std::max(NS_round(aLineSize.height), 1.0);
 
-  // The y position should be set to the middle of the line.
-  rect.y += lineThickness / 2;
-
-  aGfxContext->Rectangle
-    (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
-             gfxSize(rect.Width(), lineThickness)));
+  // The block-direction position should be set to the middle of the line.
+  if (aVertical) {
+    rect.x -= lineThickness / 2;
+    aGfxContext->Rectangle
+      (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(lineThickness / 2, 0.0)),
+               gfxSize(lineThickness, rect.Height())));
+  } else {
+    rect.y += lineThickness / 2;
+    aGfxContext->Rectangle
+      (gfxRect(gfxPoint(rect.TopLeft() - gfxPoint(0.0, lineThickness / 2)),
+               gfxSize(rect.Width(), lineThickness)));
+  }
 }
 
 nsRect
 nsCSSRendering::GetTextDecorationRect(nsPresContext* aPresContext,
                                       const gfxSize& aLineSize,
                                       const gfxFloat aAscent,
                                       const gfxFloat aOffset,
                                       const uint8_t aDecoration,
--- a/layout/base/nsCaret.cpp
+++ b/layout/base/nsCaret.cpp
@@ -304,19 +304,24 @@ nsCaret::GetGeometryForFrame(nsIFrame* a
   nsLayoutUtils::GetFontMetricsForFrame(aFrame, getter_AddRefs(fm),
     nsLayoutUtils::FontSizeInflationFor(aFrame));
   NS_ASSERTION(fm, "We should be able to get the font metrics");
   if (fm) {
     ascent = fm->MaxAscent();
     descent = fm->MaxDescent();
   }
   nscoord height = ascent + descent;
-  bool vertical = aFrame->GetWritingMode().IsVertical();
+  WritingMode wm = aFrame->GetWritingMode();
+  bool vertical = wm.IsVertical();
   if (vertical) {
-    framePos.x = baseline - ascent;
+    if (wm.IsLineInverted()) {
+      framePos.x = baseline - descent;
+    } else {
+      framePos.x = baseline - ascent;
+    }
   } else {
     framePos.y = baseline - ascent;
   }
   Metrics caretMetrics = ComputeMetrics(aFrame, aFrameOffset, height);
   rect = nsRect(framePos, vertical ? nsSize(height, caretMetrics.mCaretWidth) :
                                      nsSize(caretMetrics.mCaretWidth, height));
 
   // Clamp the inline-position to be within our scroll frame. If we don't, then
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -341,26 +341,30 @@ enum nsRestyleHint {
   // indicates that along with the replacement given, appropriate
   // switching between the style with animation and style without
   // animation should be performed by adding or removing rules that
   // should be present only in the style with animation.
   // This is implied by eRestyle_Self or eRestyle_Subtree.
   // FIXME: Remove this as part of bug 960465.
   eRestyle_ChangeAnimationPhase = (1 << 7),
 
+  // Same as the previous, except this applies to the entire subtree.
+  // FIXME: Remove this as part of bug 960465.
+  eRestyle_ChangeAnimationPhaseDescendants = (1 << 8),
+
   // Continue the restyling process to the current frame's children even
   // if this frame's restyling resulted in no style changes.
-  eRestyle_Force = (1<<8),
+  eRestyle_Force = (1<<9),
 
   // Continue the restyling process to all of the current frame's
   // descendants, even if any frame's restyling resulted in no style
   // changes.  (Implies eRestyle_Force.)  Note that this is weaker than
   // eRestyle_Subtree, which makes us rerun selector matching on all
   // descendants rather than just continuing the restyling process.
-  eRestyle_ForceDescendants = (1<<9),
+  eRestyle_ForceDescendants = (1<<10),
 };
 
 // The functions below need an integral type to cast to to avoid
 // infinite recursion.
 typedef decltype(nsRestyleHint(0) + nsRestyleHint(0)) nsRestyleHint_size_t;
 
 inline nsRestyleHint operator|(nsRestyleHint aLeft, nsRestyleHint aRight)
 {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3463,20 +3463,21 @@ nsLayoutUtils::GetFontMetricsForStyleCon
 
   nsFont font = aStyleContext->StyleFont()->mFont;
   // We need to not run font.size through floats when it's large since
   // doing so would be lossy.  Fortunately, in such cases, aInflation is
   // guaranteed to be 1.0f.
   if (aInflation != 1.0f) {
     font.size = NSToCoordRound(font.size * aInflation);
   }
-  WritingMode wm(aStyleContext->StyleVisibility());
+  WritingMode wm(aStyleContext);
   return pc->DeviceContext()->GetMetricsFor(
                   font, aStyleContext->StyleFont()->mLanguage,
-                  wm.IsVertical() ? gfxFont::eVertical : gfxFont::eHorizontal,
+                  wm.IsVertical() && !wm.IsSideways()
+                    ? gfxFont::eVertical : gfxFont::eHorizontal,
                   fs, tp, *aFontMetrics);
 }
 
 nsIFrame*
 nsLayoutUtils::FindChildContainingDescendant(nsIFrame* aParent, nsIFrame* aDescendantFrame)
 {
   nsIFrame* result = aDescendantFrame;
 
@@ -4863,19 +4864,21 @@ nsLayoutUtils::PaintTextShadow(const nsI
 
     contextBoxBlur.DoPaint();
     aDestCtx->Restore();
   }
 }
 
 /* static */ nscoord
 nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
-                                       nscoord         aLineHeight)
-{
-  nscoord fontAscent = aFontMetrics->MaxAscent();
+                                       nscoord        aLineHeight,
+                                       bool           aIsInverted)
+{
+  nscoord fontAscent = aIsInverted ? aFontMetrics->MaxDescent()
+                                   : aFontMetrics->MaxAscent();
   nscoord fontHeight = aFontMetrics->MaxHeight();
 
   nscoord leading = aLineHeight - fontHeight;
   return fontAscent + leading/2;
 }
 
 
 /* static */ bool
@@ -5841,17 +5844,17 @@ nsLayoutUtils::GetTextRunFlagsForStyle(n
     if (aStyleFont->mFont.size <
         aStyleContext->PresContext()->GetAutoQualityMinFontSize()) {
       result |= gfxTextRunFactory::TEXT_OPTIMIZE_SPEED;
     }
     break;
   default:
     break;
   }
-  WritingMode wm(aStyleContext->StyleVisibility());
+  WritingMode wm(aStyleContext);
   if (wm.IsVertical()) {
     switch (aStyleText->mTextOrientation) {
     case NS_STYLE_TEXT_ORIENTATION_MIXED:
       result |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED;
       break;
     case NS_STYLE_TEXT_ORIENTATION_UPRIGHT:
       result |= gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
       break;
@@ -7318,17 +7321,18 @@ nsLayoutUtils::SetBSizeFromFontMetrics(c
     // Do things the standard css2 way -- though it's hard to find it
     // in the css2 spec! It's actually found in the css1 spec section
     // 4.4 (you will have to read between the lines to really see
     // it).
     //
     // The height of our box is the sum of our font size plus the top
     // and bottom border and padding. The height of children do not
     // affect our height.
-    aMetrics.SetBlockStartAscent(fm->MaxAscent());
+    aMetrics.SetBlockStartAscent(aLineWM.IsLineInverted() ? fm->MaxDescent()
+                                                          : fm->MaxAscent());
     aMetrics.BSize(aLineWM) = fm->MaxHeight();
   } else {
     NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
     aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0);
   }
   aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
                                aFramePadding.BStart(aFrameWM));
   aMetrics.BSize(aLineWM) +=
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1386,21 +1386,25 @@ public:
                               const nsRect&       aDirtyRect,
                               const nscolor&      aForegroundColor,
                               TextShadowCallback  aCallback,
                               void*               aCallbackData);
 
   /**
    * Gets the baseline to vertically center text from a font within a
    * line of specified height.
+   * aIsInverted: true if the text is inverted relative to the block
+   * direction, so that the block-dir "ascent" corresponds to font
+   * descent. (Applies to sideways text in vertical-lr mode.)
    *
    * Returns the baseline position relative to the top of the line.
    */
   static nscoord GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
-                                         nscoord         aLineHeight);
+                                         nscoord        aLineHeight,
+                                         bool           aIsInverted);
 
   /**
    * Derive a baseline of |aFrame| (measured from its top border edge)
    * from its first in-flow line box (not descending into anything with
    * 'overflow' not 'visible', potentially including aFrame itself).
    *
    * Returns true if a baseline was found (and fills in aResult).
    * Otherwise returns false.
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -514,17 +514,18 @@ nsTextControlFrame::Reflow(nsPresContext
     lineHeight = nsHTMLReflowState::CalcLineHeight(GetContent(), StyleContext(),
                                                    NS_AUTOHEIGHT, inflation);
   }
   nsRefPtr<nsFontMetrics> fontMet;
   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fontMet),
                                         inflation);
   // now adjust for our borders and padding
   aDesiredSize.SetBlockStartAscent(
-    nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight) +
+    nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
+                                           wm.IsLineInverted()) +
     aReflowState.ComputedLogicalBorderPadding().BStart(wm));
 
   // overflow handling
   aDesiredSize.SetOverflowAreasToDesiredBounds();
   // perform reflow on all kids
   nsIFrame* kid = mFrames.FirstChild();
   while (kid) {
     ReflowTextControlChild(kid, aPresContext, aReflowState, aStatus, aDesiredSize);
--- a/layout/generic/WritingModes.h
+++ b/layout/generic/WritingModes.h
@@ -2,17 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WritingModes_h_
 #define WritingModes_h_
 
 #include "nsRect.h"
-#include "nsStyleStruct.h"
+#include "nsStyleContext.h"
 
 // If WRITING_MODE_VERTICAL_ENABLED is defined, we will attempt to support
 // the vertical writing-mode values; if it is not defined, then
 // WritingMode.IsVertical() will be hard-coded to return false, allowing
 // many conditional branches to be optimized away while we're in the process
 // of transitioning layout to use writing-mode and logical directions, but
 // not yet ready to ship vertical support.
 
@@ -163,22 +163,27 @@ public:
   /**
    * Return the line-relative inline flow direction as a BidiDir
    */
   BidiDir GetBidiDir() const { return BidiDir(mWritingMode & eBidiMask); }
 
   /**
    * Return true if LTR. (Convenience method)
    */
-  bool IsBidiLTR() const { return eBidiLTR == (mWritingMode & eBidiMask); }
+  bool IsBidiLTR() const { return eBidiLTR == GetBidiDir(); }
 
   /**
    * True if vertical-mode block direction is LR (convenience method).
    */
-  bool IsVerticalLR() const { return eBlockLR == (mWritingMode & eBlockMask); }
+  bool IsVerticalLR() const { return eBlockLR == GetBlockDir(); }
+
+  /**
+   * True if vertical-mode block direction is RL (convenience method).
+   */
+  bool IsVerticalRL() const { return eBlockRL == GetBlockDir(); }
 
   /**
    * True if vertical writing mode, i.e. when
    * writing-mode: vertical-lr | vertical-rl.
    */
 #ifdef WRITING_MODE_VERTICAL_ENABLED
   bool IsVertical() const { return !!(mWritingMode & eOrientationMask); }
 #else
@@ -204,57 +209,95 @@ public:
    * positioning an over- or under-line decoration).
    */
   int FlowRelativeToLineRelativeFactor() const
   {
     return IsLineInverted() ? -1 : 1;
   }
 
   /**
+   * True if the text-orientation will force all text to be rendered sideways
+   * in vertical lines, in which case we should prefer an alphabetic baseline;
+   * otherwise, the default is centered.
+   * Note that some glyph runs may be rendered sideways even if this is false,
+   * due to text-orientation:mixed resolution, but in that case the dominant
+   * baseline remains centered.
+   */
+#ifdef WRITING_MODE_VERTICAL_ENABLED
+  bool IsSideways() const { return !!(mWritingMode & eSidewaysMask); }
+#else
+  bool IsSideways() const { return false; }
+#endif
+
+  /**
    * Default constructor gives us a horizontal, LTR writing mode.
    * XXX We will probably eliminate this and require explicit initialization
    *     in all cases once transition is complete.
    */
   WritingMode()
     : mWritingMode(0)
   { }
 
   /**
    * Construct writing mode based on a style context
    */
-  explicit WritingMode(const nsStyleVisibility* aStyleVisibility)
+  explicit WritingMode(nsStyleContext* aStyleContext)
   {
-    NS_ASSERTION(aStyleVisibility, "we need an nsStyleVisibility here");
+    NS_ASSERTION(aStyleContext, "we need an nsStyleContext here");
+
+    const nsStyleVisibility* styleVisibility = aStyleContext->StyleVisibility();
 
 #ifdef WRITING_MODE_VERTICAL_ENABLED
-    switch (aStyleVisibility->mWritingMode) {
+    switch (styleVisibility->mWritingMode) {
       case NS_STYLE_WRITING_MODE_HORIZONTAL_TB:
         mWritingMode = 0;
         break;
 
       case NS_STYLE_WRITING_MODE_VERTICAL_LR:
+      {
         mWritingMode = eBlockFlowMask |
-                       eLineOrientMask | //XXX needs update when text-orientation added
+                       eLineOrientMask |
                        eOrientationMask;
+        uint8_t textOrientation = aStyleContext->StyleText()->mTextOrientation;
+#if 0 // not yet implemented
+        if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) {
+          mWritingMode &= ~eLineOrientMask;
+        }
+#endif
+        if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) {
+          mWritingMode |= eSidewaysMask;
+        }
         break;
+      }
 
       case NS_STYLE_WRITING_MODE_VERTICAL_RL:
+      {
         mWritingMode = eOrientationMask;
+        uint8_t textOrientation = aStyleContext->StyleText()->mTextOrientation;
+#if 0 // not yet implemented
+        if (textOrientation == NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_LEFT) {
+          mWritingMode |= eLineOrientMask;
+        }
+#endif
+        if (textOrientation >= NS_STYLE_TEXT_ORIENTATION_SIDEWAYS_RIGHT) {
+          mWritingMode |= eSidewaysMask;
+        }
         break;
+      }
 
       default:
         NS_NOTREACHED("unknown writing mode!");
         mWritingMode = 0;
         break;
     }
 #else
     mWritingMode = 0;
 #endif
 
-    if (NS_STYLE_DIRECTION_RTL == aStyleVisibility->mDirection) {
+    if (NS_STYLE_DIRECTION_RTL == styleVisibility->mDirection) {
       mWritingMode |= eInlineFlowMask | //XXX needs update when text-orientation added
                       eBidiMask;
     }
   }
 
   // For unicode-bidi: plaintext, reset the direction of the writing mode from
   // the bidi paragraph level of the content
 
@@ -320,16 +363,20 @@ private:
     eOrientationMask = 0x01, // true means vertical text
     eInlineFlowMask  = 0x02, // true means absolute RTL/BTT (against physical coords)
     eBlockFlowMask   = 0x04, // true means vertical-LR (or horizontal-BT if added)
     eLineOrientMask  = 0x08, // true means over != block-start
     eBidiMask        = 0x10, // true means line-relative RTL (bidi RTL)
     // Note: We have one excess bit of info; WritingMode can pack into 4 bits.
     // But since we have space, we're caching interesting things for fast access.
 
+    eSidewaysMask    = 0x20, // true means text-orientation is sideways-*,
+                             // which means we'll use alphabetic instead of
+                             // centered default baseline for vertical text
+
     // Masks for output enums
     eInlineMask = 0x03,
     eBlockMask  = 0x05
   };
 };
 
 
 /**
--- a/layout/generic/nsBRFrame.cpp
+++ b/layout/generic/nsBRFrame.cpp
@@ -117,17 +117,18 @@ BRFrame::Reflow(nsPresContext* aPresCont
       // normal inline frame.  That line-height is used is important
       // here for cases where the line-height is less than 1.
       nsRefPtr<nsFontMetrics> fm;
       nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
         nsLayoutUtils::FontSizeInflationFor(this));
       if (fm) {
         nscoord logicalHeight = aReflowState.CalcLineHeight();
         finalSize.BSize(wm) = logicalHeight;
-        aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(fm, logicalHeight));
+        aMetrics.SetBlockStartAscent(nsLayoutUtils::GetCenteredFontBaseline(
+                                       fm, logicalHeight, wm.IsLineInverted()));
       }
       else {
         aMetrics.SetBlockStartAscent(aMetrics.BSize(wm) = 0);
       }
 
       // XXX temporary until I figure out a better solution; see the
       // code in nsLineLayout::VerticalAlignFrames that zaps minY/maxY
       // if the width is zero.
--- a/layout/generic/nsBlockFrame.cpp
+++ b/layout/generic/nsBlockFrame.cpp
@@ -510,17 +510,19 @@ nsBlockFrame::GetCaretBaseline() const
     }
   }
   nsRefPtr<nsFontMetrics> fm;
   float inflation = nsLayoutUtils::FontSizeInflationFor(this);
   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm), inflation);
   nscoord lineHeight =
     nsHTMLReflowState::CalcLineHeight(GetContent(), StyleContext(),
                                       contentRect.height, inflation);
-  return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight) + bp.top;
+  const WritingMode wm = GetWritingMode();
+  return nsLayoutUtils::GetCenteredFontBaseline(fm, lineHeight,
+                                                wm.IsLineInverted()) + bp.top;
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Child frame enumeration
 
 const nsFrameList&
 nsBlockFrame::GetChildList(ChildListID aListID) const
 {
@@ -1226,16 +1228,52 @@ nsBlockFrame::Reflow(nsPresContext*     
     // Otherwise just leave the bullet where it is, up against our top padding.
   }
 
   CheckFloats(state);
 
   // Compute our final size
   nscoord blockEndEdgeOfChildren;
   ComputeFinalSize(*reflowState, state, aMetrics, &blockEndEdgeOfChildren);
+
+  // If the block direction is right-to-left, we need to update the bounds of
+  // lines that were placed relative to mContainerWidth during reflow, as
+  // we typically do not know the true container width (block-dir size of the
+  // finished paragraph/block) until we've reflowed all its children. So we
+  // use a "fake" mContainerWidth during reflow (see nsBlockReflowState's
+  // constructor) and then fix up the positions of the lines here, once the
+  // final block size is known.
+  //
+  // Note that writing-mode:vertical-rl is the only case where the block
+  // logical direction progresses in a negative physical direction, and
+  // therefore block-dir coordinate conversion depends on knowing the width
+  // of the coordinate space in order to translate between the logical and
+  // physical origins.
+  if (wm.GetBlockDir() == WritingMode::BlockDir::eBlockRL) {
+    nscoord deltaX = aMetrics.Width() - state.mContainerWidth;
+    if (deltaX) {
+      for (line_iterator line = begin_lines(), end = end_lines();
+           line != end; line++) {
+        SlideLine(state, line, -deltaX);
+      }
+      for (nsIFrame* f = mFloats.FirstChild(); f; f = f->GetNextSibling()) {
+        nsPoint physicalDelta(deltaX, 0);
+        f->MovePositionBy(physicalDelta);
+      }
+      nsFrameList* bulletList = GetOutsideBulletList();
+      if (bulletList) {
+        nsPoint physicalDelta(deltaX, 0);
+        for (nsIFrame* f = bulletList->FirstChild(); f;
+             f = f->GetNextSibling()) {
+          f->MovePositionBy(physicalDelta);
+        }
+      }
+    }
+  }
+
   nsRect areaBounds = nsRect(0, 0, aMetrics.Width(), aMetrics.Height());
   ComputeOverflowAreas(areaBounds, reflowState->mStyleDisplay,
                        blockEndEdgeOfChildren, aMetrics.mOverflowAreas);
   // Factor overflow container child bounds into the overflow area
   aMetrics.mOverflowAreas.UnionWith(ocBounds);
   // Factor pushed float child bounds into the overflow area
   aMetrics.mOverflowAreas.UnionWith(fcBounds);
 
@@ -2511,17 +2549,18 @@ nsBlockFrame::ReflowDirtyLines(nsBlockRe
         }
       }
 
       nsRefPtr<nsFontMetrics> fm;
       nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
         nsLayoutUtils::FontSizeInflationFor(this));
 
       nscoord minAscent =
-        nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight);
+        nsLayoutUtils::GetCenteredFontBaseline(fm, aState.mMinLineHeight,
+                                               wm.IsLineInverted());
       nscoord minDescent = aState.mMinLineHeight - minAscent;
 
       aState.mBCoord += std::max(minAscent, metrics.BlockStartAscent()) +
                         std::max(minDescent, metrics.BSize(wm) -
                                              metrics.BlockStartAscent());
 
       nscoord offset = minAscent - metrics.BlockStartAscent();
       if (offset > 0) {
@@ -2726,46 +2765,49 @@ nsBlockFrame::PullFrameFrom(nsLineBox*  
   VerifyOverflowSituation();
 #endif
 
   return frame;
 }
 
 void
 nsBlockFrame::SlideLine(nsBlockReflowState& aState,
-                        nsLineBox* aLine, nscoord aDY)
-{
-  NS_PRECONDITION(aDY != 0, "why slide a line nowhere?");
+                        nsLineBox* aLine, nscoord aDeltaBCoord)
+{
+  NS_PRECONDITION(aDeltaBCoord != 0, "why slide a line nowhere?");
 
   // Adjust line state
-  aLine->SlideBy(aDY, aState.mContainerWidth);
+  aLine->SlideBy(aDeltaBCoord, aState.mContainerWidth);
 
   // Adjust the frames in the line
   nsIFrame* kid = aLine->mFirstChild;
   if (!kid) {
     return;
   }
 
+  WritingMode wm = GetWritingMode();
+  LogicalPoint translation(wm, 0, aDeltaBCoord);
+
   if (aLine->IsBlock()) {
-    if (aDY) {
-      kid->MovePositionBy(nsPoint(0, aDY));
+    if (aDeltaBCoord) {
+      kid->MovePositionBy(wm, translation);
     }
 
     // Make sure the frame's view and any child views are updated
     nsContainerFrame::PlaceFrameView(kid);
   }
   else {
-    // Adjust the Y coordinate of the frames in the line.
-    // Note: we need to re-position views even if aDY is 0, because
+    // Adjust the block-dir coordinate of the frames in the line.
+    // Note: we need to re-position views even if aDeltaBCoord is 0, because
     // one of our parent frames may have moved and so the view's position
-    // relative to its parent may have changed
+    // relative to its parent may have changed.
     int32_t n = aLine->GetChildCount();
     while (--n >= 0) {
-      if (aDY) {
-        kid->MovePositionBy(nsPoint(0, aDY));
+      if (aDeltaBCoord) {
+        kid->MovePositionBy(wm, translation);
       }
       // Make sure the frame's view and any child views are updated
       nsContainerFrame::PlaceFrameView(kid);
       kid = kid->GetNextSibling();
     }
   }
 }
 
--- a/layout/generic/nsBlockFrame.h
+++ b/layout/generic/nsBlockFrame.h
@@ -403,21 +403,21 @@ protected:
                    bool*        aInOverflowLines,
                    FrameLines** aOverflowLines);
 
   void SetFlags(nsFrameState aFlags) {
     mState &= ~NS_BLOCK_FLAGS_MASK;
     mState |= aFlags;
   }
 
-  /** move the frames contained by aLine by aDY
+  /** move the frames contained by aLine by aDeltaBCoord
     * if aLine is a block, its child floats are added to the state manager
     */
   void SlideLine(nsBlockReflowState& aState,
-                 nsLineBox* aLine, nscoord aDY);
+                 nsLineBox* aLine, nscoord aDeltaBCoord);
 
   void ComputeFinalSize(const nsHTMLReflowState& aReflowState,
                         nsBlockReflowState&      aState,
                         nsHTMLReflowMetrics&     aMetrics,
                         nscoord*                 aBottomEdgeOfChildren);
 
   void ComputeOverflowAreas(const nsRect&         aBounds,
                             const nsStyleDisplay* aDisplay,
--- a/layout/generic/nsBlockReflowState.cpp
+++ b/layout/generic/nsBlockReflowState.cpp
@@ -49,18 +49,29 @@ nsBlockReflowState::nsBlockReflowState(c
   WritingMode wm = aReflowState.GetWritingMode();
   SetFlag(BRS_ISFIRSTINFLOW, aFrame->GetPrevInFlow() == nullptr);
   SetFlag(BRS_ISOVERFLOWCONTAINER, IS_TRUE_OVERFLOW_CONTAINER(aFrame));
 
   nsIFrame::LogicalSides logicalSkipSides =
     aFrame->GetLogicalSkipSides(&aReflowState);
   mBorderPadding.ApplySkipSides(logicalSkipSides);
 
-  // Note that mContainerWidth is the physical width!
-  mContainerWidth = aReflowState.ComputedWidth() + mBorderPadding.LeftRight(wm);
+  // Note that mContainerWidth is the physical width, needed to convert
+  // logical block-coordinates in vertical-rl writing mode (measured from a
+  // RHS origin) to physical coordinates within the containing block.
+  // If aReflowState doesn't have a constrained ComputedWidth(), we set it to
+  // zero, which means lines will be positioned (physically) incorrectly;
+  // we will fix them up at the end of nsBlockFrame::Reflow, after we know
+  // the total block-size of the frame.
+  mContainerWidth = aReflowState.ComputedWidth();
+  if (mContainerWidth == NS_UNCONSTRAINEDSIZE) {
+    mContainerWidth = 0;
+  }
+
+  mContainerWidth += mBorderPadding.LeftRight(wm);
 
   if ((aBStartMarginRoot && !logicalSkipSides.BStart()) ||
       0 != mBorderPadding.BStart(wm)) {
     SetFlag(BRS_ISBSTARTMARGINROOT, true);
     SetFlag(BRS_APPLYBSTARTMARGIN, true);
   }
   if ((aBEndMarginRoot && !logicalSkipSides.BEnd()) ||
       0 != mBorderPadding.BEnd(wm)) {
--- a/layout/generic/nsBlockReflowState.h
+++ b/layout/generic/nsBlockReflowState.h
@@ -198,16 +198,18 @@ public:
   }
   nscoord ContentBEnd() const {
     return mContentArea.BEnd(mReflowState.GetWritingMode());
   }
   mozilla::LogicalSize ContentSize(mozilla::WritingMode aWM) const {
     mozilla::WritingMode wm = mReflowState.GetWritingMode();
     return mContentArea.Size(wm).ConvertTo(aWM, wm);
   }
+
+  // Physical width. Use only for physical <-> logical coordinate conversion.
   nscoord mContainerWidth;
 
   // Continuation out-of-flow float frames that need to move to our
   // next in flow are placed here during reflow.  It's a pointer to
   // a frame list stored in the block's property table.
   nsFrameList *mPushedFloats;
   // This method makes sure pushed floats are accessible to
   // StealFrame. Call it before adding any frames to mPushedFloats.
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -1334,17 +1334,22 @@ nsFrame::SetAdditionalStyleContext(int32
   NS_PRECONDITION(aIndex >= 0, "invalid index number");
 }
 
 nscoord
 nsFrame::GetLogicalBaseline(WritingMode aWritingMode) const
 {
   NS_ASSERTION(!NS_SUBTREE_DIRTY(this),
                "frame must not be dirty");
-  // Default to the bottom margin edge, per CSS2.1's definition of the
+  // Baseline for inverted line content is the top (block-start) margin edge,
+  // as the frame is in effect "flipped" for alignment purposes.
+  if (aWritingMode.IsLineInverted()) {
+    return -GetLogicalUsedMargin(aWritingMode).BStart(aWritingMode);
+  }
+  // Otherwise, the bottom margin edge, per CSS2.1's definition of the
   // 'baseline' value of 'vertical-align'.
   return BSize(aWritingMode) +
          GetLogicalUsedMargin(aWritingMode).BEnd(aWritingMode);
 }
 
 const nsFrameList&
 nsFrame::GetChildList(ChildListID aListID) const
 {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -603,17 +603,17 @@ public:
    * must also be called.
    */
   void SetParent(nsContainerFrame* aParent);
 
   /**
    * The frame's writing-mode, used for logical layout computations.
    */
   mozilla::WritingMode GetWritingMode() const {
-    return mozilla::WritingMode(StyleVisibility());
+    return mozilla::WritingMode(StyleContext());
   }
 
   /**
    * Get the writing mode of this frame, but if it is styled with
    * unicode-bidi: plaintext, reset the direction to the resolved paragraph
    * level of the given subframe (typically the first frame on the line),
    * not this frame's writing mode, because the container frame could be split
    * by hard line breaks into multiple paragraphs with different base direction.
@@ -740,16 +740,25 @@ public:
    * nsHTMLReflowState::ApplyRelativePositioning is called.  When moving
    * a frame during the reflow process prior to calling
    * nsHTMLReflowState::ApplyRelativePositioning, the position should
    * simply be adjusted directly (e.g., using SetPosition()).
    */
   void MovePositionBy(const nsPoint& aTranslation);
 
   /**
+   * As above, using a logical-point delta in a given writing mode.
+   */
+  void MovePositionBy(mozilla::WritingMode aWritingMode,
+                      const mozilla::LogicalPoint& aTranslation)
+  {
+    MovePositionBy(aTranslation.GetPhysicalPoint(aWritingMode, 0));
+  }
+
+  /**
    * Return frame's position without relative positioning
    */
   nsPoint GetNormalPosition() const;
   mozilla::LogicalPoint
   GetLogicalNormalPosition(mozilla::WritingMode aWritingMode,
                            nscoord aContainerWidth) const
   {
     return mozilla::LogicalPoint(aWritingMode,
--- a/layout/generic/nsLineBox.h
+++ b/layout/generic/nsLineBox.h
@@ -468,18 +468,20 @@ public:
     { return GetOverflowArea(eScrollableOverflow); }
 
   void SlideBy(nscoord aDBCoord, nscoord aContainerWidth) {
     NS_ASSERTION(aContainerWidth == mContainerWidth || mContainerWidth == -1,
                  "container width doesn't match");
     mContainerWidth = aContainerWidth;
     mBounds.BStart(mWritingMode) += aDBCoord;
     if (mData) {
+      nsPoint physicalDelta = mozilla::LogicalPoint(mWritingMode, 0, aDBCoord).
+                                         GetPhysicalPoint(mWritingMode, 0);
       NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
-        mData->mOverflowAreas.Overflow(otype).y += aDBCoord;
+        mData->mOverflowAreas.Overflow(otype) += physicalDelta;
       }
     }
   }
 
   void IndentBy(nscoord aDICoord, nscoord aContainerWidth) {
     NS_ASSERTION(aContainerWidth == mContainerWidth || mContainerWidth == -1,
                  "container width doesn't match");
     mContainerWidth = aContainerWidth;
@@ -580,19 +582,23 @@ public:
 
 #ifdef DEBUG
   static int32_t GetCtorCount();
 #endif
 
   nsIFrame* mFirstChild;
 
   mozilla::WritingMode mWritingMode;
+
+  // Physical width. Use only for physical <-> logical coordinate conversion.
   nscoord mContainerWidth;
+
  private:
   mozilla::LogicalRect mBounds;
+
  public:
   const mozilla::LogicalRect& GetBounds() { return mBounds; }
   nsRect GetPhysicalBounds() const
   {
     if (mBounds.IsAllZero()) {
       return nsRect(0, 0, 0, 0);
     }
 
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -603,18 +603,18 @@ nsLineLayout::NewPerFrameData(nsIFrame* 
   pfd->mNext = nullptr;
   pfd->mPrev = nullptr;
   pfd->mFlags = 0;  // all flags default to false
   pfd->mFrame = aFrame;
 
   WritingMode frameWM = aFrame->GetWritingMode();
   WritingMode lineWM = mRootSpan->mWritingMode;
   pfd->mBounds = LogicalRect(lineWM);
-  pfd->mMargin = LogicalMargin(frameWM);
-  pfd->mBorderPadding = LogicalMargin(frameWM);
+  pfd->mMargin = LogicalMargin(lineWM);
+  pfd->mBorderPadding = LogicalMargin(lineWM);
   pfd->mOffsets = LogicalMargin(frameWM);
 
   pfd->mJustificationInfo = JustificationInfo();
   pfd->mJustificationAssignment = JustificationAssignment();
 
 #ifdef DEBUG
   pfd->mBlockDirAlign = 0xFF;
   mFramesAllocated++;
@@ -804,19 +804,19 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
     nsHTMLReflowState& reflowState = *reflowStateHolder;
     reflowState.mLineLayout = this;
     reflowState.mFlags.mIsTopOfPage = mIsTopOfPage;
     if (reflowState.ComputedISize() == NS_UNCONSTRAINEDSIZE) {
       reflowState.AvailableISize() = availableSpaceOnLine;
     }
     WritingMode stateWM = reflowState.GetWritingMode();
     pfd->mMargin =
-      reflowState.ComputedLogicalMargin().ConvertTo(frameWM, stateWM);
+      reflowState.ComputedLogicalMargin().ConvertTo(lineWM, stateWM);
     pfd->mBorderPadding =
-      reflowState.ComputedLogicalBorderPadding().ConvertTo(frameWM, stateWM);
+      reflowState.ComputedLogicalBorderPadding().ConvertTo(lineWM, stateWM);
     pfd->SetFlag(PFD_RELATIVEPOS,
                  reflowState.mStyleDisplay->IsRelativelyPositionedStyle());
     if (pfd->GetFlag(PFD_RELATIVEPOS)) {
       pfd->mOffsets =
         reflowState.ComputedLogicalOffsets().ConvertTo(frameWM, stateWM);
     }
 
     // Calculate whether the the frame should have a start margin and
@@ -1081,43 +1081,45 @@ nsLineLayout::ReflowFrame(nsIFrame* aFra
 void
 nsLineLayout::AllowForStartMargin(PerFrameData* pfd,
                                   nsHTMLReflowState& aReflowState)
 {
   NS_ASSERTION(!aReflowState.IsFloating(),
                "How'd we get a floated inline frame? "
                "The frame ctor should've dealt with this.");
 
-  WritingMode frameWM = pfd->mFrame->GetWritingMode();
+  WritingMode lineWM = mRootSpan->mWritingMode;
 
   // Only apply start-margin on the first-in flow for inline frames,
   // and make sure to not apply it to any inline other than the first
   // in an ib split.  Note that the ib sibling (block-in-inline
   // sibling) annotations only live on the first continuation, but we
   // don't want to apply the start margin for later continuations
   // anyway.  For box-decoration-break:clone we apply the start-margin
   // on all continuations.
   if ((pfd->mFrame->GetPrevContinuation() ||
        pfd->mFrame->FrameIsNonFirstInIBSplit()) &&
       aReflowState.mStyleBorder->mBoxDecorationBreak ==
         NS_STYLE_BOX_DECORATION_BREAK_SLICE) {
     // Zero this out so that when we compute the max-element-width of
     // the frame we will properly avoid adding in the starting margin.
-    pfd->mMargin.IStart(frameWM) = 0;
+    pfd->mMargin.IStart(lineWM) = 0;
   } else {
     NS_WARN_IF_FALSE(NS_UNCONSTRAINEDSIZE != aReflowState.AvailableISize(),
                      "have unconstrained inline-size; this should only result "
                      "from very large sizes, not attempts at intrinsic "
                      "inline-size calculation");
     if (NS_UNCONSTRAINEDSIZE == aReflowState.ComputedISize()) {
       // For inline-ish and text-ish things (which don't compute widths
       // in the reflow state), adjust available inline-size to account for the
       // start margin. The end margin will be accounted for when we
       // finish flowing the frame.
-      aReflowState.AvailableISize() -= pfd->mMargin.IStart(frameWM);
+      WritingMode wm = aReflowState.GetWritingMode();
+      aReflowState.AvailableISize() -=
+          pfd->mMargin.ConvertTo(wm, lineWM).IStart(wm);
     }
   }
 }
 
 nscoord
 nsLineLayout::GetCurrentFrameInlineDistanceFromBlock()
 {
   PerSpanData* psd;
@@ -1146,17 +1148,16 @@ nsLineLayout::CanPlaceFrame(PerFrameData
                             nsHTMLReflowMetrics& aMetrics,
                             nsReflowStatus& aStatus,
                             bool* aOptionalBreakAfterFits)
 {
   NS_PRECONDITION(pfd && pfd->mFrame, "bad args, null pointers for frame data");
 
   *aOptionalBreakAfterFits = true;
 
-  WritingMode frameWM = pfd->mFrame->GetWritingMode();
   WritingMode lineWM = mRootSpan->mWritingMode;
   /*
    * We want to only apply the end margin if we're the last continuation and
    * either not in an {ib} split or the last inline in it.  In all other
    * cases we want to zero it out.  That means zeroing it out if any of these
    * conditions hold:
    * 1) The frame is not complete (in this case it will get a next-in-flow)
    * 2) The frame is complete but has a non-fluid continuation on its
@@ -1171,24 +1172,22 @@ nsLineLayout::CanPlaceFrame(PerFrameData
    * continuations (that are not letter frames).
    */
   if ((NS_FRAME_IS_NOT_COMPLETE(aStatus) ||
        pfd->mFrame->LastInFlow()->GetNextContinuation() ||
        pfd->mFrame->FrameIsNonLastInIBSplit()) &&
       !pfd->GetFlag(PFD_ISLETTERFRAME) &&
       pfd->mFrame->StyleBorder()->mBoxDecorationBreak ==
         NS_STYLE_BOX_DECORATION_BREAK_SLICE) {
-    pfd->mMargin.IEnd(frameWM) = 0;
+    pfd->mMargin.IEnd(lineWM) = 0;
   }
 
-  // Convert the frame's margins to the line's writing mode and apply
-  // the start margin to the frame bounds.
-  LogicalMargin usedMargins = pfd->mMargin.ConvertTo(lineWM, frameWM);
-  nscoord startMargin = usedMargins.IStart(lineWM);
-  nscoord endMargin = usedMargins.IEnd(lineWM);
+  // Apply the start margin to the frame bounds.
+  nscoord startMargin = pfd->mMargin.IStart(lineWM);
+  nscoord endMargin = pfd->mMargin.IEnd(lineWM);
 
   pfd->mBounds.IStart(lineWM) += startMargin;
 
   PerSpanData* psd = mCurrentSpan;
   if (psd->mNoWrap) {
     // When wrapping is off, everything fits.
     return true;
   }
@@ -1305,29 +1304,28 @@ nsLineLayout::CanPlaceFrame(PerFrameData
 }
 
 /**
  * Place the frame. Update running counters.
  */
 void
 nsLineLayout::PlaceFrame(PerFrameData* pfd, nsHTMLReflowMetrics& aMetrics)
 {
-  WritingMode frameWM = pfd->mFrame->GetWritingMode();
   WritingMode lineWM = mRootSpan->mWritingMode;
 
   // Record ascent and update max-ascent and max-descent values
   if (aMetrics.BlockStartAscent() == nsHTMLReflowMetrics::ASK_FOR_BASELINE) {
     pfd->mAscent = pfd->mFrame->GetLogicalBaseline(lineWM);
   } else {
     pfd->mAscent = aMetrics.BlockStartAscent();
   }
 
   // Advance to next inline coordinate
   mCurrentSpan->mICoord = pfd->mBounds.IEnd(lineWM) +
-                          pfd->mMargin.ConvertTo(lineWM, frameWM).IEnd(lineWM);
+                          pfd->mMargin.IEnd(lineWM);
 
   // Count the number of non-placeholder frames on the line...
   if (pfd->mFrame->GetType() == nsGkAtoms::placeholderFrame) {
     NS_ASSERTION(pfd->mBounds.ISize(lineWM) == 0 &&
                  pfd->mBounds.BSize(lineWM) == 0,
                  "placeholders should have 0 width/height (checking "
                  "placeholders were never counted by the old code in "
                  "this function)");
@@ -1504,47 +1502,46 @@ nsLineLayout::PlaceTopBottomFrames(PerSp
                                    nscoord aDistanceFromStart,
                                    nscoord aLineBSize)
 {
   for (PerFrameData* pfd = psd->mFirstFrame; pfd; pfd = pfd->mNext) {
     PerSpanData* span = pfd->mSpan;
 #ifdef DEBUG
     NS_ASSERTION(0xFF != pfd->mBlockDirAlign, "umr");
 #endif
-    WritingMode frameWM = pfd->mFrame->GetWritingMode();
     WritingMode lineWM = mRootSpan->mWritingMode;
     nscoord containerWidth = ContainerWidthForSpan(psd);
     switch (pfd->mBlockDirAlign) {
       case VALIGN_TOP:
         if (span) {
           pfd->mBounds.BStart(lineWM) = -aDistanceFromStart - span->mMinBCoord;
         }
         else {
           pfd->mBounds.BStart(lineWM) =
-            -aDistanceFromStart + pfd->mMargin.BStart(frameWM);
+            -aDistanceFromStart + pfd->mMargin.BStart(lineWM);
         }
         pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerWidth);
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf("    ");
         nsFrame::ListTag(stdout, pfd->mFrame);
         printf(": y=%d dTop=%d [bp.top=%d topLeading=%d]\n",
                pfd->mBounds.BStart(lineWM), aDistanceFromStart,
-               span ? pfd->mBorderPadding.BStart(frameWM) : 0,
+               span ? pfd->mBorderPadding.BStart(lineWM) : 0,
                span ? span->mBStartLeading : 0);
 #endif
         break;
       case VALIGN_BOTTOM:
         if (span) {
           // Compute bottom leading
           pfd->mBounds.BStart(lineWM) =
             -aDistanceFromStart + aLineBSize - span->mMaxBCoord;
         }
         else {
           pfd->mBounds.BStart(lineWM) = -aDistanceFromStart + aLineBSize -
-            pfd->mMargin.BEnd(frameWM) - pfd->mBounds.BSize(lineWM);
+            pfd->mMargin.BEnd(lineWM) - pfd->mBounds.BSize(lineWM);
         }
         pfd->mFrame->SetRect(lineWM, pfd->mBounds, containerWidth);
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf("    ");
         nsFrame::ListTag(stdout, pfd->mFrame);
         printf(": y=%d\n", pfd->mBounds.BStart(lineWM));
 #endif
         break;
@@ -1593,17 +1590,16 @@ nsLineLayout::VerticalAlignFrames(PerSpa
                                         inflation);
 
   bool preMode = mStyleText->WhiteSpaceIsSignificant();
 
   // See if the span is an empty continuation. It's an empty continuation iff:
   // - it has a prev-in-flow
   // - it has no next in flow
   // - it's zero sized
-  WritingMode frameWM = spanFramePFD->mFrame->GetWritingMode();
   WritingMode lineWM = mRootSpan->mWritingMode;
   bool emptyContinuation = psd != mRootSpan &&
     spanFrame->GetPrevInFlow() && !spanFrame->GetNextInFlow() &&
     spanFramePFD->mBounds.IsZeroSize();
 
 #ifdef NOISY_BLOCKDIR_ALIGN
   printf("[%sSpan]", (psd == mRootSpan)?"Root":"");
   nsFrame::ListTag(stdout, spanFrame);
@@ -1611,24 +1607,24 @@ nsLineLayout::VerticalAlignFrames(PerSpa
          preMode ? "yes" : "no",
          mPresContext->CompatibilityMode() != eCompatibility_NavQuirks ? "yes" : "no",
          spanFramePFD->mBounds.ISize(lineWM),
          spanFramePFD->mBounds.BSize(lineWM),
          emptyContinuation ? "yes" : "no");
   if (psd != mRootSpan) {
     WritingMode frameWM = spanFramePFD->mFrame->GetWritingMode();
     printf(" bp=%d,%d,%d,%d margin=%d,%d,%d,%d",
-           spanFramePFD->mBorderPadding.Top(frameWM),
-           spanFramePFD->mBorderPadding.Right(frameWM),
-           spanFramePFD->mBorderPadding.Bottom(frameWM),
-           spanFramePFD->mBorderPadding.Left(frameWM),
-           spanFramePFD->mMargin.Top(frameWM),
-           spanFramePFD->mMargin.Right(frameWM),
-           spanFramePFD->mMargin.Bottom(frameWM),
-           spanFramePFD->mMargin.Left(frameWM));
+           spanFramePFD->mBorderPadding.Top(lineWM),
+           spanFramePFD->mBorderPadding.Right(lineWM),
+           spanFramePFD->mBorderPadding.Bottom(lineWM),
+           spanFramePFD->mBorderPadding.Left(lineWM),
+           spanFramePFD->mMargin.Top(lineWM),
+           spanFramePFD->mMargin.Right(lineWM),
+           spanFramePFD->mMargin.Bottom(lineWM),
+           spanFramePFD->mMargin.Left(lineWM));
   }
   printf("\n");
 #endif
 
   // Compute the span's mZeroEffectiveSpanBox flag. What we are trying
   // to determine is how we should treat the span: should it act
   // "normally" according to css2 or should it effectively
   // "disappear".
@@ -1720,18 +1716,17 @@ nsLineLayout::VerticalAlignFrames(PerSpa
     // compute the top leading.
     float inflation =
       GetInflationForBlockDirAlignment(spanFrame, mInflationMinFontSize);
     nscoord logicalBSize = nsHTMLReflowState::
       CalcLineHeight(spanFrame->GetContent(), spanFrame->StyleContext(),
                      mBlockReflowState->ComputedHeight(),
                      inflation);
     nscoord contentBSize = spanFramePFD->mBounds.BSize(lineWM) -
-      spanFramePFD->mBorderPadding.BStart(frameWM) -
-      spanFramePFD->mBorderPadding.BEnd(frameWM);
+      spanFramePFD->mBorderPadding.BStartEnd(lineWM);
 
     // Special-case for a ::first-letter frame, set the line height to
     // the frame block size if the user has left line-height == normal
     if (spanFramePFD->GetFlag(PFD_ISLETTERFRAME) &&
         !spanFrame->GetPrevInFlow() &&
         spanFrame->StyleText()->mLineHeight.GetUnit() == eStyleUnit_Normal) {
       logicalBSize = spanFramePFD->mBounds.BSize(lineWM);
     }
@@ -1754,45 +1749,44 @@ nsLineLayout::VerticalAlignFrames(PerSpa
     }
     else {
 
       // The initial values for the min and max block coord values are in the
       // span's coordinate space, and cover the logical block size of the span.
       // If there are child frames in this span that stick out of this area
       // then the minBCoord and maxBCoord are updated by the amount of logical
       // blockSize that is outside this range.
-      minBCoord = spanFramePFD->mBorderPadding.BStart(frameWM) -
+      minBCoord = spanFramePFD->mBorderPadding.BStart(lineWM) -
                   psd->mBStartLeading;
       maxBCoord = minBCoord + psd->mLogicalBSize;
     }
 
     // This is the distance from the top edge of the parents visual
     // box to the baseline. The span already computed this for us,
     // so just use it.
     *psd->mBaseline = baselineBCoord = spanFramePFD->mAscent;
 
 
 #ifdef NOISY_BLOCKDIR_ALIGN
     printf("[%sSpan]", (psd == mRootSpan)?"Root":"");
     nsFrame::ListTag(stdout, spanFrame);
     printf(": baseLine=%d logicalBSize=%d topLeading=%d h=%d bp=%d,%d zeroEffectiveSpanBox=%s\n",
            baselineBCoord, psd->mLogicalBSize, psd->mBStartLeading,
            spanFramePFD->mBounds.BSize(lineWM),
-           spanFramePFD->mBorderPadding.Top(frameWM),
-           spanFramePFD->mBorderPadding.Bottom(frameWM),
+           spanFramePFD->mBorderPadding.Top(lineWM),
+           spanFramePFD->mBorderPadding.Bottom(lineWM),
            zeroEffectiveSpanBox ? "yes" : "no");
 #endif
   }
 
   nscoord maxStartBoxBSize = 0;
   nscoord maxEndBoxBSize = 0;
   PerFrameData* pfd = psd->mFirstFrame;
   while (nullptr != pfd) {
     nsIFrame* frame = pfd->mFrame;
-    WritingMode frameWM = frame->GetWritingMode();
 
     // sanity check (see bug 105168, non-reproducible crashes from null frame)
     NS_ASSERTION(frame, "null frame in PerFrameData - something is very very bad");
     if (!frame) {
       return;
     }
 
     // Compute the logical block size of the frame
@@ -1802,17 +1796,17 @@ nsLineLayout::VerticalAlignFrames(PerSpa
       // For span frames the logical-block-size and start-leading were
       // pre-computed when the span was reflowed.
       logicalBSize = frameSpan->mLogicalBSize;
     }
     else {
       // For other elements the logical block size is the same as the
       // frame's block size plus its margins.
       logicalBSize = pfd->mBounds.BSize(lineWM) +
-                     pfd->mMargin.BStartEnd(frameWM);
+                     pfd->mMargin.BStartEnd(lineWM);
       if (logicalBSize < 0 &&
           mPresContext->CompatibilityMode() == eCompatibility_NavQuirks) {
         pfd->mAscent -= logicalBSize;
         logicalBSize = 0;
       }
     }
 
     // Get vertical-align property ("vertical-align" is the CSS name for
@@ -1910,71 +1904,71 @@ nsLineLayout::VerticalAlignFrames(PerSpa
           nscoord parentXHeight = fm->XHeight();
           if (frameSpan) {
             pfd->mBounds.BStart(lineWM) = baselineBCoord -
               (parentXHeight + pfd->mBounds.BSize(lineWM))/2;
           }
           else {
             pfd->mBounds.BStart(lineWM) = baselineBCoord -
               (parentXHeight + logicalBSize)/2 +
-              pfd->mMargin.BStart(frameWM);
+              pfd->mMargin.BStart(lineWM);
           }
           pfd->mBlockDirAlign = VALIGN_OTHER;
           break;
         }
 
         case NS_STYLE_VERTICAL_ALIGN_TEXT_TOP:
         {
           // The top of the logical box is aligned with the top of
           // the parent element's text.
           // XXX For vertical text we will need a new API to get the logical
           //     max-ascent here
           nscoord parentAscent = fm->MaxAscent();
           if (frameSpan) {
             pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent -
-              pfd->mBorderPadding.BStart(frameWM) + frameSpan->mBStartLeading;
+              pfd->mBorderPadding.BStart(lineWM) + frameSpan->mBStartLeading;
           }
           else {
             pfd->mBounds.BStart(lineWM) = baselineBCoord - parentAscent +
-                                          pfd->mMargin.BStart(frameWM);
+                                          pfd->mMargin.BStart(lineWM);
           }
           pfd->mBlockDirAlign = VALIGN_OTHER;
           break;
         }
 
         case NS_STYLE_VERTICAL_ALIGN_TEXT_BOTTOM:
         {
           // The bottom of the logical box is aligned with the
           // bottom of the parent elements text.
           nscoord parentDescent = fm->MaxDescent();
           if (frameSpan) {
             pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
                                           pfd->mBounds.BSize(lineWM) +
-                                          pfd->mBorderPadding.BEnd(frameWM) -
+                                          pfd->mBorderPadding.BEnd(lineWM) -
                                           frameSpan->mBEndLeading;
           }
           else {
             pfd->mBounds.BStart(lineWM) = baselineBCoord + parentDescent -
                                           pfd->mBounds.BSize(lineWM) -
-                                          pfd->mMargin.BEnd(frameWM);
+                                          pfd->mMargin.BEnd(lineWM);
           }
           pfd->mBlockDirAlign = VALIGN_OTHER;
           break;
         }
 
         case NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE:
         {
           // Align the midpoint of the frame with the baseline of the parent.
           if (frameSpan) {
             pfd->mBounds.BStart(lineWM) = baselineBCoord -
                                           pfd->mBounds.BSize(lineWM)/2;
           }
           else {
             pfd->mBounds.BStart(lineWM) = baselineBCoord - logicalBSize/2 +
-                                          pfd->mMargin.BStart(frameWM);
+                                          pfd->mMargin.BStart(lineWM);
           }
           pfd->mBlockDirAlign = VALIGN_OTHER;
           break;
         }
       }
     } else {
       // We have either a coord, a percent, or a calc().
       nscoord pctBasis = 0;
@@ -2030,17 +2024,17 @@ nsLineLayout::VerticalAlignFrames(PerSpa
           // For spans that were are now placing, use their position
           // plus their already computed min-Y and max-Y values for
           // computing blockStart and blockEnd.
           blockStart = pfd->mBounds.BStart(lineWM) + frameSpan->mMinBCoord;
           blockEnd = pfd->mBounds.BStart(lineWM) + frameSpan->mMaxBCoord;
         }
         else {
           blockStart = pfd->mBounds.BStart(lineWM) -
-                       pfd->mMargin.BStart(frameWM);
+                       pfd->mMargin.BStart(lineWM);
           blockEnd = blockStart + logicalBSize;
         }
         if (!preMode &&
             mPresContext->CompatibilityMode() != eCompatibility_FullStandards &&
             !logicalBSize) {
           // Check if it's a BR frame that is not alone on its line (it
           // is given a block size of zero to indicate this), and if so reset
           // blockStart and blockEnd so that BR frames don't influence the line.
@@ -2049,18 +2043,18 @@ nsLineLayout::VerticalAlignFrames(PerSpa
             blockEnd = BLOCKDIR_ALIGN_FRAMES_NO_MAXIMUM;
           }
         }
         if (blockStart < minBCoord) minBCoord = blockStart;
         if (blockEnd > maxBCoord) maxBCoord = blockEnd;
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf("     [frame]raw: a=%d h=%d bp=%d,%d logical: h=%d leading=%d y=%d minBCoord=%d maxBCoord=%d\n",
                pfd->mAscent, pfd->mBounds.BSize(lineWM),
-               pfd->mBorderPadding.Top(frameWM),
-               pfd->mBorderPadding.Bottom(frameWM),
+               pfd->mBorderPadding.Top(lineWM),
+               pfd->mBorderPadding.Bottom(lineWM),
                logicalBSize,
                frameSpan ? frameSpan->mBStartLeading : 0,
                pfd->mBounds.BStart(lineWM), minBCoord, maxBCoord);
 #endif
       }
       if (psd != mRootSpan) {
         frame->SetRect(lineWM, pfd->mBounds, ContainerWidthForSpan(psd));
       }
@@ -2099,17 +2093,18 @@ nsLineLayout::VerticalAlignFrames(PerSpa
     }
     if (applyMinLH) {
       if (psd->mHasNonemptyContent || preMode || mHasBullet) {
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf("  [span]==> adjusting min/maxBCoord: currentValues: %d,%d", minBCoord, maxBCoord);
 #endif
         nscoord minimumLineBSize = mMinLineBSize;
         nscoord blockStart =
-          -nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize);
+          -nsLayoutUtils::GetCenteredFontBaseline(fm, minimumLineBSize,
+                                                  lineWM.IsLineInverted());
         nscoord blockEnd = blockStart + minimumLineBSize;
 
         if (blockStart < minBCoord) minBCoord = blockStart;
         if (blockEnd > maxBCoord) maxBCoord = blockEnd;
 
 #ifdef NOISY_BLOCKDIR_ALIGN
         printf(" new values: %d,%d\n", minBCoord, maxBCoord);
 #endif
@@ -2141,18 +2136,18 @@ nsLineLayout::VerticalAlignFrames(PerSpa
   if ((psd != mRootSpan) && (psd->mZeroEffectiveSpanBox)) {
 #ifdef NOISY_BLOCKDIR_ALIGN
     printf("   [span]adjusting for zeroEffectiveSpanBox\n");
     printf("     Original: minBCoord=%d, maxBCoord=%d, bSize=%d, ascent=%d, logicalBSize=%d, topLeading=%d, bottomLeading=%d\n",
            minBCoord, maxBCoord, spanFramePFD->mBounds.BSize(frameWM),
            spanFramePFD->mAscent,
            psd->mLogicalBSize, psd->mBStartLeading, psd->mBEndLeading);
 #endif
-    nscoord goodMinBCoord = spanFramePFD->mBorderPadding.BStart(frameWM) -
-                            psd->mBStartLeading;
+    nscoord goodMinBCoord =
+      spanFramePFD->mBorderPadding.BStart(lineWM) - psd->mBStartLeading;
     nscoord goodMaxBCoord = goodMinBCoord + psd->mLogicalBSize;
 
     // For cases like the one in bug 714519 (text-decoration placement
     // or making nsLineLayout::IsZeroBSize() handle
     // vertical-align:top/bottom on a descendant of the line that's not
     // a child of it), we want to treat elements that are
     // vertical-align: top or bottom somewhat like children for the
     // purposes of this quirk.  To some extent, this is guessing, since
--- a/layout/generic/nsLineLayout.h
+++ b/layout/generic/nsLineLayout.h
@@ -380,19 +380,19 @@ protected:
     nscoord mAscent;
     // note that mBounds is a logical rect in the *line*'s writing mode.
     // When setting frame coordinates, we have to convert to the frame's
     //  writing mode
     mozilla::LogicalRect mBounds;
     nsOverflowAreas mOverflowAreas;
 
     // From reflow-state
-    mozilla::LogicalMargin mMargin;
-    mozilla::LogicalMargin mBorderPadding;
-    mozilla::LogicalMargin mOffsets;
+    mozilla::LogicalMargin mMargin;        // in *line* writing mode
+    mozilla::LogicalMargin mBorderPadding; // in *line* writing mode
+    mozilla::LogicalMargin mOffsets;       // in *frame* writing mode
 
     // state for text justification
     mozilla::JustificationInfo mJustificationInfo;
     mozilla::JustificationAssignment mJustificationAssignment;
     
 // PerFrameData flags
 #define PFD_RELATIVEPOS                 0x00000001
 #define PFD_ISTEXTFRAME                 0x00000002
@@ -532,16 +532,17 @@ protected:
   // Final computed line-bSize value after VerticalAlignFrames for
   // the block has been called.
   nscoord mFinalLineBSize;
   
   // Amount of trimmable whitespace inline size for the trailing text
   // frame, if any
   nscoord mTrimmableISize;
 
+  // Physical width. Use only for physical <-> logical coordinate conversion.
   nscoord mContainerWidth;
 
   bool mFirstLetterStyleOK      : 1;
   bool mIsTopOfPage             : 1;
   bool mImpactedByFloats        : 1;
   bool mLastFloatWasLetterFrame : 1;
   bool mLineIsEmpty             : 1;
   bool mLineEndsInBR            : 1;
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -1785,23 +1785,23 @@ GetHyphenTextRun(gfxTextRun* aTextRun, g
   if (!ctx)
     return nullptr;
 
   return aTextRun->GetFontGroup()->
     MakeHyphenTextRun(ctx, aTextRun->GetAppUnitsPerDevUnit());
 }
 
 static gfxFont::Metrics
-GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVertical)
+GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVerticalMetrics)
 {
   if (!aFontGroup)
     return gfxFont::Metrics();
   gfxFont* font = aFontGroup->GetFirstValidFont();
-  return font->GetMetrics(aVertical ? gfxFont::eVertical :
-                                      gfxFont::eHorizontal);
+  return font->GetMetrics(aVerticalMetrics ? gfxFont::eVertical
+                                           : gfxFont::eHorizontal);
 }
 
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NORMAL == 0);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE == 1);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_NOWRAP == 2);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_WRAP == 3);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_LINE == 4);
 PR_STATIC_ASSERT(NS_STYLE_WHITESPACE_PRE_SPACE == 5);
@@ -3156,17 +3156,17 @@ ComputeTabWidthAppUnits(nsIFrame* aFrame
 {
   // Get the number of spaces from CSS -moz-tab-size
   const nsStyleText* textStyle = aFrame->StyleText();
   
   // Round the space width when converting to appunits the same way
   // textruns do
   gfxFloat spaceWidthAppUnits =
     NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
-                                 aTextRun->IsVertical()).spaceWidth *
+                                 aTextRun->UseCenterBaseline()).spaceWidth *
              aTextRun->GetAppUnitsPerDevUnit());
   return textStyle->mTabSize * spaceWidthAppUnits;
 }
 
 // aX and the result are in whole appunits.
 static gfxFloat
 AdvanceToNextTab(gfxFloat aX, nsIFrame* aFrame,
                  gfxTextRun* aTextRun, gfxFloat* aCachedTabWidth)
@@ -4763,25 +4763,26 @@ nsTextFrame::GetTextDecorations(
                     nsTextFrame::TextDecorationColorResolution aColorResolution,
                     nsTextFrame::TextDecorations& aDecorations)
 {
   const nsCompatibility compatMode = aPresContext->CompatibilityMode();
 
   bool useOverride = false;
   nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
 
-  // frameTopOffset represents the offset to f's top from our baseline in our
+  // frameBStartOffset represents the offset to f's BStart from our baseline in our
   // coordinate space
   // baselineOffset represents the offset from our baseline to f's baseline or
   // the nearest block's baseline, in our coordinate space, whichever is closest
   // during the particular iteration
-  nscoord frameTopOffset = mAscent,
+  nscoord frameBStartOffset = mAscent,
           baselineOffset = 0;
 
   bool nearestBlockFound = false;
+  bool vertical = GetWritingMode().IsVertical();
 
   for (nsIFrame* f = this, *fChild = nullptr;
        f;
        fChild = f,
        f = nsLayoutUtils::GetParentOrPlaceholderFor(f))
   {
     nsStyleContext *const context = f->StyleContext();
     if (!context->HasTextDecorationLines()) {
@@ -4814,28 +4815,30 @@ nsTextFrame::GetTextDecorations(
       if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
 
         // Since offset is the offset in the child's coordinate space, we have
         // to undo the accumulation to bring the transform out of the block's
         // coordinate space
         const nscoord lineBaselineOffset = LazyGetLineBaselineOffset(fChild,
                                                                      fBlock);
 
-        baselineOffset =
-          frameTopOffset - fChild->GetNormalPosition().y - lineBaselineOffset;
+        baselineOffset = frameBStartOffset - lineBaselineOffset -
+          (vertical ? fChild->GetNormalPosition().x
+                    : fChild->GetNormalPosition().y);
       }
     }
     else if (!nearestBlockFound) {
       // use a dummy WritingMode, because nsTextFrame::GetLogicalBaseLine
       // doesn't use it anyway
-      baselineOffset = frameTopOffset - f->GetLogicalBaseline(WritingMode());
+      baselineOffset = frameBStartOffset - f->GetLogicalBaseline(WritingMode());
     }
 
     nearestBlockFound = nearestBlockFound || firstBlock;
-    frameTopOffset += f->GetNormalPosition().y;
+    frameBStartOffset +=
+      vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
 
     const uint8_t style = styleText->GetDecorationStyle();
     if (textDecorations) {
       nscolor color;
       if (useOverride) {
         color = overrideColor;
       } else if (IsSVGText()) {
         // XXX We might want to do something with text-decoration-color when
@@ -4910,51 +4913,55 @@ nsTextFrame::UnionAdditionalOverflow(nsP
                                      PropertyProvider& aProvider,
                                      nsRect* aVisualOverflowRect,
                                      bool aIncludeTextDecorations)
 {
   // Text-shadow overflows
   nsRect shadowRect =
     nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
   aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
-  bool vertical = mTextRun->IsVertical();
+  bool verticalRun = mTextRun->IsVertical();
+  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
+  bool inverted = GetWritingMode().IsLineInverted();
 
   if (IsFloatingFirstLetterChild()) {
     // The underline/overline drawable area must be contained in the overflow
     // rect when this is in floating first letter frame at *both* modes.
     // In this case, aBlock is the ::first-letter frame.
     uint8_t decorationStyle = aBlock->StyleContext()->
                                 StyleTextReset()->GetDecorationStyle();
     // If the style is none, let's include decoration line rect as solid style
     // since changing the style from none to solid/dotted/dashed doesn't cause
     // reflow.
     if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
       decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
     }
     nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
     nscoord underlineOffset, underlineSize;
     fontMetrics->GetUnderline(underlineOffset, underlineSize);
-    nscoord maxAscent = fontMetrics->MaxAscent();
+    nscoord maxAscent = inverted ? fontMetrics->MaxDescent()
+                                 : fontMetrics->MaxAscent();
 
     gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
     gfxFloat gfxWidth =
-      (vertical ? aVisualOverflowRect->height : aVisualOverflowRect->width) /
+      (verticalRun ? aVisualOverflowRect->height
+                   : aVisualOverflowRect->width) /
       appUnitsPerDevUnit;
     gfxFloat gfxAscent = gfxFloat(mAscent) / appUnitsPerDevUnit;
     gfxFloat gfxMaxAscent = maxAscent / appUnitsPerDevUnit;
     gfxFloat gfxUnderlineSize = underlineSize / appUnitsPerDevUnit;
     gfxFloat gfxUnderlineOffset = underlineOffset / appUnitsPerDevUnit;
     nsRect underlineRect =
       nsCSSRendering::GetTextDecorationRect(aPresContext,
         gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxUnderlineOffset,
-        NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, vertical);
+        NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle, verticalRun);
     nsRect overlineRect =
       nsCSSRendering::GetTextDecorationRect(aPresContext,
         gfxSize(gfxWidth, gfxUnderlineSize), gfxAscent, gfxMaxAscent,
-        NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, vertical);
+        NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle, verticalRun);
 
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
     aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
 
     // XXX If strikeoutSize is much thicker than the underlineSize, it may
     //     cause overflowing from the overflow rect.  However, such case
     //     isn't realistic, we don't need to compute it now.
   }
@@ -4964,108 +4971,129 @@ nsTextFrame::UnionAdditionalOverflow(nsP
     // maxima and minima are required to reliably generate the rectangle for
     // them
     TextDecorations textDecs;
     GetTextDecorations(aPresContext, eResolvedColors, textDecs);
     if (textDecs.HasDecorationLines()) {
       nscoord inflationMinFontSize =
         nsLayoutUtils::InflationMinFontSizeFor(aBlock);
 
-      const nscoord measure = vertical ? GetSize().height : GetSize().width;
+      const nscoord measure = verticalRun ? GetSize().height : GetSize().width;
       const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel(),
-                     gfxWidth = measure / appUnitsPerDevUnit,
-                     ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
-      nscoord top(nscoord_MAX), bottom(nscoord_MIN);
+                     gfxWidth = measure / appUnitsPerDevUnit;
+      gfxFloat ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
+      const WritingMode wm = GetWritingMode();
+      if (wm.IsVerticalRL()) {
+        ascent = -ascent;
+      }
+
+      nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
       // Below we loop through all text decorations and compute the rectangle
       // containing all of them, in this frame's coordinate space
       for (uint32_t i = 0; i < textDecs.mUnderlines.Length(); ++i) {
         const LineDecoration& dec = textDecs.mUnderlines[i];
         uint8_t decorationStyle = dec.mStyle;
         // If the style is solid, let's include decoration line rect of solid
         // style since changing the style from none to solid/dotted/dashed
         // doesn't cause reflow.
         if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
           decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
         }
 
         float inflation =
           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
         const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                              vertical);
+                              useVerticalMetrics);
 
         const nsRect decorationRect =
           nsCSSRendering::GetTextDecorationRect(aPresContext,
             gfxSize(gfxWidth, metrics.underlineSize),
             ascent, metrics.underlineOffset,
             NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE, decorationStyle,
-            vertical) +
+            verticalRun) +
           nsPoint(0, -dec.mBaselineOffset);
 
-        top = std::min(decorationRect.y, top);
-        bottom = std::max(decorationRect.YMost(), bottom);
+        if (verticalRun) {
+          topOrLeft = std::min(decorationRect.x, topOrLeft);
+          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+        } else {
+          topOrLeft = std::min(decorationRect.y, topOrLeft);
+          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+        }
       }
       for (uint32_t i = 0; i < textDecs.mOverlines.Length(); ++i) {
         const LineDecoration& dec = textDecs.mOverlines[i];
         uint8_t decorationStyle = dec.mStyle;
         // If the style is solid, let's include decoration line rect of solid
         // style since changing the style from none to solid/dotted/dashed
         // doesn't cause reflow.
         if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
           decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
         }
 
         float inflation =
           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
         const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                              vertical);
+                              useVerticalMetrics);
 
         const nsRect decorationRect =
           nsCSSRendering::GetTextDecorationRect(aPresContext,
             gfxSize(gfxWidth, metrics.underlineSize),
             ascent, metrics.maxAscent,
             NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, decorationStyle,
-            vertical) +
+            verticalRun) +
           nsPoint(0, -dec.mBaselineOffset);
 
-        top = std::min(decorationRect.y, top);
-        bottom = std::max(decorationRect.YMost(), bottom);
+        if (verticalRun) {
+          topOrLeft = std::min(decorationRect.x, topOrLeft);
+          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+        } else {
+          topOrLeft = std::min(decorationRect.y, topOrLeft);
+          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+        }
       }
       for (uint32_t i = 0; i < textDecs.mStrikes.Length(); ++i) {
         const LineDecoration& dec = textDecs.mStrikes[i];
         uint8_t decorationStyle = dec.mStyle;
         // If the style is solid, let's include decoration line rect of solid
         // style since changing the style from none to solid/dotted/dashed
         // doesn't cause reflow.
         if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
           decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
         }
 
         float inflation =
           GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
         const gfxFont::Metrics metrics =
           GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                              vertical);
+                              useVerticalMetrics);
 
         const nsRect decorationRect =
           nsCSSRendering::GetTextDecorationRect(aPresContext,
             gfxSize(gfxWidth, metrics.strikeoutSize),
             ascent, metrics.strikeoutOffset,
             NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH, decorationStyle,
-            vertical) +
+            verticalRun) +
           nsPoint(0, -dec.mBaselineOffset);
-        top = std::min(decorationRect.y, top);
-        bottom = std::max(decorationRect.YMost(), bottom);
+
+        if (verticalRun) {
+          topOrLeft = std::min(decorationRect.x, topOrLeft);
+          bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
+        } else {
+          topOrLeft = std::min(decorationRect.y, topOrLeft);
+          bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
+        }
       }
 
-      aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
-                                     vertical ?
-                                       nsRect(top, 0, bottom - top, measure) :
-                                       nsRect(0, top, measure, bottom - top));
+      aVisualOverflowRect->UnionRect(
+        *aVisualOverflowRect,
+        verticalRun ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
+                    : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
     }
   }
   // When this frame is not selected, the text-decoration area must be in
   // frame bounds.
   if (!IsSelected() ||
       !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
     return;
   AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
@@ -5715,63 +5743,64 @@ nsTextFrame::PaintTextSelectionDecoratio
       for (int32_t i = start; i < end; ++i) {
         selectedChars[i] = sdptr;
       }
     }
     sdptr = sdptr->mNext;
   }
 
   gfxFont* firstFont = aProvider.GetFontGroup()->GetFirstValidFont();
-  bool vertical = mTextRun->IsVertical();
+  bool verticalRun = mTextRun->IsVertical();
+  bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
   gfxFont::Metrics
-    decorationMetrics(firstFont->GetMetrics(vertical ?
+    decorationMetrics(firstFont->GetMetrics(useVerticalMetrics ?
       gfxFont::eVertical : gfxFont::eHorizontal));
-  if (!vertical) {
+  if (!useVerticalMetrics) {
     // The potential adjustment from using gfxFontGroup::GetUnderlineOffset
-    // is only valid for horizontal text.
+    // is only valid for horizontal font metrics.
     decorationMetrics.underlineOffset =
       aProvider.GetFontGroup()->GetUnderlineOffset();
   }
 
   gfxFloat startIOffset =
-    vertical ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
+    verticalRun ? aTextBaselinePt.y - aFramePt.y : aTextBaselinePt.x - aFramePt.x;
   SelectionIterator iterator(selectedChars, aContentOffset, aContentLength,
                              aProvider, mTextRun, startIOffset);
   gfxFloat iOffset, hyphenWidth;
   uint32_t offset, length;
   int32_t app = aTextPaintStyle.PresContext()->AppUnitsPerDevPixel();
   // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
   gfxPoint pt;
-  if (vertical) {
+  if (verticalRun) {
     pt.x = (aTextBaselinePt.x - mAscent) / app;
   } else {
     pt.y = (aTextBaselinePt.y - mAscent) / app;
   }
   gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
                     aDirtyRect.width / app, aDirtyRect.height / app);
   SelectionType type;
   TextRangeStyle selectedStyle;
   while (iterator.GetNextSegment(&iOffset, &offset, &length, &hyphenWidth,
                                  &type, &selectedStyle)) {
     gfxFloat advance = hyphenWidth +
       mTextRun->GetAdvanceWidth(offset, length, &aProvider);
     if (type == aSelectionType) {
-      if (vertical) {
+      if (verticalRun) {
         pt.y = (aFramePt.y + iOffset -
                (mTextRun->IsRightToLeft() ? advance : 0)) / app;
       } else {
         pt.x = (aFramePt.x + iOffset -
                (mTextRun->IsRightToLeft() ? advance : 0)) / app;
       }
       gfxFloat width = Abs(advance) / app;
       gfxFloat xInFrame = pt.x - (aFramePt.x / app);
       DrawSelectionDecorations(aCtx, dirtyRect, aSelectionType, this,
                                aTextPaintStyle, selectedStyle, pt, xInFrame,
                                width, mAscent / app, decorationMetrics,
-                               aCallbacks, vertical);
+                               aCallbacks, verticalRun);
     }
     iterator.UpdateWithAdvance(advance);
   }
 }
 
 bool
 nsTextFrame::PaintTextWithSelection(gfxContext* aCtx,
     const gfxPoint& aFramePt,
@@ -6005,35 +6034,37 @@ nsTextFrame::PaintText(nsRenderingContex
     return;
 
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   // Trim trailing whitespace
   provider.InitializeForDisplay(true);
 
   gfxContext* ctx = aRenderingContext->ThebesContext();
   const bool rtl = mTextRun->IsRightToLeft();
-  const bool vertical = mTextRun->IsVertical();
+  const bool verticalRun = mTextRun->IsVertical();
+  WritingMode wm = GetWritingMode();
   const nscoord frameWidth = GetSize().width;
   gfxPoint framePt(aPt.x, aPt.y);
   gfxPoint textBaselinePt;
-  if (vertical) {
-    textBaselinePt = gfxPoint(aPt.x + mAscent,
-             rtl ? gfxFloat(aPt.y + GetSize().height) : aPt.y);
+  if (verticalRun) {
+    textBaselinePt = // XXX sideways-left will need different handling here
+      gfxPoint(aPt.x + (wm.IsVerticalLR() ? mAscent : frameWidth - mAscent),
+               rtl ? aPt.y + GetSize().height : aPt.y);
   } else {
     textBaselinePt = gfxPoint(rtl ? gfxFloat(aPt.x + frameWidth) : framePt.x,
              nsLayoutUtils::GetSnappedBaselineY(this, ctx, aPt.y, mAscent));
   }
   uint32_t startOffset = provider.GetStart().GetSkippedOffset();
   uint32_t maxLength = ComputeTransformedLength(provider);
   nscoord snappedLeftEdge, snappedRightEdge;
   if (!MeasureCharClippedText(provider, aItem.mLeftEdge, aItem.mRightEdge,
          &startOffset, &maxLength, &snappedLeftEdge, &snappedRightEdge)) {
     return;
   }
-  if (vertical) {
+  if (verticalRun) {
     textBaselinePt.y += rtl ? -snappedRightEdge : snappedLeftEdge;
   } else {
     textBaselinePt.x += rtl ? -snappedRightEdge : snappedLeftEdge;
   }
   nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedLeftEdge,
                                              snappedRightEdge);
   nsTextPaintStyle textPaintStyle(this);
   textPaintStyle.SetResolveColors(!aCallbacks);
@@ -6148,27 +6179,53 @@ nsTextFrame::DrawTextRunAndDecorations(
     gfxFloat& aAdvanceWidth,
     bool aDrawSoftHyphen,
     const TextDecorations& aDecorations,
     const nscolor* const aDecorationOverrideColor,
     gfxTextContextPaint* aContextPaint,
     nsTextFrame::DrawPathCallbacks* aCallbacks)
 {
     const gfxFloat app = aTextStyle.PresContext()->AppUnitsPerDevPixel();
-    bool vertical = mTextRun->IsVertical();
+    bool verticalRun = mTextRun->IsVertical();
+    bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
 
     // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
     nscoord x = NSToCoordRound(aFramePt.x);
-    nscoord width = vertical ? GetRect().height : GetRect().width;
-    aClipEdges.Intersect(&x, &width);
-
-    gfxPoint decPt(x / app, 0);
-    gfxSize decSize(width / app, 0);
-    const gfxFloat ascent = gfxFloat(mAscent) / app;
-    const gfxFloat frameTop = aFramePt.y;
+    nscoord y = NSToCoordRound(aFramePt.y);
+
+    // 'measure' here is textrun-relative, so for a horizontal run it's the
+    // width, while for a vertical run it's the height of the decoration
+    const nsSize frameSize = GetSize();
+    nscoord measure = verticalRun ? frameSize.height : frameSize.width;
+
+    // XXX todo: probably should have a vertical version of this...
+    if (!verticalRun) {
+      aClipEdges.Intersect(&x, &measure);
+    }
+
+    // decPt is the physical point where the decoration is to be drawn,
+    // relative to the frame; one of its coordinates will be updated below.
+    gfxPoint decPt(x / app, y / app);
+    gfxFloat& bCoord = verticalRun ? decPt.x : decPt.y;
+
+    // decSize is a textrun-relative size, so its 'width' field is actually
+    // the run-relative measure, and 'height' will be the line thickness
+    gfxSize decSize(measure / app, 0);
+    gfxFloat ascent = gfxFloat(mAscent) / app;
+
+    // The starting edge of the frame in block direction
+    gfxFloat frameBStart = verticalRun ? aFramePt.x : aFramePt.y;
+
+    // In vertical-rl mode, block coordinates are measured from the right,
+    // so we need to adjust here.
+    const WritingMode wm = GetWritingMode();
+    if (wm.IsVerticalRL()) {
+      frameBStart += frameSize.width;
+      ascent = -ascent;
+    }
 
     gfxRect dirtyRect(aDirtyRect.x / app, aDirtyRect.y / app,
                       aDirtyRect.Width() / app, aDirtyRect.Height() / app);
 
     nscoord inflationMinFontSize =
       nsLayoutUtils::InflationMinFontSizeFor(this);
 
     // Underlines
@@ -6177,46 +6234,46 @@ nsTextFrame::DrawTextRunAndDecorations(
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
 
       float inflation =
         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
       const gfxFont::Metrics metrics =
         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                            vertical);
+                            useVerticalMetrics);
 
       decSize.height = metrics.underlineSize;
-      decPt.y = (frameTop - dec.mBaselineOffset) / app;
+      bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         metrics.underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-        dec.mStyle, eNormalDecoration, aCallbacks, vertical);
+        dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
     }
     // Overlines
     for (uint32_t i = aDecorations.mOverlines.Length(); i-- > 0; ) {
       const LineDecoration& dec = aDecorations.mOverlines[i];
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
 
       float inflation =
         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
       const gfxFont::Metrics metrics =
         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                            vertical);
+                            useVerticalMetrics);
 
       decSize.height = metrics.underlineSize;
-      decPt.y = (frameTop - dec.mBaselineOffset) / app;
+      bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         metrics.maxAscent, NS_STYLE_TEXT_DECORATION_LINE_OVERLINE, dec.mStyle,
-        eNormalDecoration, aCallbacks, vertical);
+        eNormalDecoration, aCallbacks, verticalRun);
     }
 
     // CSS 2.1 mandates that text be painted after over/underlines, and *then*
     // line-throughs
     DrawTextRun(aCtx, aTextBaselinePt, aOffset, aLength, aProvider, aTextColor,
                 aAdvanceWidth, aDrawSoftHyphen, aContextPaint, aCallbacks);
 
     // Line-throughs
@@ -6225,25 +6282,25 @@ nsTextFrame::DrawTextRunAndDecorations(
       if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
         continue;
       }
 
       float inflation =
         GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
       const gfxFont::Metrics metrics =
         GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
-                            vertical);
+                            useVerticalMetrics);
 
       decSize.height = metrics.strikeoutSize;
-      decPt.y = (frameTop - dec.mBaselineOffset) / app;
+      bCoord = (frameBStart - dec.mBaselineOffset) / app;
 
       PaintDecorationLine(this, aCtx, dirtyRect, dec.mColor,
         aDecorationOverrideColor, decPt, 0.0, decSize, ascent,
         metrics.strikeoutOffset, NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH,
-        dec.mStyle, eNormalDecoration, aCallbacks, vertical);
+        dec.mStyle, eNormalDecoration, aCallbacks, verticalRun);
     }
 }
 
 void
 nsTextFrame::DrawText(
     gfxContext* const aCtx, const gfxRect& aDirtyRect,
     const gfxPoint& aFramePt, const gfxPoint& aTextBaselinePt,
     uint32_t aOffset, uint32_t aLength,
@@ -6432,19 +6489,22 @@ nsTextFrame::CombineSelectionUnderlineRe
 
   nsRect givenRect = aRect;
 
   nsRefPtr<nsFontMetrics> fm;
   nsLayoutUtils::GetFontMetricsForFrame(this, getter_AddRefs(fm),
                                         GetFontSizeInflation());
   gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
   gfxFont* firstFont = fontGroup->GetFirstValidFont();
-  bool vertical = GetWritingMode().IsVertical();
+  WritingMode wm = GetWritingMode();
+  bool verticalRun = wm.IsVertical();
+  bool useVerticalMetrics = verticalRun && !wm.IsSideways();
   const gfxFont::Metrics& metrics =
-    firstFont->GetMetrics(vertical ? gfxFont::eVertical : gfxFont::eHorizontal);
+    firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
+                                             : gfxFont::eHorizontal);
   gfxFloat underlineOffset = fontGroup->GetUnderlineOffset();
   gfxFloat ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
   gfxFloat descentLimit =
     ComputeDescentLimitForSelectionUnderline(aPresContext, this, metrics);
 
   SelectionDetails *details = GetSelectionDetails();
   for (SelectionDetails *sd = details; sd; sd = sd->mNext) {
     if (sd->mStart == sd->mEnd || !(sd->mType & SelectionTypesWithDecorations))
@@ -6479,17 +6539,17 @@ nsTextFrame::CombineSelectionUnderlineRe
     gfxSize size(aPresContext->AppUnitsToGfxUnits(aRect.width),
                  ComputeSelectionUnderlineHeight(aPresContext,
                                                  metrics, sd->mType));
     relativeSize = std::max(relativeSize, 1.0f);
     size.height *= relativeSize;
     decorationArea =
       nsCSSRendering::GetTextDecorationRect(aPresContext, size,
         ascent, underlineOffset, NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE,
-        style, vertical, descentLimit);
+        style, verticalRun, descentLimit);
     aRect.UnionRect(aRect, decorationArea);
   }
   DestroySelectionDetails(details);
 
   return !aRect.IsEmpty() && !givenRect.Contains(aRect);
 }
 
 bool
@@ -7591,17 +7651,21 @@ nsTextFrame::ComputeTightBounds(gfxConte
 
   gfxTextRun::Metrics metrics =
         mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
                               ComputeTransformedLength(provider),
                               gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
                               aContext, &provider);
   // mAscent should be the same as metrics.mAscent, but it's what we use to
   // paint so that's the one we'll use.
-  nsRect boundingBox = RoundOut(metrics.mBoundingBox) + nsPoint(0, mAscent);
+  nsRect boundingBox = RoundOut(metrics.mBoundingBox);
+  if (GetWritingMode().IsLineInverted()) {
+    boundingBox.y = -boundingBox.YMost();
+  }
+  boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
     Swap(boundingBox.x, boundingBox.y);
     Swap(boundingBox.width, boundingBox.height);
   }
   return boundingBox;
 }
 
@@ -8276,48 +8340,64 @@ nsTextFrame::ReflowText(nsLineLayout& aL
   finalSize.ISize(wm) = NSToCoordCeil(std::max(gfxFloat(0.0),
                                                textMetrics.mAdvanceWidth));
 
   if (transformedCharsFit == 0 && !usedHyphenation) {
     aMetrics.SetBlockStartAscent(0);
     finalSize.BSize(wm) = 0;
   } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
     // Use actual text metrics for floating first letter frame.
-    aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
-    finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
-      NSToCoordCeil(textMetrics.mDescent);
+    if (wm.IsLineInverted()) {
+      aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mDescent));
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
+        NSToCoordCeil(textMetrics.mAscent);
+    } else {
+      aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
+        NSToCoordCeil(textMetrics.mDescent);
+    }
   } else {
     // Otherwise, ascent should contain the overline drawable area.
     // And also descent should contain the underline drawable area.
     // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
     nsFontMetrics* fm = provider.GetFontMetrics();
     nscoord fontAscent = fm->MaxAscent();
     nscoord fontDescent = fm->MaxDescent();
-    aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
-    nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
-    finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
+    if (wm.IsLineInverted()) {
+      aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent));
+      nscoord descent = std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent);
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
+    } else {
+      aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
+      nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
+      finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
+    }
   }
   aMetrics.SetSize(wm, finalSize);
 
   NS_ASSERTION(aMetrics.BlockStartAscent() >= 0,
                "Negative ascent???");
   NS_ASSERTION(aMetrics.BSize(aMetrics.GetWritingMode()) -
                aMetrics.BlockStartAscent() >= 0,
                "Negative descent???");
 
   mAscent = aMetrics.BlockStartAscent();
 
   // Handle text that runs outside its normal bounds.
-  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
-  aMetrics.SetOverflowAreasToDesiredBounds();
+  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
+  if (wm.IsLineInverted()) {
+    boundingBox.y = -boundingBox.YMost();
+  }
+  boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
     Swap(boundingBox.x, boundingBox.y);
     Swap(boundingBox.width, boundingBox.height);
   }
+  aMetrics.SetOverflowAreasToDesiredBounds();
   aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
 
   // When we have text decorations, we don't need to compute their overflow now
   // because we're guaranteed to do it later
   // (see nsLineLayout::RelativePositionFrames)
   UnionAdditionalOverflow(presContext, aLineLayout.LineContainerRS()->frame,
                           provider, &aMetrics.VisualOverflow(), false);
 
@@ -8523,28 +8603,33 @@ nsTextFrame::RecomputeOverflow(const nsH
   PropertyProvider provider(this, iter, nsTextFrame::eInflated);
   provider.InitializeForDisplay(true);
 
   gfxTextRun::Metrics textMetrics =
     mTextRun->MeasureText(provider.GetStart().GetSkippedOffset(),
                           ComputeTransformedLength(provider),
                           gfxFont::LOOSE_INK_EXTENTS, nullptr,
                           &provider);
-  nsRect &vis = result.VisualOverflow();
-  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox) + nsPoint(0, mAscent);
+  nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
+  if (GetWritingMode().IsLineInverted()) {
+    boundingBox.y = -boundingBox.YMost();
+  }
+  boundingBox += nsPoint(0, mAscent);
   if (mTextRun->IsVertical()) {
     // Swap line-relative textMetrics dimensions to physical coordinates.
     Swap(boundingBox.x, boundingBox.y);
     Swap(boundingBox.width, boundingBox.height);
   }
+  nsRect &vis = result.VisualOverflow();
   vis.UnionRect(vis, boundingBox);
   UnionAdditionalOverflow(PresContext(), aBlockReflowState.frame, provider,
                           &vis, true);
   return result;
 }
+
 static char16_t TransformChar(const nsStyleText* aStyle, gfxTextRun* aTextRun,
                                uint32_t aSkippedOffset, char16_t aChar)
 {
   if (aChar == '\n') {
     return aStyle->NewlineIsSignificant() ? aChar : ' ';
   }
   if (aChar == '\t') {
     return aStyle->TabIsSignificant() ? aChar : ' ';
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1088025-1-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug 1083892</title>
+<style>
+div {
+  width:300px;
+  height:300px;
+  padding:10px;
+  border:1px solid black;
+  writing-mode:vertical-rl;
+}
+</style>
+</head>
+
+<body>
+<div>
+  This is the <b><i>first</i> paragraph</b>. It's long enough to wrap onto multiple lines.<br>
+  <b>Paragraph <i>two</i></b>.<br>
+  <b><i>Third and final</i> paragraph</b> of this simple testcase. That's all, folks!
+</div>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1088025-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Bug 1083892</title>
+<style>
+div {
+  width:300px;
+  height:300px;
+  padding:10px;
+  border:1px solid black;
+  writing-mode:vertical-rl;
+}
+p {
+  margin: 0;
+}
+</style>
+</head>
+
+<body>
+<div>
+  <p>This is the <b><i>first</i> paragraph</b>. It's long enough to wrap onto multiple lines.
+  <p><b>Paragraph <i>two</i></b>.
+  <p><b><i>Third and final</i> paragraph</b> of this simple testcase. That's all, folks!
+</div>
+</body>
+
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1089388-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+.v-lr { writing-mode:vertical-lr;  }
+.v-rl { writing-mode:vertical-rl; }
+
+div {
+  width: 300px;
+  height: 200px;
+  background: #ddd;
+  margin: 50px;
+}
+</style>
+</head>
+
+<body>
+
+<div class="v-lr">
+First part of the block.
+<i id="test">New text inserted by script, to cause a reflow that slides the following lines.</i>
+We will insert enough new content that it wraps onto additional lines.
+<br><br>
+Here is some more text that follows a forced break.
+Observe what happens to it when text is added earlier.
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1089388-1.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<style>
+.v-lr { writing-mode:vertical-lr;  }
+.v-rl { writing-mode:vertical-rl; }
+
+div {
+  width: 300px;
+  height: 200px;
+  background: #ddd;
+  margin: 50px;
+}
+</style>
+
+<script>
+function doTest() {
+  document.getElementById("test").textContent =
+    "New text inserted by script, to cause a reflow that slides the following lines.";
+  document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+
+<body onload="doTest()">
+
+<div class="v-lr">
+First part of the block.
+<i id="test"></i>
+We will insert enough new content that it wraps onto additional lines.
+<br><br>
+Here is some more text that follows a forced break.
+Observe what happens to it when text is added earlier.
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1089388-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+.v-lr { writing-mode:vertical-lr;  }
+.v-rl { writing-mode:vertical-rl; }
+
+div {
+  width: 300px;
+  height: 200px;
+  background: #ddd;
+  margin: 50px;
+}
+</style>
+</head>
+
+<body>
+
+<div class="v-rl">
+First part of the block.
+<i id="test">New text inserted by script, to cause a reflow that slides the following lines.</i>
+We will insert enough new content that it wraps onto additional lines.
+<br><br>
+Here is some more text that follows a forced break.
+Observe what happens to it when text is added earlier.
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1089388-2.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+<style>
+.v-lr { writing-mode:vertical-lr;  }
+.v-rl { writing-mode:vertical-rl; }
+
+div {
+  width: 300px;
+  height: 200px;
+  background: #ddd;
+  margin: 50px;
+}
+</style>
+
+<script>
+function doTest() {
+  document.getElementById("test").textContent =
+    "New text inserted by script, to cause a reflow that slides the following lines.";
+  document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+
+<body onload="doTest()">
+
+<div class="v-rl">
+First part of the block.
+<i id="test"></i>
+We will insert enough new content that it wraps onto additional lines.
+<br><br>
+Here is some more text that follows a forced break.
+Observe what happens to it when text is added earlier.
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090159-1-ref.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+.h { writing-mode:horizontal-tb; }
+.v-lr { writing-mode:vertical-lr; text-orientation:sideways-right; }
+.v-rl { writing-mode:vertical-rl; text-orientation:sideways-right; }
+
+div {
+  width: 250px;
+  height: 250px;
+  border: 1px solid red;
+  margin: 10px;
+  display: inline-block;
+  font: 16px monospace;
+  line-height: 32px;
+}
+</style>
+</head>
+
+<body>
+
+<div class="h">
+你好吗? hello
+</div>
+
+<div class="v-lr">
+你好吗? hello
+</div>
+
+<div class="v-rl">
+你好吗? hello
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090159-1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+.h { writing-mode:horizontal-tb; }
+.v-lr { writing-mode:vertical-lr; text-orientation:sideways-right; }
+.v-rl { writing-mode:vertical-rl; text-orientation:sideways-right; }
+
+div {
+  width: 250px;
+  height: 250px;
+  border: 1px solid red;
+  margin: 10px;
+  display: inline-block;
+  font: 16px monospace;
+  line-height: 32px;
+}
+</style>
+</head>
+
+<body>
+
+<div class="h">
+你好吗? <span>hello</span>
+</div>
+
+<div class="v-lr">
+你好吗? <span>hello</span>
+</div>
+
+<div class="v-rl">
+你好吗? <span>hello</span>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-1-notref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with orientation:sideways-right
+// test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:sideways-right', 0);
+
+// Reference: horizontal text with 90° rotation
+// test(100,  50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2);
+
+// Non-reference: vertical text with orientation:mixed
+test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-1-ref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with orientation:sideways-right
+// test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:sideways-right', 0);
+
+// Reference: horizontal text with 90° rotation
+test(100,  50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2);
+
+// Non-reference: vertical text with orientation:mixed
+// test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-1.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with orientation:sideways-right
+test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:sideways-right', 0);
+
+// Reference: horizontal text with 90° rotation
+// test(100,  50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2);
+
+// Non-reference: vertical text with orientation:mixed
+// test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0);
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-2-ref.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation, baseline) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  if (baseline != '') {
+    ctx.textBaseline = baseline;
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with orientation:mixed
+// test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0, '');
+
+// Reference: horizontal text with 90° rotation and textBaseline=middle
+test(100,  50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-2.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation, baseline) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  if (baseline != '') {
+    ctx.textBaseline = baseline;
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with orientation:mixed
+test(100,  50, 'Hello', 'writing-mode:vertical-lr;text-orientation:mixed', 0, '');
+
+// Reference: horizontal text with 90° rotation and textBaseline=middle
+// test(100,  50, 'Hello', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-3-ref.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation, baseline) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  if (baseline != '') {
+    ctx.textBaseline = baseline;
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with various textBaselines
+// test(100,  50, 'Top', 'writing-mode:vertical-lr', 0, 'top');
+// test(150,  50, 'Middle', 'writing-mode:vertical-lr', 0, 'middle');
+// test(200,  50, 'Alphabetic', 'writing-mode:vertical-lr', 0, 'alphabetic');
+// test(250,  50, 'Bottom', 'writing-mode:vertical-lr', 0, 'bottom');
+
+// Reference: horizontal text with 90° rotation and the same baselines
+test(100,  50, 'Top', 'writing-mode:horizontal-tb', Math.PI/2, 'top');
+test(150,  50, 'Middle', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
+test(200,  50, 'Alphabetic', 'writing-mode:horizontal-tb', Math.PI/2, 'alphabetic');
+test(250,  50, 'Bottom', 'writing-mode:horizontal-tb', Math.PI/2, 'bottom');
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1090168-3.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script type="text/javascript">
+var testFont = '40px sans-serif';
+
+function test(x, y, text, style, rotation, baseline) {
+  var canvas = document.createElement("canvas");
+  canvas.width = 400;
+  canvas.height = 400;
+  canvas.style.cssText = 'position:absolute;' + style;
+  document.getElementsByTagName('body')[0].appendChild(canvas);
+  var ctx = canvas.getContext('2d');
+  ctx.globalAlpha = 0.5;
+  ctx.beginPath();
+  ctx.moveTo(x - 20, y); ctx.lineTo(x + 20, y);
+  ctx.moveTo(x, y - 20); ctx.lineTo(x, y + 20);
+  ctx.stroke();
+  ctx.globalAlpha = 1.0;
+  ctx.font = testFont;
+  if (rotation != 0) {
+    ctx.translate(x,y);
+    ctx.rotate(rotation);
+    ctx.translate(-x,-y);
+  }
+  if (baseline != '') {
+    ctx.textBaseline = baseline;
+  }
+  ctx.fillText(text, x, y);
+}
+
+// Testcase: vertical text with various textBaselines
+test(100,  50, 'Top', 'writing-mode:vertical-lr', 0, 'top');
+test(150,  50, 'Middle', 'writing-mode:vertical-lr', 0, 'middle');
+test(200,  50, 'Alphabetic', 'writing-mode:vertical-lr', 0, 'alphabetic');
+test(250,  50, 'Bottom', 'writing-mode:vertical-lr', 0, 'bottom');
+
+// Reference: horizontal text with 90° rotation and the same baselines
+// test(100,  50, 'Top', 'writing-mode:horizontal-tb', Math.PI/2, 'top');
+// test(150,  50, 'Middle', 'writing-mode:horizontal-tb', Math.PI/2, 'middle');
+// test(200,  50, 'Alphabetic', 'writing-mode:horizontal-tb', Math.PI/2, 'alphabetic');
+// test(250,  50, 'Bottom', 'writing-mode:horizontal-tb', Math.PI/2, 'bottom');
+
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1091058-1-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+.h { writing-mode:horizontal-tb; }
+.v-lr { writing-mode:vertical-lr; }
+.v-rl { writing-mode:vertical-rl; }
+
+div {
+  width: 250px;
+  height: 250px;
+  border: 1px solid red;
+  margin: 10px;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+
+<div class="h">
+<u>方ABC方方</u><i><u>abc</u></i><u>方方方</u><b><u>xyz</u></b><u>方</u>
+</div>
+
+<div class="v-lr">
+<u>方ABC方方</u><i><u>abc</u></i><u>方方方</u><b><u>xyz</u></b><u>方</u>
+</div>
+
+<div class="v-rl">
+<u>方ABC方方</u><i><u>abc</u></i><u>方方方</u><b><u>xyz</u></b><u>方</u>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1091058-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+.h { writing-mode:horizontal-tb; }
+.v-lr { writing-mode:vertical-lr; }
+.v-rl { writing-mode:vertical-rl; }
+
+div {
+  width: 250px;
+  height: 250px;
+  border: 1px solid red;
+  margin: 10px;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+
+<div class="h">
+<u>方ABC方方<i>abc</i>方方方<b>xyz</b>方</u>
+</div>
+
+<div class="v-lr">
+<u>方ABC方方<i>abc</i>方方方<b>xyz</b>方</u>
+</div>
+
+<div class="v-rl">
+<u>方ABC方方<i>abc</i>方方方<b>xyz</b>方</u>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1094914-1-ref.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+#outer {
+  margin: 0px;
+  background: #ddd;
+  font-size: 0;
+  width: 300px;
+}
+
+#test1 {
+  writing-mode: horizontal-tb;
+
+  height: 50px;
+  width: 100px;
+
+  margin-top: 10px;
+  margin-bottom: 0px;
+  margin-left: 50px;
+  margin-right: 0px;
+
+  border-top: 10px solid blue;
+  border-left: 20px solid red;
+  border-right: 30px solid green;
+  border-bottom: 40px solid gray;
+
+  display: inline-block;
+  background: yellow;
+}
+</style>
+</head>
+
+<body>
+
+<div id="outer">
+  <div id="test1">
+  </div>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1094914-1a.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+#outer {
+  margin: 0px;
+  background: #ddd;
+  font-size: 0;
+  width: 300px;
+}
+
+#test1 {
+  writing-mode: vertical-lr;
+
+  height: 50px;
+  width: 100px;
+
+  margin-top: 10px;
+  margin-bottom: 0px;
+  margin-left: 50px;
+  margin-right: 0px;
+
+  border-top: 10px solid blue;
+  border-left: 20px solid red;
+  border-right: 30px solid green;
+  border-bottom: 40px solid gray;
+
+  display: inline-block;
+  background: yellow;
+}
+</style>
+</head>
+
+<body>
+
+<div id="outer">
+  <div id="test1">
+  </div>
+</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1094914-1b.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+#outer {
+  margin: 0px;
+  background: #ddd;
+  font-size: 0;
+  width: 300px;
+}
+
+#test1 {
+  writing-mode: vertical-rl;
+
+  height: 50px;
+  width: 100px;
+
+  margin-top: 10px;
+  margin-bottom: 0px;
+  margin-left: 50px;
+  margin-right: 0px;
+
+  border-top: 10px solid blue;
+  border-left: 20px solid red;
+  border-right: 30px solid green;
+  border-bottom: 40px solid gray;
+
+  display: inline-block;
+  background: yellow;
+}
+</style>
+</head>
+
+<body>
+
+<div id="outer">
+  <div id="test1">
+  </div>
+</div>
+
+</body>
+</html>
--- a/layout/reftests/writing-mode/reftest.list
+++ b/layout/reftests/writing-mode/reftest.list
@@ -1,8 +1,19 @@
 # This directory contains tests for vertical text and logical layout coordinates
 # It should not be included in layout/reftests/reftest.list until vertical layout
 # is turned on
 == 1082844.html 1082844-ref.html
 == 1083748.html 1083748-ref.html
 == 1083892-1.html 1083892-1-ref.html
 == 1086883-1a.html 1086883-1-ref.html
 == 1086883-1b.html 1086883-1-ref.html
+== 1088025-1.html 1088025-1-ref.html
+== 1089388-1.html 1089388-1-ref.html
+== 1089388-2.html 1089388-2-ref.html
+== 1090159-1.html 1090159-1-ref.html
+== 1090168-1.html 1090168-1-ref.html
+!= 1090168-1.html 1090168-1-notref.html
+== 1090168-2.html 1090168-2-ref.html
+== 1090168-3.html 1090168-3-ref.html
+== 1091058-1.html 1091058-1-ref.html
+== 1094914-1a.html 1094914-1-ref.html
+== 1094914-1b.html 1094914-1-ref.html
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -283,17 +283,17 @@ GetMetricsFor(nsPresContext* aPresContex
   font.size = aFontSize;
   gfxUserFontSet *fs = nullptr;
   if (aUseUserFontSet) {
     fs = aPresContext->GetUserFontSet();
   }
   gfxTextPerfMetrics *tp = aPresContext->GetTextPerfMetrics();
   gfxFont::Orientation orientation = gfxFont::eHorizontal;
   if (aStyleContext) {
-    WritingMode wm(aStyleContext->StyleVisibility());
+    WritingMode wm(aStyleContext);
     if (wm.IsVertical()) {
       orientation = gfxFont::eVertical;
     }
   }
   nsRefPtr<nsFontMetrics> fm;
   aPresContext->DeviceContext()->GetMetricsFor(font,
                                                aStyleFont->mLanguage,
                                                orientation,
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -1355,30 +1355,47 @@ nsStyleSet::RuleNodeWithReplacement(Elem
                                     nsCSSPseudoElements::Type aPseudoType,
                                     nsRestyleHint aReplacements)
 {
   NS_ABORT_IF_FALSE(!(aReplacements & ~(eRestyle_CSSTransitions |
                                         eRestyle_CSSAnimations |
                                         eRestyle_SVGAttrAnimations |
                                         eRestyle_StyleAttribute |
                                         eRestyle_ChangeAnimationPhase |
+                                        eRestyle_ChangeAnimationPhaseDescendants |
                                         eRestyle_Force |
                                         eRestyle_ForceDescendants)),
                     // FIXME: Once bug 979133 lands we'll have a better
                     // way to print these.
                     nsPrintfCString("unexpected replacement bits 0x%lX",
                                     uint32_t(aReplacements)).get());
 
   // If we're changing animation phase, we have to reconsider what rules
   // are in these four levels.
-  if (aReplacements & eRestyle_ChangeAnimationPhase) {
-    aReplacements |= eRestyle_CSSTransitions |
-                     eRestyle_CSSAnimations |
-                     eRestyle_SVGAttrAnimations |
-                     eRestyle_StyleAttribute;
+  if (aReplacements & (eRestyle_ChangeAnimationPhase |
+                       eRestyle_ChangeAnimationPhaseDescendants)) {
+    // Animations are only on elements and on :before and :after
+    // pseudo-elements, so those are the only things we need to consider
+    // when changing animation phase.  Furthermore, the :before and
+    // :after pseudo-elements cannot have style attributes (although
+    // some other pseudo-elements can).  This lets us avoid the problem
+    // that the eRestyle_StyleAttribute case below can't handle
+    // pseudo-elements, but not adding that bit to aReplacements for
+    // pseudo-elements, since we don't need it.
+    if (aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
+      aReplacements |= eRestyle_CSSTransitions |
+                       eRestyle_CSSAnimations |
+                       eRestyle_SVGAttrAnimations |
+                       eRestyle_StyleAttribute;
+    } else if (aPseudoType == nsCSSPseudoElements::ePseudo_before ||
+               aPseudoType == nsCSSPseudoElements::ePseudo_after) {
+      aReplacements |= eRestyle_CSSTransitions |
+                       eRestyle_CSSAnimations |
+                       eRestyle_SVGAttrAnimations;
+    }
   }
 
   // FIXME (perf): This should probably not rebuild the whole path, but
   // only the path from the last change in the rule tree, like
   // ReplaceAnimationRule in nsStyleSet.cpp does.  (That could then
   // perhaps share this code, too?)
   // But if we do that, we'll need to pass whether we are rebuilding the
   // rule tree from ElementRestyler::RestyleSelf to avoid taking that
@@ -1430,30 +1447,32 @@ nsStyleSet::RuleNodeWithReplacement(Elem
               aPseudoType == nsCSSPseudoElements::ePseudo_before ||
               aPseudoType == nsCSSPseudoElements::ePseudo_after) {
             PresContext()->TransitionManager()->
               WalkTransitionRule(aElement, aPseudoType, &ruleWalker);
           }
           break;
         }
         case eRestyle_SVGAttrAnimations: {
-          MOZ_ASSERT(aReplacements & eRestyle_ChangeAnimationPhase,
+          MOZ_ASSERT(aReplacements & (eRestyle_ChangeAnimationPhase |
+                                      eRestyle_ChangeAnimationPhaseDescendants),
                      "don't know how to do this level without phase change");
 
           SVGAttrAnimationRuleProcessor* ruleProcessor =
             static_cast<SVGAttrAnimationRuleProcessor*>(
               mRuleProcessors[eSVGAttrAnimationSheet].get());
           if (ruleProcessor &&
               aPseudoType == nsCSSPseudoElements::ePseudo_NotPseudoElement) {
             ruleProcessor->ElementRulesMatching(aElement, &ruleWalker);
           }
           break;
         }
         case eRestyle_StyleAttribute: {
-          MOZ_ASSERT(aReplacements & eRestyle_ChangeAnimationPhase,
+          MOZ_ASSERT(aReplacements & (eRestyle_ChangeAnimationPhase |
+                                      eRestyle_ChangeAnimationPhaseDescendants),
                      "don't know how to do this level without phase change");
 
           if (!level->mIsImportant) {
             // First time through, we handle the non-!important rule.
             MOZ_ASSERT(aPseudoType ==
                          nsCSSPseudoElements::ePseudo_NotPseudoElement,
                        "this code doesn't know how to replace "
                        "pseudo-element rules");
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -244,8 +244,10 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_bug732209.html]
 support-files = bug732209-css.sjs
 [test_bug795520.html]
 [test_background_blend_mode.html]
 [test_property_database.html]
 [test_counter_style.html]
 [test_counter_descriptor_storage.html]
 [test_position_float_display.html]
+[test_animations_async_tests.html]
+support-files = ../../reftests/fonts/Ahem.ttf
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_animations_async_tests.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1086937</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="animation_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <style>
+  /* must use implicit value at one end */
+  @keyframes slide-left { from { margin-left: -1000px } }
+  </style>
+  <script type="application/javascript">
+
+  SimpleTest.waitForExplicitFinish();
+
+  var gDisplay;
+
+  function run() {
+    gDisplay = document.getElementById("display");
+    setTimeout(test1, 0);
+  }
+
+  /*
+   * Bug 1086937 - Animations continue correctly across load of
+   * downloadable font.
+   */
+  function test1() {
+    var animdiv = document.createElement("div");
+    animdiv.style.animation = "slide-left 100s linear"; // 10px per second
+    gDisplay.appendChild(animdiv);
+    var cs = getComputedStyle(animdiv, "");
+    advance_clock(0);
+    is(cs.marginLeft, "-1000px", "initial value of animation (force flush)");
+    advance_clock(1000);
+    is(cs.marginLeft, "-990px", "value of animation before font load");
+
+    var font = new FontFace("DownloadedAhem", "url(Ahem.ttf)");
+    document.fonts.add(font);
+
+    var fontdiv = document.createElement("div");
+    fontdiv.appendChild(document.createTextNode("A"));
+    fontdiv.style.fontFamily = "DownloadedAhem";
+    gDisplay.appendChild(fontdiv);
+
+    font.load().then(function(loadedFace) {
+      is(cs.marginLeft, "-990px", "value of animation after font load " +
+                                  "(clock only advances when we say so)");
+      advance_clock(1000);
+      is(cs.marginLeft, "-980px",
+         "animation should still be advancing after font load");
+
+      SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
+      document.fonts.delete(font);
+      animdiv.remove();
+      fontdiv.remove();
+
+      SimpleTest.finish();
+    });
+  }
+
+  </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1086937">Mozilla Bug 1086937</a>
+<div id="display"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -1544,17 +1544,17 @@ private:
 
   PointerIdMap mIdMap;
   uint32_t mNextId;
   static const size_t kIdBufLen = 16;
   char mIdBuf[kIdBufLen];
 };
 
 static void
-AnalyzeReportsImpl(JSONWriter& aWriter)
+AnalyzeReportsImpl(UniquePtr<JSONWriteFunc> aWriter)
 {
   if (!gIsDMDRunning) {
     return;
   }
 
   AutoBlockIntercepts block(Thread::Fetch());
   AutoLockState lock;
 
@@ -1567,104 +1567,105 @@ AnalyzeReportsImpl(JSONWriter& aWriter)
   PointerSet usedPcs;
   usedPcs.init(512);
 
   size_t iscSize;
 
   static int analysisCount = 1;
   StatusMsg("Dump %d {\n", analysisCount++);
 
-  aWriter.Start();
+  JSONWriter writer(Move(aWriter));
+  writer.Start();
   {
-    aWriter.IntProperty("version", kOutputVersionNumber);
+    writer.IntProperty("version", kOutputVersionNumber);
 
-    aWriter.StartObjectProperty("invocation");
+    writer.StartObjectProperty("invocation");
     {
-      aWriter.StringProperty("dmdEnvVar", gOptions->DMDEnvVar());
-      aWriter.IntProperty("sampleBelowSize", gOptions->SampleBelowSize());
+      writer.StringProperty("dmdEnvVar", gOptions->DMDEnvVar());
+      writer.IntProperty("sampleBelowSize", gOptions->SampleBelowSize());
     }
-    aWriter.EndObject();
+    writer.EndObject();
 
     StatusMsg("  Constructing the heap block list...\n");
 
     ToIdStringConverter isc;
 
-    aWriter.StartArrayProperty("blockList");
+    writer.StartArrayProperty("blockList");
     {
       for (BlockTable::Range r = gBlockTable->all(); !r.empty(); r.popFront()) {
         const Block& b = r.front();
         b.AddStackTracesToTable(usedStackTraces);
 
-        aWriter.StartObjectElement(aWriter.SingleLineStyle);
+        writer.StartObjectElement(writer.SingleLineStyle);
         {
           if (!b.IsSampled()) {
-            aWriter.IntProperty("req", b.ReqSize());
+            writer.IntProperty("req", b.ReqSize());
             if (b.SlopSize() > 0) {
-              aWriter.IntProperty("slop", b.SlopSize());
+              writer.IntProperty("slop", b.SlopSize());
             }
           }
-          aWriter.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
+          writer.StringProperty("alloc", isc.ToIdString(b.AllocStackTrace()));
           if (b.NumReports() > 0) {
-            aWriter.StartArrayProperty("reps");
+            writer.StartArrayProperty("reps");
             {
               if (b.ReportStackTrace1()) {
-                aWriter.StringElement(isc.ToIdString(b.ReportStackTrace1()));
+                writer.StringElement(isc.ToIdString(b.ReportStackTrace1()));
               }
               if (b.ReportStackTrace2()) {
-                aWriter.StringElement(isc.ToIdString(b.ReportStackTrace2()));
+                writer.StringElement(isc.ToIdString(b.ReportStackTrace2()));
               }
             }
-            aWriter.EndArray();
+            writer.EndArray();
           }
         }
-        aWriter.EndObject();
+        writer.EndObject();
       }
     }
-    aWriter.EndArray();
+    writer.EndArray();
 
     StatusMsg("  Constructing the stack trace table...\n");
 
-    aWriter.StartObjectProperty("traceTable");
+    writer.StartObjectProperty("traceTable");
     {
       for (StackTraceSet::Enum e(usedStackTraces); !e.empty(); e.popFront()) {
         const StackTrace* const st = e.front();
-        aWriter.StartArrayProperty(isc.ToIdString(st), aWriter.SingleLineStyle);
+        writer.StartArrayProperty(isc.ToIdString(st), writer.SingleLineStyle);
         {
           for (uint32_t i = 0; i < st->Length(); i++) {
             const void* pc = st->Pc(i);
-            aWriter.StringElement(isc.ToIdString(pc));
+            writer.StringElement(isc.ToIdString(pc));
             usedPcs.put(pc);
           }
         }
-        aWriter.EndArray();
+        writer.EndArray();
       }
     }
-    aWriter.EndObject();
+    writer.EndObject();
 
     StatusMsg("  Constructing the stack frame table...\n");
 
-    aWriter.StartObjectProperty("frameTable");
+    writer.StartObjectProperty("frameTable");
     {
       static const size_t locBufLen = 1024;
       char locBuf[locBufLen];
 
       for (PointerSet::Enum e(usedPcs); !e.empty(); e.popFront()) {
         const void* const pc = e.front();
 
         // Use 0 for the frame number. See the JSON format description comment
         // in DMD.h to understand why.
         locService->GetLocation(0, pc, locBuf, locBufLen);
-        aWriter.StringProperty(isc.ToIdString(pc), locBuf);
+        writer.StringProperty(isc.ToIdString(pc), locBuf);
       }
     }
-    aWriter.EndObject();
+    writer.EndObject();
 
     iscSize = isc.sizeOfExcludingThis(MallocSizeOf);
   }
-  aWriter.End();
+  writer.End();
 
   if (gOptions->ShowDumpStats()) {
     Sizes sizes;
     SizeOfInternal(&sizes);
 
     static const size_t kBufLen = 64;
     char buf1[kBufLen];
     char buf2[kBufLen];
@@ -1722,19 +1723,19 @@ AnalyzeReportsImpl(JSONWriter& aWriter)
   }
 
   InfallibleAllocPolicy::delete_(locService);
 
   StatusMsg("}\n");
 }
 
 MOZ_EXPORT void
-AnalyzeReports(JSONWriter& aWriter)
+AnalyzeReports(UniquePtr<JSONWriteFunc> aWriter)
 {
-  AnalyzeReportsImpl(aWriter);
+  AnalyzeReportsImpl(Move(aWriter));
   ClearReports();
 }
 
 //---------------------------------------------------------------------------
 // Testing
 //---------------------------------------------------------------------------
 
 MOZ_EXPORT void
--- a/memory/replace/dmd/DMD.h
+++ b/memory/replace/dmd/DMD.h
@@ -5,20 +5,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DMD_h___
 #define DMD_h___
 
 #include <string.h>
 
 #include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
 
 namespace mozilla {
 
-class JSONWriter;
+class JSONWriteFunc;
 
 namespace dmd {
 
 // Mark a heap block as reported by a memory reporter.
 MOZ_EXPORT void
 Report(const void* aPtr);
 
 // Mark a heap block as reported immediately on allocation.
@@ -116,17 +117,17 @@ ClearReports();
 //     "D": "#00: foo (Foo.cpp:123)",
 //     "E": "#00: bar (Bar.cpp:234)",
 //     "F": "#00: baz (Baz.cpp:345)",
 //     "G": "#00: quux (Quux.cpp:456)",
 //     "H": "#00: quuux (Quux.cpp:567)"
 //   }
 // }
 MOZ_EXPORT void
-AnalyzeReports(mozilla::JSONWriter& aWriter);
+AnalyzeReports(mozilla::UniquePtr<mozilla::JSONWriteFunc>);
 
 struct Sizes
 {
   size_t mStackTracesUsed;
   size_t mStackTracesUnused;
   size_t mStackTraceTable;
   size_t mBlockTable;
 
--- a/memory/replace/dmd/test/SmokeDMD.cpp
+++ b/memory/replace/dmd/test/SmokeDMD.cpp
@@ -114,18 +114,17 @@ RunTests()
 
   // The file manipulations above may have done some heap allocations.
   // Clear all knowledge of existing blocks to give us a clean slate.
   ClearBlocks();
 
   //---------
 
   // AnalyzeReports 1.  Zero for everything.
-  JSONWriter writer1(Move(f1));
-  AnalyzeReports(writer1);
+  AnalyzeReports(Move(f1));
 
   //---------
 
   // AnalyzeReports 2: 1 freed, 9 out of 10 unreported.
   // AnalyzeReports 3: still present and unreported.
   int i;
   char* a = nullptr;
   for (i = 0; i < seven + 3; i++) {
@@ -241,35 +240,33 @@ RunTests()
 //void* y = valloc(1);                  // rounds up to 4096
 //UseItOrLoseIt(y, seven);
 
   // XXX: C11 only
 //void* z = aligned_alloc(64, 256);
 //UseItOrLoseIt(z, seven);
 
   // AnalyzeReports 2.
-  JSONWriter writer2(Move(f2));
-  AnalyzeReports(writer2);
+  AnalyzeReports(Move(f2));
 
   //---------
 
   Report(a2);
   Report(a2);
   free(c);
   free(e);
   Report(e2);
   free(e3);
 //free(w);
 //free(x);
 //free(y);
 //free(z);
 
   // AnalyzeReports 3.
-  JSONWriter writer3(Move(f3));
-  AnalyzeReports(writer3);
+  AnalyzeReports(Move(f3));
 
   //---------
 
   // The first part of this test requires sampling to be disabled.
   SetSampleBelowSize(128);
 
   // Clear all knowledge of existing blocks to give us a clean slate.
   ClearBlocks();
@@ -318,18 +315,17 @@ RunTests()
     s = (char*) malloc(i * 16);
     UseItOrLoseIt(s, seven);
   }
 
   // At the end we're 64 bytes into the current sample so we report ~1,424
   // bytes of allocation overall, which is 64 less than the real value 1,488.
 
   // AnalyzeReports 4.
-  JSONWriter writer4(Move(f4));
-  AnalyzeReports(writer4);
+  AnalyzeReports(Move(f4));
 }
 
 int main()
 {
   RunTests();
 
   return 0;
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -419,19 +419,24 @@ pref("media.mediasource.enabled", false)
 #else
 pref("media.mediasource.enabled", true);
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 pref("media.mediasource.mp4.enabled", false);
 pref("media.mediasource.webm.enabled", false);
 #else
+#ifdef XP_WIN
+pref("media.mediasource.mp4.enabled", true);
+pref("media.mediasource.webm.enabled", false);
+#else
 pref("media.mediasource.mp4.enabled", false);
 pref("media.mediasource.webm.enabled", true);
 #endif
+#endif
 
 #ifdef MOZ_WEBSPEECH
 pref("media.webspeech.recognition.enable", false);
 pref("media.webspeech.synth.enabled", false);
 #endif
 #ifdef MOZ_WEBM_ENCODER
 pref("media.encoder.webm.enabled", true);
 #endif
@@ -1651,21 +1656,17 @@ pref("network.proxy.ssl_port",          
 pref("network.proxy.socks",                 "");
 pref("network.proxy.socks_port",            0);
 pref("network.proxy.socks_version",         5);
 pref("network.proxy.socks_remote_dns",      false);
 pref("network.proxy.proxy_over_tls",        true);
 pref("network.proxy.no_proxies_on",         "localhost, 127.0.0.1");
 pref("network.proxy.failover_timeout",      1800); // 30 minutes
 pref("network.online",                      true); //online/offline
-#ifdef RELEASE_BUILD
 pref("network.cookie.cookieBehavior",       0); // 0-Accept, 1-dontAcceptForeign, 2-dontUse, 3-limitForeign
-#else
-pref("network.cookie.cookieBehavior",       3); // 0-Accept, 1-dontAcceptForeign, 2-dontUse, 3-limitForeign
-#endif
 #ifdef ANDROID
 pref("network.cookie.cookieBehavior",       0); // Keep the old default of accepting all cookies
 #endif
 pref("network.cookie.thirdparty.sessionOnly", false);
 pref("network.cookie.lifetimePolicy",       0); // accept normally, 1-askBeforeAccepting, 2-acceptForSession,3-acceptForNDays
 pref("network.cookie.alwaysAcceptSessionCookies", false);
 pref("network.cookie.prefsMigrated",        false);
 pref("network.cookie.lifetime.days",        90);
--- a/netwerk/sctp/src/moz.build
+++ b/netwerk/sctp/src/moz.build
@@ -26,17 +26,16 @@ SOURCES += [
     'netinet/sctp_timer.c',
     'netinet/sctp_userspace.c',
     'netinet/sctp_usrreq.c',
     'netinet/sctputil.c',
     'netinet6/sctp6_usrreq.c',
     'user_environment.c',
     'user_mbuf.c',
     'user_recv_thread.c',
-    'user_sctp_timer_iterate.c',
     'user_socket.c',
 ]
 
 if CONFIG['OS_TARGET'] == 'Android':
     SOURCES += [
         'ifaddrs_android.cpp',
     ]
 
--- a/netwerk/sctp/src/netinet/sctp_callout.c
+++ b/netwerk/sctp/src/netinet/sctp_callout.c
@@ -25,19 +25,37 @@
  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
+#if defined(__Userspace__)
+#include <sys/types.h>
+#if !defined (__Userspace_os_Windows)
+#include <sys/wait.h>
+#include <unistd.h>
+#include <pthread.h>
+#endif
+#if defined(__Userspace_os_NaCl)
+#include <sys/select.h>
+#endif
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#else
 #include <netinet/sctp_os.h>
 #include <netinet/sctp_callout.h>
 #include <netinet/sctp_pcb.h>
+#endif
 
 /*
  * Callout/Timer routines for OS that doesn't have them
  */
 #if defined(__APPLE__) || defined(__Userspace__)
 int ticks = 0;
 #else
 extern int ticks;
@@ -112,34 +130,26 @@ sctp_os_timer_stop(sctp_os_timer_t *c)
 	if (c == sctp_os_timer_next) {
 		sctp_os_timer_next = TAILQ_NEXT(c, tqe);
 	}
 	TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe);
 	SCTP_TIMERQ_UNLOCK();
 	return (1);
 }
 
-#if defined(__APPLE__)
-/*
- * For __APPLE__, use a single main timer at a faster resolution than
- * fastim.  The timer just calls this existing callout infrastructure.
- */
-#endif
-void
-sctp_timeout(void *arg SCTP_UNUSED)
+static void
+sctp_handle_tick(int delta)
 {
 	sctp_os_timer_t *c;
 	void (*c_func)(void *);
 	void *c_arg;
 
 	SCTP_TIMERQ_LOCK();
-#if defined(__APPLE__)
 	/* update our tick count */
-	ticks += SCTP_BASE_VAR(sctp_main_timer_ticks);
-#endif
+	ticks += delta;
 	c = TAILQ_FIRST(&SCTP_BASE_INFO(callqueue));
 	while (c) {
 		if (c->c_time <= ticks) {
 			sctp_os_timer_next = TAILQ_NEXT(c, tqe);
 			TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe);
 			c_func = c->c_func;
 			c_arg = c->c_arg;
 			c->c_flags &= ~SCTP_CALLOUT_PENDING;
@@ -150,14 +160,65 @@ sctp_timeout(void *arg SCTP_UNUSED)
 			sctp_os_timer_current = NULL;
 			c = sctp_os_timer_next;
 		} else {
 			c = TAILQ_NEXT(c, tqe);
 		}
 	}
 	sctp_os_timer_next = NULL;
 	SCTP_TIMERQ_UNLOCK();
+}
 
 #if defined(__APPLE__)
-	/* restart the main timer */
+void
+sctp_timeout(void *arg SCTP_UNUSED)
+{
+	sctp_handle_tick(SCTP_BASE_VAR(sctp_main_timer_ticks));
 	sctp_start_main_timer();
+}
+#endif
+
+#if defined(__Userspace__)
+#define TIMEOUT_INTERVAL 10
+
+void *
+user_sctp_timer_iterate(void *arg)
+{
+	for (;;) {
+#if defined (__Userspace_os_Windows)
+		Sleep(TIMEOUT_INTERVAL);
+#else
+		struct timeval timeout;
+
+		timeout.tv_sec  = 0;
+		timeout.tv_usec = 1000 * TIMEOUT_INTERVAL;
+		select(0, NULL, NULL, NULL, &timeout);
+#endif
+		if (SCTP_BASE_VAR(timer_thread_should_exit)) {
+			break;
+		}
+		sctp_handle_tick(MSEC_TO_TICKS(TIMEOUT_INTERVAL));
+	}
+	return (NULL);
+}
+
+void
+sctp_start_timer(void)
+{
+	/*
+	 * No need to do SCTP_TIMERQ_LOCK_INIT();
+	 * here, it is being done in sctp_pcb_init()
+	 */
+#if defined (__Userspace_os_Windows)
+	if ((SCTP_BASE_VAR(timer_thread) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)user_sctp_timer_iterate, NULL, 0, NULL)) == NULL) {
+		SCTP_PRINTF("ERROR; Creating ithread failed\n");
+	}
+#else
+	int rc;
+
+	rc = pthread_create(&SCTP_BASE_VAR(timer_thread), NULL, user_sctp_timer_iterate, NULL);
+	if (rc) {
+		SCTP_PRINTF("ERROR; return code from pthread_create() is %d\n", rc);
+	}
 #endif
 }
+
+#endif
--- a/netwerk/sctp/src/netinet/sctp_callout.h
+++ b/netwerk/sctp/src/netinet/sctp_callout.h
@@ -59,17 +59,16 @@
 #else
 #define SCTP_TIMERQ_LOCK()          (void)pthread_mutex_lock(&SCTP_BASE_VAR(timer_mtx))
 #define SCTP_TIMERQ_UNLOCK()        (void)pthread_mutex_unlock(&SCTP_BASE_VAR(timer_mtx))
 #define SCTP_TIMERQ_LOCK_INIT()     (void)pthread_mutex_init(&SCTP_BASE_VAR(timer_mtx), NULL)
 #define SCTP_TIMERQ_LOCK_DESTROY()  (void)pthread_mutex_destroy(&SCTP_BASE_VAR(timer_mtx))
 #endif
 
 extern int ticks;
-extern void sctp_start_timer();
 #endif
 
 TAILQ_HEAD(calloutlist, sctp_callout);
 
 struct sctp_callout {
 	TAILQ_ENTRY(sctp_callout) tqe;
 	int c_time;		/* ticks to the event */
 	void *c_arg;		/* function argument */
@@ -89,11 +88,16 @@ int sctp_os_timer_stop(sctp_os_timer_t *
 #define SCTP_OS_TIMER_START	sctp_os_timer_start
 #define SCTP_OS_TIMER_STOP	sctp_os_timer_stop
 /* MT FIXME: Is the following correct? */
 #define SCTP_OS_TIMER_STOP_DRAIN SCTP_OS_TIMER_STOP
 #define	SCTP_OS_TIMER_PENDING(tmr) ((tmr)->c_flags & SCTP_CALLOUT_PENDING)
 #define	SCTP_OS_TIMER_ACTIVE(tmr) ((tmr)->c_flags & SCTP_CALLOUT_ACTIVE)
 #define	SCTP_OS_TIMER_DEACTIVATE(tmr) ((tmr)->c_flags &= ~SCTP_CALLOUT_ACTIVE)
 
+#if defined(__Userspace__)
+void sctp_start_timer(void);
+#endif
+#if defined(__APPLE__)
 void sctp_timeout(void *);
+#endif
 
 #endif
--- a/netwerk/sctp/src/netinet/sctp_usrreq.c
+++ b/netwerk/sctp/src/netinet/sctp_usrreq.c
@@ -51,16 +51,19 @@
 #include <netinet/sctp_output.h>
 #include <netinet/sctp_uio.h>
 #include <netinet/sctp_asconf.h>
 #include <netinet/sctputil.h>
 #include <netinet/sctp_indata.h>
 #include <netinet/sctp_timer.h>
 #include <netinet/sctp_auth.h>
 #include <netinet/sctp_bsd_addr.h>
+#if defined(__Userspace__)
+#include <netinet/sctp_callout.h>
+#endif
 #if !defined(__Userspace_os_Windows)
 #include <netinet/udp.h>
 #endif
 
 #if defined(HAVE_SCTP_PEELOFF_SOCKOPT)
 #include <netinet/sctp_peeloff.h>
 #endif				/* HAVE_SCTP_PEELOFF_SOCKOPT */
 
deleted file mode 100755
--- a/netwerk/sctp/src/user_sctp_timer_iterate.c
+++ /dev/null
@@ -1,119 +0,0 @@
-/*-
- * Copyright (c) 2012 Michael Tuexen
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- *
- */
-
-#include <sys/types.h>
-#if !defined (__Userspace_os_Windows)
-#include <sys/wait.h>
-#include <unistd.h>
-#include <pthread.h>
-#endif
-#include <stdlib.h>
-#include <string.h>
-#include <stdio.h>
-#include <errno.h>
-#include <netinet/sctp_pcb.h>
-#include <netinet/sctp_sysctl.h>
-#include "netinet/sctp_callout.h"
-
-/* This is the polling time of callqueue in milliseconds
- * 10ms seems to work well. 1ms was giving erratic behavior
- */
-#define TIMEOUT_INTERVAL 10
-
-extern int ticks;
-
-void *
-user_sctp_timer_iterate(void *arg)
-{
-	sctp_os_timer_t *c;
-	void (*c_func)(void *);
-	void *c_arg;
-	sctp_os_timer_t *sctp_os_timer_next;
-	/*
-	 * The MSEC_TO_TICKS conversion depends on hz. The to_ticks in
-	 * sctp_os_timer_start also depends on hz. E.g. if hz=1000 then
-	 * for multiple INIT the to_ticks is 2000, 4000, 8000, 16000, 32000, 60000
-	 * and further to_ticks level off at 60000 i.e. 60 seconds.
-	 * If hz=100 then for multiple INIT the to_ticks are 200, 400, 800 and so-on.
-	 */
-	for (;;) {
-#if defined (__Userspace_os_Windows)
-		Sleep(TIMEOUT_INTERVAL);
-#else
-		struct timeval timeout;
-
-		timeout.tv_sec  = 0;
-		timeout.tv_usec = 1000 * TIMEOUT_INTERVAL;
-		select(0, NULL, NULL, NULL, &timeout);
-#endif
-		if (SCTP_BASE_VAR(timer_thread_should_exit)) {
-			break;
-		}
-		SCTP_TIMERQ_LOCK();
-		/* update our tick count */
-		ticks += MSEC_TO_TICKS(TIMEOUT_INTERVAL);
-		c = TAILQ_FIRST(&SCTP_BASE_INFO(callqueue));
-		while (c) {
-			if (c->c_time <= ticks) {
-				sctp_os_timer_next = TAILQ_NEXT(c, tqe);
-				TAILQ_REMOVE(&SCTP_BASE_INFO(callqueue), c, tqe);
-				c_func = c->c_func;
-				c_arg = c->c_arg;
-				c->c_flags &= ~SCTP_CALLOUT_PENDING;
-				SCTP_TIMERQ_UNLOCK();
-				c_func(c_arg);
-				SCTP_TIMERQ_LOCK();
-				c = sctp_os_timer_next;
-			} else {
-				c = TAILQ_NEXT(c, tqe);
-			}
-		}
-		SCTP_TIMERQ_UNLOCK();
-	}
-	return (NULL);
-}
-
-void
-sctp_start_timer(void)
-{
-	/*
-	 * No need to do SCTP_TIMERQ_LOCK_INIT();
-	 * here, it is being done in sctp_pcb_init()
-	 */
-#if defined (__Userspace_os_Windows)
-	if ((SCTP_BASE_VAR(timer_thread) = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)user_sctp_timer_iterate, NULL, 0, NULL)) == NULL) {
-		SCTP_PRINTF("ERROR; Creating ithread failed\n");
-	}
-#else
-	int rc;
-
-	rc = pthread_create(&SCTP_BASE_VAR(timer_thread), NULL, user_sctp_timer_iterate, NULL);
-	if (rc) {
-		SCTP_PRINTF("ERROR; return code from pthread_create() is %d\n", rc);
-	}
-#endif
-}
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -151,16 +151,18 @@ user_pref("layout.css.ruby.enabled", tru
 // Enable CSS Font Loading API for testing
 user_pref("layout.css.font-loading-api.enabled", true);
 
 // Disable spammy layout warnings because they pollute test logs
 user_pref("layout.spammy_warnings.enabled", false);
 
 // Enable Media Source Extensions for testing
 user_pref("media.mediasource.enabled", true);
+user_pref("media.mediasource.mp4.enabled", false);
+user_pref("media.mediasource.webm.enabled", true);
 
 // Enable mozContacts
 user_pref("dom.mozContacts.enabled", true);
 
 // Enable mozSettings
 user_pref("dom.mozSettings.enabled", true);
 
 // Make sure the disk cache doesn't get auto disabled
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/meta/html/dom/documents/resource-metadata-management/document-cookie.html.ini
@@ -0,0 +1,4 @@
+[document-cookie.html]
+  type: testharness
+  [getting cookie for a cookie-averse document returns empty string, setting does nothing]
+    expected: FAIL
--- a/xpcom/base/nsMemoryInfoDumper.cpp
+++ b/xpcom/base/nsMemoryInfoDumper.cpp
@@ -806,17 +806,16 @@ nsMemoryInfoDumper::DumpDMDToFile(FILE* 
 {
   nsRefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter();
   nsresult rv = gzWriter->InitANSIFileDesc(aFile);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   // Dump DMD's memory reports analysis to the file.
-  JSONWriter jsonWriter(MakeUnique<GZWriterWrapper>(gzWriter));
-  dmd::AnalyzeReports(jsonWriter);
+  dmd::AnalyzeReports(MakeUnique<GZWriterWrapper>(gzWriter));
 
   rv = gzWriter->Finish();
   NS_WARN_IF(NS_FAILED(rv));
   return rv;
 }
 #endif  // MOZ_DMD