Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 01 May 2015 13:00:21 -0400
changeset 273371 3e1cb78ad72beeab811453987537f1bf1e087da5
parent 273370 14c8c3fd403cf01f23fef4cc255b22b06ae3155f (current diff)
parent 273354 e70555ac58d8a6b15efe667bb419726849b1cfc3 (diff)
child 273372 20d215b7378b769f47881ae815f96708d91b0a1e
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team. a=merge
testing/taskcluster/tasks/builds/b2g_aries_lightsaber_nightly.yml
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -1036,16 +1036,28 @@ GetWrapperFor(ProxyAccessible* aProxy)
 static uint16_t
 GetInterfacesForProxy(ProxyAccessible* aProxy, uint32_t aInterfaces)
 {
   uint16_t interfaces = 1 << MAI_INTERFACE_COMPONENT;
   if (aInterfaces & Interfaces::HYPERTEXT)
     interfaces |= (1 << MAI_INTERFACE_HYPERTEXT) | (1 << MAI_INTERFACE_TEXT)
         | (1 << MAI_INTERFACE_EDITABLE_TEXT);
 
+  if (aInterfaces & Interfaces::HYPERLINK)
+    interfaces |= MAI_INTERFACE_HYPERLINK_IMPL;
+
+  if (aInterfaces & Interfaces::VALUE)
+    interfaces |= MAI_INTERFACE_VALUE;
+
+  if (aInterfaces & Interfaces::TABLE)
+    interfaces |= MAI_INTERFACE_TABLE;
+
+  if (aInterfaces & Interfaces::IMAGE)
+    interfaces |= MAI_INTERFACE_IMAGE;
+
   return interfaces;
 }
 
 void
 a11y::ProxyCreated(ProxyAccessible* aProxy, uint32_t aInterfaces)
 {
   GType type = GetMaiAtkType(GetInterfacesForProxy(aProxy, aInterfaces));
   NS_ASSERTION(type, "why don't we have a type!");
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -22,16 +22,28 @@ namespace a11y {
 
 static uint32_t
 InterfacesFor(Accessible* aAcc)
 {
   uint32_t interfaces = 0;
   if (aAcc->IsHyperText() && aAcc->AsHyperText()->IsTextRole())
     interfaces |= Interfaces::HYPERTEXT;
 
+  if (aAcc->IsLink())
+    interfaces |= Interfaces::HYPERLINK;
+
+  if (aAcc->HasNumericValue())
+    interfaces |= Interfaces::VALUE;
+
+  if (aAcc->IsImage())
+    interfaces |= Interfaces::IMAGE;
+
+  if (aAcc->IsTableCell())
+    interfaces |= Interfaces::TABLECELL;
+
   return interfaces;
 }
 
 static void
 SerializeTree(Accessible* aRoot, nsTArray<AccessibleData>& aTree)
 {
   uint64_t id = reinterpret_cast<uint64_t>(aRoot->UniqueID());
   uint32_t role = aRoot->Role();
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -309,15 +309,20 @@ private:
   uintptr_t mWrapper;
   uint64_t mID;
   role mRole : 31;
   bool mOuterDoc : 1;
 };
 
 enum Interfaces
 {
-  HYPERTEXT = 1
+  HYPERTEXT = 1,
+  HYPERLINK = 2,
+  IMAGE = 4,
+  VALUE = 8,
+  TABLE = 16,
+  TABLECELL = 32,
 };
 
 }
 }
 
 #endif
--- a/accessible/windows/msaa/Compatibility.h
+++ b/accessible/windows/msaa/Compatibility.h
@@ -34,16 +34,36 @@ public:
    */
   static bool IsWE() { return !!(sConsumers & WE); }
 
   /**
    * Return true if Dolphin mode is enabled.
    */
   static bool IsDolphin() { return !!(sConsumers & DOLPHIN); }
 
+  /**
+   * Return true if we should disable e10s due to a detected
+   * accessibility client.
+   */
+  static bool IsBlacklistedForE10S()
+  {
+    // We currently blacklist everything except UNKNOWN and UIAUTOMATION
+    return !!(sConsumers &
+              (NVDA     |
+               JAWS     |
+               OLDJAWS  |
+               WE       |
+               DOLPHIN  |
+               SEROTEK  |
+               COBRA    |
+               ZOOMTEXT |
+               KAZAGURU |
+               YOUDAO));
+  }
+
 private:
   Compatibility();
   Compatibility(const Compatibility&);
   Compatibility& operator = (const Compatibility&);
 
   /**
    * Initialize compatibility mode. Called by platform (see Platform.h) during
    * accessibility initialization.
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1126,8 +1126,12 @@ pref("dom.mozSettings.SettingsService.ve
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
 // RequestSync API is enabled by default on B2G.
 pref("dom.requestSync.enabled", true);
 
 // Resample touch events on b2g
 pref("gfx.touch.resample", true);
+
+// mulet apparently loads firefox.js as well as b2g.js, so we have to explicitly
+// disable serviceworkers here to get them disabled in mulet.
+pref("dom.serviceWorkers.enabled", false);
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
@@ -129,16 +129,16 @@
   <project name="platform/external/icu4c" path="external/icu4c" revision="2bb01561780583cc37bc667f0ea79f48a122d8a2"/>
   <!-- dolphin specific things -->
   <project name="device/sprd" path="device/sprd" revision="a26ba0ab998133ad590102be1e5950818b86ce82"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="39a5b5bcadad745df3e6882316fce426f98b1669"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="8bb69db127112fc66da75f8ca7a1158614b919f6"/>
   <project name="platform/hardware/akm" path="hardware/akm" revision="6d3be412647b0eab0adff8a2768736cf4eb68039"/>
   <project groups="invensense" name="platform/hardware/invensense" path="hardware/invensense" revision="e6d9ab28b4f4e7684f6c07874ee819c9ea0002a2"/>
   <project name="platform/hardware/ril" path="hardware/ril" revision="865ce3b4a2ba0b3a31421ca671f4d6c5595f8690"/>
-  <project name="kernel/common" path="kernel" revision="0be9cc12cd81b145e1471016c19722429ff9285e"/>
+  <project name="kernel/common" path="kernel" revision="a2977758950865feb5980996d8e889677dea3bd4"/>
   <project name="platform/system/core" path="system/core" revision="7992618bd4ee33ce96897675a5c0a9b619122f13"/>
   <project name="u-boot" path="u-boot" revision="f1502910977ac88f43da7bf9277c3523ad4b0b2f"/>
   <project name="vendor/sprd/gps" path="vendor/sprd/gps" revision="7d6e1269be7186b2073fa568958b357826692c4b"/>
   <project name="vendor/sprd/open-source" path="vendor/sprd/open-source" revision="e503b1d14d7fdee532b8f391407299da193c1b2d"/>
   <project name="vendor/sprd/partner" path="vendor/sprd/partner" revision="8649c7145972251af11b0639997edfecabfc7c2e"/>
   <project name="vendor/sprd/proprietories" path="vendor/sprd/proprietories" revision="d2466593022f7078aaaf69026adf3367c2adb7bb"/>
 </manifest>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8c2d32bccc7061e9ca0165135457c3fd53e7107e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="173b3104bfcbd23fc9dccd4b0035fc49aae3d444">
     <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="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="9a9797062c6001d6346504161c51187a2968466b"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="8c2d32bccc7061e9ca0165135457c3fd53e7107e"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="34ea6163f9f0e0122fb0bb03607eccdca31ced7a"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="2eda36a4795012a5d1275b77ebb20ac377c8cd26">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
@@ -120,17 +120,17 @@
   <project name="platform/hardware/libhardware_legacy" path="hardware/libhardware_legacy" revision="76c4bf4bc430a1b8317f2f21ef735867733e50cc"/>
   <project name="platform/system/media" path="system/media" revision="c1332c21c608f4932a6d7e83450411cde53315ef"/>
   <!--original fetch url was git://github.com/t2m-foxfone/-->
   <remote fetch="https://git.mozilla.org/external/t2m-foxfone" name="t2m"/>
   <default remote="caf" revision="LNX.LA.3.5.2.1.1" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="1bb28abbc215f45220620af5cd60a8ac1be93722"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="2501e5940ba69ece7654ff85611c76ae5bda299c"/>
-  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="b83fc73de7b64594cd74b33e498bf08332b5d87b"/>
+  <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="a9f3f8fb8b0844724de32426b7bcc4e6dc4fa2ed"/>
   <project name="codeaurora_kernel_msm" path="kernel" remote="b2g" revision="0865bc4134b67220df4058625fba29305d6b10c3"/>
   <project name="kernel_lk" path="bootable/bootloader/lk" remote="b2g" revision="fda40423ffa573dc6cafd3780515010cb2a086be"/>
   <project name="platform_bootable_recovery" path="bootable/recovery" remote="b2g" revision="d5e53ed6f22fa06052351dc03510af9473af01ea"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="d61fc97258c8b0c362430dd2eb195dcc4d266f14"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="5b71e40213f650459e95d35b6f14af7e88d8ab62"/>
   <project name="platform_external_libnfc-nci" path="external/libnfc-nci" remote="t2m" revision="4186bdecb4dae911b39a8202252cc2310d91b0be"/>
   <project name="platform_external_libnfc-pn547" path="external/libnfc-pn547" remote="b2g" revision="5bb999b84b8adc14f6bea004d523ba258dea8188"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="65f5144987afff35a932262c0c5fad6ecce0c04a"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "759a1f935a6a81c32ad66e39a6353b334dfa4f91", 
+        "git_revision": "8e64346ce8197b50b815a294278797bc144aa3e6", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "d88d79f407d14635451063c4397724d836efcbaa", 
+    "revision": "1bbaa54b674c42e0d8b2fe31d8b0080901811fa0", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="4efd19d199ae52656604f794c5a77518400220fd">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="61e82f99bb8bc78d52b5717e9a2481ec7267fa33">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="759a1f935a6a81c32ad66e39a6353b334dfa4f91"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="8e64346ce8197b50b815a294278797bc144aa3e6"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="d3868ff4bb3a4b81382795e2784258c210fe6cb8"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1b3591a50ed352fc6ddb77462b7b35d0bfa555a3"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="adb24954bf8068f21705b570450475d183336b2d"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="5ef30994f4778b4052e58a4383dbe7890048c87e"/>
   <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="0627790166dccd8dd370fa7d9f434ed9fc027fb4"/>
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -96,27 +96,29 @@ let handleContentContextMenu = function 
 
   // Media related cache info parent needs for saving
   let contentType = null;
   let contentDisposition = null;
   if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
       event.target instanceof Ci.nsIImageLoadingContent &&
       event.target.currentURI) {
     try {
-      let imageCache = Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
-                                                       .getImgCacheForDocument(doc);
+      let imageCache = 
+        Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+                                        .getImgCacheForDocument(doc);
       let props =
         imageCache.findEntryProperties(event.target.currentURI);
-      if (props) {
+      try {
         contentType = props.get("type", Ci.nsISupportsCString).data;
-        contentDisposition = props.get("content-disposition", Ci.nsISupportsCString).data;
-      }
-    } catch (e) {
-      Cu.reportError(e);
-    }
+      } catch(e) {}
+      try {
+        contentDisposition =
+          props.get("content-disposition", Ci.nsISupportsCString).data;
+      } catch(e) {}
+    } catch(e) {}
   }
 
   let selectionInfo = BrowserUtils.getSelectionDetails(content);
 
   if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
     let editFlags = SpellCheckHelper.isEditable(event.target, content);
     let spellInfo;
     if (editFlags &
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1030,18 +1030,22 @@
         <body>
           <![CDATA[
             var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
             if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
               return;
 
             if (!aForceUpdate) {
               TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
-              window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
-                                                             .beginTabSwitch();
+              if (!Services.appinfo.browserTabsRemoteAutostart) {
+                // old way of measuring tab paint which is not
+                // valid with e10s.
+                window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+                                                               .beginTabSwitch();
+              }
             }
 
             var oldTab = this.mCurrentTab;
 
             // Preview mode should not reset the owner
             if (!this._previewMode && !oldTab.selected)
               oldTab.owner = null;
 
@@ -2980,18 +2984,16 @@
             // removed from the set upon MozAfterPaint.
             maybeVisibleTabs: new Set([this.selectedTab]),
 
             STATE_UNLOADED: 0,
             STATE_LOADING: 1,
             STATE_LOADED: 2,
             STATE_UNLOADING: 3,
 
-            logging: false,
-
             getTabState: function(tab) {
               let state = this.tabState.get(tab);
               if (state === undefined) {
                 return this.STATE_UNLOADED;
               }
               return state;
             },
 
@@ -3083,26 +3085,28 @@
               } else {
                 // Show the requested tab. If it's not available, we'll show the spinner.
                 showTab = this.requestedTab;
               }
 
               // Show or hide the spinner as needed.
               let needSpinner = this.getTabState(showTab) != this.STATE_LOADED;
               if (!needSpinner && this.spinnerTab) {
+                this.spinnerHidden();
                 this.tabbrowser.removeAttribute("pendingpaint");
                 this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 this.spinnerTab = null;
               } else if (needSpinner && this.spinnerTab !== showTab) {
                 if (this.spinnerTab) {
                   this.spinnerTab.linkedBrowser.removeAttribute("pendingpaint");
                 }
                 this.spinnerTab = showTab;
                 this.tabbrowser.setAttribute("pendingpaint", "true");
                 this.spinnerTab.linkedBrowser.setAttribute("pendingpaint", "true");
+                this.spinnerDisplayed();
               }
 
               // Switch to the tab we've decided to make visible.
               if (this.visibleTab !== showTab) {
                 this.visibleTab = showTab;
 
                 this.maybeVisibleTabs.add(showTab);
 
@@ -3275,16 +3279,17 @@
               }
             },
 
             // Fires when we paint the screen. Any tab switches we initiated
             // previously are done, so there's no need to keep the old layers
             // around.
             onPaint: function() {
               this.maybeVisibleTabs.clear();
+              this.finishTabSwitch();
             },
 
             // Called when we're done clearing the layers for a tab.
             onLayersCleared: function(browser) {
               this.logState("onLayersCleared");
 
               let tab = this.tabbrowser.getTabForBrowser(browser);
               if (tab) {
@@ -3308,16 +3313,17 @@
 
             // Called when the user asks to switch to a given tab.
             requestTab: function(tab) {
               if (tab === this.requestedTab) {
                 return;
               }
 
               this.logState("requestTab " + this.tinfo(tab));
+              this.startTabSwitch();
 
               this.requestedTab = tab;
 
               this.preActions();
 
               clearTimeout(this.unloadTimer);
               this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
 
@@ -3335,50 +3341,114 @@
                 this.onLayersCleared(event.originalTarget);
               } else if (event.type == "TabRemotenessChange") {
                 this.onRemotenessChange(event.target);
               }
 
               this.postActions();
             },
 
+            /*
+             * Telemetry related helpers for recording tab switch timing.
+             */
+
+            startTabSwitch: function () {
+              TelemetryStopwatch.start("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+            },
+
+            finishTabSwitch: function () {
+              if (this.requestedTab && this.getTabState(this.requestedTab) == this.STATE_LOADED) {
+                let time = TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+                if (time != -1) {
+                  TelemetryStopwatch.finish("FX_TAB_SWITCH_TOTAL_E10S_MS", window);
+                  this.log("DEBUG: tab switch time = " + time);
+                }
+              }
+            },
+
+            spinnerDisplayed: function () {
+              if (this.spinnerTab) {
+                TelemetryStopwatch.start("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+              }
+            },
+
+            spinnerHidden: function () {
+              if (this.spinnerTab) {
+                this.log("DEBUG: spinner time = " +
+                         TelemetryStopwatch.timeElapsed("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window));
+                TelemetryStopwatch.finish("FX_TAB_SWITCH_SPINNER_VISIBLE_MS", window);
+                // we do not get a onPaint after displaying the spinner
+                this.finishTabSwitch();
+              }
+            },
+
+            /*
+             * Debug related logging for switcher.
+             */
+
+            _useDumpForLogging: false,
+            _logInit: false,
+
+            logging: function () {
+              if (this._useDumpForLogging)
+                return true;
+              if (this._logInit)
+                return this._shouldLog;
+              let result = false;
+              try {
+                result = Services.prefs.getBoolPref("browser.tabs.remote.logSwitchTiming");
+              } catch (ex) {
+              }
+              this._shouldLog = result;
+              this._logInit = true;
+              return this._shouldLog;
+            },
+
             tinfo: function(tab) {
               if (tab) {
                 return tab._tPos + "(" + tab.linkedBrowser.currentURI.spec + ")";
               } else {
                 return "null";
               }
             },
 
             log: function(s) {
-              if (!this.logging)
+              if (!this.logging())
                 return;
-              dump(s + "\n");
+              if (this._useDumpForLogging) {
+                dump(s + "\n");
+              } else {
+                Services.console.logStringMessage(s);
+              }
             },
 
             logState: function(prefix) {
-              if (!this.logging)
+              if (!this.logging())
                 return;
 
-              dump(prefix + " ");
+              let accum = prefix + " ";
               for (let i = 0; i < this.tabbrowser.tabs.length; i++) {
                 let tab = this.tabbrowser.tabs[i];
                 let state = this.getTabState(tab);
 
-                dump(i + ":");
+                accum += i + ":";
                 if (tab === this.lastVisibleTab) dump("V");
                 if (tab === this.loadingTab) dump("L");
                 if (tab === this.requestedTab) dump("R");
                 if (state == this.STATE_LOADED) dump("(+)");
                 if (state == this.STATE_LOADING) dump("(+?)");
                 if (state == this.STATE_UNLOADED) dump("(-)");
                 if (state == this.STATE_UNLOADING) dump("(-?)");
-                dump(" ");
+                accum += " ";
               }
-              dump("\n");
+              if (this._useDumpForLogging) {
+                dump(accum + "\n");
+              } else {
+                Services.console.logStringMessage(accum);
+              }
             },
           };
           this._switcher = switcher;
           switcher.init();
           return switcher;
         ]]></body>
       </method>
 
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2806,18 +2806,17 @@ let E10SUINotification = {
 
       if (!activationNoticeShown) {
         this._showE10sActivatedNotice();
       }
 
       // e10s doesn't work with accessibility, so we prompt to disable
       // e10s if a11y is enabled, now or in the future.
       Services.obs.addObserver(this, "a11y-init-or-shutdown", true);
-      if (Services.appinfo.accessibilityEnabled &&
-          !Services.appinfo.accessibilityIsUIA) {
+      if (Services.appinfo.accessibilityIsBlacklistedForE10S) {
         this._showE10sAccessibilityWarning();
       }
     } else {
       let displayFeedbackRequest = false;
       try {
         displayFeedbackRequest = Services.prefs.getBoolPref("browser.requestE10sFeedback");
       } catch (e) {}
 
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1689,17 +1689,17 @@ richlistitem[type~="action"][actiontype=
   display: none;
 }
 
 /* All tabs menupopup */
 .alltabs-item > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 
-.alltabs-item[visuallyselected="true"] {
+.alltabs-item[selected="true"] {
   font-weight: bold;
 }
 
 .alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
 }
 
 .alltabs-item[tabIsVisible] {
--- a/docshell/base/AutoTimelineMarker.h
+++ b/docshell/base/AutoTimelineMarker.h
@@ -61,13 +61,16 @@ public:
   }
 
   ~AutoTimelineMarker()
   {
     if (mDocShell) {
       mDocShell->AddProfileTimelineMarker(mName, TRACING_INTERVAL_END);
     }
   }
+
+  AutoTimelineMarker(const AutoTimelineMarker& aOther) = delete;
+  void operator=(const AutoTimelineMarker& aOther) = delete;
 };
 
 } // namespace mozilla
 
 #endif /* AutoTimelineMarker_h__ */
copy from dom/animation/test/css-animations/test_animation-cancel.html
copy to dom/animation/test/css-animations/file_animation-cancel.html
--- a/dom/animation/test/css-animations/test_animation-cancel.html
+++ b/dom/animation/test/css-animations/file_animation-cancel.html
@@ -1,26 +1,24 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes translateAnim {
   to { transform: translate(100px) }
 }
 @keyframes marginLeftAnim {
   to { margin-left: 100px }
 }
 @keyframes marginLeftAnim100To200 {
   from { margin-left: 100px }
   to { margin-left: 200px }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t, { style: 'animation: translateAnim 100s' });
 
   var animation = div.getAnimations()[0];
   animation.ready.then(t.step_func(function() {
@@ -155,10 +153,12 @@ test(function(t) {
   div.style.animationPlayState = 'running';
   assert_equals(animation.playState, 'idle',
                 'Animation is still idle after re-setting'
                 + ' animation-play-state: running');
 
 }, 'After cancelling an animation, updating animation-play-state doesn\'t'
    + ' make it live again');
 
+done();
 </script>
+</body>
 </html>
copy from dom/animation/test/css-animations/test_animation-currenttime.html
copy to dom/animation/test/css-animations/file_animation-currenttime.html
--- a/dom/animation/test/css-animations/test_animation-currenttime.html
+++ b/dom/animation/test/css-animations/file_animation-currenttime.html
@@ -13,22 +13,19 @@
 }
 
 @keyframes anim {
   from { margin-left: 100px; }
   to { margin-left: 200px; }
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -540,11 +537,12 @@ test(function(t) {
   div.style.animation = 'anim 100s';
 
   var animation = div.getAnimations()[0];
   animation.cancel();
   assert_equals(animation.currentTime, null,
                 'The currentTime of a cancelled animation should be null');
 }, 'Animation.currentTime after cancelling');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-animations/test_animation-finish.html
copy to dom/animation/test/css-animations/file_animation-finish.html
--- a/dom/animation/test/css-animations/test_animation-finish.html
+++ b/dom/animation/test/css-animations/file_animation-finish.html
@@ -1,26 +1,24 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 
 .animated-div {
   margin-left: 10px;
 }
 
 @keyframes anim {
   from { margin-left: 100px; }
   to { margin-left: 200px; }
 }
 
 </style>
+<body>
 <script>
 
 'use strict';
 
 const ANIM_PROP_VAL = 'anim 100s';
 const ANIM_DURATION = 100000; // ms
 
 test(function(t) {
@@ -152,9 +150,11 @@ async_test(function(t) {
     var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
     assert_equals(marginLeft, 10,
                   'The computed style should be reset when finish() is ' +
                   'called');
     t.done();
   }));
 }, 'Test resetting of computed style');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-finished.html
copy to dom/animation/test/css-animations/file_animation-finished.html
--- a/dom/animation/test/css-animations/test_animation-finished.html
+++ b/dom/animation/test/css-animations/file_animation-finished.html
@@ -1,20 +1,18 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes abc {
   to { transform: translate(10px) }
 }
 @keyframes def {}
 </style>
+<body>
 <script>
 'use strict';
 
 const ANIM_PROP_VAL = 'abc 100s';
 const ANIM_DURATION = 100000; // ms
 
 async_test(function(t) {
   var div = addDiv(t);
@@ -343,9 +341,11 @@ async_test(function(t) {
                   '"running" on finished animation');
     assert_equals(animation.currentTime, ANIM_DURATION,
                   'currentTime should not change when animation-play-state ' +
                   'changes to "running" on finished animation');
     t.done();
   }));
 }, 'Test finished promise changes when animationPlayState set to running');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-pausing.html
copy to dom/animation/test/css-animations/file_animation-pausing.html
--- a/dom/animation/test/css-animations/test_animation-pausing.html
+++ b/dom/animation/test/css-animations/file_animation-pausing.html
@@ -1,20 +1,18 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim { 
   0% { margin-left: 0px }
   100% { margin-left: 10000px }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 function getMarginLeft(cs) {
   return parseFloat(cs.marginLeft);
 }
 
 async_test(function(t) {
@@ -194,9 +192,11 @@ async_test(function(t) {
     // The ready promise should now be resolved. If it's not then test will
     // probably time out before anything else happens that causes it to resolve.
     return animation.ready;
   })).then(t.step_func(function() {
     t.done();
   }));
 }, 'Setting the current time cancels a pending pause');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-playstate.html
copy to dom/animation/test/css-animations/file_animation-playstate.html
--- a/dom/animation/test/css-animations/test_animation-playstate.html
+++ b/dom/animation/test/css-animations/file_animation-playstate.html
@@ -1,17 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim { }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   var cs = window.getComputedStyle(div);
   div.style.animation = 'anim 1000s';
 
@@ -81,9 +79,11 @@ test(function(t) {
 
   var animation = div.getAnimations()[0];
   animation.cancel();
   animation.currentTime = 50 * 1000;
   assert_equals(animation.playState, 'paused',
                 'After seeking an idle animation, it is effectively paused');
 }, 'After cancelling an animation, seeking it makes it paused');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-ready.html
copy to dom/animation/test/css-animations/file_animation-ready.html
--- a/dom/animation/test/css-animations/test_animation-ready.html
+++ b/dom/animation/test/css-animations/file_animation-ready.html
@@ -1,19 +1,17 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes abc {
   to { transform: translate(10px) }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
 
@@ -243,9 +241,11 @@ async_test(function(t) {
     assert_equals(resolvedAnimation, animation,
                   'Promise received when ready Promise for a pause operation'
                   + ' is completed is the animation on which the pause was'
                   + ' performed');
     t.done();
   }));
 }, 'When a pause is complete the Promise callback gets the correct animation');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_animation-starttime.html
copy to dom/animation/test/css-animations/file_animation-starttime.html
--- a/dom/animation/test/css-animations/test_animation-starttime.html
+++ b/dom/animation/test/css-animations/file_animation-starttime.html
@@ -13,22 +13,19 @@
 }
 
 @keyframes anim {
   from { margin-left: 100px; }
   to { margin-left: 200px; }
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -549,11 +546,12 @@ async_test(function(t) {
   animation.ready.then(t.step_func(function() {
     animation.cancel();
     assert_equals(animation.startTime, null,
                   'The startTime of a cancelled animation should be null');
     t.done();
   }));
 }, 'Animation.startTime after cancelling');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-animations/test_animations-dynamic-changes.html
copy to dom/animation/test/css-animations/file_animations-dynamic-changes.html
--- a/dom/animation/test/css-animations/test_animations-dynamic-changes.html
+++ b/dom/animation/test/css-animations/file_animations-dynamic-changes.html
@@ -1,20 +1,18 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim1 {
   to { left: 100px }
 }
 @keyframes anim2 { }
 </style>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim1 100s';
 
   var originalAnimation = div.getAnimations()[0];
@@ -151,9 +149,11 @@ async_test(function(t) {
     assert_true(animations[1].startTime > animations[0].startTime,
                 'Interleaved animation starts later than existing animations');
     assert_true(animations[0].startTime > animations[2].startTime,
                 'Original animations retain their start time');
     t.done();
   }));
 }, 'Animation state is preserved when interleaving animations in list');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_effect-name.html
copy to dom/animation/test/css-animations/file_effect-name.html
--- a/dom/animation/test/css-animations/test_effect-name.html
+++ b/dom/animation/test/css-animations/file_effect-name.html
@@ -1,19 +1,17 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes xyz {
   to { left: 100px }
 }
 </style>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'xyz 100s';
   assert_equals(div.getAnimations()[0].effect.name, 'xyz',
                 'Animation effect name matches keyframes rule name');
@@ -29,9 +27,11 @@ test(function(t) {
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'x\\79 z 100s';
   assert_equals(div.getAnimations()[0].effect.name, 'xyz',
                 'Hex-escaped animation name matches keyframes rule'
                 + ' name');
 }, 'Animation name with hex-escape');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_effect-target.html
copy to dom/animation/test/css-animations/file_effect-target.html
--- a/dom/animation/test/css-animations/test_effect-target.html
+++ b/dom/animation/test/css-animations/file_effect-target.html
@@ -1,21 +1,21 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim { }
 </style>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim 100s';
   var animation = div.getAnimations()[0];
   assert_equals(animation.effect.target, div,
     'Animation.target is the animatable div');
 }, 'Returned CSS animations have the correct Animation.target');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-animations/test_element-get-animations.html
copy to dom/animation/test/css-animations/file_element-get-animations.html
--- a/dom/animation/test/css-animations/test_element-get-animations.html
+++ b/dom/animation/test/css-animations/file_element-get-animations.html
@@ -1,26 +1,24 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes anim1 {
   to { left: 100px }
 }
 @keyframes anim2 {
   to { top: 100px }
 }
 @keyframes multiPropAnim {
   to { background: green, opacity: 0.5, left: 100px, top: 100px }
 }
 @keyframes empty { }
 </style>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
   assert_equals(div.getAnimations().length, 0,
     'getAnimations returns an empty sequence for an element'
     + ' with no animations');
@@ -264,9 +262,11 @@ test(function(t) {
     'getAnimations does not return cancelled animations');
 
   animation.play();
   assert_equals(div.getAnimations().length, 1,
     'getAnimations returns cancelled animations that have been re-started');
 
 }, 'getAnimations for CSS Animations that are cancelled');
 
+done();
 </script>
+</body>
--- a/dom/animation/test/css-animations/test_animation-cancel.html
+++ b/dom/animation/test/css-animations/test_animation-cancel.html
@@ -1,164 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes translateAnim {
-  to { transform: translate(100px) }
-}
-@keyframes marginLeftAnim {
-  to { margin-left: 100px }
-}
-@keyframes marginLeftAnim100To200 {
-  from { margin-left: 100px }
-  to { margin-left: 200px }
-}
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: translateAnim 100s' });
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(getComputedStyle(div).transform, 'none',
-                      'transform style is animated before cancelling');
-    animation.cancel();
-    assert_equals(getComputedStyle(div).transform, 'none',
-                  'transform style is no longer animated after cancelling');
-    t.done();
-  }));
-}, 'Animated style is cleared after cancelling a running CSS animation');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: translateAnim 100s forwards' });
-
-  var animation = div.getAnimations()[0];
-  animation.finish();
-
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(getComputedStyle(div).transform, 'none',
-                      'transform style is filling before cancelling');
-    animation.cancel();
-    assert_equals(getComputedStyle(div).transform, 'none',
-                  'fill style is cleared after cancelling');
-    t.done();
-  }));
-}, 'Animated style is cleared after cancelling a filling CSS animation');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: translateAnim 100s' });
-  var animation = div.getAnimations()[0];
-
-  div.addEventListener('animationend', t.step_func(function() {
-    assert_unreached('Got unexpected end event on cancelled animation');
-  }));
-
-  animation.ready.then(t.step_func(function() {
-    // Seek to just before the end then cancel
-    animation.currentTime = 99.9 * 1000;
-    animation.cancel();
-
-    // Then wait a couple of frames and check that no event was dispatched
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Cancelled CSS animations do not dispatch events');
-
-test(function(t) {
-  var div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' });
-
-  var animation = div.getAnimations()[0];
-
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is not animated after cancelling');
-
-  animation.currentTime = 50 * 1000;
-  assert_equals(getComputedStyle(div).marginLeft, '50px',
-                'margin-left style is updated when cancelled animation is'
-                + ' seeked');
-}, 'After cancelling an animation, it can still be seeked');
-
-async_test(function(t) {
-  var div =
-    addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, '0px',
-                  'margin-left style is not animated after cancelling');
-    animation.play();
-    assert_equals(getComputedStyle(div).marginLeft, '100px',
-                  'margin-left style is animated after re-starting animation');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Animation succeeds in running after being re-started');
-    t.done();
-  }));
-}, 'After cancelling an animation, it can still be re-used');
-
-test(function(t) {
-  var div =
-    addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is not animated after cancelling');
-
-  // Trigger a change to some animation properties and check that this
-  // doesn't cause the animation to become live again
-  div.style.animationDuration = '200s';
-  flushComputedStyle(div);
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is still not animated after updating'
-                + ' animation-duration');
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after updating animation-duration');
-}, 'After cancelling an animation, updating animation properties doesn\'t make'
-   + ' it live again');
-
-test(function(t) {
-  var div =
-    addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '0px',
-                'margin-left style is not animated after cancelling');
-
-  // Make some changes to animation-play-state and check that the
-  // animation doesn't become live again. This is because it should be
-  // possible to cancel an animation from script such that all future
-  // changes to style are ignored.
-
-  // Redundant change
-  div.style.animationPlayState = 'running';
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after a redundant change to'
-                + ' animation-play-state');
-
-  // Pause
-  div.style.animationPlayState = 'paused';
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after setting'
-                + ' animation-play-state: paused');
-
-  // Play
-  div.style.animationPlayState = 'running';
-  assert_equals(animation.playState, 'idle',
-                'Animation is still idle after re-setting'
-                + ' animation-play-state: running');
-
-}, 'After cancelling an animation, updating animation-play-state doesn\'t'
-   + ' make it live again');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-cancel.html");
+  });
 </script>
 </html>
--- a/dom/animation/test/css-animations/test_animation-currenttime.html
+++ b/dom/animation/test/css-animations/test_animation-currenttime.html
@@ -1,550 +1,15 @@
 <!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests for the effect of setting a CSS animation's
-           Animation.currentTime</title>
-    <style>
-
-.animated-div {
-  margin-left: 10px;
-  /* Make it easier to calculate expected values: */
-  animation-timing-function: linear ! important;
-}
-
-@keyframes anim {
-  from { margin-left: 100px; }
-  to { margin-left: 200px; }
-}
-
-    </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="../testcommon.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
 'use strict';
-
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
-// TODO: Once the computedTiming property is implemented, add checks to the
-// checker helpers to ensure that computedTiming's properties are updated as
-// expected.
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
-
-
-const CSS_ANIM_EVENTS =
-  ['animationstart', 'animationiteration', 'animationend'];
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
-
-/**
- * These helpers get the value that the currentTime needs to be set to, to put
- * an animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into
- * the middle of various phases or points through the active duration.
- */
-function currentTimeForBeforePhase(timeline) {
-  return ANIM_DELAY_MS / 2;
-}
-function currentTimeForActivePhase(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS / 2;
-}
-function currentTimeForAfterPhase(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2;
-}
-function currentTimeForStartOfActiveInterval(timeline) {
-  return ANIM_DELAY_MS;
-}
-function currentTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5;
-}
-function currentTimeForEndOfActiveInterval(timeline) {
-  return ANIM_DELAY_MS + ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const UNANIMATED_POSITION = 10;
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when currentTime is set to zero (the beginning of the start delay).
-function checkStateOnSettingCurrentTimeToZero(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the start delay');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the start delay');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'at the beginning of the start delay');
-}
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  // the 0.0001 here is for rounding error
-  assert_less_than_equal(animation.currentTime,
-    animation.timeline.currentTime - animation.startTime + 0.0001,
-    'Animation.currentTime should be less than the local time ' +
-    'equivalent of the timeline\'s currentTime on the first paint tick ' +
-    'after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" on the first paint tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when currentTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when currentTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, "running",
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"finished" at the end of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-    'the computed value of margin-left should be unaffected ' +
-    'by the animation at the end of the active duration when the ' +
-    'animation-fill-mode is none');
-}
-
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  // Animations shouldn't start until the next paint tick, so:
-  assert_equals(animation.currentTime, 0,
-    'Animation.currentTime should be zero when an animation ' +
-    'is initially created');
-
-  assert_equals(animation.playState, "pending",
-    'Animation.playState should be "pending" when an animation ' +
-    'is initially created');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" when an animation is initially created');
-
-  // XXX Ideally we would have a test to check the ready Promise is initially
-  // unresolved, but currently there is no Web API to do that. Waiting for the
-  // ready Promise with a timeout doesn't work because the resolved callback
-  // will be called (async) regardless of whether the Promise was resolved in
-  // the past or is resolved in the future.
-
-  // So that animation is running instead of paused when we set currentTime:
-  animation.startTime = animation.timeline.currentTime;
-
-  assert_approx_equals(animation.currentTime, 0, 0.0001, // rounding error
-    'Check setting of currentTime actually works');
-
-  checkStateOnSettingCurrentTimeToZero(animation);
-}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
-   'currentTime');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-
-    animation.currentTime =
-      currentTimeForStartOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.currentTime =
-      currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.currentTime =
-      currentTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-currenttime.html");
   });
-}, 'Skipping forward through animation');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  // So that animation is running instead of paused when we set currentTime:
-  animation.startTime = animation.timeline.currentTime;
-
-  animation.currentTime = currentTimeForEndOfActiveInterval(animation.timeline);
-
-  var previousTimelineTime = animation.timeline.currentTime;
-
-  // Skipping over the active interval will dispatch an 'animationstart' then
-  // an 'animationend' event. We need to wait for these events before we start
-  // testing going backwards since EventWatcher will fail the test if it gets
-  // an event that we haven't told it about.
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(t.step_func(function() {
-    assert_true(document.timeline.currentTime - previousTimelineTime <
-                  ANIM_DUR_MS,
-                'Sanity check that seeking worked rather than the events ' +
-                'firing after normal playback through the very long ' +
-                'animation duration');
-
-    // Now we can start the tests for skipping backwards, but first we check
-    // that after the events we're still in the same end time state:
-    checkStateAtActiveIntervalEndTime(animation);
-
-    animation.currentTime =
-      currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-
-    // Despite going backwards from after the end of the animation (to being
-    // in the active interval), we now expect an 'animationstart' event
-    // because the animation should go from being inactive to active.
-    //
-    // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
-    // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.wait_for('animationstart');
-    checkStateAtFiftyPctOfActiveInterval(animation);
-    return promise;
-  })).then(t.step_func(function() {
-    animation.currentTime =
-      currentTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.currentTime = 0;
-    // Despite going backwards from just after the active interval starts to
-    // the animation start time, we now expect an animationend event
-    // because we went from inside to outside the active interval.
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-
-  // This must come after we've set up the Promise chain, since requesting
-  // computed style will force events to be dispatched.
-  // XXX For some reason this fails occasionally (either the animation.playState
-  // check or the marginLeft check).
-  //checkStateAtActiveIntervalEndTime(animation);
-}, 'Skipping backwards through animation');
-
-
-// Next we have multiple tests to check that redundant currentTime changes do
-// NOT dispatch events. It's impossible to distinguish between events not being
-// dispatched and events just taking an incredibly long time to dispatch
-// without waiting an infinitely long time. Obviously we don't want to do that
-// (block this test from finishing forever), so instead we just listen for
-// events until two animation frames (i.e. requestAnimationFrame callbacks)
-// have happened, then assume that no events will ever be dispatched for the
-// redundant changes if no events were detected in that time.
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-  animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> active, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-  animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.currentTime = currentTimeForBeforePhase(animation.timeline);
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.currentTime = currentTimeForActivePhase(animation.timeline);
-    animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.currentTime = currentTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> active, then back');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var exception;
-    try {
-      animation.currentTime = null;
-    } catch (e) {
-      exception = e;
-    }
-    assert_equals(exception.name, 'TypeError',
-      'Expect TypeError exception on trying to set ' +
-      'Animation.currentTime to null');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Setting currentTime to null');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-  var pauseTime;
-
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(animation.currentTime, null,
-      'Animation.currentTime not null on ready Promise resolve');
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    pauseTime = animation.currentTime;
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(animation.currentTime, pauseTime,
-      'Animation.currentTime is unchanged after pausing');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Animation.currentTime after pausing');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    // just before animation ends:
-    animation.currentTime = ANIM_DELAY_MS + ANIM_DUR_MS - 1;
-
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
-      'Animation.currentTime should not continue to increase after the ' +
-      'animation has finished');
-    t.done();
-  }));
-}, 'Animation.currentTime clamping');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    // play backwards:
-    animation.playbackRate = -1;
-
-    // just before animation ends (at the "start"):
-    animation.currentTime = 1;
-
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    assert_equals(animation.currentTime, 0,
-      'Animation.currentTime should not continue to decrease after an ' +
-      'animation running in reverse has finished and currentTime is zero');
-    t.done();
-  }));
-}, 'Animation.currentTime clamping for reversed animation');
-
-test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(animation.currentTime, null,
-                'The currentTime of a cancelled animation should be null');
-}, 'Animation.currentTime after cancelling');
-
-    </script>
-  </body>
+</script>
 </html>
--- a/dom/animation/test/css-animations/test_animation-finish.html
+++ b/dom/animation/test/css-animations/test_animation-finish.html
@@ -1,160 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-
-.animated-div {
-  margin-left: 10px;
-}
-
-@keyframes anim {
-  from { margin-left: 100px; }
-  to { margin-left: 200px; }
-}
-
-</style>
 <script>
-
 'use strict';
-
-const ANIM_PROP_VAL = 'anim 100s';
-const ANIM_DURATION = 100000; // ms
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.playbackRate = 0;
-
-  var threw = false;
-  try {
-    animation.finish();
-  } catch (e) {
-    threw = true;
-    assert_equals(e.name, 'InvalidStateError',
-                  'Exception should be an InvalidStateError exception when ' +
-                  'trying to finish an animation with playbackRate == 0');
-  }
-  assert_true(threw,
-              'Expect InvalidStateError exception trying to finish an ' +
-              'animation with playbackRate == 0');
-}, 'Test exceptions when finishing non-running animation');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  div.style.animationIterationCount = 'infinite';
-  var animation = div.getAnimations()[0];
-
-  var threw = false;
-  try {
-    animation.finish();
-  } catch (e) {
-    threw = true;
-    assert_equals(e.name, 'InvalidStateError',
-                  'Exception should be an InvalidStateError exception when ' +
-                  'trying to finish an infinite animation');
-  }
-  assert_true(threw,
-              'Expect InvalidStateError exception trying to finish an ' +
-              'infinite animation');
-}, 'Test exceptions when finishing infinite animation');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.finish();
-  assert_equals(animation.currentTime, ANIM_DURATION,
-                'After finishing, the currentTime should be set to the end ' +
-                'of the active duration');
-}, 'Test finishing of animation');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION + 1000; // 1s past effect end
-
-  animation.finish();
-  assert_equals(animation.currentTime, ANIM_DURATION,
-                'After finishing, the currentTime should be set back to the ' +
-                'end of the active duration');
-}, 'Test finishing of animation with a current time past the effect end');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.playbackRate = -1;
-    animation.finish();
-    assert_equals(animation.currentTime, 0,
-                  'After finishing a reversed animation the currentTime ' +
-                  'should be set to zero');
-    t.done();
-  }));
-}, 'Test finishing of reversed animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.playbackRate = -1;
-
-    animation.currentTime = -1000;
-
-    animation.finish();
-    assert_equals(animation.currentTime, 0,
-                  'After finishing a reversed animation the currentTime ' +
-                  'should be set back to zero');
-    t.done();
-  }));
-}, 'Test finishing of reversed animation with with a current time less ' +
-   'than zero');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.pause();
-
-  animation.ready.then(t.step_func(function() {
-    animation.finish();
-    assert_equals(animation.playState, 'paused',
-                  'The play state of a paused animation should remain ' +
-                  '"paused" even after finish() is called');
-    t.done();
-  }));
-}, 'Test paused state after finishing of animation');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    animation.finish();
-    var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-    assert_equals(marginLeft, 10,
-                  'The computed style should be reset when finish() is ' +
-                  'called');
-    t.done();
-  }));
-}, 'Test resetting of computed style');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-finish.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-finished.html
+++ b/dom/animation/test/css-animations/test_animation-finished.html
@@ -1,351 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes abc {
-  to { transform: translate(10px) }
-}
-@keyframes def {}
-</style>
 <script>
 'use strict';
-
-const ANIM_PROP_VAL = 'abc 100s';
-const ANIM_DURATION = 100000; // ms
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same object when playing starts');
-    animation.pause();
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise does not change when pausing');
-    animation.play();
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise does not change when play() unpauses');
-
-    animation.currentTime = ANIM_DURATION;
-
-    return animation.finished;
-  })).then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same object when playing completes');
-    t.done();
-  }));
-}, 'Test pausing then playing does not change the finished promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same object when playing completes');
-    animation.play();
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise changes when replaying animation');
-
-    previousFinishedPromise = animation.finished;
-    animation.play();
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is the same after redundant play() call');
-
-    t.done();
-  }));
-}, 'Test restarting a finished animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    previousFinishedPromise = animation.finished;
-    animation.playbackRate = -1;
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should be replaced when reversing a ' +
-                      'finished promise');
-    animation.currentTime = 0;
-    return animation.finished;
-  })).then(t.step_func(function() {
-    previousFinishedPromise = animation.finished;
-    animation.play();
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise is replaced after play() call on ' +
-                      'finished, reversed animation');
-    t.done();
-  }));
-}, 'Test restarting a reversed finished animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.currentTime = ANIM_DURATION + 1000;
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Finished promise is unchanged jumping past end of ' +
-                  'finished animation');
-
-    t.done();
-  }));
-}, 'Test redundant finishing of animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = ANIM_DURATION;
-  animation.finished.then(t.step_func(function(resolvedAnimation) {
-    assert_equals(resolvedAnimation, animation,
-                  'Object identity of animation passed to Promise callback'
-                  + ' matches the animation object owning the Promise');
-    t.done();
-  }));
-}, 'The finished promise is fulfilled with its Animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up pending animation
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  var previousFinishedPromise = animation.finished;
-
-  // Set up listeners on finished promise
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise is fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after the original is ' +
-                      'rejected');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now cancel the animation and flush styles
-  div.style.animation = '';
-  window.getComputedStyle(div).animation;
-
-}, 'finished promise is rejected when an animation is cancelled by resetting ' +
-   'the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // As before, but this time instead of removing all animations, simply update
-  // the list of animations. At least for Firefox, updating is a different
-  // code path.
-
-  // Set up pending animation
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  var previousFinishedPromise = animation.finished;
-
-  // Set up listeners on finished promise
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after the original is ' +
-                      'rejected');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now update the animation and flush styles
-  div.style.animation = 'def 100s';
-  window.getComputedStyle(div).animation;
-
-}, 'finished promise is rejected when an animation is cancelled by changing ' +
-   'the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  var previousFinishedPromise = animation.finished;
-
-  // Set up listeners on finished promise
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after the original is ' +
-                      'rejected');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  animation.cancel();
-
-}, 'finished promise is rejected when an animation is cancelled by calling ' +
-   'cancel()');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(t.step_func(function() {
-    animation.cancel();
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'A new finished promise should be created when'
-                      + ' cancelling a finished player');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'cancelling an already-finished player replaces the finished promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-
-  // The spec says we still create a new finished promise and reject the old
-  // one even if we're already idle. That behavior might change, but for now
-  // test that we do that.
-  animation.finished.catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'finished promise is rejected with AbortError');
-    t.done();
-  }));
-
-  // Redundant call to cancel();
-  var previousFinishedPromise = animation.finished;
-  animation.cancel();
-  assert_not_equals(animation.finished, previousFinishedPromise,
-                    'A redundant call to cancel() should still generate a new'
-                    + ' finished promise');
-}, 'cancelling an idle player still replaces the finished promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  const HALF_DUR = ANIM_DURATION / 2;
-  const QUARTER_DUR = ANIM_DURATION / 4;
-
-  animation.currentTime = HALF_DUR;
-  div.style.animationDuration = QUARTER_DUR + 'ms';
-  // Animation should now be finished
-
-  // Below we use gotNextFrame to check that shortening of the animation
-  // duration causes the finished promise to resolve, rather than it just
-  // getting resolved on the next animation frame. This relies on the fact
-  // that the promises are resolved as a micro-task before the next frame
-  // happens.
-
-  window.getComputedStyle(div).animationDuration; // flush style
-  var gotNextFrame = false;
-  waitForFrame().then(function() {
-    gotNextFrame = true;
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-finished.html");
   });
-
-  animation.finished.then(t.step_func(function() {
-    assert_false(gotNextFrame, 'shortening of the animation duration should ' +
-                               'resolve the finished promise');
-    assert_equals(animation.currentTime, HALF_DUR,
-                  'currentTime should be unchanged when duration shortened');
-    var previousFinishedPromise = animation.finished;
-    div.style.animationDuration = ANIM_DURATION + 'ms'; // now active again
-    window.getComputedStyle(div).animationDuration; // flush style
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change after lengthening the ' +
-                      'duration causes the animation to become active');
-    t.done();
-  }));
-}, 'Test finished promise changes for animation duration changes');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    animation.playbackRate = 0;
-    animation.currentTime = ANIM_DURATION + 1000;
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('finished promise should not resolve when playbackRate ' +
-                     'is zero');
-  }));
-}, 'Test finished promise changes when playbackRate == 0');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    animation.playbackRate = -1;
-    return animation.finished;
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Test finished promise resolves when playbackRate set to a negative value');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.currentTime = ANIM_DURATION;
-
-  animation.finished.then(function() {
-    div.style.animationPlayState = 'running';
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    assert_equals(animation.finished, previousFinishedPromise,
-                  'Should not replay when animation-play-state changes to ' +
-                  '"running" on finished animation');
-    assert_equals(animation.currentTime, ANIM_DURATION,
-                  'currentTime should not change when animation-play-state ' +
-                  'changes to "running" on finished animation');
-    t.done();
-  }));
-}, 'Test finished promise changes when animationPlayState set to running');
-
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-pausing.html
+++ b/dom/animation/test/css-animations/test_animation-pausing.html
@@ -1,202 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim { 
-  0% { margin-left: 0px }
-  100% { margin-left: 10000px }
-}
-</style>
 <script>
 'use strict';
-
-function getMarginLeft(cs) {
-  return parseFloat(cs.marginLeft);
-}
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(getMarginLeft(cs) > previousAnimVal,
-                'margin-left is initially increasing');
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    previousAnimVal = getMarginLeft(cs);
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'margin-left does not increase after calling pause()');
-    t.done();
-  }));
-}, 'pause() a running animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  animation.pause();
-  div.style.animationPlayState = 'running';
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'running',
-                  'animation-play-state is running');
-    assert_equals(getMarginLeft(cs), 0,
-                  'Paused value of margin-left is zero');
-    t.done();
-  }));
-}, 'pause() overrides animation-play-state');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  animation.play();
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(getMarginLeft(cs) > 0,
-                'Playing value of margin-left is greater than zero');
-    t.done();
-  }));
-}, 'play() overrides animation-play-state');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  animation.play();
-
-  var previousAnimVal;
-
-  animation.ready.then(function() {
-    div.style.animationPlayState = 'running';
-    cs.animationPlayState; // Trigger style resolution
-    return waitForFrame();
-  }).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'running',
-                  'animation-play-state is running');
-    div.style.animationPlayState = 'paused';
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'paused',
-                  'animation-play-state is paused');
-    previousAnimVal = getMarginLeft(cs);
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'Animated value of margin-left does not change when'
-                  + ' paused by style');
-    t.done();
-  }));
-}, 'play() is overridden by later setting "animation-play-state: paused"');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  // Set the specified style first. If implementations fail to
-  // apply the style changes first, they will ignore the redundant
-  // call to play() and fail to correctly override the pause style.
-  div.style.animationPlayState = 'paused';
-  animation.play();
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'paused',
-                  'animation-play-state is paused');
-    assert_true(getMarginLeft(cs) > previousAnimVal,
-                'Playing value of margin-left is increasing');
-    t.done();
-  }));
-}, 'play() flushes pending changes to animation-play-state first');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-
-  // Unlike the previous test for play(), since calling pause() is sticky,
-  // we'll apply it even if the underlying style also says we're paused.
-  //
-  // We would like to test that implementations flush styles before running
-  // pause() but actually there's no style we can currently set that will
-  // change the behavior of pause(). That may change in the future
-  // (e.g. if we introduce animation-timeline or animation-playback-rate etc.).
-  //
-  // For now this just serves as a sanity check that we do the same thing
-  // even if we set style before calling the API.
-  div.style.animationPlayState = 'running';
-  animation.pause();
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_equals(cs.animationPlayState, 'running',
-                  'animation-play-state is running');
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'Paused value of margin-left does not change');
-    t.done();
-  }));
-}, 'pause() applies pending changes to animation-play-state first');
-// (Note that we can't actually test for this; see comment above, in test-body.)
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  var animation = div.getAnimations()[0];
-
-  var readyPromiseRun = false;
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    assert_equals(animation.playState, 'pending', 'Animation is pause pending');
-
-    // Set current time
-    animation.currentTime = 5000;
-    assert_equals(animation.playState, 'running',
-                  'Animation is running immediately after setting currentTime');
-
-    // The ready promise should now be resolved. If it's not then test will
-    // probably time out before anything else happens that causes it to resolve.
-    return animation.ready;
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Setting the current time cancels a pending pause');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-pausing.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-playstate.html
+++ b/dom/animation/test/css-animations/test_animation-playstate.html
@@ -1,89 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim { }
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'running');
-    t.done();
-  }));
-}, 'Animation returns correct playState when running');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'paused');
-    t.done();
-  }));
-}, 'Animation returns correct playState when paused');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  animation.pause();
-  assert_equals(animation.playState, 'pending');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'paused');
-    t.done();
-  }));
-}, 'Animation.playState updates when paused by script');
-
-test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-  div.style.animation = 'anim 1000s paused';
-
-  var animation = div.getAnimations()[0];
-  div.style.animationPlayState = 'running';
-  // This test also checks that calling playState flushes style
-  assert_equals(animation.playState, 'pending',
-                'Animation.playState reports pending after updating'
-                + ' animation-play-state (got: ' + animation.playState + ')');
-}, 'Animation.playState updates when resumed by setting style');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(animation.playState, 'idle');
-}, 'Animation returns correct playState when cancelled');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim 1000s';
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  animation.currentTime = 50 * 1000;
-  assert_equals(animation.playState, 'paused',
-                'After seeking an idle animation, it is effectively paused');
-}, 'After cancelling an animation, seeking it makes it paused');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-playstate.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-ready.html
+++ b/dom/animation/test/css-animations/test_animation-ready.html
@@ -1,251 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes abc {
-  to { transform: translate(10px) }
-}
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  var originalReadyPromise = animation.ready;
-  var pauseReadyPromise;
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.ready, originalReadyPromise,
-                  'Ready promise is the same object when playing completes');
-    animation.pause();
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'A new ready promise is created when pausing');
-    pauseReadyPromise = animation.ready;
-    // Wait for the promise to fulfill since if we abort the pause the ready
-    // promise object is reused.
-    return animation.ready;
-  })).then(t.step_func(function() {
-    animation.play();
-    assert_not_equals(animation.ready, pauseReadyPromise,
-                      'A new ready promise is created when playing');
-    t.done();
-  }));
-}, 'A new ready promise is created when play()/pause() is called');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s paused';
-  var animation = div.getAnimations()[0];
-
-  var originalReadyPromise = animation.ready;
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'running';
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'After updating animation-play-state a new ready promise'
-                      + ' object is created');
-    t.done();
-  }));
-}, 'A new ready promise is created when setting animation-play-state: running');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var promiseBeforeCallingPlay = animation.ready;
-    animation.play();
-    assert_equals(animation.ready, promiseBeforeCallingPlay,
-                  'Ready promise has same object identity after redundant call'
-                  + ' to play()');
-    t.done();
-  }));
-}, 'Redundant calls to play() do not generate new ready promise objects');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function(resolvedAnimation) {
-    assert_equals(resolvedAnimation, animation,
-                  'Object identity of Animation passed to Promise callback'
-                  + ' matches the Animation object owning the Promise');
-    t.done();
-  }));
-}, 'The ready promise is fulfilled with its Animation');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up pending animation
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending',
-               'Animation is initially pending');
-
-  // Set up listeners on ready promise
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise is fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now cancel the animation and flush styles
-  div.style.animation = '';
-  window.getComputedStyle(div).animation;
-
-}, 'ready promise is rejected when an animation is cancelled by resetting'
-   + ' the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // As before, but this time instead of removing all animations, simply update
-  // the list of animations. At least for Firefox, updating is a different
-  // code path.
-
-  // Set up pending animation
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending',
-                'Animation is initially pending');
-
-  // Set up listeners on ready promise
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now update the animation and flush styles
-  div.style.animation = 'def 100s';
-  window.getComputedStyle(div).animation;
-
-}, 'ready promise is rejected when an animation is cancelled by updating'
-   + ' the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  animation.cancel();
-}, 'ready promise is rejected when a play-pending animation is cancelled by'
-   + ' calling cancel()');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'abc 100s';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    animation.pause();
-
-    // Set up listeners on pause-pending ready promise
-    animation.ready.then(t.step_func(function() {
-      assert_unreached('ready promise was fulfilled');
-    })).catch(t.step_func(function(err) {
-      assert_equals(err.name, 'AbortError',
-                    'ready promise is rejected with AbortError');
-    })).then(t.step_func(function() {
-      t.done();
-    }));
-
-    animation.cancel();
-  }));
-}, 'ready promise is rejected when a pause-pending animation is cancelled by'
-   + ' calling cancel()');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  var originalReadyPromise = animation.ready;
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'A new Promise object is generated when setting'
-                      + ' animation-play-state: paused');
-    t.done();
-  }));
-}, 'A new ready promise is created when setting animation-play-state: paused');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    var firstReadyPromise = animation.ready;
-    animation.pause();
-    assert_equals(animation.ready, firstReadyPromise,
-                  'Ready promise objects are identical after redundant pause');
-    t.done();
-  }));
-}, 'Pausing twice re-uses the same Promise');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-
-    // Flush style and verify we're pending at the same time
-    assert_equals(animation.playState, 'pending', 'Animation is pending');
-    var pauseReadyPromise = animation.ready;
-
-    // Now play again immediately
-    div.style.animationPlayState = 'running';
-    assert_equals(animation.playState, 'pending', 'Animation is still pending');
-    assert_equals(animation.ready, pauseReadyPromise,
-                  'The pause Promise is re-used when playing while waiting'
-                  + ' to pause');
-
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Animation is running after aborting a pause');
-    t.done();
-  }));
-}, 'If a pause operation is interrupted, the ready promise is reused');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: abc 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    return animation.ready;
-  })).then(t.step_func(function(resolvedAnimation) {
-    assert_equals(resolvedAnimation, animation,
-                  'Promise received when ready Promise for a pause operation'
-                  + ' is completed is the animation on which the pause was'
-                  + ' performed');
-    t.done();
-  }));
-}, 'When a pause is complete the Promise callback gets the correct animation');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-ready.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_animation-starttime.html
+++ b/dom/animation/test/css-animations/test_animation-starttime.html
@@ -1,559 +1,15 @@
 <!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests for the effect of setting a CSS animation's
-           Animation.startTime</title>
-    <style>
-
-.animated-div {
-  margin-left: 10px;
-  /* Make it easier to calculate expected values: */
-  animation-timing-function: linear ! important;
-}
-
-@keyframes anim {
-  from { margin-left: 100px; }
-  to { margin-left: 200px; }
-}
-
-    </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="../testcommon.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
 'use strict';
-
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
-// TODO: Once the computedTiming property is implemented, add checks to the
-// checker helpers to ensure that computedTiming's properties are updated as
-// expected.
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
-
-
-const CSS_ANIM_EVENTS =
-  ['animationstart', 'animationiteration', 'animationend'];
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-const ANIM_PROPERTY_VAL = 'anim ' + ANIM_DUR_MS + 'ms ' + ANIM_DELAY_MS + 'ms';
-
-/**
- * These helpers get the value that the startTime needs to be set to, to put an
- * animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into the
- * middle of various phases or points through the active duration.
- */
-function startTimeForBeforePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS / 2;
-}
-function startTimeForActivePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS / 2;
-}
-function startTimeForAfterPhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS - ANIM_DELAY_MS / 2;
-}
-function startTimeForStartOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS;
-}
-function startTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS * 0.5;
-}
-function startTimeForEndOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const UNANIMATED_POSITION = 10;
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
-    'Animation.startTime should be less than the timeline\'s ' +
-    'currentTime on the first paint tick after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" on the first paint tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when startTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when startTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  assert_equals(animation.effect.target.style.animationPlayState, "running",
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"finished" at the end of the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-    'the computed value of margin-left should be unaffected ' +
-    'by the animation at the end of the active duration when the ' +
-    'animation-fill-mode is none');
-}
-
-test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.startTime, null, 'startTime is unresolved');
-}, 'startTime of a newly created (play-pending) animation is unresolved');
-
-test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.startTime, null, 'startTime is unresolved');
-}, 'startTime of a newly created (pause-pending) animation is unresolved');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    assert_true(animation.startTime > 0,
-                'startTime is resolved when running');
-    t.done();
-  }));
-}, 'startTime is resolved when running');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-                  'startTime is unresolved when paused');
-    t.done();
-  }));
-}, 'startTime is unresolved when paused');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    div.style.animationPlayState = 'paused';
-    getComputedStyle(div).animationPlayState;
-    assert_not_equals(animation.startTime, null,
-                      'startTime is resolved when pause-pending');
-
-    div.style.animationPlayState = 'running';
-    getComputedStyle(div).animationPlayState;
-    assert_not_equals(animation.startTime, null,
-                      'startTime is preserved when a pause is aborted');
-    t.done();
-  }));
-}, 'startTime while pause-pending and play-pending');
-
-async_test(function(t)
-{
-  var div = addDiv(t, { 'style': 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-  // Seek to end to put us in the finished state
-  // FIXME: Once we implement finish(), use that here.
-  animation.currentTime = 100 * 1000;
-  animation.ready.then(t.step_func(function() {
-    // Call play() which puts us back in the running state
-    animation.play();
-    // FIXME: Enable this once we implement finishing behavior (bug 1074630)
-    /*
-    assert_equals(animation.startTime, null, 'startTime is unresolved');
-    */
-    t.done();
-  }));
-}, 'startTime while play-pending from finished state');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  var animation = div.getAnimations()[0];
-
-  assert_equals(animation.startTime, null, 'The initial startTime is null');
-  var initialTimelineTime = document.timeline.currentTime;
-
-  animation.ready.then(t.step_func(function() {
-    assert_true(animation.startTime > initialTimelineTime,
-                'After the animation has started, startTime is greater than ' +
-                'the time when it was started');
-    var startTimeBeforePausing = animation.startTime;
-
-    div.style.animationPlayState = 'paused';
-    // Flush styles just in case querying animation.startTime doesn't flush
-    // styles (which would be a bug in of itself and could mask a further bug
-    // by causing startTime to appear to not change).
-    getComputedStyle(div).animationPlayState;
-
-    assert_equals(animation.startTime, startTimeBeforePausing,
-                  'The startTime does not change when pausing-pending');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-                  'After actually pausing, the startTime of an animation ' +
-                  'is null');
-    t.done();
-  }));
-}, 'Pausing should make the startTime become null');
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-  var currentTime = animation.timeline.currentTime;
-  animation.startTime = currentTime;
-  assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
-    'Check setting of startTime actually works');
-}, 'Sanity test to check round-tripping assigning to a new animation\'s ' +
-   'startTime');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-starttime.html");
   });
-}, 'Skipping forward through animation');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-
-  var previousTimelineTime = animation.timeline.currentTime;
-
-  // Skipping over the active interval will dispatch an 'animationstart' then
-  // an 'animationend' event. We need to wait for these events before we start
-  // testing going backwards since EventWatcher will fail the test if it gets
-  // an event that we haven't told it about.
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(t.step_func(function() {
-    assert_true(document.timeline.currentTime - previousTimelineTime <
-                  ANIM_DUR_MS,
-                'Sanity check that seeking worked rather than the events ' +
-                'firing after normal playback through the very long ' +
-                'animation duration');
-
-    // Now we can start the tests for skipping backwards, but first we check
-    // that after the events we're still in the same end time state:
-    checkStateAtActiveIntervalEndTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-
-    // Despite going backwards from after the end of the animation (to being
-    // in the active interval), we now expect an 'animationstart' event
-    // because the animation should go from being inactive to active.
-    //
-    // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
-    // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.wait_for('animationstart');
-    checkStateAtFiftyPctOfActiveInterval(animation);
-    return promise;
-  })).then(t.step_func(function() {
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.startTime = animation.timeline.currentTime;
-    // Despite going backwards from just after the active interval starts to
-    // the animation start time, we now expect an animationend event
-    // because we went from inside to outside the active interval.
-    return eventWatcher.wait_for('animationend');
-  })).then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-
-  // This must come after we've set up the Promise chain, since requesting
-  // computed style will force events to be dispatched.
-  // XXX For some reason this fails occasionally (either the animation.playState
-  // check or the marginLeft check).
-  //checkStateAtActiveIntervalEndTime(animation);
-}, 'Skipping backwards through animation');
-
-
-// Next we have multiple tests to check that redundant startTime changes do NOT
-// dispatch events. It's impossible to distinguish between events not being
-// dispatched and events just taking an incredibly long time to dispatch
-// without waiting an infinitely long time. Obviously we don't want to do that
-// (block this test from finishing forever), so instead we just listen for
-// events until two animation frames (i.e. requestAnimationFrame callbacks)
-// have happened, then assume that no events will ever be dispatched for the
-// redundant changes if no events were detected in that time.
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-  animation.startTime = startTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> active, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-  animation.startTime = startTimeForBeforePhase(animation.timeline);
-
-  waitForAnimationFrames(2).then(function() {
-    t.done();
-  });
-}, 'Redundant change, before -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.startTime = startTimeForBeforePhase(animation.timeline);
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for('animationstart').then(function() {
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForActivePhase(animation.timeline);
-}, 'Redundant change, active -> after, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.startTime = startTimeForBeforePhase(animation.timeline);
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> before, then back');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
-  div.style.animation = ANIM_PROPERTY_VAL;
-  var animation = div.getAnimations()[0];
-
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(function() {
-    animation.startTime = startTimeForActivePhase(animation.timeline);
-    animation.startTime = startTimeForAfterPhase(animation.timeline);
-
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
-  });
-  // get us into the initial state:
-  animation.startTime = startTimeForAfterPhase(animation.timeline);
-}, 'Redundant change, after -> active, then back');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROPERTY_VAL;
-
-  var animation = div.getAnimations()[0];
-
-  var storedCurrentTime;
-
-  animation.ready.then(t.step_func(function() {
-    storedCurrentTime = animation.currentTime;
-    animation.startTime = null;
-    return animation.ready;
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(t.step_func(function() {
-    assert_equals(animation.currentTime, storedCurrentTime,
-      'Test that hold time is correct');
-    t.done();
-  }));
-}, 'Setting startTime to null');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var savedStartTime = animation.startTime;
-
-    assert_not_equals(animation.startTime, null,
-      'Animation.startTime not null on ready Promise resolve');
-
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-      'Animation.startTime is null after paused');
-    assert_equals(animation.playState, 'paused',
-      'Animation.playState is "paused" after pause() call');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Animation.startTime after pausing');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = 'anim 100s';
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.cancel();
-    assert_equals(animation.startTime, null,
-                  'The startTime of a cancelled animation should be null');
-    t.done();
-  }));
-}, 'Animation.startTime after cancelling');
-
-    </script>
-  </body>
+</script>
 </html>
--- a/dom/animation/test/css-animations/test_animations-dynamic-changes.html
+++ b/dom/animation/test/css-animations/test_animations-dynamic-changes.html
@@ -1,159 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim1 {
-  to { left: 100px }
-}
-@keyframes anim2 { }
-</style>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-
-  var originalAnimation = div.getAnimations()[0];
-  var originalStartTime;
-  var originalCurrentTime;
-
-  // Wait a moment so we can confirm the startTime doesn't change (and doesn't
-  // simply reflect the current time).
-  originalAnimation.ready.then(function() {
-    originalStartTime = originalAnimation.startTime;
-    originalCurrentTime = originalAnimation.currentTime;
-
-    // Wait a moment so we can confirm the startTime doesn't change (and
-    // doesn't simply reflect the current time).
-    return waitForFrame();
-  }).then(t.step_func(function() {
-    div.style.animationDuration = '200s';
-    var animation = div.getAnimations()[0];
-    assert_equals(animation, originalAnimation,
-                  'The same Animation is returned after updating'
-                  + ' animation duration');
-    assert_equals(animation.startTime, originalStartTime,
-                  'Animations returned by getAnimations preserve'
-                  + ' their startTime even when they are updated');
-    // Sanity check
-    assert_not_equals(animation.currentTime, originalCurrentTime,
-                      'Animation.currentTime has updated in next'
-                      + ' requestAnimationFrame callback');
-    t.done();
-  }));
-}, 'Animations preserve their startTime when changed');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, anim1 100s';
-
-  // Store original state
-  var animations = div.getAnimations();
-  var animation1 = animations[0];
-  var animation2 = animations[1];
-
-  // Update first in list
-  div.style.animationDuration = '200s, 100s';
-  animations = div.getAnimations();
-  assert_equals(animations[0], animation1,
-                'First Animation is in same position after update');
-  assert_equals(animations[1], animation2,
-                'Second Animation is in same position after update');
-}, 'Updated Animations maintain their order in the list');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 200s, anim1 100s';
-
-  // Store original state
-  var animations = div.getAnimations();
-  var animation1 = animations[0];
-  var animation2 = animations[1];
-
-  // Wait before continuing so we can compare start times (otherwise the
-  // new Animation objects and existing Animation objects will all have the same
-  // start time).
-  waitForAllAnimations(animations).then(waitForFrame).then(t.step_func(function() {
-    // Swap duration of first and second in list and prepend animation at the
-    // same time
-    div.style.animation = 'anim1 100s, anim1 100s, anim1 200s';
-    animations = div.getAnimations();
-    assert_true(animations[0] !== animation1 && animations[0] !== animation2,
-                'New Animation is prepended to start of list');
-    assert_equals(animations[1], animation1,
-                  'First Animation is in second position after update');
-    assert_equals(animations[2], animation2,
-                  'Second Animation is in third position after update');
-    assert_equals(animations[1].startTime, animations[2].startTime,
-                  'Old Animations have the same start time');
-    // TODO: Check that animations[0].startTime === null
-    return animations[0].ready;
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime > animations[1].startTime,
-                'New Animation has later start time');
-    t.done();
-  }));
-}, 'Only the startTimes of existing animations are preserved');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, anim1 100s';
-  var secondAnimation = div.getAnimations()[1];
-
-  // Wait before continuing so we can compare start times
-  secondAnimation.ready.then(waitForFrame).then(t.step_func(function() {
-    // Trim list of animations
-    div.style.animationName = 'anim1';
-    var animations = div.getAnimations();
-    assert_equals(animations.length, 1, 'List of Animations was trimmed');
-    assert_equals(animations[0], secondAnimation,
-                  'Remaining Animation is the second one in the list');
-    assert_equals(typeof(animations[0].startTime), 'number',
-                  'Remaining Animation has resolved startTime');
-    assert_true(animations[0].startTime < animations[0].timeline.currentTime,
-                'Remaining Animation preserves startTime');
-    t.done();
-  }));
-}, 'Animations are removed from the start of the list while preserving'
-   + ' the state of existing Animations');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-  var firstAddedAnimation = div.getAnimations()[0],
-      secondAddedAnimation,
-      animations;
-
-  // Wait and add second Animation
-  firstAddedAnimation.ready.then(waitForFrame).then(t.step_func(function() {
-    div.style.animation = 'anim1 100s, anim1 100s';
-    secondAddedAnimation = div.getAnimations()[0];
-
-    // Wait again and add another Animation
-    return secondAddedAnimation.ready.then(waitForFrame);
-  })).then(t.step_func(function() {
-    div.style.animation = 'anim1 100s, anim2 100s, anim1 100s';
-    animations = div.getAnimations();
-    assert_not_equals(firstAddedAnimation, secondAddedAnimation,
-                      'New Animations are added to start of the list');
-    assert_equals(animations[0], secondAddedAnimation,
-                  'Second Animation remains in same position after'
-                  + ' interleaving');
-    assert_equals(animations[2], firstAddedAnimation,
-                  'First Animation remains in same position after'
-                  + ' interleaving');
-    return animations[1].ready;
-  })).then(t.step_func(function() {
-    assert_true(animations[1].startTime > animations[0].startTime,
-                'Interleaved animation starts later than existing animations');
-    assert_true(animations[0].startTime > animations[2].startTime,
-                'Original animations retain their start time');
-    t.done();
-  }));
-}, 'Animation state is preserved when interleaving animations in list');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animations-dynamic-changes.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_effect-name.html
+++ b/dom/animation/test/css-animations/test_effect-name.html
@@ -1,37 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes xyz {
-  to { left: 100px }
-}
-</style>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'xyz 100s';
-  assert_equals(div.getAnimations()[0].effect.name, 'xyz',
-                'Animation effect name matches keyframes rule name');
-}, 'Effect name makes keyframe rule');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'x\\yz 100s';
-  assert_equals(div.getAnimations()[0].effect.name, 'xyz',
-                'Escaped animation effect name matches keyframes rule name');
-}, 'Escaped animation name');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'x\\79 z 100s';
-  assert_equals(div.getAnimations()[0].effect.name, 'xyz',
-                'Hex-escaped animation name matches keyframes rule'
-                + ' name');
-}, 'Animation name with hex-escape');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_effect-name.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_effect-target.html
+++ b/dom/animation/test/css-animations/test_effect-target.html
@@ -1,21 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim { }
-</style>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim 100s';
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.effect.target, div,
-    'Animation.target is the animatable div');
-}, 'Returned CSS animations have the correct Animation.target');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_effect-target.html");
+  });
 </script>
+</html>
--- a/dom/animation/test/css-animations/test_element-get-animations.html
+++ b/dom/animation/test/css-animations/test_element-get-animations.html
@@ -1,272 +1,15 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes anim1 {
-  to { left: 100px }
-}
-@keyframes anim2 {
-  to { top: 100px }
-}
-@keyframes multiPropAnim {
-  to { background: green, opacity: 0.5, left: 100px, top: 100px }
-}
-@keyframes empty { }
-</style>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-  assert_equals(div.getAnimations().length, 0,
-    'getAnimations returns an empty sequence for an element'
-    + ' with no animations');
-}, 'getAnimations for non-animated content');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Add an animation
-  div.style.animation = 'anim1 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns an Animation running CSS Animations');
-  animations[0].ready.then(t.step_func(function() {
-    var startTime = animations[0].startTime;
-    assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
-      'CSS animation has a sensible start time');
-
-    // Wait a moment then add a second animation.
-    //
-    // We wait for the next frame so that we can test that the start times of
-    // the animations differ.
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    div.style.animation = 'anim1 100s, anim2 100s';
-    animations = div.getAnimations();
-    assert_equals(animations.length, 2,
-      'getAnimations returns one Animation for each value of'
-      + ' animation-name');
-    // Wait until both Animations are ready
-    // (We don't make any assumptions about the order of the Animations since
-    //  that is the purpose of the following test.)
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime < animations[1].startTime,
-      'Additional Animations for CSS animations start after the original'
-      + ' animation and appear later in the list');
-    t.done();
-  }));
-}, 'getAnimations for CSS Animations');
-
-test(function(t) {
-  var div = addDiv(t);
-
-  // Add an animation that targets multiple properties
-  div.style.animation = 'multiPropAnim 100s';
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns only one Animation for a CSS Animation'
-    + ' that targets multiple properties');
-}, 'getAnimations for multi-property animations');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Add an animation
-  div.style.backgroundColor = 'red';
-  div.style.animation = 'anim1 100s';
-  window.getComputedStyle(div).backgroundColor;
-
-  // Wait until a frame after the animation starts, then add a transition
-  var animations = div.getAnimations();
-  animations[0].ready.then(waitForFrame).then(t.step_func(function() {
-    div.style.transition = 'all 100s';
-    div.style.backgroundColor = 'green';
-
-    animations = div.getAnimations();
-    assert_equals(animations.length, 2,
-      'getAnimations returns Animations for both animations and'
-      + ' transitions that run simultaneously');
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime > animations[1].startTime,
-      'Animations for transitions appear before animations even if they'
-      + ' start later');
-    t.done();
-  }));
-}, 'getAnimations for both CSS Animations and Transitions at once');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up event listener
-  div.addEventListener('animationend', t.step_func(function() {
-    assert_equals(div.getAnimations().length, 0,
-      'getAnimations does not return Animations for finished '
-      + ' (and non-forwards-filling) CSS Animations');
-    t.done();
-  }));
-
-  // Add a very short animation
-  div.style.animation = 'anim1 0.01s';
-}, 'getAnimations for CSS Animations that have finished');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up event listener
-  div.addEventListener('animationend', t.step_func(function() {
-    assert_equals(div.getAnimations().length, 1,
-      'getAnimations returns Animations for CSS Animations that have'
-      + ' finished but are filling forwards');
-    t.done();
-  }));
-
-  // Add a very short animation
-  div.style.animation = 'anim1 0.01s forwards';
-}, 'getAnimations for CSS Animations that have finished but are'
-   + ' forwards filling');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'none 100s';
-
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 0,
-    'getAnimations returns an empty sequence for an element'
-    + ' with animation-name: none');
-
-  div.style.animation = 'none 100s, anim1 100s';
-  animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns Animations only for those CSS Animations whose'
-    + ' animation-name is not none');
-}, 'getAnimations for CSS Animations with animation-name: none');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'missing 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 0,
-    'getAnimations returns an empty sequence for an element'
-    + ' with animation-name: missing');
-
-  div.style.animation = 'anim1 100s, missing 100s';
-  animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns Animations only for those CSS Animations whose'
-    + ' animation-name is found');
-}, 'getAnimations for CSS Animations with animation-name: missing');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, notyet 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations initally only returns Animations for CSS Animations whose'
-    + ' animation-name is found');
-
-  animations[0].ready.then(waitForFrame).then(t.step_func(function() {
-    var keyframes = '@keyframes notyet { to { left: 100px; } }';
-    document.styleSheets[0].insertRule(keyframes, 0);
-    animations = div.getAnimations();
-    assert_equals(animations.length, 2,
-      'getAnimations includes Animation when @keyframes rule is added'
-      + ' later');
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[0].startTime < animations[1].startTime,
-      'Newly added animation has a later start time');
-    document.styleSheets[0].deleteRule(0);
-    t.done();
-  }));
-}, 'getAnimations for CSS Animations where the @keyframes rule is added'
-   + ' later');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s, anim1 100s';
-  assert_equals(div.getAnimations().length, 2,
-    'getAnimations returns one Animation for each CSS animation-name'
-    + ' even if the names are duplicated');
-}, 'getAnimations for CSS Animations with duplicated animation-name');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'empty 100s';
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns Animations for CSS animations with an'
-    + ' empty keyframes rule');
-}, 'getAnimations for CSS Animations with empty keyframes rule');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s 100s';
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 1,
-    'getAnimations returns animations for CSS animations whose'
-    + ' delay makes them start later');
-  animations[0].ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(animations[0].startTime <= document.timeline.currentTime,
-      'For CSS Animations in delay phase, the start time of the Animation is'
-      + ' not in the future');
-    t.done();
-  }));
-}, 'getAnimations for CSS animations in delay phase');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 0s 100s';
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns animations for CSS animations whose'
-    + ' duration is zero');
-  div.remove();
-}, 'getAnimations for zero-duration CSS Animations');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-  var originalAnimation = div.getAnimations()[0];
-
-  // Update pause state (an Animation change)
-  div.style.animationPlayState = 'paused';
-  var pendingAnimation = div.getAnimations()[0];
-  assert_equals(pendingAnimation.playState, 'pending',
-                'animation\'s play state is updated');
-  assert_equals(originalAnimation, pendingAnimation,
-                'getAnimations returns the same objects even when their'
-                + ' play state changes');
-
-  // Update duration (an Animation change)
-  div.style.animationDuration = '200s';
-  var extendedAnimation = div.getAnimations()[0];
-  // FIXME: Check extendedAnimation.effect.timing.duration has changed once the
-  // API is available
-  assert_equals(originalAnimation, extendedAnimation,
-                'getAnimations returns the same objects even when their'
-                + ' duration changes');
-}, 'getAnimations returns objects with the same identity');
-
-test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = 'anim1 100s';
-
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns an animation before cancelling');
-
-  var animation = div.getAnimations()[0];
-
-  animation.cancel();
-  assert_equals(div.getAnimations().length, 0,
-    'getAnimations does not return cancelled animations');
-
-  animation.play();
-  assert_equals(div.getAnimations().length, 1,
-    'getAnimations returns cancelled animations that have been re-started');
-
-}, 'getAnimations for CSS Animations that are cancelled');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_element-get-animations.html");
+  });
 </script>
+</html>
copy from dom/animation/test/css-transitions/test_animation-cancel.html
copy to dom/animation/test/css-transitions/file_animation-cancel.html
--- a/dom/animation/test/css-transitions/test_animation-cancel.html
+++ b/dom/animation/test/css-transitions/file_animation-cancel.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t, { style: 'margin-left: 0px' });
   flushComputedStyle(div);
 
   div.style.transition = 'margin-left 100s';
@@ -120,10 +118,11 @@ test(function(t) {
   assert_equals(getComputedStyle(div).marginLeft, '1000px',
                 'margin-left style is still not animated after updating'
                 + ' transition-duration');
   assert_equals(animation.playState, 'idle',
                 'Transition is still idle after updating transition-duration');
 }, 'After cancelling a transition, updating transition properties doesn\'t make'
    + ' it live again');
 
+done();
 </script>
-</html>
+</body>
copy from dom/animation/test/css-transitions/test_animation-currenttime.html
copy to dom/animation/test/css-transitions/file_animation-currenttime.html
--- a/dom/animation/test/css-transitions/test_animation-currenttime.html
+++ b/dom/animation/test/css-transitions/file_animation-currenttime.html
@@ -7,22 +7,19 @@
     <style>
 
 .animated-div {
   margin-left: 100px;
   transition: margin-left 1000s linear 1000s;
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -305,11 +302,12 @@ async_test(function(t) {
       'Animation.currentTime is unchanged after pausing');
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Animation.currentTime after pausing');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-transitions/test_animation-finished.html
copy to dom/animation/test/css-transitions/file_animation-finished.html
--- a/dom/animation/test/css-transitions/test_animation-finished.html
+++ b/dom/animation/test/css-transitions/file_animation-finished.html
@@ -1,22 +1,20 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 
 .animated-div {
   margin-left: 100px;
   transition: margin-left 1000s linear 1000s;
 }
 
 </style>
+<body>
 <script>
 
 'use strict';
 
 const ANIM_DELAY_MS = 1000000; // 1000s
 const ANIM_DUR_MS = 1000000; // 1000s
 
 async_test(function(t) {
@@ -53,9 +51,11 @@ async_test(function(t) {
     // 1108055) we should use that here.
     assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
                   'Replaying a finished reversed transition should reset ' +
                   'its currentTime to the end of the effect');
     t.done();
   }));
 }, 'Test restarting a reversed finished transition');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_animation-pausing.html
copy to dom/animation/test/css-transitions/file_animation-pausing.html
--- a/dom/animation/test/css-transitions/test_animation-pausing.html
+++ b/dom/animation/test/css-transitions/file_animation-pausing.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 function getMarginLeft(cs) {
   return parseFloat(cs.marginLeft);
 }
 
 async_test(function(t) {
@@ -42,9 +40,11 @@ async_test(function(t) {
     return animation.ready.then(waitForFrame);
   })).then(t.step_func(function() {
     assert_true(getMarginLeft(cs) > previousAnimVal,
                 'margin-left increases after calling play()');
     t.done();
   }));
 }, 'pause() and play() a transition');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_animation-ready.html
copy to dom/animation/test/css-transitions/file_animation-ready.html
--- a/dom/animation/test/css-transitions/test_animation-ready.html
+++ b/dom/animation/test/css-transitions/file_animation-ready.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.transform = 'translate(0px)';
   window.getComputedStyle(div).transform;
   div.style.transition = 'transform 100s';
@@ -88,9 +86,11 @@ async_test(function(t) {
 
   // Now update the transition to animate to something not-interpolable
   div.style.marginLeft = 'auto';
   window.getComputedStyle(div).marginLeft;
 
 }, 'ready promise is rejected when a transition is cancelled by changing'
    + ' the transition property to something not interpolable');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_animation-starttime.html
copy to dom/animation/test/css-transitions/file_animation-starttime.html
--- a/dom/animation/test/css-transitions/test_animation-starttime.html
+++ b/dom/animation/test/css-transitions/file_animation-starttime.html
@@ -7,22 +7,19 @@
     <style>
 
 .animated-div {
   margin-left: 100px;
   transition: margin-left 1000s linear 1000s;
 }
 
     </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
     <script src="../testcommon.js"></script>
   </head>
   <body>
-    <div id="log"></div>
     <script type="text/javascript">
 
 'use strict';
 
 // TODO: add equivalent tests without an animation-delay, but first we need to
 // change the timing of animationstart dispatch. (Right now the animationstart
 // event will fire before the ready Promise is resolved if there is no
 // animation-delay.)
@@ -282,11 +279,12 @@ async_test(function(t) {
       'Animation.playState is "paused" after pause() call');
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Animation.startTime after paused');
 
+done();
     </script>
   </body>
 </html>
copy from dom/animation/test/css-transitions/test_effect-name.html
copy to dom/animation/test/css-transitions/file_effect-name.html
--- a/dom/animation/test/css-transitions/test_effect-name.html
+++ b/dom/animation/test/css-transitions/file_effect-name.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
 
   // Add a transition
   div.style.left = '0px';
@@ -16,9 +14,11 @@ test(function(t) {
   div.style.transition = 'all 100s';
   div.style.left = '100px';
 
   assert_equals(div.getAnimations()[0].effect.name, 'left',
                 'The name for the transitions corresponds to the property ' +
                 'being transitioned');
 }, 'Effect name for transitions');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_effect-target.html
copy to dom/animation/test/css-transitions/file_effect-target.html
--- a/dom/animation/test/css-transitions/test_effect-target.html
+++ b/dom/animation/test/css-transitions/file_effect-target.html
@@ -1,23 +1,23 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 test(function(t) {
   var div = addDiv(t);
 
   div.style.left = '0px';
   window.getComputedStyle(div).transitionProperty;
   div.style.transition = 'left 100s';
   div.style.left = '100px';
 
   var animation = div.getAnimations()[0];
   assert_equals(animation.effect.target, div,
     'Animation.target is the animatable div');
 }, 'Returned CSS transitions have the correct Animation.target');
 
+done();
 </script>
+</body>
copy from dom/animation/test/css-transitions/test_element-get-animations.html
copy to dom/animation/test/css-transitions/file_element-get-animations.html
--- a/dom/animation/test/css-transitions/test_element-get-animations.html
+++ b/dom/animation/test/css-transitions/file_element-get-animations.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
+<body>
 <script>
 'use strict';
 
 async_test(function(t) {
   var div = addDiv(t);
 
   // Add a couple of transitions
   div.style.left = '0px';
@@ -86,9 +84,11 @@ test(function(t) {
   div.style.transition = 'all 100s';
   div.style.setProperty('-vendor-unsupported', '100px', '');
 
   assert_equals(div.getAnimations().length, 0,
     'getAnimations returns an empty sequence for a transition'
     + ' of an unsupported property');
 }, 'getAnimations for transition on unsupported property');
 
+done();
 </script>
+</body>
--- a/dom/animation/test/css-transitions/test_animation-cancel.html
+++ b/dom/animation/test/css-transitions/test_animation-cancel.html
@@ -1,129 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_not_equals(getComputedStyle(div).marginLeft, '1000px',
-                      'transform style is animated before cancelling');
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, div.style.marginLeft,
-                  'transform style is no longer animated after cancelling');
-    t.done();
-  }));
-}, 'Animated style is cleared after cancelling a running CSS transition');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  div.addEventListener('transitionend', t.step_func(function() {
-    assert_unreached('Got unexpected end event on cancelled transition');
-  }));
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    // Seek to just before the end then cancel
-    animation.currentTime = 99.9 * 1000;
-    animation.cancel();
-
-    // Then wait a couple of frames and check that no event was dispatched
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Cancelled CSS transitions do not dispatch events');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                  'margin-left style is not animated after cancelling');
-    animation.play();
-    assert_equals(getComputedStyle(div).marginLeft, '0px',
-                  'margin-left style is animated after re-starting transition');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Transition succeeds in running after being re-started');
-    t.done();
-  }));
-}, 'After cancelling a transition, it can still be re-used');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
-    animation.finish();
-    animation.cancel();
-    assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                  'margin-left style is not animated after cancelling');
-    animation.play();
-    assert_equals(getComputedStyle(div).marginLeft, '0px',
-                  'margin-left style is animated after re-starting transition');
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.playState, 'running',
-                  'Transition succeeds in running after being re-started');
-    t.done();
-  }));
-}, 'After cancelling a finished transition, it can still be re-used');
-
-test(function(t) {
-  var div = addDiv(t, { style: 'margin-left: 0px' });
-  flushComputedStyle(div);
-
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '1000px';
-  flushComputedStyle(div);
-
-  var animation = div.getAnimations()[0];
-  animation.cancel();
-  assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                'margin-left style is not animated after cancelling');
-
-  // Trigger a change to a transition property and check that this
-  // doesn't cause the animation to become live again
-  div.style.transitionDuration = '200s';
-  flushComputedStyle(div);
-  assert_equals(getComputedStyle(div).marginLeft, '1000px',
-                'margin-left style is still not animated after updating'
-                + ' transition-duration');
-  assert_equals(animation.playState, 'idle',
-                'Transition is still idle after updating transition-duration');
-}, 'After cancelling a transition, updating transition properties doesn\'t make'
-   + ' it live again');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-cancel.html");
+  });
 </script>
-</html>
--- a/dom/animation/test/css-transitions/test_animation-currenttime.html
+++ b/dom/animation/test/css-transitions/test_animation-currenttime.html
@@ -1,315 +1,14 @@
 <!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests for the effect of setting a CSS transition's
-           Animation.currentTime</title>
-    <style>
-
-.animated-div {
-  margin-left: 100px;
-  transition: margin-left 1000s linear 1000s;
-}
-
-    </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="../testcommon.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
 'use strict';
-
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
-// TODO: Once the computedTiming property is implemented, add checks to the
-// checker helpers to ensure that computedTiming's properties are updated as
-// expected.
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
-
-
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-
-/**
- * These helpers get the value that the currentTime needs to be set to, to put
- * an animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into
- * the middle of various phases or points through the active duration.
- */
-function currentTimeForBeforePhase() {
-  return ANIM_DELAY_MS / 2;
-}
-function currentTimeForActivePhase() {
-  return ANIM_DELAY_MS + ANIM_DUR_MS / 2;
-}
-function currentTimeForAfterPhase() {
-  return ANIM_DELAY_MS + ANIM_DUR_MS + ANIM_DELAY_MS / 2;
-}
-function currentTimeForStartOfActiveInterval() {
-  return ANIM_DELAY_MS;
-}
-function currentTimeForFiftyPercentThroughActiveInterval() {
-  return ANIM_DELAY_MS + ANIM_DUR_MS * 0.5;
-}
-function currentTimeForEndOfActiveInterval() {
-  return ANIM_DELAY_MS + ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   http://w3c.github.io/web-animations/#animation-effect-phases-and-states
-
-// Called when currentTime is set to zero (the beginning of the start delay).
-function checkStateOnSettingCurrentTimeToZero(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the start delay');
-
-  assert_equals(animation.effect.target.style.animationPlayState, 'running',
-    'Animation.effect.target.style.animationPlayState should be ' +
-    '"running" at the start of the start delay');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, UNANIMATED_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'at the beginning of the start delay');
-}
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  // the 0.0001 here is for rounding error
-  assert_less_than_equal(animation.currentTime,
-    animation.timeline.currentTime - animation.startTime + 0.0001,
-    'Animation.currentTime should be less than the local time ' +
-    'equivalent of the timeline\'s currentTime on the first paint tick ' +
-    'after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, INITIAL_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when currentTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when currentTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.currentTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, END_POSITION,
-    'the computed value of margin-left should be the final transitioned-to ' +
-    'value at the end of the active duration');
-}
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.currentTime, 0, 'currentTime should be zero');
-}, 'currentTime of a newly created transition is zero');
-
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  // So that animation is running instead of paused when we set currentTime:
-  animation.startTime = animation.timeline.currentTime;
-
-  animation.currentTime = 10;
-  assert_equals(animation.currentTime, 10,
-    'Check setting of currentTime actually works');
-}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
-   'currentTime');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, 'transitionend');
-
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-
-    animation.currentTime = currentTimeForStartOfActiveInterval();
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.currentTime = currentTimeForEndOfActiveInterval();
-    return eventWatcher.wait_for('transitionend');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-currenttime.html");
   });
-}, 'Skipping forward through transition');
-
-
-test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, 'transitionend');
-
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  // Unlike in the case of CSS animations, we cannot skip to the end and skip
-  // backwards since when we reach the end the transition effect is removed and
-  // changes to the Animation object no longer affect the element. For
-  // this reason we only skip forwards as far as the 50% through point.
-
-  animation.ready.then(t.step_func(function() {
-    animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.currentTime = currentTimeForStartOfActiveInterval();
-
-    // Despite going backwards from being in the active interval to being
-    // before it, we now expect a 'transitionend' event because the transition
-    // should go from being active to inactive.
-    //
-    // Calling checkStateAtActiveIntervalStartTime will check computed style,
-    // causing computed style to be updated and the 'transitionend' event to
-    // be dispatched synchronously. We need to call wait_for first
-    // otherwise eventWatcher will assert that the event was unexpected.
-    eventWatcher.wait_for('transitionend').then(function() {
-      t.done();
-    });
-    checkStateAtActiveIntervalStartTime(animation);
-  }));
-}, 'Skipping backwards through transition');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var exception;
-    try {
-      animation.currentTime = null;
-    } catch (e) {
-      exception = e;
-    }
-    assert_equals(exception.name, 'TypeError',
-      'Expect TypeError exception on trying to set ' +
-      'Animation.currentTime to null');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Setting currentTime to null');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-  var pauseTime;
-
-  animation.ready.then(t.step_func(function() {
-    assert_not_equals(animation.currentTime, null,
-      'Animation.currentTime not null on ready Promise resolve');
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    pauseTime = animation.currentTime;
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(animation.currentTime, pauseTime,
-      'Animation.currentTime is unchanged after pausing');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Animation.currentTime after pausing');
-
-    </script>
-  </body>
-</html>
+</script>
--- a/dom/animation/test/css-transitions/test_animation-finished.html
+++ b/dom/animation/test/css-transitions/test_animation-finished.html
@@ -1,61 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-
-.animated-div {
-  margin-left: 100px;
-  transition: margin-left 1000s linear 1000s;
-}
-
-</style>
 <script>
-
 'use strict';
-
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  animation.finish();
-
-  animation.finished.then(t.step_func(function() {
-    animation.play();
-    assert_equals(animation.currentTime, 0,
-                  'Replaying a finished transition should reset its ' +
-                  'currentTime');
-    t.done();
-  }));
-}, 'Test restarting a finished transition');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    animation.playbackRate = -1;
-    return animation.finished;
-  }).then(t.step_func(function() {
-    animation.play();
-    // FIXME: once animation.effect.computedTiming.endTime is available (bug
-    // 1108055) we should use that here.
-    assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
-                  'Replaying a finished reversed transition should reset ' +
-                  'its currentTime to the end of the effect');
-    t.done();
-  }));
-}, 'Test restarting a reversed finished transition');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-finished.html");
+  });
 </script>
--- a/dom/animation/test/css-transitions/test_animation-pausing.html
+++ b/dom/animation/test/css-transitions/test_animation-pausing.html
@@ -1,50 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-function getMarginLeft(cs) {
-  return parseFloat(cs.marginLeft);
-}
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-
-  div.style.marginLeft = '0px';
-  cs.marginLeft; // Flush style to set up transition start point
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '10000px';
-  cs.marginLeft;
-
-  var animation = div.getAnimations()[0];
-  assert_equals(getMarginLeft(cs), 0,
-                'Initial value of margin-left is zero');
-  var previousAnimVal = getMarginLeft(cs);
-
-  animation.ready.then(waitForFrame).then(t.step_func(function() {
-    assert_true(getMarginLeft(cs) > previousAnimVal,
-                'margin-left is initially increasing');
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    previousAnimVal = getMarginLeft(cs);
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    assert_equals(getMarginLeft(cs), previousAnimVal,
-                  'margin-left does not increase after calling pause()');
-    previousAnimVal = getMarginLeft(cs);
-    animation.play();
-    return animation.ready.then(waitForFrame);
-  })).then(t.step_func(function() {
-    assert_true(getMarginLeft(cs) > previousAnimVal,
-                'margin-left increases after calling play()');
-    t.done();
-  }));
-}, 'pause() and play() a transition');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-pausing.html");
+  });
 </script>
--- a/dom/animation/test/css-transitions/test_animation-ready.html
+++ b/dom/animation/test/css-transitions/test_animation-ready.html
@@ -1,96 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.transform = 'translate(0px)';
-  window.getComputedStyle(div).transform;
-  div.style.transition = 'transform 100s';
-  div.style.transform = 'translate(10px)';
-  window.getComputedStyle(div).transform;
-
-  var animation = div.getAnimations()[0];
-  var originalReadyPromise = animation.ready;
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.ready, originalReadyPromise,
-                  'Ready promise is the same object when playing completes');
-    animation.pause();
-    assert_not_equals(animation.ready, originalReadyPromise,
-                      'Ready promise object identity differs when pausing');
-    t.done();
-  }));
-}, 'A new ready promise is created each time play() is called'
-   + ' the animation property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up pending transition
-  div.style.transform = 'translate(0px)';
-  window.getComputedStyle(div).transform;
-  div.style.transition = 'transform 100s';
-  div.style.transform = 'translate(10px)';
-  window.getComputedStyle(div).transform;
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending', 'Animation is initially pending');
-
-  // Set up listeners on ready promise
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-    assert_equals(animation.playState, 'idle',
-                  'Animation is idle after transition was cancelled');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now remove transform from transition-property and flush styles
-  div.style.transitionProperty = 'none';
-  window.getComputedStyle(div).transitionProperty;
-
-}, 'ready promise is rejected when a transition is cancelled by updating'
-   + ' transition-property');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up pending transition
-  div.style.marginLeft = '0px';
-  window.getComputedStyle(div).marginLeft;
-  div.style.transition = 'margin-left 100s';
-  div.style.marginLeft = '100px';
-  window.getComputedStyle(div).marginLeft;
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.playState, 'pending', 'Animation is initially pending');
-
-  // Set up listeners on ready promise
-  animation.ready.then(t.step_func(function() {
-    assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
-    assert_equals(err.name, 'AbortError',
-                  'ready promise is rejected with AbortError');
-    assert_equals(animation.playState, 'idle',
-                  'Animation is idle after transition was cancelled');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
-  // Now update the transition to animate to something not-interpolable
-  div.style.marginLeft = 'auto';
-  window.getComputedStyle(div).marginLeft;
-
-}, 'ready promise is rejected when a transition is cancelled by changing'
-   + ' the transition property to something not interpolable');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-ready.html");
+  });
 </script>
--- a/dom/animation/test/css-transitions/test_animation-starttime.html
+++ b/dom/animation/test/css-transitions/test_animation-starttime.html
@@ -1,292 +1,14 @@
 <!doctype html>
-<html>
-  <head>
-    <meta charset=utf-8>
-    <title>Tests for the effect of setting a CSS transition's
-           Animation.startTime</title>
-    <style>
-
-.animated-div {
-  margin-left: 100px;
-  transition: margin-left 1000s linear 1000s;
-}
-
-    </style>
-    <script src="/resources/testharness.js"></script>
-    <script src="/resources/testharnessreport.js"></script>
-    <script src="../testcommon.js"></script>
-  </head>
-  <body>
-    <div id="log"></div>
-    <script type="text/javascript">
-
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
 'use strict';
-
-// TODO: add equivalent tests without an animation-delay, but first we need to
-// change the timing of animationstart dispatch. (Right now the animationstart
-// event will fire before the ready Promise is resolved if there is no
-// animation-delay.)
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1134163
-
-// TODO: Once the computedTiming property is implemented, add checks to the
-// checker helpers to ensure that computedTiming's properties are updated as
-// expected.
-// See https://bugzilla.mozilla.org/show_bug.cgi?id=1108055
-
-
-const ANIM_DELAY_MS = 1000000; // 1000s
-const ANIM_DUR_MS = 1000000; // 1000s
-
-/**
- * These helpers get the value that the startTime needs to be set to, to put an
- * animation that uses the above ANIM_DELAY_MS and ANIM_DUR_MS values into the
- * middle of various phases or points through the active duration.
- */
-function startTimeForBeforePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS / 2;
-}
-function startTimeForActivePhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS / 2;
-}
-function startTimeForAfterPhase(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS - ANIM_DELAY_MS / 2;
-}
-function startTimeForStartOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS;
-}
-function startTimeForFiftyPercentThroughActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS * 0.5;
-}
-function startTimeForEndOfActiveInterval(timeline) {
-  return timeline.currentTime - ANIM_DELAY_MS - ANIM_DUR_MS;
-}
-
-
-// Expected computed 'margin-left' values at points during the active interval:
-// When we assert_between_inclusive using these values we could in theory cause
-// intermittent failure due to very long delays between paints, but since the
-// active duration is 1000s long, a delay would need to be around 100s to cause
-// that. If that's happening then there are likely other issues that should be
-// fixed, so a failure to make us look into that seems like a good thing.
-const INITIAL_POSITION = 100;
-const TEN_PCT_POSITION = 110;
-const FIFTY_PCT_POSITION = 150;
-const END_POSITION = 200;
-
-// The terms used for the naming of the following helper functions refer to
-// terms used in the Web Animations specification for specific phases of an
-// animation. The terms can be found here:
-//
-//   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
-//
-// Note the distinction between the "animation start time" which occurs before
-// the start delay and the start of the active interval which occurs after it.
-
-// Called when the ready Promise's callbacks should happen
-function checkStateOnReadyPromiseResolved(animation)
-{
-  assert_less_than_equal(animation.startTime, animation.timeline.currentTime,
-    'Animation.startTime should be less than the timeline\'s ' +
-    'currentTime on the first paint tick after animation creation');
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" on the first paint ' +
-    'tick after animation creation');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, INITIAL_POSITION,
-                'the computed value of margin-left should be unaffected ' +
-                'by an animation with a delay on ready Promise resolve');
-}
-
-// Called when startTime is set to the time the active interval starts.
-function checkStateAtActiveIntervalStartTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'running',
-    'Animation.playState should be "running" at the start of ' +
-    'the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_between_inclusive(marginLeft, INITIAL_POSITION, TEN_PCT_POSITION,
-    'the computed value of margin-left should be close to the value at the ' +
-    'beginning of the animation');
-}
-
-function checkStateAtFiftyPctOfActiveInterval(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, FIFTY_PCT_POSITION,
-    'the computed value of margin-left should be half way through the ' +
-    'animation at the midpoint of the active interval');
-}
-
-// Called when startTime is set to the time the active interval ends.
-function checkStateAtActiveIntervalEndTime(animation)
-{
-  // We don't test animation.startTime since our caller just set it.
-
-  assert_equals(animation.playState, 'finished',
-    'Animation.playState should be "finished" at the end of ' +
-    'the active interval');
-
-  var div = animation.effect.target;
-  var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
-  assert_equals(marginLeft, END_POSITION,
-    'the computed value of margin-left should be the final transitioned-to ' +
-    'value at the end of the active duration');
-}
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.startTime, null, 'startTime is unresolved');
-}, 'startTime of a newly created transition is unresolved');
-
-
-test(function(t)
-{
-  var div = addDiv(t, {'class': 'animated-div'});
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-  var currentTime = animation.timeline.currentTime;
-  animation.startTime = currentTime;
-  assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
-    'Check setting of startTime actually works');
-}, 'Sanity test to check round-tripping assigning to new animation\'s ' +
-   'startTime');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, 'transitionend');
-
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    checkStateOnReadyPromiseResolved(animation);
-
-    animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    checkStateAtActiveIntervalStartTime(animation);
-
-    animation.startTime =
-      startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-    checkStateAtFiftyPctOfActiveInterval(animation);
-
-    animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.wait_for('transitionend');
-  })).then(t.step_func(function() {
-    checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_animation-starttime.html");
   });
-}, 'Skipping forward through animation');
-
-
-test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(t, div, 'transitionend');
-
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  // Unlike in the case of CSS animations, we cannot skip to the end and skip
-  // backwards since when we reach the end the transition effect is removed and
-  // changes to the Animation object no longer affect the element. For
-  // this reason we only skip forwards as far as the 90% through point.
-
-  animation.startTime =
-    startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
-  checkStateAtFiftyPctOfActiveInterval(animation);
-
-  animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-
-  // Despite going backwards from being in the active interval to being before
-  // it, we now expect an 'animationend' event because the animation should go
-  // from being active to inactive.
-  //
-  // Calling checkStateAtActiveIntervalStartTime will check computed style,
-  // causing computed style to be updated and the 'transitionend' event to
-  // be dispatched synchronously. We need to call waitForEvent first
-  // otherwise eventWatcher will assert that the event was unexpected.
-  eventWatcher.wait_for('transitionend').then(function() {
-    t.done();
-  });
-  checkStateAtActiveIntervalStartTime(animation);
-}, 'Skipping backwards through transition');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  var storedCurrentTime;
-
-  animation.ready.then(t.step_func(function() {
-    storedCurrentTime = animation.currentTime;
-    animation.startTime = null;
-    return animation.ready;
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(t.step_func(function() {
-    assert_equals(animation.currentTime, storedCurrentTime,
-      'Test that hold time is correct');
-    t.done();
-  }));
-}, 'Setting startTime to null');
-
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-
-  flushComputedStyle(div);
-  div.style.marginLeft = '200px'; // initiate transition
-
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var savedStartTime = animation.startTime;
-
-    assert_not_equals(animation.startTime, null,
-      'Animation.startTime not null on ready Promise resolve');
-
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    assert_equals(animation.startTime, null,
-      'Animation.startTime is null after paused');
-    assert_equals(animation.playState, 'paused',
-      'Animation.playState is "paused" after pause() call');
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
-  });
-}, 'Animation.startTime after paused');
-
-    </script>
-  </body>
-</html>
+</script>
--- a/dom/animation/test/css-transitions/test_effect-name.html
+++ b/dom/animation/test/css-transitions/test_effect-name.html
@@ -1,24 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-
-  // Add a transition
-  div.style.left = '0px';
-  window.getComputedStyle(div).transitionProperty;
-  div.style.transition = 'all 100s';
-  div.style.left = '100px';
-
-  assert_equals(div.getAnimations()[0].effect.name, 'left',
-                'The name for the transitions corresponds to the property ' +
-                'being transitioned');
-}, 'Effect name for transitions');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_effect-name.html");
+  });
 </script>
--- a/dom/animation/test/css-transitions/test_effect-target.html
+++ b/dom/animation/test/css-transitions/test_effect-target.html
@@ -1,23 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-test(function(t) {
-  var div = addDiv(t);
-
-  div.style.left = '0px';
-  window.getComputedStyle(div).transitionProperty;
-  div.style.transition = 'left 100s';
-  div.style.left = '100px';
-
-  var animation = div.getAnimations()[0];
-  assert_equals(animation.effect.target, div,
-    'Animation.target is the animatable div');
-}, 'Returned CSS transitions have the correct Animation.target');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_effect-target.html");
+  });
 </script>
--- a/dom/animation/test/css-transitions/test_element-get-animations.html
+++ b/dom/animation/test/css-transitions/test_element-get-animations.html
@@ -1,94 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
 <script>
 'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Add a couple of transitions
-  div.style.left = '0px';
-  div.style.top = '0px';
-  window.getComputedStyle(div).transitionProperty;
-
-  div.style.transition = 'all 100s';
-  div.style.left = '100px';
-  div.style.top = '100px';
-
-  var animations = div.getAnimations();
-  assert_equals(animations.length, 2,
-    'getAnimations() returns one Animation per transitioning property');
-  waitForAllAnimations(animations).then(t.step_func(function() {
-    var startTime = animations[0].startTime;
-    assert_true(startTime > 0 && startTime <= document.timeline.currentTime,
-                'CSS transitions have sensible start times');
-    assert_equals(animations[0].startTime, animations[1].startTime,
-      'CSS transitions started together have the same start time');
-    // Wait a moment then add a third transition
-    return waitForFrame();
-  })).then(t.step_func(function() {
-    div.style.backgroundColor = 'green';
-    animations = div.getAnimations();
-    assert_equals(animations.length, 3,
-      'getAnimations returns Animations for all running CSS Transitions');
-    return waitForAllAnimations(animations);
-  })).then(t.step_func(function() {
-    assert_true(animations[1].startTime < animations[2].startTime,
-      'Animation for additional CSS transition starts after the original'
-      + ' transitions and appears later in the list');
-    t.done();
-  }));
-}, 'getAnimations for CSS Transitions');
-
-async_test(function(t) {
-  var div = addDiv(t);
-
-  // Set up event listener
-  div.addEventListener('transitionend', t.step_func(function() {
-    assert_equals(div.getAnimations().length, 0,
-      'getAnimations does not return finished CSS Transitions');
-    t.done();
-  }));
-
-  // Add a very short transition
-  div.style.left = '0px';
-  window.getComputedStyle(div).left;
-
-  div.style.transition = 'all 0.01s';
-  div.style.left = '100px';
-  window.getComputedStyle(div).left;
-}, 'getAnimations for CSS Transitions that have finished');
-
-test(function(t) {
-  var div = addDiv(t);
-
-  // Try to transition non-animatable property animation-duration
-  div.style.animationDuration = '10s';
-  window.getComputedStyle(div).animationDuration;
-
-  div.style.transition = 'all 100s';
-  div.style.animationDuration = '100s';
-
-  assert_equals(div.getAnimations().length, 0,
-    'getAnimations returns an empty sequence for a transition'
-    + ' of a non-animatable property');
-}, 'getAnimations for transition on non-animatable property');
-
-test(function(t) {
-  var div = addDiv(t);
-
-  div.style.setProperty('-vendor-unsupported', '0px', '');
-  window.getComputedStyle(div).transitionProperty;
-  div.style.transition = 'all 100s';
-  div.style.setProperty('-vendor-unsupported', '100px', '');
-
-  assert_equals(div.getAnimations().length, 0,
-    'getAnimations returns an empty sequence for a transition'
-    + ' of an unsupported property');
-}, 'getAnimations for transition on unsupported property');
-
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_element-get-animations.html");
+  });
 </script>
copy from dom/animation/test/document-timeline/test_document-timeline.html
copy to dom/animation/test/document-timeline/file_document-timeline.html
--- a/dom/animation/test/document-timeline/test_document-timeline.html
+++ b/dom/animation/test/document-timeline/file_document-timeline.html
@@ -1,14 +1,12 @@
 <!doctype html>
 <meta charset=utf-8>
 <title>Web Animations API: DocumentTimeline tests</title>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
+<script src="../testcommon.js"></script>
 <iframe src="data:text/html;charset=utf-8," width="10" height="10" id="iframe"></iframe>
 <iframe src="data:text/html;charset=utf-8,%3Chtml%20style%3D%22display%3Anone%22%3E%3C%2Fhtml%3E" width="10" height="10" id="hidden-iframe"></iframe>
 <script>
 'use strict';
 
 test(function() {
   assert_equals(document.timeline, document.timeline,
     'document.timeline returns the same object every time');
@@ -128,9 +126,10 @@ async_test(function(t) {
 
   if (hiddenIFrame.contentDocument.readyState === 'complete') {
     testToRunOnLoad();
   } else {
     hiddenIFrame.addEventListener("load", testToRunOnLoad);
   }
 }, 'document.timeline.currentTime hidden subframe dynamic test');
 
+done();
 </script>
--- a/dom/animation/test/document-timeline/test_document-timeline.html
+++ b/dom/animation/test/document-timeline/test_document-timeline.html
@@ -1,136 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
-<title>Web Animations API: DocumentTimeline tests</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
-<iframe src="data:text/html;charset=utf-8," width="10" height="10" id="iframe"></iframe>
-<iframe src="data:text/html;charset=utf-8,%3Chtml%20style%3D%22display%3Anone%22%3E%3C%2Fhtml%3E" width="10" height="10" id="hidden-iframe"></iframe>
 <script>
 'use strict';
-
-test(function() {
-  assert_equals(document.timeline, document.timeline,
-    'document.timeline returns the same object every time');
-  var iframe = document.getElementById('iframe');
-  assert_not_equals(document.timeline, iframe.contentDocument.timeline,
-    'document.timeline returns a different object for each document');
-  assert_not_equals(iframe.contentDocument.timeline, null,
-    'document.timeline on an iframe is not null');
-},
-'document.timeline identity tests',
-{
-  help:   'http://dev.w3.org/fxtf/web-animations/#the-document-timeline',
-  assert: [ 'Each document has a timeline called the document timeline' ],
-  author: 'Brian Birtles'
-});
-
-async_test(function(t) {
-  assert_true(document.timeline.currentTime > 0,
-    'document.timeline.currentTime is positive');
-  // document.timeline.currentTime should be set even before document
-  // load fires. We expect this code to be run before document load and hence
-  // the above assertion is sufficient.
-  // If the following assertion fails, this test needs to be redesigned.
-  assert_true(document.readyState !== 'complete',
-    'Test is running prior to document load');
-
-  // Test that the document timeline's current time is measured from
-  // navigationStart.
-  //
-  // We can't just compare document.timeline.currentTime to
-  // window.performance.now() because currentTime is only updated on a sample
-  // so we use requestAnimationFrame instead.
-  window.requestAnimationFrame(t.step_func(function(rafTime) {
-    assert_equals(document.timeline.currentTime, rafTime,
-                  'document.timeline.currentTime matches' +
-                  ' requestAnimationFrame time');
-    t.done();
-  }));
-},
-'document.timeline.currentTime value tests',
-{
-  help: [
-    'http://dev.w3.org/fxtf/web-animations/#the-global-clock',
-    'http://dev.w3.org/fxtf/web-animations/#the-document-timeline'
-  ],
-  assert: [
-    'The global clock is a source of monotonically increasing time values',
-    'The time values of the document timeline are calculated as a fixed' +
-    ' offset from the global clock',
-    'the zero time corresponds to the navigationStart moment',
-    'the time value of each document timeline must be equal to the time ' +
-    'passed to animation frame request callbacks for that browsing context'
-  ],
-  author: 'Brian Birtles'
-});
-
-async_test(function(t) {
-  var valueAtStart = document.timeline.currentTime;
-  var timeAtStart = window.performance.now();
-  while (window.performance.now() - timeAtStart < 100) {
-    // Wait 100ms
-  }
-  assert_equals(document.timeline.currentTime, valueAtStart,
-    'document.timeline.currentTime does not change within a script block');
-  window.requestAnimationFrame(t.step_func(function() {
-    assert_true(document.timeline.currentTime > valueAtStart,
-      'document.timeline.currentTime increases between script blocks');
-    t.done();
-  }));
-},
-'document.timeline.currentTime liveness tests',
-{
-  help: 'http://dev.w3.org/fxtf/web-animations/#script-execution-and-live-updates-to-the-model',
-  assert: [ 'The value returned by the currentTime attribute of a' +
-            ' document timeline will not change within a script block' ],
-  author: 'Brian Birtles'
-});
-
-test(function() {
-  var hiddenIFrame = document.getElementById('hidden-iframe');
-  assert_equals(typeof hiddenIFrame.contentDocument.timeline.currentTime,
-    'number',
-    'currentTime of an initially hidden subframe\'s timeline is a number');
-  assert_true(hiddenIFrame.contentDocument.timeline.currentTime >= 0,
-    'currentTime of an initially hidden subframe\'s timeline is >= 0');
-}, 'document.timeline.currentTime hidden subframe test');
-
-async_test(function(t) {
-  var hiddenIFrame = document.getElementById('hidden-iframe');
-
-  // Don't run the test until after the iframe has completed loading or else the
-  // contentDocument may change.
-  var testToRunOnLoad = t.step_func(function() {
-    // Remove display:none
-    hiddenIFrame.style.display = 'block';
-    window.getComputedStyle(hiddenIFrame).display;
-
-    window.requestAnimationFrame(t.step_func(function() {
-      assert_true(hiddenIFrame.contentDocument.timeline.currentTime > 0,
-        'document.timeline.currentTime is positive after removing'
-        + ' display:none');
-      var previousValue = hiddenIFrame.contentDocument.timeline.currentTime;
-
-      // Re-introduce display:none
-      hiddenIFrame.style.display = 'none';
-      window.getComputedStyle(hiddenIFrame).display;
-
-      window.requestAnimationFrame(t.step_func(function() {
-        assert_true(
-          hiddenIFrame.contentDocument.timeline.currentTime >= previousValue,
-          'document.timeline.currentTime does not go backwards after'
-          + ' re-setting display:none');
-        t.done();
-      }));
-    }));
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_document-timeline.html");
   });
-
-  if (hiddenIFrame.contentDocument.readyState === 'complete') {
-    testToRunOnLoad();
-  } else {
-    hiddenIFrame.addEventListener("load", testToRunOnLoad);
-  }
-}, 'document.timeline.currentTime hidden subframe dynamic test');
-
 </script>
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -1,31 +1,54 @@
 [DEFAULT]
 support-files =
   testcommon.js
 
 [css-animations/test_animations-dynamic-changes.html]
+support-files = css-animations/file_animations-dynamic-changes.html
 [css-animations/test_animation-cancel.html]
+support-files = css-animations/file_animation-cancel.html
 [css-animations/test_animation-currenttime.html]
+support-files = css-animations/file_animation-currenttime.html
 [css-animations/test_animation-finish.html]
+support-files = css-animations/file_animation-finish.html
 [css-animations/test_animation-finished.html]
+support-files = css-animations/file_animation-finished.html
 [css-animations/test_animation-pausing.html]
+support-files = css-animations/file_animation-pausing.html
 [css-animations/test_animation-playstate.html]
+support-files = css-animations/file_animation-playstate.html
 [css-animations/test_animation-ready.html]
+support-files = css-animations/file_animation-ready.html
 [css-animations/test_animation-starttime.html]
+support-files = css-animations/file_animation-starttime.html
 [css-animations/test_effect-name.html]
+support-files = css-animations/file_effect-name.html
 [css-animations/test_effect-target.html]
+support-files = css-animations/file_effect-target.html
 [css-animations/test_element-get-animations.html]
 skip-if = buildapp == 'mulet'
+support-files = css-animations/file_element-get-animations.html
 [css-transitions/test_animation-cancel.html]
+support-files = css-transitions/file_animation-cancel.html
 [css-transitions/test_animation-currenttime.html]
+support-files = css-transitions/file_animation-currenttime.html
 [css-transitions/test_animation-finished.html]
+support-files = css-transitions/file_animation-finished.html
 [css-transitions/test_animation-pausing.html]
+support-files = css-transitions/file_animation-pausing.html
 [css-transitions/test_animation-ready.html]
+support-files = css-transitions/file_animation-ready.html
 [css-transitions/test_animation-starttime.html]
+support-files = css-transitions/file_animation-starttime.html
 [css-transitions/test_effect-name.html]
+support-files = css-transitions/file_effect-name.html
 [css-transitions/test_effect-target.html]
+support-files = css-transitions/file_effect-target.html
 [css-transitions/test_element-get-animations.html]
 skip-if = buildapp == 'mulet'
+support-files = css-transitions/file_element-get-animations.html
 [document-timeline/test_document-timeline.html]
+support-files = document-timeline/file_document-timeline.html
 [document-timeline/test_request_animation_frame.html]
 skip-if = buildapp == 'mulet'
 [mozilla/test_deferred_start.html]
+support-files = mozilla/file_deferred_start.html
copy from dom/animation/test/mozilla/test_deferred_start.html
copy to dom/animation/test/mozilla/file_deferred_start.html
--- a/dom/animation/test/mozilla/test_deferred_start.html
+++ b/dom/animation/test/mozilla/file_deferred_start.html
@@ -1,27 +1,25 @@
 <!doctype html>
 <meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
 <script src="../testcommon.js"></script>
-<div id="log"></div>
 <style>
 @keyframes empty { }
 @keyframes animTransform {
   from { transform: translate(0px); }
   to { transform: translate(100px); }
 }
 .target {
   /* Element needs geometry to be eligible for layerization */
   width: 100px;
   height: 100px;
   background-color: white;
 }
 </style>
+<body>
 <script>
 'use strict';
 
 function waitForDocLoad() {
   return new Promise(function(resolve, reject) {
     if (document.readyState === 'complete') {
       resolve();
     } else {
@@ -80,17 +78,17 @@ async_test(function(t) {
 //
 // NOTE: It is important that we DON'T use
 // SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh here since that takes
 // us through a different code path.
 async_test(function(t) {
   // This test only applies to compositor animations
   const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
   var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
-                    SpecialPowers.getBoolPref(OMTAPrefKey);
+                    opener.SpecialPowers.getBoolPref(OMTAPrefKey);
   if (!omtaEnabled) {
     t.done();
     return;
   }
 
   // Setup animation
   var div = addDiv(t);
   div.classList.add('target');
@@ -115,9 +113,11 @@ async_test(function(t) {
     // half-way through the animation
     assert_true(matrixComponents[4] >= 50,
                 'Animation is at least half-way through on the compositor'
                 + ' (got translation of ' + matrixComponents[4] + ')');
     t.done();
   }));
 }, 'Starting an animation with a delay starts from the correct point');
 
+done();
 </script>
+</body>
--- a/dom/animation/test/mozilla/test_deferred_start.html
+++ b/dom/animation/test/mozilla/test_deferred_start.html
@@ -1,123 +1,14 @@
 <!doctype html>
 <meta charset=utf-8>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
-<script src="../testcommon.js"></script>
 <div id="log"></div>
-<style>
-@keyframes empty { }
-@keyframes animTransform {
-  from { transform: translate(0px); }
-  to { transform: translate(100px); }
-}
-.target {
-  /* Element needs geometry to be eligible for layerization */
-  width: 100px;
-  height: 100px;
-  background-color: white;
-}
-</style>
 <script>
 'use strict';
-
-function waitForDocLoad() {
-  return new Promise(function(resolve, reject) {
-    if (document.readyState === 'complete') {
-      resolve();
-    } else {
-      window.addEventListener('load', resolve);
-    }
+setup({explicit_done: true});
+SpecialPowers.pushPrefEnv(
+  { "set": [["dom.animations-api.core.enabled", true]]},
+  function() {
+    window.open("file_deferred_start.html");
   });
-}
-
-async_test(function(t) {
-  var div = addDiv(t);
-  var cs = window.getComputedStyle(div);
-
-  // Test that empty animations actually start.
-  //
-  // Normally we tie the start of animations to when their first frame of
-  // the animation is rendered. However, for animations that don't actually
-  // trigger a paint (e.g. because they are empty, or are animating something
-  // that doesn't render or is offscreen) we want to make sure they still
-  // start.
-  //
-  // Before we start, wait for the document to finish loading. This is because
-  // during loading we will have other paint events taking place which might,
-  // by luck, happen to trigger animations that otherwise would not have been
-  // triggered, leading to false positives.
-  //
-  // As a result, it's better to wait until we have a more stable state before
-  // continuing.
-  var promiseCallbackDone = false;
-  waitForDocLoad().then(function() {
-    div.style.animation = 'empty 1000s';
-    var animation = div.getAnimations()[0];
-
-    animation.ready.then(function() {
-      promiseCallbackDone = true;
-    }).catch(function() {
-      assert_unreached('ready promise was rejected');
-    });
-
-  // We need to wait for up to three frames. This is because in some
-  // cases it can take up to two frames for the initial layout
-  // to take place. Even after that happens we don't actually resolve the
-  // ready promise until the following tick.
-  })
-  .then(waitForFrame)
-  .then(waitForFrame)
-  .then(waitForFrame)
-  .then(t.step_func(function() {
-    assert_true(promiseCallbackDone,
-                'ready promise for an empty animation was resolved'
-                + ' within three animation frames');
-    t.done();
-  }));
-}, 'Animation.ready is resolved for an empty animation');
-
-// Test that compositor animations with delays get synced correctly
-//
-// NOTE: It is important that we DON'T use
-// SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh here since that takes
-// us through a different code path.
-async_test(function(t) {
-  // This test only applies to compositor animations
-  const OMTAPrefKey = 'layers.offmainthreadcomposition.async-animations';
-  var omtaEnabled = SpecialPowers.DOMWindowUtils.layerManagerRemote &&
-                    SpecialPowers.getBoolPref(OMTAPrefKey);
-  if (!omtaEnabled) {
-    t.done();
-    return;
-  }
-
-  // Setup animation
-  var div = addDiv(t);
-  div.classList.add('target');
-  div.style.animation = 'animTransform 100s -50s forwards';
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(t.step_func(function() {
-    var transformStr =
-      SpecialPowers.DOMWindowUtils.getOMTAStyle(div, 'transform');
-
-    var matrixComponents =
-      transformStr.startsWith('matrix(')
-      ? transformStr.substring('matrix('.length, transformStr.length-1)
-                    .split(',')
-                    .map(component => Number(component))
-      : [];
-    assert_equals(matrixComponents.length, 6,
-                  'Got a valid transform matrix on the compositor'
-                  + ' (got: "' + transformStr + '")');
-
-    // If the delay has been applied correctly we should be at least
-    // half-way through the animation
-    assert_true(matrixComponents[4] >= 50,
-                'Animation is at least half-way through on the compositor'
-                + ' (got translation of ' + matrixComponents[4] + ')');
-    t.done();
-  }));
-}, 'Starting an animation with a delay starts from the correct point');
-
 </script>
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -72,8 +72,23 @@ function waitForAllAnimations(animations
  * to be computed so that when we synchronouslyet set it to a different value
  * we actually get a transition instead of that being the initial value.
  */
 function flushComputedStyle(elem) {
   var cs = window.getComputedStyle(elem);
   cs.marginLeft;
 }
 
+for (var funcName of ["async_test", "assert_not_equals", "assert_equals",
+                      "assert_approx_equals", "assert_less_than_equal",
+                      "assert_between_inclusive", "assert_true", "assert_false",
+                      "test"]) {
+  window[funcName] = opener[funcName].bind(opener);
+}
+
+window.EventWatcher = opener.EventWatcher;
+
+function done() {
+  opener.add_completion_callback(function() {
+    self.close();
+  });
+  opener.done();
+}
--- a/dom/base/Crypto.cpp
+++ b/dom/base/Crypto.cpp
@@ -105,17 +105,17 @@ Crypto::GetRandomValues(JSContext* aCx, 
     uint8_t *buf = GetRandomValues(dataLen);
 
     if (!buf) {
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     memcpy(data, buf, dataLen);
-    NS_Free(buf);
+    free(buf);
   }
 
   aRetval.set(view);
 }
 
 SubtleCrypto*
 Crypto::Subtle()
 {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4042,19 +4042,16 @@ nsDocument::SetSubDocumentFor(Element* a
         PL_DHashVoidPtrKeyStub,
         PL_DHashMatchEntryStub,
         PL_DHashMoveEntryStub,
         SubDocClearEntry,
         SubDocInitEntry
       };
 
       mSubDocuments = PL_NewDHashTable(&hash_table_ops, sizeof(SubDocMapEntry));
-      if (!mSubDocuments) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
     }
 
     // Add a mapping to the hash table
     SubDocMapEntry *entry = static_cast<SubDocMapEntry*>
       (PL_DHashTableAdd(mSubDocuments, aElement, fallible));
 
     if (!entry) {
       return NS_ERROR_OUT_OF_MEMORY;
--- a/dom/base/nsScriptNameSpaceManager.cpp
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -318,31 +318,25 @@ nsScriptNameSpaceManager::Init()
   {
     GlobalNameHashHashKey,
     GlobalNameHashMatchEntry,
     PL_DHashMoveEntryStub,
     GlobalNameHashClearEntry,
     GlobalNameHashInitEntry
   };
 
-  mIsInitialized = PL_DHashTableInit(&mGlobalNames, &hash_table_ops,
-                                     sizeof(GlobalNameMapEntry),
-                                     fallible,
-                                     GLOBALNAME_HASHTABLE_INITIAL_LENGTH);
-  NS_ENSURE_TRUE(mIsInitialized, NS_ERROR_OUT_OF_MEMORY);
+  PL_DHashTableInit(&mGlobalNames, &hash_table_ops,
+                    sizeof(GlobalNameMapEntry),
+                    GLOBALNAME_HASHTABLE_INITIAL_LENGTH);
 
-  mIsInitialized = PL_DHashTableInit(&mNavigatorNames, &hash_table_ops,
-                                     sizeof(GlobalNameMapEntry),
-                                     fallible,
-                                     GLOBALNAME_HASHTABLE_INITIAL_LENGTH);
-  if (!mIsInitialized) {
-    PL_DHashTableFinish(&mGlobalNames);
+  PL_DHashTableInit(&mNavigatorNames, &hash_table_ops,
+                    sizeof(GlobalNameMapEntry),
+                    GLOBALNAME_HASHTABLE_INITIAL_LENGTH);
 
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
+  mIsInitialized = true;
 
   RegisterWeakMemoryReporter(this);
 
   nsresult rv = NS_OK;
 
   rv = RegisterExternalInterfaces(false);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1298,21 +1298,22 @@ WebGLContext::ClearScreen()
     GLbitfield clearMask = LOCAL_GL_COLOR_BUFFER_BIT;
     if (mOptions.depth)
         clearMask |= LOCAL_GL_DEPTH_BUFFER_BIT;
     if (mOptions.stencil)
         clearMask |= LOCAL_GL_STENCIL_BUFFER_BIT;
 
     colorAttachmentsMask[0] = true;
 
-    ForceClearFramebufferWithDefaultValues(clearMask, colorAttachmentsMask);
+    ForceClearFramebufferWithDefaultValues(mNeedsFakeNoAlpha, clearMask,
+                                           colorAttachmentsMask);
 }
 
 void
-WebGLContext::ForceClearFramebufferWithDefaultValues(GLbitfield mask,
+WebGLContext::ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfield mask,
                                                      const bool colorAttachmentsMask[kMaxColorAttachments])
 {
     MakeContextCurrent();
 
     bool initializeColorBuffer = 0 != (mask & LOCAL_GL_COLOR_BUFFER_BIT);
     bool initializeDepthBuffer = 0 != (mask & LOCAL_GL_DEPTH_BUFFER_BIT);
     bool initializeStencilBuffer = 0 != (mask & LOCAL_GL_STENCIL_BUFFER_BIT);
     bool drawBuffersIsEnabled = IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers);
@@ -1348,17 +1349,17 @@ WebGLContext::ForceClearFramebufferWithD
             // calling draw buffers can cause resolves on adreno drivers so
             // we try to avoid calling it
             if (shouldOverrideDrawBuffers)
                 gl->fDrawBuffers(mGLMaxDrawBuffers, drawBuffersCommand);
         }
 
         gl->fColorMask(1, 1, 1, 1);
 
-        if (mNeedsFakeNoAlpha) {
+        if (fakeNoAlpha) {
             gl->fClearColor(0.0f, 0.0f, 0.0f, 1.0f);
         } else {
             gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
         }
     }
 
     if (initializeDepthBuffer) {
         gl->fDepthMask(1);
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -321,17 +321,17 @@ public:
     const WebGLRectangleObject* CurValidReadFBRectObject() const;
 
     static const size_t kMaxColorAttachments = 16;
 
     // This is similar to GLContext::ClearSafely, but tries to minimize the
     // amount of work it does.
     // It only clears the buffers we specify, and can reset its state without
     // first having to query anything, as WebGL knows its state at all times.
-    void ForceClearFramebufferWithDefaultValues(GLbitfield mask,
+    void ForceClearFramebufferWithDefaultValues(bool fakeNoAlpha, GLbitfield mask,
                                                 const bool colorAttachmentsMask[kMaxColorAttachments]);
 
     // Calls ForceClearFramebufferWithDefaultValues() for the Context's 'screen'.
     void ClearScreen();
     void ClearBackbufferIfNeeded();
 
     bool MinCapabilityMode() const { return mMinCapability; }
 
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -797,17 +797,17 @@ WebGLFramebuffer::CheckAndInitializeAtta
     for (size_t i = 0; i < moreColorAttachmentCount; i++) {
         if (mMoreColorAttachments[i].HasUninitializedImageData()) {
           colorAttachmentsMask[1 + i] = true;
           mask |= LOCAL_GL_COLOR_BUFFER_BIT;
         }
     }
 
     // Clear!
-    mContext->ForceClearFramebufferWithDefaultValues(mask, colorAttachmentsMask);
+    mContext->ForceClearFramebufferWithDefaultValues(false, mask, colorAttachmentsMask);
 
     // Mark all the uninitialized images as initialized.
     if (mColorAttachment0.HasUninitializedImageData())
         mColorAttachment0.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mDepthAttachment.HasUninitializedImageData())
         mDepthAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mStencilAttachment.HasUninitializedImageData())
         mStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -525,17 +525,17 @@ ClearByMask(WebGLContext* webgl, GLbitfi
     if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE)
         return false;
 
     bool colorAttachmentsMask[WebGLContext::kMaxColorAttachments] = {false};
     if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
         colorAttachmentsMask[0] = true;
     }
 
-    webgl->ForceClearFramebufferWithDefaultValues(mask, colorAttachmentsMask);
+    webgl->ForceClearFramebufferWithDefaultValues(false, mask, colorAttachmentsMask);
     return true;
 }
 
 // `mask` from glClear.
 static bool
 ClearWithTempFB(WebGLContext* webgl, GLuint tex,
                 TexImageTarget texImageTarget, GLint level,
                 TexInternalFormat baseInternalFormat,
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -9,16 +9,17 @@
 #include "nsIContent.h"
 #include "nsIEditor.h"
 #include "nsIPresShell.h"
 #include "nsPresContext.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/TextComposition.h"
 #include "mozilla/TextEvents.h"
 
 using namespace mozilla::widget;
 
 namespace mozilla {
 
 #define IDEOGRAPHIC_SPACE (NS_LITERAL_STRING("\x3000"))
@@ -38,16 +39,19 @@ TextComposition::TextComposition(nsPresC
   , mCompositionTargetOffset(0)
   , mIsSynthesizedForTests(aCompositionEvent->mFlags.mIsSynthesizedForTests)
   , mIsComposing(false)
   , mIsEditorHandlingEvent(false)
   , mIsRequestingCommit(false)
   , mIsRequestingCancel(false)
   , mRequestedToCommitOrCancel(false)
   , mWasNativeCompositionEndEventDiscarded(false)
+  , mAllowControlCharacters(
+      Preferences::GetBool("dom.compositionevent.allow_control_characters",
+                           false))
 {
 }
 
 void
 TextComposition::Destroy()
 {
   mPresContext = nullptr;
   mNode = nullptr;
@@ -128,23 +132,81 @@ TextComposition::OnCompositionEventDisca
   //     TSF.
   if (!aCompositionEvent->CausesDOMCompositionEndEvent()) {
     return;
   }
 
   mWasNativeCompositionEndEventDiscarded = true;
 }
 
+static inline bool
+IsControlChar(uint32_t aCharCode)
+{
+  return aCharCode < ' ' || aCharCode == 0x7F;
+}
+
+static size_t
+FindFirstControlCharacter(const nsAString& aStr)
+{
+  const char16_t* sourceBegin = aStr.BeginReading();
+  const char16_t* sourceEnd = aStr.EndReading();
+
+  for (const char16_t* source = sourceBegin; source < sourceEnd; ++source) {
+    if (*source != '\t' && IsControlChar(*source)) {
+      return source - sourceBegin;
+    }
+  }
+
+  return -1;
+}
+
+static void
+RemoveControlCharactersFrom(nsAString& aStr, TextRangeArray* aRanges)
+{
+  size_t firstControlCharOffset = FindFirstControlCharacter(aStr);
+  if (firstControlCharOffset == (size_t)-1) {
+    return;
+  }
+
+  nsAutoString copy(aStr);
+  const char16_t* sourceBegin = copy.BeginReading();
+  const char16_t* sourceEnd = copy.EndReading();
+
+  char16_t* dest = aStr.BeginWriting();
+  if (NS_WARN_IF(!dest)) {
+    return;
+  }
+
+  char16_t* curDest = dest + firstControlCharOffset;
+  size_t i = firstControlCharOffset;
+  for (const char16_t* source = sourceBegin + firstControlCharOffset;
+       source < sourceEnd; ++source) {
+    if (*source == '\t' || !IsControlChar(*source)) {
+      *curDest = *source;
+      ++curDest;
+      ++i;
+    } else if (aRanges) {
+      aRanges->RemoveCharacter(i);
+    }
+  }
+
+  aStr.SetLength(curDest - dest);
+}
+
 void
 TextComposition::DispatchCompositionEvent(
                    WidgetCompositionEvent* aCompositionEvent,
                    nsEventStatus* aStatus,
                    EventDispatchingCallback* aCallBack,
                    bool aIsSynthesized)
 {
+  if (!mAllowControlCharacters) {
+    RemoveControlCharactersFrom(aCompositionEvent->mData,
+                                aCompositionEvent->mRanges);
+  }
   if (aCompositionEvent->message == NS_COMPOSITION_COMMIT_AS_IS) {
     NS_ASSERTION(!aCompositionEvent->mRanges,
                  "mRanges of NS_COMPOSITION_COMMIT_AS_IS should be null");
     aCompositionEvent->mRanges = nullptr;
     NS_ASSERTION(aCompositionEvent->mData.IsEmpty(),
                  "mData of NS_COMPOSITION_COMMIT_AS_IS should be empty string");
     if (mLastData == IDEOGRAPHIC_SPACE) {
       // If the last data is an ideographic space (FullWidth space), it must be
--- a/dom/events/TextComposition.h
+++ b/dom/events/TextComposition.h
@@ -216,16 +216,23 @@ private:
   //       mIsRequestingCancel are set false.
   bool mRequestedToCommitOrCancel;
 
   // mWasNativeCompositionEndEventDiscarded is true if this composition was
   // requested commit or cancel itself but native compositionend event is
   // discarded by PresShell due to not safe to dispatch events.
   bool mWasNativeCompositionEndEventDiscarded;
 
+  // Allow control characters appear in composition string.
+  // When this is false, control characters except
+  // CHARACTER TABULATION (horizontal tab) are removed from
+  // both composition string and data attribute of compositionupdate
+  // and compositionend events.
+  bool mAllowControlCharacters;
+
   // Hide the default constructor and copy constructor.
   TextComposition() {}
   TextComposition(const TextComposition& aOther);
 
   /**
    * GetEditor() returns nsIEditor pointer of mEditorWeak.
    */
   already_AddRefed<nsIEditor> GetEditor() const;
--- a/dom/events/test/test_bug524674.xul
+++ b/dom/events/test/test_bug524674.xul
@@ -75,17 +75,22 @@ https://bugzilla.mozilla.org/show_bug.cg
   function changeListener(array) {
     if (typeof tests[0] == "function") {
       return;
     }
     var expectedEventTargets = tests[0];
     var e = array.enumerate();
     var i = 0;
     while (e.hasMoreElements()) {
-      var current = e.getNext();
+      var current;
+      try {
+        current = e.getNext();
+      } catch(ex) {
+        continue;
+      }
       var expected = expectedEventTargets[i];
       if (current == expected) {
         is(current, expected, current + " = " + expected);
         // We may get random other event listener changes here too, not just the one from the
         // test.
         ++i
       }
     }
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -753,17 +753,17 @@ private:
     NS_ConvertUTF8toUTF16 name(mName);
 
     if (mFilename.IsVoid()) {
       mFormData->Append(name, NS_ConvertUTF8toUTF16(body));
     } else {
       // Unfortunately we've to copy the data first since all our strings are
       // going to free it. We also need fallible alloc, so we can't just use
       // ToNewCString().
-      char* copy = static_cast<char*>(NS_Alloc(body.Length()));
+      char* copy = static_cast<char*>(moz_xmalloc(body.Length()));
       if (!copy) {
         NS_WARNING("Failed to copy File entry body.");
         return false;
       }
       nsCString::const_iterator bodyIter, bodyEnd;
       body.BeginReading(bodyIter);
       body.EndReading(bodyEnd);
       char *p = copy;
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -2318,17 +2318,17 @@ HTMLInputElement::MozGetFileNameArray(ui
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   nsTArray<nsString> array;
   MozGetFileNameArray(array);
 
   *aLength = array.Length();
   char16_t** ret =
-    static_cast<char16_t**>(NS_Alloc(*aLength * sizeof(char16_t*)));
+    static_cast<char16_t**>(moz_xmalloc(*aLength * sizeof(char16_t*)));
   if (!ret) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   for (uint32_t i = 0; i < *aLength; ++i) {
     ret[i] = NS_strdup(array[i].get());
   }
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -941,17 +941,17 @@ void HTMLMediaElement::NotifyMediaStream
   bool videoHasChanged = IsVideo() && HasVideo() != !VideoTracks()->IsEmpty();
 
   if (videoHasChanged) {
     // We are a video element and HasVideo() changed so update the screen
     // wakelock
     NotifyOwnerDocumentActivityChanged();
   }
 
-  mReadyStateUpdater->Notify();
+  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
 }
 
 void HTMLMediaElement::LoadFromSourceChildren()
 {
   NS_ASSERTION(mDelayingLoadEvent,
                "Should delay load event (if in document) during load");
   NS_ASSERTION(mIsLoadingFromSourceChildren,
                "Must remember we're loading from source children");
@@ -2037,20 +2037,20 @@ HTMLMediaElement::LookupMediaElementURIT
       }
     }
   }
   return nullptr;
 }
 
 HTMLMediaElement::HTMLMediaElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsGenericHTMLElement(aNodeInfo),
+    mWatchManager(this, AbstractThread::MainThread()),
     mCurrentLoadID(0),
     mNetworkState(nsIDOMHTMLMediaElement::NETWORK_EMPTY),
     mReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING, "HTMLMediaElement::mReadyState"),
-    mReadyStateUpdater("HTMLMediaElement::mReadyStateUpdater"),
     mLoadWaitStatus(NOT_WAITING),
     mVolume(1.0),
     mPreloadAction(PRELOAD_UNDEFINED),
     mLastCurrentTime(0.0),
     mFragmentStart(-1.0),
     mFragmentEnd(-1.0),
     mDefaultPlaybackRate(1.0),
     mPlaybackRate(1.0),
@@ -2094,32 +2094,30 @@ HTMLMediaElement::HTMLMediaElement(alrea
 {
 #ifdef PR_LOGGING
   if (!gMediaElementLog) {
     gMediaElementLog = PR_NewLogModule("nsMediaElement");
   }
   if (!gMediaElementEventsLog) {
     gMediaElementEventsLog = PR_NewLogModule("nsMediaElementEvents");
   }
-  EnsureStateWatchingLog();
 #endif
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
   mPaused.SetOuter(this);
 
   RegisterActivityObserver();
   NotifyOwnerDocumentActivityChanged();
 
   MOZ_ASSERT(NS_IsMainThread());
-  mReadyStateUpdater->AddWeakCallback(this, &HTMLMediaElement::UpdateReadyStateInternal);
-  mReadyStateUpdater->Watch(mDownloadSuspendedByCache);
+  mWatchManager.Watch(mDownloadSuspendedByCache, &HTMLMediaElement::UpdateReadyStateInternal);
   // Paradoxically, there is a self-edge whereby UpdateReadyStateInternal refuses
   // to run until mReadyState reaches at least HAVE_METADATA by some other means.
-  mReadyStateUpdater->Watch(mReadyState);
+  mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateReadyStateInternal);
 }
 
 HTMLMediaElement::~HTMLMediaElement()
 {
   NS_ASSERTION(!mHasSelfReference,
                "How can we be destroyed if we're still holding a self reference?");
 
   if (mVideoFrameContainer) {
@@ -3068,17 +3066,17 @@ void HTMLMediaElement::SetupSrcMediaStre
   if (stream) {
     stream->SetAudioChannelType(mAudioChannel);
   }
 
   // XXX if we ever support capturing the output of a media element which is
   // playing a stream, we'll need to add a CombineWithPrincipal call here.
   mMediaStreamListener = new StreamListener(this, "HTMLMediaElement::mMediaStreamListener");
   mMediaStreamSizeListener = new StreamSizeListener(this);
-  mReadyStateUpdater->Watch(*mMediaStreamListener);
+  mWatchManager.Watch(*mMediaStreamListener, &HTMLMediaElement::UpdateReadyStateInternal);
 
   GetSrcMediaStream()->AddListener(mMediaStreamListener);
   // Listen for an initial image size on mSrcStream so we can get results even
   // if we block the mPlaybackStream.
   stream->AddListener(mMediaStreamSizeListener);
   if (mPaused) {
     GetSrcMediaStream()->ChangeExplicitBlockerCount(1);
   }
@@ -3118,17 +3116,17 @@ void HTMLMediaElement::EndSrcMediaStream
   }
   mSrcStream->DisconnectTrackListListeners(AudioTracks(), VideoTracks());
 
   if (mPlaybackStreamInputPort) {
     mPlaybackStreamInputPort->Destroy();
   }
 
   // Kill its reference to this element
-  mReadyStateUpdater->Unwatch(*mMediaStreamListener);
+  mWatchManager.Unwatch(*mMediaStreamListener, &HTMLMediaElement::UpdateReadyStateInternal);
   mMediaStreamListener->Forget();
   mMediaStreamListener = nullptr;
   mMediaStreamSizeListener->Forget();
   mMediaStreamSizeListener = nullptr;
   if (stream) {
     stream->RemoveAudioOutput(this);
   }
   VideoFrameContainer* container = GetVideoFrameContainer();
@@ -3221,17 +3219,17 @@ void HTMLMediaElement::MetadataLoaded(co
 
   // If this element had a video track, but consists only of an audio track now,
   // delete the VideoFrameContainer. This happens when the src is changed to an
   // audio only file.
   // Else update its dimensions.
   if (!aInfo->HasVideo()) {
     ResetState();
   } else {
-    mReadyStateUpdater->Notify();
+    mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
   }
 
   if (IsVideo() && aInfo->HasVideo()) {
     // We are a video element playing video so update the screen wakelock
     NotifyOwnerDocumentActivityChanged();
   }
 }
 
@@ -3882,17 +3880,17 @@ void HTMLMediaElement::NotifyDecoderPrin
 void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize)
 {
   if (IsVideo() && mReadyState != HAVE_NOTHING &&
       mMediaInfo.mVideo.mDisplay != aSize) {
     DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
   }
 
   mMediaInfo.mVideo.mDisplay = aSize;
-  mReadyStateUpdater->Notify();
+  mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
 }
 
 void HTMLMediaElement::UpdateInitialMediaSize(const nsIntSize& aSize)
 {
   if (!mMediaInfo.HasVideo()) {
     UpdateMediaSize(aSize);
   }
 }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -212,16 +212,19 @@ public:
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() final override;
   layers::ImageContainer* GetImageContainer();
 
   // Dispatch events
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) final override;
 
+  // Triggers a recomputation of readyState.
+  void UpdateReadyState() override { UpdateReadyStateInternal(); }
+
   // Dispatch events that were raised while in the bfcache
   nsresult DispatchPendingMediaEvents();
 
   // Return true if we can activate autoplay assuming enough data has arrived.
   bool CanActivateAutoplay();
 
   // Notify that state has changed that might cause an autoplay element to
   // start playing.
@@ -637,27 +640,17 @@ protected:
   virtual ~HTMLMediaElement();
 
   class MediaLoadListener;
   class MediaStreamTracksAvailableCallback;
   class StreamListener;
   class StreamSizeListener;
 
   MediaDecoderOwner::NextFrameStatus NextFrameStatus();
-
-  void SetDecoder(MediaDecoder* aDecoder)
-  {
-    if (mDecoder) {
-      mReadyStateUpdater->Unwatch(mDecoder->ReadyStateWatchTarget());
-    }
-    mDecoder = aDecoder;
-    if (mDecoder) {
-      mReadyStateUpdater->Watch(mDecoder->ReadyStateWatchTarget());
-    }
-  }
+  void SetDecoder(MediaDecoder* aDecoder) { mDecoder = aDecoder; }
 
   virtual void GetItemValueText(DOMString& text) override;
   virtual void SetItemValueText(const nsAString& text) override;
 
   class WakeLockBoolWrapper {
   public:
     explicit WakeLockBoolWrapper(bool val = false)
       : mValue(val), mCanPlay(true), mOuter(nullptr) {}
@@ -1027,16 +1020,19 @@ protected:
   using nsGenericHTMLElement::DispatchEvent;
   // For nsAsyncEventRunner.
   nsresult DispatchEvent(const nsAString& aName);
 
   // The current decoder. Load() has been called on this decoder.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<MediaDecoder> mDecoder;
 
+  // State-watching manager.
+  WatchManager<HTMLMediaElement> mWatchManager;
+
   // A reference to the VideoFrameContainer which contains the current frame
   // of video to display.
   nsRefPtr<VideoFrameContainer> mVideoFrameContainer;
 
   // Holds a reference to the DOM wrapper for the MediaStream that has been
   // set in the src attribute.
   nsRefPtr<DOMMediaStream> mSrcAttrStream;
 
@@ -1097,18 +1093,16 @@ protected:
   // These events get re-dispatched when the bfcache is exited.
   nsTArray<nsString> mPendingEvents;
 
   // Media loading flags. See:
   //   http://www.whatwg.org/specs/web-apps/current-work/#video)
   nsMediaNetworkState mNetworkState;
   Watchable<nsMediaReadyState> mReadyState;
 
-  WatcherHolder mReadyStateUpdater;
-
   enum LoadAlgorithmState {
     // No load algorithm instance is waiting for a source to be added to the
     // media in order to continue loading.
     NOT_WAITING,
     // We've run the load algorithm, and we tried all source children of the
     // media element, and failed to load any successfully. We're waiting for
     // another source element to be added to the media element, and will try
     // to load any such element when its added.
--- a/dom/html/UndoManager.cpp
+++ b/dom/html/UndoManager.cpp
@@ -1023,17 +1023,17 @@ UndoManager::ItemInternal(uint32_t aInde
     aRv.Throw(rv);
     return;
   }
 
   for (uint32_t i = 0; i < listDataLength; i++) {
     aItems.AppendElement(static_cast<DOMTransaction*>(listData[i]));
     NS_RELEASE(listData[i]);
   }
-  NS_Free(listData);
+  free(listData);
 }
 
 void
 UndoManager::Item(uint32_t aIndex,
                   Nullable<nsTArray<nsRefPtr<DOMTransaction> > >& aItems,
                   ErrorResult& aRv)
 {
   int32_t numRedo;
--- a/dom/indexedDB/ActorsChild.cpp
+++ b/dom/indexedDB/ActorsChild.cpp
@@ -67,16 +67,22 @@ ThreadLocal::ThreadLocal(const nsID& aBa
   , mCurrentTransaction(0)
 #ifdef DEBUG
   , mOwningThread(PR_GetCurrentThread())
 #endif
 {
   MOZ_ASSERT(mOwningThread);
 
   MOZ_COUNT_CTOR(mozilla::dom::indexedDB::ThreadLocal);
+
+  // NSID_LENGTH counts the null terminator, SetLength() does not.
+  mLoggingIdString.SetLength(NSID_LENGTH - 1);
+
+  aBackgroundChildLoggingId.ToProvidedString(
+    *reinterpret_cast<char(*)[NSID_LENGTH]>(mLoggingIdString.BeginWriting()));
 }
 
 ThreadLocal::~ThreadLocal()
 {
   MOZ_COUNT_DTOR(mozilla::dom::indexedDB::ThreadLocal);
 }
 
 #ifdef DEBUG
--- a/dom/indexedDB/ActorsChild.h
+++ b/dom/indexedDB/ActorsChild.h
@@ -48,16 +48,17 @@ class SerializedStructuredCloneReadInfo;
 
 class ThreadLocal
 {
   friend class nsAutoPtr<ThreadLocal>;
   friend class IDBFactory;
 
   LoggingInfo mLoggingInfo;
   IDBTransaction* mCurrentTransaction;
+  nsCString mLoggingIdString;
 
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
 public:
   void
   AssertIsOnOwningThread() const
@@ -78,16 +79,24 @@ public:
   const nsID&
   Id() const
   {
     AssertIsOnOwningThread();
 
     return mLoggingInfo.backgroundChildLoggingId();
   }
 
+  const nsCString&
+  IdString() const
+  {
+    AssertIsOnOwningThread();
+
+    return mLoggingIdString;
+  }
+
   int64_t
   NextTransactionSN(IDBTransaction::Mode aMode)
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(mLoggingInfo.nextTransactionSerialNumber() < INT64_MAX);
     MOZ_ASSERT(mLoggingInfo.nextVersionChangeTransactionSerialNumber() >
                  INT64_MIN);
 
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -21263,18 +21263,16 @@ ObjectStoreAddOrPutRequestOp::DoDatabase
     reinterpret_cast<const char*>(mParams.cloneInfo().data().Elements());
   size_t uncompressedLength = mParams.cloneInfo().data().Length();
 
   // We don't have a smart pointer class that calls free, so we need to
   // manage | compressed | manually.
   {
     size_t compressedLength = snappy::MaxCompressedLength(uncompressedLength);
 
-    // malloc is equivalent to NS_Alloc, which we use because mozStorage
-    // expects to be able to free the adopted pointer with NS_Free.
     char* compressed = static_cast<char*>(malloc(compressedLength));
     if (NS_WARN_IF(!compressed)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     snappy::RawCompress(uncompressed, uncompressedLength, compressed,
                         &compressedLength);
 
--- a/dom/indexedDB/ProfilerHelpers.h
+++ b/dom/indexedDB/ProfilerHelpers.h
@@ -39,44 +39,45 @@ namespace indexedDB {
 class MOZ_STACK_CLASS LoggingIdString final
   : public nsAutoCString
 {
 public:
   LoggingIdString()
   {
     using mozilla::ipc::BackgroundChildImpl;
 
-    BackgroundChildImpl::ThreadLocal* threadLocal =
-      BackgroundChildImpl::GetThreadLocalForCurrentThread();
-    MOZ_ASSERT(threadLocal);
-
-    ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal;
-    MOZ_ASSERT(idbThreadLocal);
-
-    Init(idbThreadLocal->Id());
+    if (IndexedDatabaseManager::GetLoggingMode() !=
+          IndexedDatabaseManager::Logging_Disabled) {
+      const BackgroundChildImpl::ThreadLocal* threadLocal =
+        BackgroundChildImpl::GetThreadLocalForCurrentThread();
+      if (threadLocal) {
+        const ThreadLocal* idbThreadLocal = threadLocal->mIndexedDBThreadLocal;
+        if (idbThreadLocal) {
+          Assign(idbThreadLocal->IdString());
+        }
+      }
+    }
   }
 
   explicit
   LoggingIdString(const nsID& aID)
   {
-    Init(aID);
-  }
-
-private:
-  void
-  Init(const nsID& aID)
-  {
     static_assert(NSID_LENGTH > 1, "NSID_LENGTH is set incorrectly!");
+    static_assert(NSID_LENGTH <= kDefaultStorageSize,
+                  "nID string won't fit in our storage!");
     MOZ_ASSERT(Capacity() > NSID_LENGTH);
 
-    // NSID_LENGTH counts the null terminator, SetLength() does not.
-    SetLength(NSID_LENGTH - 1);
+    if (IndexedDatabaseManager::GetLoggingMode() !=
+          IndexedDatabaseManager::Logging_Disabled) {
+      // NSID_LENGTH counts the null terminator, SetLength() does not.
+      SetLength(NSID_LENGTH - 1);
 
-    aID.ToProvidedString(
-      *reinterpret_cast<char(*)[NSID_LENGTH]>(BeginWriting()));
+      aID.ToProvidedString(
+        *reinterpret_cast<char(*)[NSID_LENGTH]>(BeginWriting()));
+    }
   }
 };
 
 class MOZ_STACK_CLASS LoggingString final
   : public nsAutoCString
 {
   static const char kQuote = '\"';
   static const char kOpenBracket = '[';
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -4044,17 +4044,17 @@ ContentParent::RecvGetRandomValues(const
         return true;
     }
 
     randomValues->SetCapacity(length);
     randomValues->SetLength(length);
 
     memcpy(randomValues->Elements(), buf, length);
 
-    NS_Free(buf);
+    free(buf);
 
     return true;
 }
 
 bool
 ContentParent::RecvGetSystemMemory(const uint64_t& aGetterId)
 {
     uint32_t memoryTotal = 0;
--- a/dom/json/nsJSON.cpp
+++ b/dom/json/nsJSON.cpp
@@ -339,25 +339,25 @@ nsJSONWriter::WriteToStream(nsIOutputStr
   uint32_t bytesWritten;
 
   // The bytes written to the stream might differ from the char16_t size
   int32_t aDestLength;
   rv = encoder->GetMaxLength(aBuffer, srcLength, &aDestLength);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create the buffer we need
-  char* destBuf = (char *) NS_Alloc(aDestLength);
+  char* destBuf = (char *) moz_xmalloc(aDestLength);
   if (!destBuf)
     return NS_ERROR_OUT_OF_MEMORY;
 
   rv = encoder->Convert(aBuffer, &srcLength, destBuf, &aDestLength);
   if (NS_SUCCEEDED(rv))
     rv = aStream->Write(destBuf, aDestLength, &bytesWritten);
 
-  NS_Free(destBuf);
+  free(destBuf);
   mDidWrite = true;
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsJSON::Decode(const nsAString& json, JSContext* cx,
                JS::MutableHandle<JS::Value> aRetval)
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -112,20 +112,30 @@ public:
     if (decoders.IsEmpty()) {
       sUniqueInstance = nullptr;
     }
   }
 };
 
 StaticRefPtr<MediaMemoryTracker> MediaMemoryTracker::sUniqueInstance;
 
+PRLogModuleInfo* gStateWatchingLog;
+PRLogModuleInfo* gMediaPromiseLog;
+PRLogModuleInfo* gMediaTimerLog;
+
 void
 MediaDecoder::InitStatics()
 {
   AbstractThread::InitStatics();
+
+  // Log modules.
+  gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
+  gMediaPromiseLog = PR_NewLogModule("MediaPromise");
+  gStateWatchingLog = PR_NewLogModule("StateWatching");
+  gMediaTimerLog = PR_NewLogModule("MediaTimer");
 }
 
 NS_IMPL_ISUPPORTS(MediaMemoryTracker, nsIMemoryReporter)
 
 NS_IMPL_ISUPPORTS(MediaDecoder, nsIObserver)
 
 void MediaDecoder::NotifyOwnerActivityChanged()
 {
@@ -580,27 +590,34 @@ void MediaDecoder::SetInfinite(bool aInf
 
 bool MediaDecoder::IsInfinite()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mInfiniteStream;
 }
 
 MediaDecoder::MediaDecoder() :
-  mReadyStateWatchTarget("MediaDecoder::mReadyStateWatchTarget"),
+  mWatchManager(this, AbstractThread::MainThread()),
+  mNextFrameStatus(AbstractThread::MainThread(),
+                   MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
+                   "MediaDecoder::mNextFrameStatus (Mirror)"),
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mCurrentTime(0.0),
   mInitialVolume(0.0),
   mInitialPlaybackRate(1.0),
   mInitialPreservesPitch(true),
   mDuration(-1),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
+  mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
+             "MediaDecoder::mPlayState (Canonical)"),
+  mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
+             "MediaDecoder::mNextState (Canonical)"),
   mIgnoreProgressData(false),
   mInfiniteStream(false),
   mOwner(nullptr),
   mPlaybackStatistics(new MediaChannelStatistics()),
   mPinnedForSeek(false),
   mShuttingDown(false),
   mPausedForPlaybackRateNull(false),
   mMinimizePreroll(false),
@@ -612,37 +629,22 @@ MediaDecoder::MediaDecoder() :
   mHeuristicDormantTimeout(
     Preferences::GetInt("media.decoder.heuristic.dormant.timeout",
                         DEFAULT_HEURISTIC_DORMANT_TIMEOUT_MSECS)),
   mIsHeuristicDormant(false)
 {
   MOZ_COUNT_CTOR(MediaDecoder);
   MOZ_ASSERT(NS_IsMainThread());
   MediaMemoryTracker::AddMediaDecoder(this);
-#ifdef PR_LOGGING
-  if (!gMediaDecoderLog) {
-    gMediaDecoderLog = PR_NewLogModule("MediaDecoder");
-  }
-  EnsureStateWatchingLog();
-#endif
-
-  // Initialize canonicals.
-  mPlayState.Init(AbstractThread::MainThread(), PLAY_STATE_LOADING, "MediaDecoder::mPlayState (Canonical)");
-  mNextState.Init(AbstractThread::MainThread(), PLAY_STATE_PAUSED, "MediaDecoder::mNextState (Canonical)");
-
-  // Initialize mirrors.
-  mNextFrameStatus.Init(AbstractThread::MainThread(), MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
-                        "MediaDecoder::mNextFrameStatus (Mirror)");
-
 
   mAudioChannel = AudioChannelService::GetDefaultAudioChannel();
 
   // Initialize watchers.
-  mReadyStateWatchTarget->Watch(mPlayState);
-  mReadyStateWatchTarget->Watch(mNextFrameStatus);
+  mWatchManager.Watch(mPlayState, &MediaDecoder::UpdateReadyState);
+  mWatchManager.Watch(mNextFrameStatus, &MediaDecoder::UpdateReadyState);
 }
 
 bool MediaDecoder::Init(MediaDecoderOwner* aOwner)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mOwner = aOwner;
   mVideoFrameContainer = aOwner->GetVideoFrameContainer();
   MediaShutdownManager::Instance().Register(this);
@@ -1643,17 +1645,17 @@ size_t MediaDecoder::SizeOfAudioQueue() 
 
 void MediaDecoder::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) {
   if (mDecoderStateMachine) {
     mDecoderStateMachine->NotifyDataArrived(aBuffer, aLength, aOffset);
   }
 
   // ReadyState computation depends on MediaDecoder::CanPlayThrough, which
   // depends on the download rate.
-  mReadyStateWatchTarget->Notify();
+  UpdateReadyState();
 }
 
 // Provide access to the state machine object
 MediaDecoderStateMachine* MediaDecoder::GetStateMachine() const {
   return mDecoderStateMachine;
 }
 
 void
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -1023,17 +1023,22 @@ public:
   // Increments the parsed and decoded frame counters by the passed in counts.
   // Can be called on any thread.
   virtual void NotifyDecodedFrames(uint32_t aParsed, uint32_t aDecoded,
                                    uint32_t aDropped) override
   {
     GetFrameStatistics().NotifyDecodedFrames(aParsed, aDecoded, aDropped);
   }
 
-  WatchTarget& ReadyStateWatchTarget() { return *mReadyStateWatchTarget; }
+  void UpdateReadyState()
+  {
+    if (mOwner) {
+      mOwner->UpdateReadyState();
+    }
+  }
 
   virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus() { return mNextFrameStatus; }
 
 protected:
   virtual ~MediaDecoder();
   void SetStateMachineParameters();
 
   static void DormantTimerExpired(nsITimer *aTimer, void *aClosure);
@@ -1042,20 +1047,21 @@ protected:
   void StartDormantTimer();
 
   // Cancel a timer for heuristic dormant.
   void CancelDormantTimer();
 
   // Return true if the decoder has reached the end of playback
   bool IsEnded() const;
 
-  WatcherHolder mReadyStateWatchTarget;
+  // State-watching manager.
+  WatchManager<MediaDecoder> mWatchManager;
 
   // NextFrameStatus, mirrored from the state machine.
-  Mirror<MediaDecoderOwner::NextFrameStatus>::Holder mNextFrameStatus;
+  Mirror<MediaDecoderOwner::NextFrameStatus> mNextFrameStatus;
 
   /******
    * The following members should be accessed with the decoder lock held.
    ******/
 
   // Current decoding position in the stream. This is where the decoder
   // is up to consuming the stream. This is not adjusted during decoder
   // seek operations, but it's updated at the end when we start playing
@@ -1131,24 +1137,24 @@ protected:
   nsAutoPtr<DecodedStreamData> mDecodedStream;
 
   // Set to one of the valid play states.
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
   // Any change to the state on the main thread must call NotifyAll on the
   // monitor so the decode thread can wake up.
-  Canonical<PlayState>::Holder mPlayState;
+  Canonical<PlayState> mPlayState;
 
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
   // Any change to the state must call NotifyAll on the monitor.
   // This can only be PLAY_STATE_PAUSED or PLAY_STATE_PLAYING.
-  Canonical<PlayState>::Holder mNextState;
+  Canonical<PlayState> mNextState;
 public:
   AbstractCanonical<PlayState>* CanonicalPlayState() { return &mPlayState; }
   AbstractCanonical<PlayState>* CanonicalNextPlayState() { return &mNextState; }
 protected:
 
   // Position to seek to when the seek notification is received by the
   // decode thread.
   // This can only be changed on the main thread while holding the decoder
--- a/dom/media/MediaDecoderOwner.h
+++ b/dom/media/MediaDecoderOwner.h
@@ -19,16 +19,19 @@ class MediaDecoderOwner
 {
 public:
   // Called by the media decoder to indicate that the download is progressing.
   virtual void DownloadProgressed() = 0;
 
   // Dispatch an asynchronous event to the decoder owner
   virtual nsresult DispatchAsyncEvent(const nsAString& aName) = 0;
 
+  // Triggers a recomputation of readyState.
+  virtual void UpdateReadyState() = 0;
+
   /**
    * Fires a timeupdate event. If aPeriodic is true, the event will only
    * be fired if we've not fired a timeupdate event (for any reason) in the
    * last 250ms, as required by the spec when the current time is periodically
    * increasing during playback.
    */
   virtual void FireTimeUpdate(bool aPeriodic) = 0;
 
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -28,36 +28,16 @@ extern PRLogModuleInfo* gMediaDecoderLog
 #define DECODER_LOG(x, ...)
 #endif
 
 // Same workaround as MediaDecoderStateMachine.cpp.
 #define DECODER_WARN_HELPER(a, b) NS_WARNING b
 #define DECODER_WARN(x, ...) \
   DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoder, ##__VA_ARGS__).get()))
 
-
-PRLogModuleInfo* gMediaPromiseLog;
-PRLogModuleInfo* gStateWatchingLog;
-
-void
-EnsureMediaPromiseLog()
-{
-  if (!gMediaPromiseLog) {
-    gMediaPromiseLog = PR_NewLogModule("MediaPromise");
-  }
-}
-
-void
-EnsureStateWatchingLog()
-{
-  if (!gStateWatchingLog) {
-    gStateWatchingLog = PR_NewLogModule("StateWatching");
-  }
-}
-
 class VideoQueueMemoryFunctor : public nsDequeFunctor {
 public:
   VideoQueueMemoryFunctor() : mSize(0) {}
 
   MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
 
   virtual void* operator()(void* aObject) {
     const VideoData* v = static_cast<const VideoData*>(aObject);
@@ -91,18 +71,16 @@ MediaDecoderReader::MediaDecoderReader(A
   , mStartTime(-1)
   , mHitAudioDecodeError(false)
   , mShutdown(false)
   , mTaskQueueIsBorrowed(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
-  EnsureMediaPromiseLog();
-  EnsureStateWatchingLog();
 }
 
 MediaDecoderReader::~MediaDecoderReader()
 {
   MOZ_ASSERT(mShutdown);
   MOZ_ASSERT(!mDecoder);
   ResetDecode();
   MOZ_COUNT_DTOR(MediaDecoderReader);
@@ -196,37 +174,37 @@ MediaDecoderReader::ComputeStartTime(con
   }
   DECODER_LOG("ComputeStartTime first video frame start %lld", aVideo ? aVideo->mTime : -1);
   DECODER_LOG("ComputeStartTime first audio frame start %lld", aAudio ? aAudio->mTime : -1);
   NS_ASSERTION(startTime >= 0, "Start time is negative");
   return startTime;
 }
 
 nsRefPtr<MediaDecoderReader::MetadataPromise>
-MediaDecoderReader::CallReadMetadata()
+MediaDecoderReader::AsyncReadMetadata()
 {
   typedef ReadMetadataFailureReason Reason;
 
   MOZ_ASSERT(OnTaskQueue());
   mDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
-  DECODER_LOG("MediaDecoderReader::CallReadMetadata");
+  DECODER_LOG("MediaDecoderReader::AsyncReadMetadata");
 
   // PreReadMetadata causes us to try to allocate various hardware and OS
   // resources, which may not be available at the moment.
   PreReadMetadata();
   if (IsWaitingMediaResources()) {
     return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__);
   }
 
   // Attempt to read the metadata.
   nsRefPtr<MetadataHolder> metadata = new MetadataHolder();
   nsresult rv = ReadMetadata(&metadata->mInfo, getter_Transfers(metadata->mTags));
 
-  // Reading metadata can cause us to discover that we need resources (like
-  // encryption keys).
+  // Reading metadata can cause us to discover that we need resources (a hardware
+  // resource initialized but not yet ready for use).
   if (IsWaitingMediaResources()) {
     return MetadataPromise::CreateAndReject(Reason::WAITING_FOR_RESOURCES, __func__);
   }
 
   // We're not waiting for anything. If we didn't get the metadata, that's an
   // error.
   if (NS_FAILED(rv) || !metadata->mInfo.HasValidMedia()) {
     DECODER_WARN("ReadMetadata failed, rv=%x HasValidMedia=%d", rv, metadata->mInfo.HasValidMedia());
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -162,20 +162,20 @@ public:
   // in buffering mode. Some readers support a promise-based mechanism by which
   // they notify the state machine when the data arrives.
   virtual bool IsWaitForDataSupported() { return false; }
   virtual nsRefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType) { MOZ_CRASH(); }
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
-  // The ReadMetadata API is unfortunately synchronous. We should fix that at
-  // some point, but for now we can make things a bit better by using a
-  // promise-y API on top of a synchronous call.
-  nsRefPtr<MetadataPromise> CallReadMetadata();
+  // The default implementation of AsyncReadMetadata is implemented in terms of
+  // synchronous PreReadMetadata() / ReadMetadata() calls. Implementations may also
+  // override AsyncReadMetadata to create a more proper async implementation.
+  virtual nsRefPtr<MetadataPromise> AsyncReadMetadata();
 
   // A function that is called before ReadMetadata() call.
   virtual void PreReadMetadata() {};
 
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -197,25 +197,32 @@ static const uint32_t MAX_VIDEO_QUEUE_SI
 
 static uint32_t sVideoQueueDefaultSize = MAX_VIDEO_QUEUE_SIZE;
 static uint32_t sVideoQueueHWAccelSize = MIN_VIDEO_QUEUE_SIZE;
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoder(aDecoder),
+  mTaskQueue(new MediaTaskQueue(GetMediaThreadPool(), /* aAssertTailDispatch = */ true)),
+  mWatchManager(this, mTaskQueue),
   mRealTime(aRealTime),
   mDispatchedStateMachine(false),
   mDelayedScheduler(this),
   mState(DECODER_STATE_DECODING_NONE, "MediaDecoderStateMachine::mState"),
   mPlayDuration(0),
   mStartTime(-1),
   mEndTime(-1),
   mDurationSet(false),
-  mNextFrameStatusUpdater("MediaDecoderStateMachine::mNextFrameStatusUpdater"),
+  mPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING,
+             "MediaDecoderStateMachine::mPlayState (Mirror)"),
+  mNextPlayState(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED,
+                 "MediaDecoderStateMachine::mNextPlayState (Mirror)"),
+  mNextFrameStatus(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
+                   "MediaDecoderStateMachine::mNextFrameStatus (Canonical)"),
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mDecodedAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mDecodedVideoEndTime(-1),
@@ -245,36 +252,19 @@ MediaDecoderStateMachine::MediaDecoderSt
   mDecodingFrozenAtStateDecoding(false),
   mSentLoadedMetadataEvent(false),
   mSentFirstFrameLoadedEvent(false),
   mSentPlaybackEndedEvent(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderStateMachine);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 
-  // Set up our task queue.
-  RefPtr<SharedThreadPool> pool(GetMediaThreadPool());
-  MOZ_DIAGNOSTIC_ASSERT(pool);
-  mTaskQueue = new MediaTaskQueue(pool.forget(), /* aAssertTailDispatch = */ true);
-
-  // Initialize canonicals.
-  mNextFrameStatus.Init(mTaskQueue, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
-                        "MediaDecoderStateMachine::mNextFrameStatus (Canonical)");
-
-  // Initialize mirrors.
-  mPlayState.Init(mTaskQueue, MediaDecoder::PLAY_STATE_LOADING, "MediaDecoderStateMachine::mPlayState (Mirror)",
-                  aDecoder->CanonicalPlayState());
-  mNextPlayState.Init(mTaskQueue, MediaDecoder::PLAY_STATE_PAUSED, "MediaDecoderStateMachine::mNextPlayState (Mirror)",
-                  aDecoder->CanonicalNextPlayState());
-
-  // Skip the initial notification we get when we Watch the value, since we're
-  // not on the right thread yet.
-  mNextFrameStatusUpdater->Watch(mState, /* aSkipInitialNotify = */ true);
-  mNextFrameStatusUpdater->Watch(mAudioCompleted, /* aSkipInitialNotify = */ true);
-  mNextFrameStatusUpdater->AddWeakCallback(this, &MediaDecoderStateMachine::UpdateNextFrameStatus);
+  // Dispatch initialization that needs to happen on that task queue.
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::InitializationTask);
+  mTaskQueue->Dispatch(r.forget());
 
   static bool sPrefCacheInit = false;
   if (!sPrefCacheInit) {
     sPrefCacheInit = true;
     Preferences::AddUintVarCache(&sVideoQueueDefaultSize,
                                  "media.video-queue.default-size",
                                  MAX_VIDEO_QUEUE_SIZE);
     Preferences::AddUintVarCache(&sVideoQueueHWAccelSize,
@@ -304,16 +294,30 @@ MediaDecoderStateMachine::~MediaDecoderS
 
   mReader = nullptr;
 
 #ifdef XP_WIN
   timeEndPeriod(1);
 #endif
 }
 
+void
+MediaDecoderStateMachine::InitializationTask()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  // Connect mirrors.
+  mPlayState.Connect(mDecoder->CanonicalPlayState());
+  mNextPlayState.Connect(mDecoder->CanonicalNextPlayState());
+
+  // Initialize watchers.
+  mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
+  mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
+}
+
 bool MediaDecoderStateMachine::HasFutureAudio() {
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
   // We've got audio ready to play if:
   // 1. We've not completed playback of audio, and
   // 2. we either have more than the threshold of decoded audio available, or
   //    we've completely decoded all audio (but not finished playing it yet
   //    as per 1).
@@ -2520,16 +2524,19 @@ MediaDecoderStateMachine::FinishShutdown
   // mPendingWakeDecoder being needed again. Revoke it.
   mPendingWakeDecoder = nullptr;
 
   // Disconnect canonicals and mirrors before shutting down our task queue.
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mNextFrameStatus.DisconnectAll();
 
+  // Shut down the watch manager before shutting down our task queue.
+  mWatchManager.Shutdown();
+
   MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
              "How did we escape from the shutdown state?");
   // We must daisy-chain these events to destroy the decoder. We must
   // destroy the decoder on the main thread, but we can't destroy the
   // decoder while this thread holds the decoder monitor. We can't
   // dispatch an event to the main thread to destroy the decoder from
   // here, as the event may run before the dispatch returns, and we
   // hold the decoder monitor here. We also want to guarantee that the
@@ -2603,19 +2610,19 @@ nsresult MediaDecoderStateMachine::RunSt
     case DECODER_STATE_DECODING_NONE: {
       SetState(DECODER_STATE_DECODING_METADATA);
       ScheduleStateMachine();
       return NS_OK;
     }
 
     case DECODER_STATE_DECODING_METADATA: {
       if (!mMetadataRequest.Exists()) {
-        DECODER_LOG("Dispatching CallReadMetadata");
+        DECODER_LOG("Dispatching AsyncReadMetadata");
         mMetadataRequest.Begin(ProxyMediaCall(DecodeTaskQueue(), mReader.get(), __func__,
-                                              &MediaDecoderReader::CallReadMetadata)
+                                              &MediaDecoderReader::AsyncReadMetadata)
           ->RefableThen(TaskQueue(), __func__, this,
                         &MediaDecoderStateMachine::OnMetadataRead,
                         &MediaDecoderStateMachine::OnMetadataNotRead));
 
       }
       return NS_OK;
     }
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -158,16 +158,21 @@ public:
   void SetAudioCaptured();
 
   // Check if the decoder needs to become dormant state.
   bool IsDormantNeeded();
   // Set/Unset dormant state.
   void SetDormant(bool aDormant);
 
 private:
+  // Initialization that needs to happen on the task queue. This is the first
+  // task that gets run on the task queue, and is dispatched from the MDSM
+  // constructor immediately after the task queue is created.
+  void InitializationTask();
+
   void Shutdown();
 public:
 
   void DispatchShutdown()
   {
     TaskQueue()->Dispatch(NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown));
   }
 
@@ -770,16 +775,19 @@ public:
   // shutting down, the state machine will then release this reference,
   // causing the decoder to be destroyed. This is accessed on the decode,
   // state machine, audio and main threads.
   nsRefPtr<MediaDecoder> mDecoder;
 
   // Task queue for running the state machine.
   nsRefPtr<MediaTaskQueue> mTaskQueue;
 
+  // State-watching manager.
+  WatchManager<MediaDecoderStateMachine> mWatchManager;
+
   // True is we are decoding a realtime stream, like a camera stream.
   bool mRealTime;
 
   // True if we've dispatched a task to run the state machine but the task has
   // yet to run.
   bool mDispatchedStateMachine;
 
   // Class for managing delayed dispatches of the state machine.
@@ -881,35 +889,34 @@ public:
   int64_t mEndTime;
 
   // Will be set when SetDuration has been called with a value != -1
   // mDurationSet false doesn't indicate that we do not have a valid duration
   // as mStartTime and mEndTime could have been set separately.
   bool mDurationSet;
 
   // The current play state and next play state, mirrored from the main thread.
-  Mirror<MediaDecoder::PlayState>::Holder mPlayState;
-  Mirror<MediaDecoder::PlayState>::Holder mNextPlayState;
+  Mirror<MediaDecoder::PlayState> mPlayState;
+  Mirror<MediaDecoder::PlayState> mNextPlayState;
 
   // Returns true if we're logically playing, that is, if the Play() has
   // been called and Pause() has not or we have not yet reached the end
   // of media. This is irrespective of the seeking state; if the owner
   // calls Play() and then Seek(), we still count as logically playing.
   // The decoder monitor must be held.
   bool IsLogicallyPlaying()
   {
     MOZ_ASSERT(OnTaskQueue());
     return mPlayState == MediaDecoder::PLAY_STATE_PLAYING ||
            mNextPlayState == MediaDecoder::PLAY_STATE_PLAYING;
   }
 
   // The status of our next frame. Mirrored on the main thread and used to
   // compute ready state.
-  WatcherHolder mNextFrameStatusUpdater;
-  Canonical<NextFrameStatus>::Holder mNextFrameStatus;
+  Canonical<NextFrameStatus> mNextFrameStatus;
 public:
   AbstractCanonical<NextFrameStatus>* CanonicalNextFrameStatus() { return &mNextFrameStatus; }
 protected:
 
   struct SeekJob {
     void Steal(SeekJob& aOther)
     {
       MOZ_DIAGNOSTIC_ASSERT(!Exists());
--- a/dom/media/MediaDevices.cpp
+++ b/dom/media/MediaDevices.cpp
@@ -92,27 +92,27 @@ public:
     {
       void* rawArray;
       uint32_t arrayLen;
       nsresult rv;
       rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (elementType != nsIDataType::VTYPE_INTERFACE) {
-        NS_Free(rawArray);
+        free(rawArray);
         return NS_ERROR_FAILURE;
       }
 
       nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
       for (uint32_t i = 0; i < arrayLen; ++i) {
         nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
         devices.AppendElement(device);
         NS_IF_RELEASE(supportsArray[i]); // explicitly decrease refcount for rawptr
       }
-      NS_Free(rawArray); // explicitly free memory from nsIVariant::GetAsArray
+      free(rawArray); // explicitly free memory from nsIVariant::GetAsArray
     }
     nsTArray<nsRefPtr<MediaDeviceInfo>> infos;
     for (auto& device : devices) {
       nsString type;
       device->GetType(type);
       bool isVideo = type.EqualsLiteral("video");
       bool isAudio = type.EqualsLiteral("audio");
       if (isVideo || isAudio) {
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -1,27 +1,32 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #if !defined(MediaInfo_h)
 #define MediaInfo_h
 
+#include "mozilla/UniquePtr.h"
 #include "nsRect.h"
 #include "nsRefPtr.h"
 #include "nsSize.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "ImageTypes.h"
 #include "MediaData.h"
 #include "StreamBuffer.h" // for TrackID
 
 namespace mozilla {
 
+class AudioInfo;
+class VideoInfo;
+class TextInfo;
+
 class TrackInfo {
 public:
   enum TrackType {
     kUndefinedTrack,
     kAudioTrack,
     kVideoTrack,
     kTextTrack
   };
@@ -37,16 +42,17 @@ public:
     , mLabel(aLabel)
     , mLanguage(aLanguage)
     , mEnabled(aEnabled)
     , mTrackId(aTrackId)
     , mDuration(0)
     , mMediaTime(0)
     , mType(aType)
   {
+    MOZ_COUNT_CTOR(TrackInfo);
   }
 
   // Only used for backward compatibility. Do not use in new code.
   void Init(TrackType aType,
             const nsAString& aId,
             const nsAString& aKind,
             const nsAString& aLabel,
             const nsAString& aLanguage,
@@ -71,34 +77,84 @@ public:
 
   TrackID mTrackId;
 
   nsAutoCString mMimeType;
   int64_t mDuration;
   int64_t mMediaTime;
   CryptoTrack mCrypto;
 
+  virtual AudioInfo* GetAsAudioInfo()
+  {
+    return nullptr;
+  }
+  virtual VideoInfo* GetAsVideoInfo()
+  {
+    return nullptr;
+  }
+  virtual TextInfo* GetAsTextInfo()
+  {
+    return nullptr;
+  }
+  virtual const AudioInfo* GetAsAudioInfo() const
+  {
+    return nullptr;
+  }
+  virtual const VideoInfo* GetAsVideoInfo() const
+  {
+    return nullptr;
+  }
+  virtual const TextInfo* GetAsTextInfo() const
+  {
+    return nullptr;
+  }
+
   bool IsAudio() const
   {
-    return mType == kAudioTrack;
+    return !!GetAsAudioInfo();
   }
   bool IsVideo() const
   {
-    return mType == kVideoTrack;
+    return !!GetAsVideoInfo();
   }
   bool IsText() const
   {
-    return mType == kTextTrack;
+    return !!GetAsTextInfo();
   }
   TrackType GetType() const
   {
     return mType;
   }
+
   bool virtual IsValid() const = 0;
 
+  virtual UniquePtr<TrackInfo> Clone() const = 0;
+
+  virtual ~TrackInfo()
+  {
+    MOZ_COUNT_DTOR(TrackInfo);
+  }
+
+protected:
+  TrackInfo(const TrackInfo& aOther)
+  {
+    mId = aOther.mId;
+    mKind = aOther.mKind;
+    mLabel = aOther.mLabel;
+    mLanguage = aOther.mLanguage;
+    mEnabled = aOther.mEnabled;
+    mTrackId = aOther.mTrackId;
+    mMimeType = aOther.mMimeType;
+    mDuration = aOther.mDuration;
+    mMediaTime = aOther.mMediaTime;
+    mCrypto = aOther.mCrypto;
+    mType = aOther.mType;
+    MOZ_COUNT_CTOR(TrackInfo);
+  }
+
 private:
   TrackType mType;
 };
 
 // Stores info relevant to presenting media frames.
 class VideoInfo : public TrackInfo {
 public:
   VideoInfo()
@@ -112,21 +168,46 @@ public:
     , mDisplay(nsIntSize(aWidth, aHeight))
     , mStereoMode(StereoMode::MONO)
     , mImage(nsIntSize(aWidth, aHeight))
     , mCodecSpecificConfig(new MediaByteBuffer)
     , mExtraData(new MediaByteBuffer)
   {
   }
 
+  VideoInfo(const VideoInfo& aOther)
+    : TrackInfo(aOther)
+    , mDisplay(aOther.mDisplay)
+    , mStereoMode(aOther.mStereoMode)
+    , mImage(aOther.mImage)
+    , mCodecSpecificConfig(aOther.mCodecSpecificConfig)
+    , mExtraData(aOther.mExtraData)
+  {
+  }
+
   virtual bool IsValid() const override
   {
     return mDisplay.width > 0 && mDisplay.height > 0;
   }
 
+  virtual VideoInfo* GetAsVideoInfo()
+  {
+    return this;
+  }
+
+  virtual const VideoInfo* GetAsVideoInfo() const
+  {
+    return this;
+  }
+
+  virtual UniquePtr<TrackInfo> Clone() const
+  {
+    return MakeUnique<VideoInfo>(*this);
+  }
+
   // Size in pixels at which the video is rendered. This is after it has
   // been scaled by its aspect ratio.
   nsIntSize mDisplay;
 
   // Indicates the frame layout for single track stereo videos.
   StereoMode mStereoMode;
 
   // Size in pixels of decoded video's image.
@@ -145,16 +226,48 @@ public:
     , mBitDepth(0)
     , mProfile(0)
     , mExtendedProfile(0)
     , mCodecSpecificConfig(new MediaByteBuffer)
     , mExtraData(new MediaByteBuffer)
   {
   }
 
+  AudioInfo(const AudioInfo& aOther)
+    : TrackInfo(aOther)
+    , mRate(aOther.mRate)
+    , mChannels(aOther.mChannels)
+    , mBitDepth(aOther.mBitDepth)
+    , mProfile(aOther.mProfile)
+    , mExtendedProfile(aOther.mExtendedProfile)
+    , mCodecSpecificConfig(aOther.mCodecSpecificConfig)
+    , mExtraData(aOther.mExtraData)
+  {
+  }
+
+  virtual bool IsValid() const override
+  {
+    return mChannels > 0 && mRate > 0;
+  }
+
+  virtual AudioInfo* GetAsAudioInfo()
+  {
+    return this;
+  }
+
+  virtual const AudioInfo* GetAsAudioInfo() const
+  {
+    return this;
+  }
+
+  virtual UniquePtr<TrackInfo> Clone() const
+  {
+    return MakeUnique<AudioInfo>(*this);
+  }
+
   // Sample rate.
   uint32_t mRate;
 
   // Number of audio channels.
   uint32_t mChannels;
 
   // Bits per sample.
   uint32_t mBitDepth;
@@ -163,20 +276,16 @@ public:
   int8_t mProfile;
 
   // Extended codec profile.
   int8_t mExtendedProfile;
 
   nsRefPtr<MediaByteBuffer> mCodecSpecificConfig;
   nsRefPtr<MediaByteBuffer> mExtraData;
 
-  virtual bool IsValid() const override
-  {
-    return mChannels > 0 && mRate > 0;
-  }
 };
 
 class EncryptionInfo {
 public:
   struct InitData {
     template<typename AInitDatas>
     InitData(const nsAString& aType, AInitDatas&& aInitData)
       : mType(aType)
--- a/dom/media/MediaPermissionGonk.cpp
+++ b/dom/media/MediaPermissionGonk.cpp
@@ -352,30 +352,30 @@ MediaDeviceSuccessCallback::OnSuccess(ns
   void* rawArray;
   uint32_t arrayLen;
 
   nsresult rv;
   rv = aDevices->GetAsArray(&elementType, &elementIID, &arrayLen, &rawArray);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (elementType != nsIDataType::VTYPE_INTERFACE) {
-    NS_Free(rawArray);
+    free(rawArray);
     return NS_ERROR_FAILURE;
   }
 
   // Create array for nsIMediaDevice
   nsTArray<nsCOMPtr<nsIMediaDevice> > devices;
 
   nsISupports **supportsArray = reinterpret_cast<nsISupports **>(rawArray);
   for (uint32_t i = 0; i < arrayLen; ++i) {
     nsCOMPtr<nsIMediaDevice> device(do_QueryInterface(supportsArray[i]));
     devices.AppendElement(device);
     NS_IF_RELEASE(supportsArray[i]); // explicitly decrease reference count for raw pointer
   }
-  NS_Free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray
+  free(rawArray); // explicitly free for the memory from nsIVariant::GetAsArray
 
   // Send MediaPermissionRequest
   nsRefPtr<MediaPermissionRequest> req = new MediaPermissionRequest(mRequest, devices);
   rv = DoPrompt(req);
 
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
--- a/dom/media/MediaTimer.cpp
+++ b/dom/media/MediaTimer.cpp
@@ -11,34 +11,25 @@
 #include "nsComponentManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/RefPtr.h"
 #include "SharedThreadPool.h"
 
 namespace mozilla {
 
-PRLogModuleInfo* gMediaTimerLog;
-static void EnsureMediaTimerLog()
-{
-  if (!gMediaTimerLog) {
-    gMediaTimerLog = PR_NewLogModule("MediaTimer");
-  }
-}
-
 NS_IMPL_ADDREF(MediaTimer)
 NS_IMPL_RELEASE_WITH_DESTROY(MediaTimer, DispatchDestroy())
 
 MediaTimer::MediaTimer()
   : mMonitor("MediaTimer Monitor")
   , mTimer(do_CreateInstance("@mozilla.org/timer;1"))
   , mCreationTimeStamp(TimeStamp::Now())
   , mUpdateScheduled(false)
 {
-  EnsureMediaTimerLog();
   TIMER_LOG("MediaTimer::MediaTimer");
 
   // Use the SharedThreadPool to create an nsIThreadPool with a maximum of one
   // thread, which is equivalent to an nsIThread for our purposes.
   RefPtr<SharedThreadPool> threadPool(
     SharedThreadPool::Get(NS_LITERAL_CSTRING("MediaTimer"), 1));
   mThread = threadPool.get();
   mTimer->SetTarget(mThread);
--- a/dom/media/StateMirroring.h
+++ b/dom/media/StateMirroring.h
@@ -99,301 +99,286 @@ protected:
 /*
  * Canonical<T> is a wrapper class that allows a given value to be mirrored by other
  * threads. It maintains a list of active mirrors, and queues updates for them
  * when the internal value changes. When changing the value, the caller needs to
  * pass a TaskDispatcher object, which fires the updates at the appropriate time.
  * Canonical<T> is also a WatchTarget, and may be set up to trigger other routines
  * (on the same thread) when the canonical value changes.
  *
- * Do not instantiate a Canonical<T> directly as a member. Instead, instantiate a
- * Canonical<T>::Holder, which handles lifetime issues and may eventually be
- * extended to do other things as well.
+ * Canonical<T> is intended to be used as a member variable, so it doesn't actually
+ * inherit AbstractCanonical<T> (a refcounted type). Rather, it contains an inner
+ * class called |Impl| that implements most of the interesting logic.
  */
 template<typename T>
-class Canonical : public AbstractCanonical<T>, public WatchTarget
+class Canonical
 {
 public:
-  using AbstractCanonical<T>::OwnerThread;
-
   Canonical(AbstractThread* aThread, const T& aInitialValue, const char* aName)
-    : AbstractCanonical<T>(aThread), WatchTarget(aName), mValue(aInitialValue)
-  {
-    MIRROR_LOG("%s [%p] initialized", mName, this);
-    MOZ_ASSERT(aThread->RequiresTailDispatch(), "Can't get coherency without tail dispatch");
-  }
-
-  void AddMirror(AbstractMirror<T>* aMirror) override
   {
-    MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror);
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    MOZ_ASSERT(!mMirrors.Contains(aMirror));
-    mMirrors.AppendElement(aMirror);
-    aMirror->OwnerThread()->Dispatch(MakeNotifier(aMirror), AbstractThread::DontAssertDispatchSuccess);
-  }
-
-  void RemoveMirror(AbstractMirror<T>* aMirror) override
-  {
-    MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror);
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    MOZ_ASSERT(mMirrors.Contains(aMirror));
-    mMirrors.RemoveElement(aMirror);
+    mImpl = new Impl(aThread, aInitialValue, aName);
   }
 
-  void DisconnectAll()
+
+  ~Canonical() {}
+
+private:
+  class Impl : public AbstractCanonical<T>, public WatchTarget
   {
-    MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this);
-    for (size_t i = 0; i < mMirrors.Length(); ++i) {
-      nsCOMPtr<nsIRunnable> r =
-        NS_NewRunnableMethod(mMirrors[i], &AbstractMirror<T>::NotifyDisconnected);
-      mMirrors[i]->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
+  public:
+    using AbstractCanonical<T>::OwnerThread;
+
+    Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName)
+      : AbstractCanonical<T>(aThread), WatchTarget(aName), mValue(aInitialValue)
+    {
+      MIRROR_LOG("%s [%p] initialized", mName, this);
+      MOZ_ASSERT(aThread->RequiresTailDispatch(), "Can't get coherency without tail dispatch");
     }
-    mMirrors.Clear();
-  }
 
-  operator const T&()
-  {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    return mValue;
-  }
+    void AddMirror(AbstractMirror<T>* aMirror) override
+    {
+      MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror);
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      MOZ_ASSERT(!mMirrors.Contains(aMirror));
+      mMirrors.AppendElement(aMirror);
+      aMirror->OwnerThread()->Dispatch(MakeNotifier(aMirror), AbstractThread::DontAssertDispatchSuccess);
+    }
 
-  void Set(const T& aNewValue)
-  {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+    void RemoveMirror(AbstractMirror<T>* aMirror) override
+    {
+      MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror);
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      MOZ_ASSERT(mMirrors.Contains(aMirror));
+      mMirrors.RemoveElement(aMirror);
+    }
 
-    if (aNewValue == mValue) {
-      return;
+    void DisconnectAll()
+    {
+      MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this);
+      for (size_t i = 0; i < mMirrors.Length(); ++i) {
+        nsCOMPtr<nsIRunnable> r =
+          NS_NewRunnableMethod(mMirrors[i], &AbstractMirror<T>::NotifyDisconnected);
+        mMirrors[i]->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
+      }
+      mMirrors.Clear();
+    }
+
+    operator const T&()
+    {
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      return mValue;
     }
 
-    // Notify same-thread watchers. The state watching machinery will make sure
-    // that notifications run at the right time.
-    NotifyWatchers();
+    void Set(const T& aNewValue)
+    {
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+
+      if (aNewValue == mValue) {
+        return;
+      }
+
+      // Notify same-thread watchers. The state watching machinery will make sure
+      // that notifications run at the right time.
+      NotifyWatchers();
 
-    // Check if we've already got a pending update. If so we won't schedule another
-    // one.
-    bool alreadyNotifying = mInitialValue.isSome();
+      // Check if we've already got a pending update. If so we won't schedule another
+      // one.
+      bool alreadyNotifying = mInitialValue.isSome();
 
-    // Stash the initial value if needed, then update to the new value.
-    if (mInitialValue.isNothing()) {
-      mInitialValue.emplace(mValue);
+      // Stash the initial value if needed, then update to the new value.
+      if (mInitialValue.isNothing()) {
+        mInitialValue.emplace(mValue);
+      }
+      mValue = aNewValue;
+
+      // We wait until things have stablized before sending state updates so that
+      // we can avoid sending multiple updates, and possibly avoid sending any
+      // updates at all if the value ends up where it started.
+      if (!alreadyNotifying) {
+        nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &Impl::DoNotify);
+        AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget());
+      }
     }
-    mValue = aNewValue;
+
+    Impl& operator=(const T& aNewValue) { Set(aNewValue); return *this; }
+    Impl& operator=(const Impl& aOther) { Set(aOther); return *this; }
+    Impl(const Impl& aOther) = delete;
+
+  protected:
+    ~Impl() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); }
+
+  private:
+    void DoNotify()
+    {
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      MOZ_ASSERT(mInitialValue.isSome());
+      bool same = mInitialValue.ref() == mValue;
+      mInitialValue.reset();
+
+      if (same) {
+        MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this);
+        return;
+      }
+
+      for (size_t i = 0; i < mMirrors.Length(); ++i) {
+        OwnerThread()->TailDispatcher().AddStateChangeTask(mMirrors[i]->OwnerThread(), MakeNotifier(mMirrors[i]));
+      }
+    }
 
-    // We wait until things have stablized before sending state updates so that
-    // we can avoid sending multiple updates, and possibly avoid sending any
-    // updates at all if the value ends up where it started.
-    if (!alreadyNotifying) {
-      nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &Canonical::DoNotify);
-      AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget());
+    already_AddRefed<nsIRunnable> MakeNotifier(AbstractMirror<T>* aMirror)
+    {
+      nsCOMPtr<nsIRunnable> r =
+        NS_NewRunnableMethodWithArg<T>(aMirror, &AbstractMirror<T>::UpdateValue, mValue);
+      return r.forget();
     }
-  }
+
+    T mValue;
+    Maybe<T> mInitialValue;
+    nsTArray<nsRefPtr<AbstractMirror<T>>> mMirrors;
+  };
+public:
 
+  // NB: Because mirror-initiated disconnection can race with canonical-
+  // initiated disconnection, a canonical should never be reinitialized.
+  // Forward control operations to the Impl.
+  void DisconnectAll() { return mImpl->DisconnectAll(); }
+
+  // Access to the Impl.
+  operator Impl&() { return *mImpl; }
+  Impl* operator&() { return mImpl; }
+
+  // Access to the T.
+  const T& Ref() const { return *mImpl; }
+  operator const T&() const { return Ref(); }
+  void Set(const T& aNewValue) { mImpl->Set(aNewValue); }
   Canonical& operator=(const T& aNewValue) { Set(aNewValue); return *this; }
   Canonical& operator=(const Canonical& aOther) { Set(aOther); return *this; }
   Canonical(const Canonical& aOther) = delete;
 
-  class Holder
-  {
-  public:
-    Holder() {}
-    ~Holder() { MOZ_DIAGNOSTIC_ASSERT(mCanonical, "Should have initialized me"); }
-
-    // NB: Because mirror-initiated disconnection can race with canonical-
-    // initiated disconnection, a canonical should never be reinitialized.
-    void Init(AbstractThread* aThread, const T& aInitialValue, const char* aName)
-    {
-      mCanonical = new Canonical<T>(aThread, aInitialValue, aName);
-    }
-
-    // Forward control operations to the Canonical<T>.
-    void DisconnectAll() { return mCanonical->DisconnectAll(); }
-
-    // Access to the Canonical<T>.
-    operator Canonical<T>&() { return *mCanonical; }
-    Canonical<T>* operator&() { return mCanonical; }
-
-    // Access to the T.
-    const T& Ref() const { return *mCanonical; }
-    operator const T&() const { return Ref(); }
-    void Set(const T& aNewValue) { mCanonical->Set(aNewValue); }
-    Holder& operator=(const T& aNewValue) { Set(aNewValue); return *this; }
-    Holder& operator=(const Holder& aOther) { Set(aOther); return *this; }
-    Holder(const Holder& aOther) = delete;
-
-  private:
-    nsRefPtr<Canonical<T>> mCanonical;
-  };
-
-protected:
-  ~Canonical() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); }
-
 private:
-  void DoNotify()
-  {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    MOZ_ASSERT(mInitialValue.isSome());
-    bool same = mInitialValue.ref() == mValue;
-    mInitialValue.reset();
-
-    if (same) {
-      MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this);
-      return;
-    }
-
-    for (size_t i = 0; i < mMirrors.Length(); ++i) {
-      OwnerThread()->TailDispatcher().AddStateChangeTask(mMirrors[i]->OwnerThread(), MakeNotifier(mMirrors[i]));
-    }
-  }
-
-  already_AddRefed<nsIRunnable> MakeNotifier(AbstractMirror<T>* aMirror)
-  {
-    nsCOMPtr<nsIRunnable> r =
-      NS_NewRunnableMethodWithArg<T>(aMirror, &AbstractMirror<T>::UpdateValue, mValue);
-    return r.forget();
-  }
-
-  T mValue;
-  Maybe<T> mInitialValue;
-  nsTArray<nsRefPtr<AbstractMirror<T>>> mMirrors;
+  nsRefPtr<Impl> mImpl;
 };
 
 /*
  * Mirror<T> is a wrapper class that allows a given value to mirror that of a
  * Canonical<T> owned by another thread. It registers itself with a Canonical<T>,
  * and is periodically updated with new values. Mirror<T> is also a WatchTarget,
  * and may be set up to trigger other routines (on the same thread) when the
  * mirrored value changes.
  *
- * Do not instantiate a Mirror<T> directly as a member. Instead, instantiate a
- * Mirror<T>::Holder, which handles lifetime issues and whose destructor
- * initiates an asynchronous teardown of the reference-counted Mirror<T>,
- * breaking the inherent cycle between Mirror<T> and Canonical<T>.
+ * Mirror<T> is intended to be used as a member variable, so it doesn't actually
+ * inherit AbstractMirror<T> (a refcounted type). Rather, it contains an inner
+ * class called |Impl| that implements most of the interesting logic.
  */
 template<typename T>
-class Mirror : public AbstractMirror<T>, public WatchTarget
+class Mirror
 {
 public:
-  using AbstractMirror<T>::OwnerThread;
-
-  Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName,
-         AbstractCanonical<T>* aCanonical)
-    : AbstractMirror<T>(aThread), WatchTarget(aName), mValue(aInitialValue)
+  Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName)
   {
-    MIRROR_LOG("%s [%p] initialized", mName, this);
-
-    if (aCanonical) {
-      ConnectInternal(aCanonical);
-    }
+    mImpl = new Impl(aThread, aInitialValue, aName);
   }
 
-  operator const T&()
+  ~Mirror()
   {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    return mValue;
-  }
-
-  virtual void UpdateValue(const T& aNewValue) override
-  {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    if (mValue != aNewValue) {
-      mValue = aNewValue;
-      WatchTarget::NotifyWatchers();
+    if (mImpl->OwnerThread()->IsCurrentThreadIn()) {
+      mImpl->DisconnectIfConnected();
+    } else {
+      // If holder destruction happens on a thread other than the mirror's
+      // owner thread, manual disconnection is mandatory. We should make this
+      // more automatic by hooking it up to task queue shutdown.
+      MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected());
     }
   }
 
-  virtual void NotifyDisconnected() override
-  {
-    MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this, mCanonical.get());
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    mCanonical = nullptr;
-  }
-
-  bool IsConnected() const { return !!mCanonical; }
-
-  void Connect(AbstractCanonical<T>* aCanonical)
-  {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    ConnectInternal(aCanonical);
-  }
-
 private:
-  // We separate the guts of Connect into a helper so that we can call it from
-  // initialization while not necessarily on the owner thread.
-  void ConnectInternal(AbstractCanonical<T>* aCanonical)
+  class Impl : public AbstractMirror<T>, public WatchTarget
   {
-    MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical);
-    MOZ_ASSERT(!IsConnected());
+  public:
+    using AbstractMirror<T>::OwnerThread;
 
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<StorensRefPtrPassByPtr<AbstractMirror<T>>>
-                                (aCanonical, &AbstractCanonical<T>::AddMirror, this);
-    aCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
-    mCanonical = aCanonical;
-  }
-public:
-
-  void DisconnectIfConnected()
-  {
-    MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
-    if (!IsConnected()) {
-      return;
+    Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName)
+      : AbstractMirror<T>(aThread), WatchTarget(aName), mValue(aInitialValue)
+    {
+      MIRROR_LOG("%s [%p] initialized", mName, this);
     }
 
-    MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, mCanonical.get());
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<StorensRefPtrPassByPtr<AbstractMirror<T>>>
-                                (mCanonical, &AbstractCanonical<T>::RemoveMirror, this);
-    mCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
-    mCanonical = nullptr;
-  }
+    operator const T&()
+    {
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      return mValue;
+    }
 
-  class Holder
-  {
-  public:
-    Holder() {}
-    ~Holder()
+    virtual void UpdateValue(const T& aNewValue) override
     {
-      MOZ_DIAGNOSTIC_ASSERT(mMirror, "Should have initialized me");
-      if (mMirror->OwnerThread()->IsCurrentThreadIn()) {
-        mMirror->DisconnectIfConnected();
-      } else {
-        // If holder destruction happens on a thread other than the mirror's
-        // owner thread, manual disconnection is mandatory. We should make this
-        // more automatic by hooking it up to task queue shutdown.
-        MOZ_DIAGNOSTIC_ASSERT(!mMirror->IsConnected());
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      if (mValue != aNewValue) {
+        mValue = aNewValue;
+        WatchTarget::NotifyWatchers();
       }
     }
 
-    // NB: Because mirror-initiated disconnection can race with canonical-
-    // initiated disconnection, a mirror should never be reinitialized.
-    void Init(AbstractThread* aThread, const T& aInitialValue, const char* aName,
-              AbstractCanonical<T>* aCanonical = nullptr)
+    virtual void NotifyDisconnected() override
     {
-      mMirror = new Mirror<T>(aThread, aInitialValue, aName, aCanonical);
+      MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this, mCanonical.get());
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      mCanonical = nullptr;
     }
 
-    // Forward control operations to the Mirror<T>.
-    void Connect(AbstractCanonical<T>* aCanonical) { mMirror->Connect(aCanonical); }
-    void DisconnectIfConnected() { mMirror->DisconnectIfConnected(); }
+    bool IsConnected() const { return !!mCanonical; }
+
+    void Connect(AbstractCanonical<T>* aCanonical)
+    {
+      MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical);
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      MOZ_ASSERT(!IsConnected());
+
+      nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<StorensRefPtrPassByPtr<AbstractMirror<T>>>
+                                  (aCanonical, &AbstractCanonical<T>::AddMirror, this);
+      aCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
+      mCanonical = aCanonical;
+    }
+  public:
 
-    // Access to the Mirror<T>.
-    operator Mirror<T>&() { return *mMirror; }
-    Mirror<T>* operator&() { return mMirror; }
+    void DisconnectIfConnected()
+    {
+      MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn());
+      if (!IsConnected()) {
+        return;
+      }
 
-    // Access to the T.
-    const T& Ref() const { return *mMirror; }
-    operator const T&() const { return Ref(); }
+      MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, mCanonical.get());
+      nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<StorensRefPtrPassByPtr<AbstractMirror<T>>>
+                                  (mCanonical, &AbstractCanonical<T>::RemoveMirror, this);
+      mCanonical->OwnerThread()->Dispatch(r.forget(), AbstractThread::DontAssertDispatchSuccess);
+      mCanonical = nullptr;
+    }
+
+  protected:
+    ~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); }
 
   private:
-    nsRefPtr<Mirror<T>> mMirror;
+    T mValue;
+    nsRefPtr<AbstractCanonical<T>> mCanonical;
   };
+public:
 
-protected:
-  ~Mirror() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); }
+  // Forward control operations to the Impl<T>.
+  void Connect(AbstractCanonical<T>* aCanonical) { mImpl->Connect(aCanonical); }
+  void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); }
+
+  // Access to the Impl<T>.
+  operator Impl&() { return *mImpl; }
+  Impl* operator&() { return mImpl; }
+
+  // Access to the T.
+  const T& Ref() const { return *mImpl; }
+  operator const T&() const { return Ref(); }
 
 private:
-  T mValue;
-  nsRefPtr<AbstractCanonical<T>> mCanonical;
+  nsRefPtr<Impl> mImpl;
 };
 
 #undef MIRROR_LOG
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/StateWatching.h
+++ b/dom/media/StateWatching.h
@@ -38,84 +38,68 @@
  * This file provides a set of primitives that automatically handle updates and
  * allow the programmers to explicitly construct a graph of state dependencies.
  * When used correctly, it eliminates the guess-work and wasted cycles described
  * above.
  *
  * There are two basic pieces:
  *   (1) Objects that can be watched for updates. These inherit WatchTarget.
  *   (2) Objects that receive objects and trigger processing. These inherit
- *       AbstractWatcher. Note that some watchers may themselves be watched,
- *       allowing watchers to be composed together.
+ *       AbstractWatcher. In the current machinery, these exist only internally
+ *       within the WatchManager, though that could change.
  *
  * Note that none of this machinery is thread-safe - it must all happen on the
  * same owning thread. To solve multi-threaded use-cases, use state mirroring
  * and watch the mirrored value.
  *
  * Given that semantics may change and comments tend to go out of date, we
  * deliberately don't provide usage examples here. Grep around to find them.
  */
 
 namespace mozilla {
 
 extern PRLogModuleInfo* gStateWatchingLog;
 
-void EnsureStateWatchingLog();
-
 #define WATCH_LOG(x, ...) \
   MOZ_ASSERT(gStateWatchingLog); \
   PR_LOG(gStateWatchingLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
 
 /*
  * AbstractWatcher is a superclass from which all watchers must inherit.
  */
 class AbstractWatcher
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher)
   AbstractWatcher() : mDestroyed(false) {}
   bool IsDestroyed() { return mDestroyed; }
   virtual void Notify() = 0;
 
 protected:
-  virtual ~AbstractWatcher() {}
-  virtual void CustomDestroy() {}
-
-private:
-  // Only the holder is allowed to invoke Destroy().
-  friend class WatcherHolder;
-  void Destroy()
-  {
-    mDestroyed = true;
-    CustomDestroy();
-  }
-
+  virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); }
   bool mDestroyed;
 };
 
 /*
  * WatchTarget is a superclass from which all watchable things must inherit.
  * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass
  * needs only to invoke NotifyWatchers when something changes.
  *
  * The functionality that this class provides is not threadsafe, and should only
  * be used on the thread that owns that WatchTarget.
  */
 class WatchTarget
 {
 public:
   explicit WatchTarget(const char* aName) : mName(aName) {}
 
-  void AddWatcher(AbstractWatcher* aWatcher, bool aSkipInitialNotify)
+  void AddWatcher(AbstractWatcher* aWatcher)
   {
     MOZ_ASSERT(!mWatchers.Contains(aWatcher));
     mWatchers.AppendElement(aWatcher);
-    if (!aSkipInitialNotify) {
-      aWatcher->Notify();
-    }
   }
 
   void RemoveWatcher(AbstractWatcher* aWatcher)
   {
     MOZ_ASSERT(mWatchers.Contains(aWatcher));
     mWatchers.RemoveElement(aWatcher);
   }
 
@@ -172,95 +156,160 @@ public:
 
 private:
   Watchable(const Watchable& aOther); // Not implemented
   Watchable& operator=(const Watchable& aOther); // Not implemented
 
   T mValue;
 };
 
-/*
- * Watcher is the concrete AbstractWatcher implementation. It registers itself with
- * various WatchTargets, and accepts any number of callbacks that will be
- * invoked whenever any WatchTarget notifies of a change. It may also be watched
- * by other watchers.
- *
- * When a notification is received, a runnable is passed as "direct" to the
- * thread's tail dispatcher, which run directly (rather than via dispatch) when
- * the tail dispatcher fires. All subsequent notifications are ignored until the
- * runnable executes, triggering the updates and resetting the flags.
- */
-class Watcher : public AbstractWatcher, public WatchTarget
+// Manager class for state-watching. Declare one of these in any class for which
+// you want to invoke method callbacks.
+//
+// Internally, WatchManager maintains one AbstractWatcher per callback method.
+// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple.
+// This causes an AbstractWatcher for |Callback| to be instantiated if it doesn't
+// already exist, and registers it with |WatchTarget|.
+//
+// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire
+// watch callbacks no more than once per task, once all other operations for that
+// task have been completed.
+//
+// WatchManager<OwnerType> is intended to be declared as a member of |OwnerType|
+// objects. Given that, it and its owned objects can't hold permanent strong refs to
+// the owner, since that would keep the owner alive indefinitely. Instead, it
+// _only_ holds strong refs while waiting for Direct Tasks to fire. This ensures
+// that everything is kept alive just long enough.
+template <typename OwnerType>
+class WatchManager
 {
 public:
-  explicit Watcher(const char* aName)
-    : WatchTarget(aName), mNotifying(false) {}
-
-  void Notify() override
-  {
-    if (mNotifying) {
-      return;
-    }
-    mNotifying = true;
-
-    // Queue up our notification jobs to run in a stable state.
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &Watcher::DoNotify);
-    AbstractThread::GetCurrent()->TailDispatcher().AddDirectTask(r.forget());
-  }
-
-  void Watch(WatchTarget& aTarget, bool aSkipInitialNotify = false) { aTarget.AddWatcher(this, aSkipInitialNotify); }
-  void Unwatch(WatchTarget& aTarget) { aTarget.RemoveWatcher(this); }
+  typedef void(OwnerType::*CallbackMethod)();
+  explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread)
+    : mOwner(aOwner), mOwnerThread(aOwnerThread) {}
 
-  template<typename ThisType>
-  void AddWeakCallback(ThisType* aThisVal, void(ThisType::*aMethod)())
+  ~WatchManager()
   {
-    mCallbacks.AppendElement(NS_NewNonOwningRunnableMethod(aThisVal, aMethod));
-  }
-
-protected:
-  void CustomDestroy() override { mCallbacks.Clear(); }
-
-  void DoNotify()
-  {
-    MOZ_ASSERT(mNotifying);
-    mNotifying = false;
-
-    // Notify dependent watchers.
-    NotifyWatchers();
-
-    for (size_t i = 0; i < mCallbacks.Length(); ++i) {
-      mCallbacks[i]->Run();
+    if (!IsShutdown()) {
+      Shutdown();
     }
   }
 
-private:
-  nsTArray<nsCOMPtr<nsIRunnable>> mCallbacks;
+  bool IsShutdown() const { return !mOwner; }
 
-  bool mNotifying;
-};
+  // Shutdown needs to happen on mOwnerThread. If the WatchManager will be
+  // destroyed on a different thread, Shutdown() must be called manually.
+  void Shutdown()
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    for (size_t i = 0; i < mWatchers.Length(); ++i) {
+      mWatchers[i]->Destroy();
+    }
+    mWatchers.Clear();
+    mOwner = nullptr;
+  }
 
-/*
- * WatcherHolder encapsulates a Watcher and handles lifetime issues. Use it to
- * holder watcher members.
- */
-class WatcherHolder
-{
-public:
-  explicit WatcherHolder(const char* aName) { mWatcher = new Watcher(aName); }
-  operator Watcher*() { return mWatcher; }
-  Watcher* operator->() { return mWatcher; }
+  void Watch(WatchTarget& aTarget, CallbackMethod aMethod)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    aTarget.AddWatcher(&EnsureWatcher(aMethod));
+  }
 
-  ~WatcherHolder()
+  void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod)
   {
-    mWatcher->Destroy();
-    mWatcher = nullptr;
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    PerCallbackWatcher* watcher = GetWatcher(aMethod);
+    MOZ_ASSERT(watcher);
+    aTarget.RemoveWatcher(watcher);
+  }
+
+  void ManualNotify(CallbackMethod aMethod)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    PerCallbackWatcher* watcher = GetWatcher(aMethod);
+    MOZ_ASSERT(watcher);
+    watcher->Notify();
   }
 
 private:
-  nsRefPtr<Watcher> mWatcher;
+  class PerCallbackWatcher : public AbstractWatcher
+  {
+  public:
+    PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, CallbackMethod aMethod)
+      : mOwner(aOwner), mOwnerThread(aOwnerThread), mCallbackMethod(aMethod) {}
+
+    void Destroy()
+    {
+      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+      mDestroyed = true;
+      mOwner = nullptr;
+    }
+
+    void Notify() override
+    {
+      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+      MOZ_DIAGNOSTIC_ASSERT(mOwner, "mOwner is only null after destruction, "
+                                    "at which point we shouldn't be notified");
+      if (mStrongRef) {
+        // We've already got a notification job in the pipe.
+        return;
+      }
+      mStrongRef = mOwner; // Hold the owner alive while notifying.
+
+      // Queue up our notification jobs to run in a stable state.
+      nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(this, &PerCallbackWatcher::DoNotify);
+      mOwnerThread->TailDispatcher().AddDirectTask(r.forget());
+    }
+
+    bool CallbackMethodIs(CallbackMethod aMethod) const
+    {
+      return mCallbackMethod == aMethod;
+    }
+
+  private:
+    ~PerCallbackWatcher() {}
+
+    void DoNotify()
+    {
+      MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+      MOZ_ASSERT(mStrongRef);
+      nsRefPtr<OwnerType> ref = mStrongRef.forget();
+      ((*ref).*mCallbackMethod)();
+    }
+
+    OwnerType* mOwner; // Never null.
+    nsRefPtr<OwnerType> mStrongRef; // Only non-null when notifying.
+    nsRefPtr<AbstractThread> mOwnerThread;
+    CallbackMethod mCallbackMethod;
+  };
+
+  PerCallbackWatcher* GetWatcher(CallbackMethod aMethod)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    for (size_t i = 0; i < mWatchers.Length(); ++i) {
+      if (mWatchers[i]->CallbackMethodIs(aMethod)) {
+        return mWatchers[i];
+      }
+    }
+    return nullptr;
+  }
+
+  PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    PerCallbackWatcher* watcher = GetWatcher(aMethod);
+    if (watcher) {
+      return *watcher;
+    }
+    watcher = mWatchers.AppendElement(new PerCallbackWatcher(mOwner, mOwnerThread, aMethod))->get();
+    return *watcher;
+  }
+
+  nsTArray<nsRefPtr<PerCallbackWatcher>> mWatchers;
+  OwnerType* mOwner;
+  nsRefPtr<AbstractThread> mOwnerThread;
 };
 
-
 #undef WATCH_LOG
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -275,17 +275,17 @@ GenerateRandomName(nsCString& aOutSalt, 
   uint8_t* buffer;
   rv = rg->GenerateRandomBytes(requiredBytesLength, &buffer);
   if (NS_FAILED(rv)) return rv;
 
   nsAutoCString temp;
   nsDependentCSubstring randomData(reinterpret_cast<const char*>(buffer),
                                    requiredBytesLength);
   rv = Base64Encode(randomData, temp);
-  NS_Free(buffer);
+  free(buffer);
   buffer = nullptr;
   if (NS_FAILED (rv)) return rv;
 
   aOutSalt = temp;
   return NS_OK;
 }
 
 nsresult
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MP4Reader.h"
 #include "MP4Stream.h"
 #include "MediaData.h"
+#include "MediaInfo.h"
 #include "MediaResource.h"
 #include "nsPrintfCString.h"
 #include "nsSize.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "ImageContainer.h"
 #include "Layers.h"
 #include "SharedThreadPool.h"
@@ -52,23 +53,24 @@ using namespace mp4_demuxer;
 
 namespace mozilla {
 
 // Uncomment to enable verbose per-sample logging.
 //#define LOG_SAMPLE_DECODE 1
 
 #ifdef PR_LOGGING
 static const char*
-TrackTypeToStr(TrackType aTrack)
+TrackTypeToStr(TrackInfo::TrackType aTrack)
 {
-  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
+  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
+             aTrack == TrackInfo::kVideoTrack);
   switch (aTrack) {
-  case kAudio:
+  case TrackInfo::kAudioTrack:
     return "Audio";
-  case kVideo:
+  case TrackInfo::kVideoTrack:
     return "Video";
   default:
     return "Unknown";
   }
 }
 #endif
 
 uint8_t sTestExtraData[40] = { 0x01, 0x64, 0x00, 0x0a, 0xff, 0xe1, 0x00, 0x17, 0x67, 0x64, 0x00, 0x0a, 0xac, 0xd9, 0x44, 0x26, 0x84, 0x00, 0x00, 0x03,
@@ -212,30 +214,30 @@ MP4Reader::~MP4Reader()
 }
 
 nsRefPtr<ShutdownPromise>
 MP4Reader::Shutdown()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
 
   if (mAudio.mDecoder) {
-    Flush(kAudio);
+    Flush(TrackInfo::kAudioTrack);
     mAudio.mDecoder->Shutdown();
     mAudio.mDecoder = nullptr;
   }
   if (mAudio.mTaskQueue) {
     mAudio.mTaskQueue->BeginShutdown();
     mAudio.mTaskQueue->AwaitShutdownAndIdle();
     mAudio.mTaskQueue = nullptr;
   }
   mAudio.mPromise.SetMonitor(nullptr);
   MOZ_ASSERT(mAudio.mPromise.IsEmpty());
 
   if (mVideo.mDecoder) {
-    Flush(kVideo);
+    Flush(TrackInfo::kVideoTrack);
     mVideo.mDecoder->Shutdown();
     mVideo.mDecoder = nullptr;
   }
   if (mVideo.mTaskQueue) {
     mVideo.mTaskQueue->BeginShutdown();
     mVideo.mTaskQueue->AwaitShutdownAndIdle();
     mVideo.mTaskQueue = nullptr;
   }
@@ -430,22 +432,22 @@ MP4Reader::ReadMetadata(MediaInfo* aInfo
     mDemuxerInitialized = true;
   } else if (mPlatform && !IsWaitingMediaResources()) {
     *aInfo = mInfo;
     *aTags = nullptr;
   }
 
   if (HasAudio()) {
     mInfo.mAudio = mDemuxer->AudioConfig();
-    mAudio.mCallback = new DecoderCallback(this, kAudio);
+    mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack);
   }
 
   if (HasVideo()) {
     mInfo.mVideo = mDemuxer->VideoConfig();
-    mVideo.mCallback = new DecoderCallback(this, kVideo);
+    mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack);
 
     // Collect telemetry from h264 AVCC SPS.
     if (!mFoundSPSForTelemetry) {
       mFoundSPSForTelemetry = AccumulateSPSTelemetry(mInfo.mVideo.mExtraData);
     }
   }
 
   if (mCrypto.valid) {
@@ -561,17 +563,18 @@ MP4Reader::EnsureDecodersSetup()
     nsresult rv = mAudio.mDecoder->Init();
     NS_ENSURE_SUCCESS(rv, false);
   }
 
   if (HasVideo()) {
     NS_ENSURE_TRUE(IsSupportedVideoMimeType(mDemuxer->VideoConfig().mMimeType),
                    false);
 
-    if (mSharedDecoderManager && mPlatform->SupportsSharedDecoders(mDemuxer->VideoConfig())) {
+    if (mSharedDecoderManager &&
+        mPlatform->SupportsSharedDecoders(mDemuxer->VideoConfig())) {
       mVideo.mDecoder =
         mSharedDecoderManager->CreateVideoDecoder(mPlatform,
                                                   mDemuxer->VideoConfig(),
                                                   mLayersBackendType,
                                                   mDecoder->GetImageContainer(),
                                                   mVideo.mTaskQueue,
                                                   mVideo.mCallback);
     } else {
@@ -615,18 +618,19 @@ bool
 MP4Reader::HasVideo()
 {
   return mVideo.mActive;
 }
 
 MP4Reader::DecoderData&
 MP4Reader::GetDecoderData(TrackType aTrack)
 {
-  MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
-  if (aTrack == kAudio) {
+  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
+             aTrack == TrackInfo::kVideoTrack);
+  if (aTrack == TrackInfo::kAudioTrack) {
     return mAudio;
   }
   return mVideo;
 }
 
 Microseconds
 MP4Reader::GetNextKeyframeTime()
 {
@@ -644,17 +648,17 @@ MP4Reader::DisableHardwareAcceleration()
     if (!mSharedDecoderManager->Recreate(video)) {
       MonitorAutoLock mon(mVideo.mMonitor);
       mVideo.mError = true;
       if (mVideo.HasPromise()) {
         mVideo.RejectPromise(DECODE_ERROR, __func__);
       }
     } else {
       MonitorAutoLock lock(mVideo.mMonitor);
-      ScheduleUpdate(kVideo);
+      ScheduleUpdate(TrackInfo::kVideoTrack);
     }
   }
 }
 
 bool
 MP4Reader::ShouldSkip(bool aSkipToNextKeyframe, int64_t aTimeThreshold)
 {
   // The MP4Reader doesn't do normal skip-to-next-keyframe if the demuxer
@@ -701,17 +705,17 @@ MP4Reader::RequestVideoData(bool aSkipTo
 
   MonitorAutoLock lock(mVideo.mMonitor);
   nsRefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
   if (mVideo.mError) {
     mVideo.mPromise.Reject(DECODE_ERROR, __func__);
   } else if (eos) {
     mVideo.mPromise.Reject(END_OF_STREAM, __func__);
   } else {
-    ScheduleUpdate(kVideo);
+    ScheduleUpdate(TrackInfo::kVideoTrack);
   }
 
   return p;
 }
 
 nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MP4Reader::RequestAudioData()
 {
@@ -725,17 +729,17 @@ MP4Reader::RequestAudioData()
 
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MP4Reader!");
     return AudioDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   MonitorAutoLock lock(mAudio.mMonitor);
   nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
-  ScheduleUpdate(kAudio);
+  ScheduleUpdate(TrackInfo::kAudioTrack);
   return p;
 }
 
 void
 MP4Reader::ScheduleUpdate(TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
   decoder.mMonitor.AssertCurrentThreadOwns();
@@ -786,18 +790,19 @@ MP4Reader::Update(TrackType aTrack)
   {
     MonitorAutoLock lock(decoder.mMonitor);
     decoder.mUpdateScheduled = false;
     if (NeedInput(decoder)) {
       needInput = true;
       decoder.mInputExhausted = false;
       decoder.mNumSamplesInput++;
     }
-    if (aTrack == kVideo) {
-      uint64_t delta = decoder.mNumSamplesOutput - mLastReportedNumDecodedFrames;
+    if (aTrack == TrackInfo::kVideoTrack) {
+      uint64_t delta =
+        decoder.mNumSamplesOutput - mLastReportedNumDecodedFrames;
       a.mDecoded = static_cast<uint32_t>(delta);
       mLastReportedNumDecodedFrames = decoder.mNumSamplesOutput;
     }
     if (decoder.HasPromise()) {
       needOutput = true;
       if (!decoder.mOutput.IsEmpty()) {
         nsRefPtr<MediaData> output = decoder.mOutput[0];
         decoder.mOutput.RemoveElementAt(0);
@@ -821,17 +826,17 @@ MP4Reader::Update(TrackType aTrack)
     // Collect telemetry from h264 Annex B SPS.
     if (!mFoundSPSForTelemetry && sample && AnnexB::HasSPS(sample)) {
       nsRefPtr<MediaByteBuffer> extradata = AnnexB::ExtractExtraData(sample);
       mFoundSPSForTelemetry = AccumulateSPSTelemetry(extradata);
     }
 
     if (sample) {
       decoder.mDecoder->Input(sample);
-      if (aTrack == kVideo) {
+      if (aTrack == TrackInfo::kVideoTrack) {
         a.mParsed++;
       }
     } else {
       {
         MonitorAutoLock lock(decoder.mMonitor);
         MOZ_ASSERT(!decoder.mDemuxEOS);
         decoder.mDemuxEOS = true;
       }
@@ -847,29 +852,29 @@ MP4Reader::ReturnOutput(MediaData* aData
   auto& decoder = GetDecoderData(aTrack);
   decoder.mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(decoder.HasPromise());
   if (decoder.mDiscontinuity) {
     decoder.mDiscontinuity = false;
     aData->mDiscontinuity = true;
   }
 
-  if (aTrack == kAudio) {
+  if (aTrack == TrackInfo::kAudioTrack) {
     AudioData* audioData = static_cast<AudioData*>(aData);
 
     if (audioData->mChannels != mInfo.mAudio.mChannels ||
         audioData->mRate != mInfo.mAudio.mRate) {
       LOG("change of sampling rate:%d->%d",
           mInfo.mAudio.mRate, audioData->mRate);
       mInfo.mAudio.mRate = audioData->mRate;
       mInfo.mAudio.mChannels = audioData->mChannels;
     }
 
     mAudio.mPromise.Resolve(audioData, __func__);
-  } else if (aTrack == kVideo) {
+  } else if (aTrack == TrackInfo::kVideoTrack) {
     mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__);
   }
 }
 
 already_AddRefed<MediaRawData>
 MP4Reader::PopSample(TrackType aTrack)
 {
   MonitorAutoLock mon(mDemuxerMonitor);
@@ -877,21 +882,21 @@ MP4Reader::PopSample(TrackType aTrack)
 }
 
 already_AddRefed<MediaRawData>
 MP4Reader::PopSampleLocked(TrackType aTrack)
 {
   mDemuxerMonitor.AssertCurrentThreadOwns();
   nsRefPtr<MediaRawData> sample;
   switch (aTrack) {
-    case kAudio:
+    case TrackInfo::kAudioTrack:
       sample =
         InvokeAndRetry(this, &MP4Reader::DemuxAudioSample, mStream, &mDemuxerMonitor);
       return sample.forget();
-    case kVideo:
+    case TrackInfo::kVideoTrack:
       if (mQueuedVideoSample) {
         return mQueuedVideoSample.forget();
       }
       sample =
         InvokeAndRetry(this, &MP4Reader::DemuxVideoSample, mStream, &mDemuxerMonitor);
       return sample.forget();
     default:
       return nullptr;
@@ -910,45 +915,45 @@ MP4Reader::DemuxVideoSample()
 {
   nsRefPtr<MediaRawData> sample = mVideo.mTrackDemuxer->DemuxSample();
   return sample;
 }
 
 size_t
 MP4Reader::SizeOfVideoQueueInFrames()
 {
-  return SizeOfQueue(kVideo);
+  return SizeOfQueue(TrackInfo::kVideoTrack);
 }
 
 size_t
 MP4Reader::SizeOfAudioQueueInFrames()
 {
-  return SizeOfQueue(kAudio);
+  return SizeOfQueue(TrackInfo::kAudioTrack);
 }
 
 size_t
 MP4Reader::SizeOfQueue(TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
   MonitorAutoLock lock(decoder.mMonitor);
   return decoder.mOutput.Length() + (decoder.mNumSamplesInput - decoder.mNumSamplesOutput);
 }
 
 nsresult
 MP4Reader::ResetDecode()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
-  Flush(kVideo);
+  Flush(TrackInfo::kVideoTrack);
   {
     MonitorAutoLock mon(mDemuxerMonitor);
     if (mVideo.mTrackDemuxer) {
       mVideo.mTrackDemuxer->Seek(0);
     }
   }
-  Flush(kAudio);
+  Flush(TrackInfo::kAudioTrack);
   {
     MonitorAutoLock mon(mDemuxerMonitor);
     if (mAudio.mTrackDemuxer) {
       mAudio.mTrackDemuxer->Seek(0);
     }
   }
   return MediaDecoderReader::ResetDecode();
 }
@@ -1041,34 +1046,34 @@ MP4Reader::Flush(TrackType aTrack)
     data.mNumSamplesOutput = 0;
     data.mInputExhausted = false;
     if (data.HasPromise()) {
       data.RejectPromise(CANCELED, __func__);
     }
     data.mDiscontinuity = true;
     data.mUpdateScheduled = false;
   }
-  if (aTrack == kVideo) {
+  if (aTrack == TrackInfo::kVideoTrack) {
     mQueuedVideoSample = nullptr;
   }
   VLOG("Flush(%s) END", TrackTypeToStr(aTrack));
 }
 
 bool
 MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
 
   MOZ_ASSERT(mVideo.mDecoder);
 
-  Flush(kVideo);
+  Flush(TrackInfo::kVideoTrack);
 
   // Loop until we reach the next keyframe after the threshold.
   while (true) {
-    nsRefPtr<MediaRawData> compressed(PopSample(kVideo));
+    nsRefPtr<MediaRawData> compressed(PopSample(TrackInfo::kVideoTrack));
     if (!compressed) {
       // EOS, or error. This code assumes EOS, which may or may not be right.
       MonitorAutoLock mon(mVideo.mMonitor);
       mVideo.mDemuxEOS = true;
       return false;
     }
     parsed++;
     if (!compressed->mKeyframe ||
@@ -1092,17 +1097,17 @@ MP4Reader::Seek(int64_t aTime, int64_t a
     VLOG("Seek() END (Unseekable)");
     return SeekPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   int64_t seekTime = aTime;
   mQueuedVideoSample = nullptr;
   if (mDemuxer->HasValidVideo()) {
     mVideo.mTrackDemuxer->Seek(seekTime);
-    mQueuedVideoSample = PopSampleLocked(kVideo);
+    mQueuedVideoSample = PopSampleLocked(TrackInfo::kVideoTrack);
     if (mQueuedVideoSample) {
       seekTime = mQueuedVideoSample->mTime;
     }
   }
   if (mDemuxer->HasValidAudio()) {
     mAudio.mTrackDemuxer->Seek(seekTime);
   }
   LOG("aTime=%lld exit", aTime);
@@ -1232,20 +1237,20 @@ MP4Reader::NotifyDataArrived(const char*
   }
   int64_t end = aOffset + aLength;
   if (end <= mLastSeenEnd) {
     return;
   }
   mLastSeenEnd = end;
 
   if (HasVideo()) {
-    auto& decoder = GetDecoderData(kVideo);
+    auto& decoder = GetDecoderData(TrackInfo::kVideoTrack);
     MonitorAutoLock lock(decoder.mMonitor);
     decoder.mDemuxEOS = false;
   }
   if (HasAudio()) {
-    auto& decoder = GetDecoderData(kAudio);
+    auto& decoder = GetDecoderData(TrackInfo::kAudioTrack);
     MonitorAutoLock lock(decoder.mMonitor);
     decoder.mDemuxEOS = false;
   }
 }
 
 } // namespace mozilla
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -30,17 +30,17 @@ class MP4Stream;
 #if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG)
 #define MP4_READER_DORMANT_HEURISTIC
 #else
 #undef MP4_READER_DORMANT_HEURISTIC
 #endif
 
 class MP4Reader final : public MediaDecoderReader
 {
-  typedef mp4_demuxer::TrackType TrackType;
+  typedef TrackInfo::TrackType TrackType;
 
 public:
   explicit MP4Reader(AbstractMediaDecoder* aDecoder);
 
   virtual ~MP4Reader();
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) override;
 
@@ -109,28 +109,28 @@ private:
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // Blocks until the demuxer produces an sample of specified type.
   // Returns nullptr on error on EOS. Caller must delete sample.
-  already_AddRefed<MediaRawData> PopSample(mp4_demuxer::TrackType aTrack);
-  already_AddRefed<MediaRawData> PopSampleLocked(mp4_demuxer::TrackType aTrack);
+  already_AddRefed<MediaRawData> PopSample(TrackType aTrack);
+  already_AddRefed<MediaRawData> PopSampleLocked(TrackType aTrack);
 
   bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed);
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
-  void Output(mp4_demuxer::TrackType aType, MediaData* aSample);
-  void InputExhausted(mp4_demuxer::TrackType aTrack);
-  void Error(mp4_demuxer::TrackType aTrack);
-  void Flush(mp4_demuxer::TrackType aTrack);
-  void DrainComplete(mp4_demuxer::TrackType aTrack);
+  void Output(TrackType aType, MediaData* aSample);
+  void InputExhausted(TrackType aTrack);
+  void Error(TrackType aTrack);
+  void Flush(TrackType aTrack);
+  void DrainComplete(TrackType aTrack);
   void UpdateIndex();
   bool IsSupportedAudioMimeType(const nsACString& aMimeType);
   bool IsSupportedVideoMimeType(const nsACString& aMimeType);
   void NotifyResourcesStatusChanged();
   virtual bool IsWaitingOnCDMResource() override;
 
   Microseconds GetNextKeyframeTime();
   bool ShouldSkip(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
@@ -139,18 +139,17 @@ private:
 
   nsRefPtr<MP4Stream> mStream;
   nsRefPtr<mp4_demuxer::MP4Demuxer> mDemuxer;
   nsRefPtr<PlatformDecoderModule> mPlatform;
   mp4_demuxer::CryptoFile mCrypto;
 
   class DecoderCallback : public MediaDataDecoderCallback {
   public:
-    DecoderCallback(MP4Reader* aReader,
-                    mp4_demuxer::TrackType aType)
+    DecoderCallback(MP4Reader* aReader, TrackType aType)
       : mReader(aReader)
       , mType(aType)
     {
     }
     virtual void Output(MediaData* aSample) override {
       mReader->Output(mType, aSample);
     }
     virtual void InputExhausted() override {
@@ -165,17 +164,17 @@ private:
     virtual void NotifyResourcesStatusChanged() override {
       mReader->NotifyResourcesStatusChanged();
     }
     virtual void ReleaseMediaResources() override {
       mReader->ReleaseMediaResources();
     }
   private:
     MP4Reader* mReader;
-    mp4_demuxer::TrackType mType;
+    TrackType mType;
   };
 
   struct DecoderData {
     DecoderData(MediaData::Type aType,
                 uint32_t aDecodeAhead)
       : mType(aType)
       , mMonitor(aType == MediaData::AUDIO_DATA ? "MP4 audio decoder data"
                                                 : "MP4 video decoder data")
@@ -259,17 +258,17 @@ private:
   bool NeedInput(DecoderData& aDecoder);
 
   // The last number of decoded output frames that we've reported to
   // MediaDecoder::NotifyDecoded(). We diff the number of output video
   // frames every time that DecodeVideoData() is called, and report the
   // delta there.
   uint64_t mLastReportedNumDecodedFrames;
 
-  DecoderData& GetDecoderData(mp4_demuxer::TrackType aTrack);
+  DecoderData& GetDecoderData(TrackType aTrack);
 
   layers::LayersBackend mLayersBackendType;
 
   // For use with InvokeAndRetry as an already_refed can't be converted to bool
   nsRefPtr<MediaRawData> DemuxVideoSample();
   nsRefPtr<MediaRawData> DemuxAudioSample();
 
   // True if we've read the streams' metadata.
--- a/dom/media/fmp4/PlatformDecoderModule.cpp
+++ b/dom/media/fmp4/PlatformDecoderModule.cpp
@@ -180,36 +180,36 @@ already_AddRefed<MediaDataDecoder>
 PlatformDecoderModule::CreateDecoder(const TrackInfo& aConfig,
                                      FlushableMediaTaskQueue* aTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      layers::LayersBackend aLayersBackend,
                                      layers::ImageContainer* aImageContainer)
 {
   nsRefPtr<MediaDataDecoder> m;
 
-  if (aConfig.IsAudio()) {
-    m = CreateAudioDecoder(static_cast<const AudioInfo&>(aConfig),
+  if (aConfig.GetAsAudioInfo()) {
+    m = CreateAudioDecoder(*aConfig.GetAsAudioInfo(),
                            aTaskQueue,
                            aCallback);
     return m.forget();
   }
 
-  if (!aConfig.IsVideo()) {
+  if (!aConfig.GetAsVideoInfo()) {
     return nullptr;
   }
 
   if (H264Converter::IsH264(aConfig)) {
     m = new H264Converter(this,
-                          static_cast<const VideoInfo&>(aConfig),
+                          *aConfig.GetAsVideoInfo(),
                           aLayersBackend,
                           aImageContainer,
                           aTaskQueue,
                           aCallback);
   } else {
-    m = CreateVideoDecoder(static_cast<const VideoInfo&>(aConfig),
+    m = CreateVideoDecoder(*aConfig.GetAsVideoInfo(),
                            aLayersBackend,
                            aImageContainer,
                            aTaskQueue,
                            aCallback);
   }
   return m.forget();
 }
 
--- a/dom/media/fmp4/SharedDecoderManager.cpp
+++ b/dom/media/fmp4/SharedDecoderManager.cpp
@@ -237,20 +237,17 @@ SharedDecoderProxy::Shutdown()
 {
   mManager->SetIdle(this);
   return NS_OK;
 }
 
 bool
 SharedDecoderProxy::IsWaitingMediaResources()
 {
-  if (mManager->mActiveProxy == this) {
-    return mManager->mDecoder->IsWaitingMediaResources();
-  }
-  return mManager->mActiveProxy != nullptr;
+  return mManager->mDecoder->IsWaitingMediaResources();
 }
 
 bool
 SharedDecoderProxy::IsHardwareAccelerated() const
 {
   return mManager->mDecoder->IsHardwareAccelerated();
 }
 
--- a/dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/fmp4/wmf/WMFMediaDataDecoder.cpp
@@ -123,17 +123,17 @@ WMFMediaDataDecoder::Decode()
     HRESULT hr = mMFTManager->Input(input);
     if (FAILED(hr)) {
       NS_WARNING("MFTManager rejected sample");
       {
         MonitorAutoLock mon(mMonitor);
         PurgeInputQueue();
       }
       mCallback->Error();
-      return;
+      continue; // complete flush if flushing
     }
 
     mLastStreamOffset = input->mOffset;
 
     ProcessOutput();
   }
 }
 
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/ResourceQueue.cpp
@@ -0,0 +1,215 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeque.h"
+#include "MediaData.h"
+#include "prlog.h"
+
+#ifdef PR_LOGGING
+extern PRLogModuleInfo* GetSourceBufferResourceLog();
+
+/* Polyfill __func__ on MSVC to pass to the log. */
+#ifdef _MSC_VER
+#define __func__ __FUNCTION__
+#endif
+
+#define SBR_DEBUG(arg, ...) PR_LOG(GetSourceBufferResourceLog(), PR_LOG_DEBUG, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#define SBR_DEBUGV(arg, ...) PR_LOG(GetSourceBufferResourceLog(), PR_LOG_DEBUG+1, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
+#else
+#define SBR_DEBUG(...)
+#define SBR_DEBUGV(...)
+#endif
+
+namespace mozilla {
+
+ResourceItem::ResourceItem(MediaLargeByteBuffer* aData)
+  : mData(aData)
+{
+}
+
+size_t
+ResourceItem::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // size including this
+  size_t size = aMallocSizeOf(this);
+
+  // size excluding this
+  size += mData->SizeOfExcludingThis(aMallocSizeOf);
+
+  return size;
+}
+
+class ResourceQueueDeallocator : public nsDequeFunctor {
+  virtual void* operator() (void* aObject) {
+    delete static_cast<ResourceItem*>(aObject);
+    return nullptr;
+  }
+};
+
+ResourceQueue::ResourceQueue()
+  : nsDeque(new ResourceQueueDeallocator())
+  , mLogicalLength(0)
+  , mOffset(0)
+{
+}
+
+uint64_t
+ResourceQueue::GetOffset()
+{
+  return mOffset;
+}
+
+uint64_t
+ResourceQueue::GetLength()
+{
+  return mLogicalLength;
+}
+
+void
+ResourceQueue::CopyData(uint64_t aOffset, uint32_t aCount, char* aDest)
+{
+  uint32_t offset = 0;
+  uint32_t start = GetAtOffset(aOffset, &offset);
+  uint32_t end = std::min(GetAtOffset(aOffset + aCount, nullptr) + 1, uint32_t(GetSize()));
+  for (uint32_t i = start; i < end; ++i) {
+    ResourceItem* item = ResourceAt(i);
+    uint32_t bytes = std::min(aCount, uint32_t(item->mData->Length() - offset));
+    if (bytes != 0) {
+      memcpy(aDest, &(*item->mData)[offset], bytes);
+      offset = 0;
+      aCount -= bytes;
+      aDest += bytes;
+    }
+  }
+}
+
+void
+ResourceQueue::AppendItem(MediaLargeByteBuffer* aData)
+{
+  mLogicalLength += aData->Length();
+  Push(new ResourceItem(aData));
+}
+
+uint32_t
+ResourceQueue::Evict(uint64_t aOffset, uint32_t aSizeToEvict)
+{
+  SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)",
+            aOffset, aSizeToEvict);
+  return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict));
+}
+
+uint32_t ResourceQueue::EvictBefore(uint64_t aOffset)
+{
+  SBR_DEBUG("EvictBefore(%llu)", aOffset);
+  uint32_t evicted = 0;
+  while (ResourceItem* item = ResourceAt(0)) {
+    SBR_DEBUG("item=%p length=%d offset=%llu",
+              item, item->mData->Length(), mOffset);
+    if (item->mData->Length() + mOffset >= aOffset) {
+      if (aOffset <= mOffset) {
+        break;
+      }
+      uint32_t offset = aOffset - mOffset;
+      mOffset += offset;
+      evicted += offset;
+      nsRefPtr<MediaLargeByteBuffer> data = new MediaLargeByteBuffer;
+      data->AppendElements(item->mData->Elements() + offset,
+                           item->mData->Length() - offset);
+      item->mData = data;
+      break;
+    }
+    mOffset += item->mData->Length();
+    evicted += item->mData->Length();
+    delete PopFront();
+  }
+  return evicted;
+}
+
+uint32_t
+ResourceQueue::EvictAll()
+{
+  SBR_DEBUG("EvictAll()");
+  uint32_t evicted = 0;
+  while (ResourceItem* item = ResourceAt(0)) {
+    SBR_DEBUG("item=%p length=%d offset=%llu",
+              item, item->mData->Length(), mOffset);
+    mOffset += item->mData->Length();
+    evicted += item->mData->Length();
+    delete PopFront();
+  }
+  return evicted;
+}
+
+size_t
+ResourceQueue::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Calculate the size of the internal deque.
+  size_t size = nsDeque::SizeOfExcludingThis(aMallocSizeOf);
+
+  // Sum the ResourceItems.
+  for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
+    const ResourceItem* item = ResourceAt(i);
+    size += item->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return size;
+}
+
+#if defined(DEBUG)
+void
+ResourceQueue::Dump(const char* aPath)
+{
+  for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
+    ResourceItem* item = ResourceAt(i);
+
+    char buf[255];
+    PR_snprintf(buf, sizeof(buf), "%s/%08u.bin", aPath, i);
+    FILE* fp = fopen(buf, "wb");
+    if (!fp) {
+      return;
+    }
+    fwrite(item->mData->Elements(), item->mData->Length(), 1, fp);
+    fclose(fp);
+  }
+}
+#endif
+
+ResourceItem*
+ResourceQueue::ResourceAt(uint32_t aIndex) const
+{
+  return static_cast<ResourceItem*>(ObjectAt(aIndex));
+}
+
+uint32_t
+ResourceQueue::GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset)
+{
+  MOZ_RELEASE_ASSERT(aOffset >= mOffset);
+  uint64_t offset = mOffset;
+  for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
+    ResourceItem* item = ResourceAt(i);
+    // If the item contains the start of the offset we want to
+    // break out of the loop.
+    if (item->mData->Length() + offset > aOffset) {
+      if (aResourceOffset) {
+        *aResourceOffset = aOffset - offset;
+      }
+      return i;
+    }
+    offset += item->mData->Length();
+  }
+  return GetSize();
+}
+
+ResourceItem*
+ResourceQueue::PopFront()
+{
+  return static_cast<ResourceItem*>(nsDeque::PopFront());
+}
+
+#undef SBR_DEBUG
+#undef SBR_DEBUGV
+
+} // namespace mozilla
--- a/dom/media/mediasource/ResourceQueue.h
+++ b/dom/media/mediasource/ResourceQueue.h
@@ -2,230 +2,81 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_RESOURCEQUEUE_H_
 #define MOZILLA_RESOURCEQUEUE_H_
 
-#include <algorithm>
 #include "nsDeque.h"
 #include "MediaData.h"
-#include "prlog.h"
-
-#ifdef PR_LOGGING
-extern PRLogModuleInfo* GetSourceBufferResourceLog();
-
-/* Polyfill __func__ on MSVC to pass to the log. */
-#ifdef _MSC_VER
-#define __func__ __FUNCTION__
-#endif
-
-#define SBR_DEBUG(arg, ...) PR_LOG(GetSourceBufferResourceLog(), PR_LOG_DEBUG, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
-#define SBR_DEBUGV(arg, ...) PR_LOG(GetSourceBufferResourceLog(), PR_LOG_DEBUG+1, ("ResourceQueue(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
-#else
-#define SBR_DEBUG(...)
-#define SBR_DEBUGV(...)
-#endif
 
 namespace mozilla {
 
 // A SourceBufferResource has a queue containing the data that is appended
 // to it. The queue holds instances of ResourceItem which is an array of the
 // bytes. Appending data to the SourceBufferResource pushes this onto the
 // queue.
 
 // Data is evicted once it reaches a size threshold. This pops the items off
 // the front of the queue and deletes it.  If an eviction happens then the
 // MediaSource is notified (done in SourceBuffer::AppendData) which then
 // requests all SourceBuffers to evict data up to approximately the same
 // timepoint.
 
 struct ResourceItem {
-  explicit ResourceItem(MediaLargeByteBuffer* aData)
-  : mData(aData)
-  {
-  }
-
-  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
-    // size including this
-    size_t size = aMallocSizeOf(this);
-
-    // size excluding this
-    size += mData->SizeOfExcludingThis(aMallocSizeOf);
-
-    return size;
-  }
-
+  explicit ResourceItem(MediaLargeByteBuffer* aData);
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
   nsRefPtr<MediaLargeByteBuffer> mData;
 };
 
-class ResourceQueueDeallocator : public nsDequeFunctor {
-  virtual void* operator() (void* aObject) {
-    delete static_cast<ResourceItem*>(aObject);
-    return nullptr;
-  }
-};
-
 class ResourceQueue : private nsDeque {
 public:
-  ResourceQueue()
-    : nsDeque(new ResourceQueueDeallocator())
-    , mLogicalLength(0)
-    , mOffset(0)
-  {
-  }
+  ResourceQueue();
 
   // Returns the logical byte offset of the start of the data.
-  uint64_t GetOffset() {
-    return mOffset;
-  }
+  uint64_t GetOffset();
 
   // Returns the length of all items in the queue plus the offset.
   // This is the logical length of the resource.
-  uint64_t GetLength() {
-    return mLogicalLength;
-  }
+  uint64_t GetLength();
 
   // Copies aCount bytes from aOffset in the queue into aDest.
-  void CopyData(uint64_t aOffset, uint32_t aCount, char* aDest) {
-    uint32_t offset = 0;
-    uint32_t start = GetAtOffset(aOffset, &offset);
-    uint32_t end = std::min(GetAtOffset(aOffset + aCount, nullptr) + 1, uint32_t(GetSize()));
-    for (uint32_t i = start; i < end; ++i) {
-      ResourceItem* item = ResourceAt(i);
-      uint32_t bytes = std::min(aCount, uint32_t(item->mData->Length() - offset));
-      if (bytes != 0) {
-        memcpy(aDest, &(*item->mData)[offset], bytes);
-        offset = 0;
-        aCount -= bytes;
-        aDest += bytes;
-      }
-    }
-  }
+  void CopyData(uint64_t aOffset, uint32_t aCount, char* aDest);
 
-  void AppendItem(MediaLargeByteBuffer* aData) {
-    mLogicalLength += aData->Length();
-    Push(new ResourceItem(aData));
-  }
+  void AppendItem(MediaLargeByteBuffer* aData);
 
   // Tries to evict at least aSizeToEvict from the queue up until
   // aOffset. Returns amount evicted.
-  uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict) {
-    SBR_DEBUG("Evict(aOffset=%llu, aSizeToEvict=%u)",
-              aOffset, aSizeToEvict);
-    return EvictBefore(std::min(aOffset, mOffset + (uint64_t)aSizeToEvict));
-  }
+  uint32_t Evict(uint64_t aOffset, uint32_t aSizeToEvict);
+
+  uint32_t EvictBefore(uint64_t aOffset);
 
-  uint32_t EvictBefore(uint64_t aOffset) {
-    SBR_DEBUG("EvictBefore(%llu)", aOffset);
-    uint32_t evicted = 0;
-    while (ResourceItem* item = ResourceAt(0)) {
-      SBR_DEBUG("item=%p length=%d offset=%llu",
-                item, item->mData->Length(), mOffset);
-      if (item->mData->Length() + mOffset >= aOffset) {
-        if (aOffset <= mOffset) {
-          break;
-        }
-        uint32_t offset = aOffset - mOffset;
-        mOffset += offset;
-        evicted += offset;
-        nsRefPtr<MediaLargeByteBuffer> data = new MediaLargeByteBuffer;
-        data->AppendElements(item->mData->Elements() + offset,
-                             item->mData->Length() - offset);
-        item->mData = data;
-        break;
-      }
-      mOffset += item->mData->Length();
-      evicted += item->mData->Length();
-      delete PopFront();
-    }
-    return evicted;
-  }
+  uint32_t EvictAll();
 
-  uint32_t EvictAll() {
-    SBR_DEBUG("EvictAll()");
-    uint32_t evicted = 0;
-    while (ResourceItem* item = ResourceAt(0)) {
-      SBR_DEBUG("item=%p length=%d offset=%llu",
-                item, item->mData->Length(), mOffset);
-      mOffset += item->mData->Length();
-      evicted += item->mData->Length();
-      delete PopFront();
-    }
-    return evicted;
-  }
-
-  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
-    // Calculate the size of the internal deque.
-    size_t size = nsDeque::SizeOfExcludingThis(aMallocSizeOf);
-
-    // Sum the ResourceItems.
-    for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
-      const ResourceItem* item = ResourceAt(i);
-      size += item->SizeOfIncludingThis(aMallocSizeOf);
-    }
-
-    return size;
-  }
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
 
 #if defined(DEBUG)
-  void Dump(const char* aPath) {
-    for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
-      ResourceItem* item = ResourceAt(i);
-
-      char buf[255];
-      PR_snprintf(buf, sizeof(buf), "%s/%08u.bin", aPath, i);
-      FILE* fp = fopen(buf, "wb");
-      if (!fp) {
-        return;
-      }
-      fwrite(item->mData->Elements(), item->mData->Length(), 1, fp);
-      fclose(fp);
-    }
-  }
+  void Dump(const char* aPath);
 #endif
 
 private:
-  ResourceItem* ResourceAt(uint32_t aIndex) const {
-    return static_cast<ResourceItem*>(ObjectAt(aIndex));
-  }
+  ResourceItem* ResourceAt(uint32_t aIndex) const;
 
   // Returns the index of the resource that contains the given
   // logical offset. aResourceOffset will contain the offset into
   // the resource at the given index returned if it is not null.  If
   // no such resource exists, returns GetSize() and aOffset is
   // untouched.
-  uint32_t GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset) {
-    MOZ_RELEASE_ASSERT(aOffset >= mOffset);
-    uint64_t offset = mOffset;
-    for (uint32_t i = 0; i < uint32_t(GetSize()); ++i) {
-      ResourceItem* item = ResourceAt(i);
-      // If the item contains the start of the offset we want to
-      // break out of the loop.
-      if (item->mData->Length() + offset > aOffset) {
-        if (aResourceOffset) {
-          *aResourceOffset = aOffset - offset;
-        }
-        return i;
-      }
-      offset += item->mData->Length();
-    }
-    return GetSize();
-  }
+  uint32_t GetAtOffset(uint64_t aOffset, uint32_t *aResourceOffset);
 
-  ResourceItem* PopFront() {
-    return static_cast<ResourceItem*>(nsDeque::PopFront());
-  }
+  ResourceItem* PopFront();
 
   // Logical length of the resource.
   uint64_t mLogicalLength;
 
   // Logical offset into the resource of the first element in the queue.
   uint64_t mOffset;
 };
 
-#undef SBR_DEBUG
-#undef SBR_DEBUGV
-
 } // namespace mozilla
 #endif /* MOZILLA_RESOURCEQUEUE_H_ */
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -11,16 +11,17 @@
 #include "MediaSourceDecoder.h"
 #include "SharedThreadPool.h"
 #include "MediaTaskQueue.h"
 #include "SourceBufferDecoder.h"
 #include "SourceBufferResource.h"
 #include "VideoUtils.h"
 #include "mozilla/dom/TimeRanges.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/TypeTraits.h"
 #include "nsError.h"
 #include "nsIRunnable.h"
 #include "nsThreadUtils.h"
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaSourceLog();
 
@@ -103,16 +104,17 @@ private:
 };
 
 nsRefPtr<ShutdownPromise>
 TrackBuffer::Shutdown()
 {
   mParentDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   mShutdown = true;
   mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+  mMetadataRequest.DisconnectIfExists();
 
   MOZ_ASSERT(mShutdownPromise.IsEmpty());
   nsRefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
 
   RefPtr<MediaTaskQueue> queue = mTaskQueue;
   mTaskQueue = nullptr;
   queue->BeginShutdown()
        ->Then(mParentDecoder->GetReader()->GetTaskQueue(), __func__, this,
@@ -543,124 +545,168 @@ TrackBuffer::NewDecoder(int64_t aTimesta
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   mCurrentDecoder = decoder;
   mDecoders.AppendElement(decoder);
 
   mLastStartTimestamp = 0;
   mLastEndTimestamp.reset();
   mLastTimestampOffset = aTimestampOffset;
 
-  decoder->SetTaskQueue(mTaskQueue);
+  decoder->SetTaskQueue(decoder->GetReader()->GetTaskQueue());
   return decoder.forget();
 }
 
 bool
 TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
 {
-  if (NS_WARN_IF(!mTaskQueue)) {
-    mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
-    return false;
-  }
-
+  // Bug 1153295: We must ensure that the nsIRunnable hold a strong reference
+  // to aDecoder.
+  static_assert(mozilla::IsBaseOf<nsISupports, SourceBufferDecoder>::value,
+                "SourceBufferDecoder must be inheriting from nsISupports");
   RefPtr<nsIRunnable> task =
     NS_NewRunnableMethodWithArg<SourceBufferDecoder*>(this,
                                                       &TrackBuffer::InitializeDecoder,
                                                       aDecoder);
-  mTaskQueue->Dispatch(task);
+  // We need to initialize the reader on its own task queue
+  aDecoder->GetReader()->GetTaskQueue()->Dispatch(task);
   return true;
 }
 
+// MetadataRecipient is a is used to pass extra values required by the
+// MetadataPromise's target methods
+class MetadataRecipient {
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MetadataRecipient);
+
+  MetadataRecipient(TrackBuffer* aOwner,
+                    SourceBufferDecoder* aDecoder,
+                    bool aWasEnded)
+    : mOwner(aOwner)
+    , mDecoder(aDecoder)
+    , mWasEnded(aWasEnded) { }
+
+  void OnMetadataRead(MetadataHolder* aMetadata)
+  {
+    mOwner->OnMetadataRead(aMetadata, mDecoder, mWasEnded);
+  }
+
+  void OnMetadataNotRead(ReadMetadataFailureReason aReason)
+  {
+    mOwner->OnMetadataNotRead(aReason, mDecoder);
+  }
+
+private:
+  ~MetadataRecipient() {}
+  nsRefPtr<TrackBuffer> mOwner;
+  nsRefPtr<SourceBufferDecoder> mDecoder;
+  bool mWasEnded;
+};
+
 void
 TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
 {
   if (!mParentDecoder) {
     MSE_DEBUG("decoder was shutdown. Aborting initialization.");
     return;
   }
   // ReadMetadata may block the thread waiting on data, so we must be able
   // to leave the monitor while we call it. For the rest of this function
   // we want to hold the monitor though, since we run on a different task queue
   // from the reader and interact heavily with it.
   mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
   if (mCurrentDecoder != aDecoder) {
     MSE_DEBUG("append was cancelled. Aborting initialization.");
-    RemoveDecoder(aDecoder);
     // If we reached this point, the SourceBuffer would have disconnected
     // the promise. So no need to reject it.
     return;
   }
 
   // We may be shut down at any time by the reader on another thread. So we need
   // to check for this each time we acquire the monitor. If that happens, we
   // need to abort immediately, because the reader has forgotten about us, and
   // important pieces of our state (like mTaskQueue) have also been torn down.
   if (mShutdown) {
     MSE_DEBUG("was shut down. Aborting initialization.");
-    RemoveDecoder(aDecoder);
     return;
   }
 
-  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+  MOZ_ASSERT(aDecoder->GetReader()->GetTaskQueue()->IsCurrentThreadIn());
+
   MediaDecoderReader* reader = aDecoder->GetReader();
+
   MSE_DEBUG("Initializing subdecoder %p reader %p",
             aDecoder, reader);
 
-  MediaInfo mi;
-  nsAutoPtr<MetadataTags> tags; // TODO: Handle metadata.
-  nsresult rv;
-
   // HACK WARNING:
   // We only reach this point once we know that we have a complete init segment.
   // We don't want the demuxer to do a blocking read as no more data can be
   // appended while this routine is running. Marking the SourceBufferResource
   // as ended will cause any incomplete reads to abort.
   // As this decoder hasn't been initialized yet, the resource isn't yet in use
   // and so it is safe to do so.
   bool wasEnded = aDecoder->GetResource()->IsEnded();
   if (!wasEnded) {
     aDecoder->GetResource()->Ended();
   }
+  nsRefPtr<MetadataRecipient> recipient =
+    new MetadataRecipient(this, aDecoder, wasEnded);
+  nsRefPtr<MediaDecoderReader::MetadataPromise> promise;
   {
     ReentrantMonitorAutoExit mon(mParentDecoder->GetReentrantMonitor());
-    rv = reader->ReadMetadata(&mi, getter_Transfers(tags));
+    promise = reader->AsyncReadMetadata();
   }
-  if (!wasEnded) {
-    // Adding an empty buffer will reopen the SourceBufferResource
-    nsRefPtr<MediaLargeByteBuffer> emptyBuffer = new MediaLargeByteBuffer;
-    aDecoder->GetResource()->AppendData(emptyBuffer);
-  }
-  // HACK END.
 
-  reader->SetIdle();
   if (mShutdown) {
     MSE_DEBUG("was shut down while reading metadata. Aborting initialization.");
     return;
   }
   if (mCurrentDecoder != aDecoder) {
     MSE_DEBUG("append was cancelled. Aborting initialization.");
-    RemoveDecoder(aDecoder);
     return;
   }
 
-  if (NS_SUCCEEDED(rv) && reader->IsWaitingOnCDMResource()) {
+  mMetadataRequest.Begin(promise
+                           ->RefableThen(reader->GetTaskQueue(), __func__,
+                                         recipient.get(),
+                                         &MetadataRecipient::OnMetadataRead,
+                                         &MetadataRecipient::OnMetadataNotRead));
+}
+
+void
+TrackBuffer::OnMetadataRead(MetadataHolder* aMetadata,
+                            SourceBufferDecoder* aDecoder,
+                            bool aWasEnded)
+{
+  MOZ_ASSERT(aDecoder->GetReader()->GetTaskQueue()->IsCurrentThreadIn());
+
+  mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
+  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+
+  mMetadataRequest.Complete();
+
+  // Adding an empty buffer will reopen the SourceBufferResource
+  if (!aWasEnded) {
+    nsRefPtr<MediaLargeByteBuffer> emptyBuffer = new MediaLargeByteBuffer;
+    aDecoder->GetResource()->AppendData(emptyBuffer);
+  }
+  // HACK END.
+
+  MediaDecoderReader* reader = aDecoder->GetReader();
+  reader->SetIdle();
+
+  if (reader->IsWaitingOnCDMResource()) {
     mIsWaitingOnCDM = true;
   }
 
   aDecoder->SetTaskQueue(nullptr);
 
-  if (NS_FAILED(rv) || (!mi.HasVideo() && !mi.HasAudio())) {
-    // XXX: Need to signal error back to owning SourceBuffer.
-    MSE_DEBUG("Reader %p failed to initialize rv=%x audio=%d video=%d",
-              reader, rv, mi.HasAudio(), mi.HasVideo());
-    RemoveDecoder(aDecoder);
-    mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
-    return;
-  }
+  // A MediaDataPromise is only resolved if MediaInfo.HasValidMedia() is true.
+  MediaInfo mi = aMetadata->mInfo;
 
   if (mi.HasVideo()) {
     MSE_DEBUG("Reader %p video resolution=%dx%d",
               reader, mi.mVideo.mDisplay.width, mi.mVideo.mDisplay.height);
   }
   if (mi.HasAudio()) {
     MSE_DEBUG("Reader %p audio sampleRate=%d channels=%d",
               reader, mi.mAudio.mRate, mi.mAudio.mChannels);
@@ -674,34 +720,56 @@ TrackBuffer::InitializeDecoder(SourceBuf
     MSE_DEBUG("Failed to enqueue decoder initialization task");
     RemoveDecoder(aDecoder);
     mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
     return;
   }
 }
 
 void
+TrackBuffer::OnMetadataNotRead(ReadMetadataFailureReason aReason,
+                               SourceBufferDecoder* aDecoder)
+{
+  MOZ_ASSERT(aDecoder->GetReader()->GetTaskQueue()->IsCurrentThreadIn());
+
+  mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
+  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+
+  mMetadataRequest.Complete();
+
+  MediaDecoderReader* reader = aDecoder->GetReader();
+  reader->SetIdle();
+
+  aDecoder->SetTaskQueue(nullptr);
+
+  MSE_DEBUG("Reader %p failed to initialize", reader);
+
+  RemoveDecoder(aDecoder);
+  mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+}
+
+void
 TrackBuffer::CompleteInitializeDecoder(SourceBufferDecoder* aDecoder)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   if (!mParentDecoder) {
     MSE_DEBUG("was shutdown. Aborting initialization.");
     return;
   }
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
   if (mCurrentDecoder != aDecoder) {
     MSE_DEBUG("append was cancelled. Aborting initialization.");
     // If we reached this point, the SourceBuffer would have disconnected
     // the promise. So no need to reject it.
-    RemoveDecoder(aDecoder);
     return;
   }
 
   if (mShutdown) {
     MSE_DEBUG("was shut down. Aborting initialization.");
-    RemoveDecoder(aDecoder);
     return;
   }
 
   if (!RegisterDecoder(aDecoder)) {
     MSE_DEBUG("Reader %p not activated",
               aDecoder->GetReader());
     RemoveDecoder(aDecoder);
     mInitializationPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
@@ -854,17 +922,26 @@ TrackBuffer::ResetParserState()
     mParser = ContainerParser::CreateForMIMEType(mType);
     DiscardCurrentDecoder();
   }
 }
 
 void
 TrackBuffer::AbortAppendData()
 {
+  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+
+  nsRefPtr<SourceBufferDecoder> current = mCurrentDecoder;
   DiscardCurrentDecoder();
+
+  if (mMetadataRequest.Exists() || !mInitializationPromise.IsEmpty()) {
+    MOZ_ASSERT(current);
+    RemoveDecoder(current);
+  }
+  mMetadataRequest.DisconnectIfExists();
   // The SourceBuffer would have disconnected its promise.
   // However we must ensure that the MediaPromiseHolder handle all pending
   // promises.
   mInitializationPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
 }
 
 const nsTArray<nsRefPtr<SourceBufferDecoder>>&
 TrackBuffer::Decoders()
--- a/dom/media/mediasource/TrackBuffer.h
+++ b/dom/media/mediasource/TrackBuffer.h
@@ -117,16 +117,17 @@ public:
 #endif
 
 #if defined(DEBUG)
   void Dump(const char* aPath);
 #endif
 
 private:
   friend class DecodersToInitialize;
+  friend class MetadataRecipient;
   ~TrackBuffer();
 
   // Create a new decoder, set mCurrentDecoder to the new decoder and
   // returns it. The new decoder must be queued using QueueInitializeDecoder
   // for initialization.
   // The decoder is not considered initialized until it is added to
   // mInitializedDecoders.
   already_AddRefed<SourceBufferDecoder> NewDecoder(int64_t aTimestampOffset /* microseconds */);
@@ -162,16 +163,23 @@ private:
   // to clean up the decoder.  If aDecoder was added to
   // mInitializedDecoders, it must have been removed before calling this
   // function.
   void RemoveDecoder(SourceBufferDecoder* aDecoder);
 
   // Remove all empty decoders from the provided list;
   void RemoveEmptyDecoders(nsTArray<SourceBufferDecoder*>& aDecoders);
 
+  void OnMetadataRead(MetadataHolder* aMetadata,
+                      SourceBufferDecoder* aDecoder,
+                      bool aWasEnded);
+
+  void OnMetadataNotRead(ReadMetadataFailureReason aReason,
+                         SourceBufferDecoder* aDecoder);
+
   nsAutoPtr<ContainerParser> mParser;
 
   // A task queue using the shared media thread pool.  Used exclusively to
   // initialize (i.e. call ReadMetadata on) decoders as they are created via
   // NewDecoder.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
   // All of the decoders managed by this TrackBuffer.  Access protected by
@@ -212,13 +220,14 @@ private:
   MediaInfo mInfo;
 
   void ContinueShutdown();
   MediaPromiseHolder<ShutdownPromise> mShutdownPromise;
   bool mDecoderPerSegment;
   bool mShutdown;
 
   MediaPromiseHolder<TrackBufferAppendPromise> mInitializationPromise;
-
+  // Track our request for metadata from the reader.
+  MediaPromiseConsumerHolder<MediaDecoderReader::MetadataPromise> mMetadataRequest;
 };
 
 } // namespace mozilla
 #endif /* MOZILLA_TRACKBUFFER_H_ */
--- a/dom/media/mediasource/moz.build
+++ b/dom/media/mediasource/moz.build
@@ -18,16 +18,17 @@ EXPORTS.mozilla.dom += [
 ]
 
 UNIFIED_SOURCES += [
     'ContainerParser.cpp',
     'MediaSource.cpp',
     'MediaSourceDecoder.cpp',
     'MediaSourceReader.cpp',
     'MediaSourceUtils.cpp',
+    'ResourceQueue.cpp',
     'SourceBuffer.cpp',