Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 20 Apr 2016 11:53:09 +0200
changeset 294101 e9088179190a3129cc705ea795e40669c97327d0
parent 294100 f71acdbd0d45ebdd0c69621dbf0f2f6ac74f9f43 (current diff)
parent 294016 f05a1242fb29023bd7ebc492897ed3d6907733c7 (diff)
child 294102 19954943888496006f660e90b3eee0c2482f5fca
push id75435
push userkwierso@gmail.com
push dateWed, 20 Apr 2016 21:19:31 +0000
treeherdermozilla-inbound@898c9f87a4c4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to fx-team
build/win32/mozconfig.vs2010
dom/animation/test/css-animations/file_animation-oncancel.html
dom/animation/test/css-animations/file_animation-onfinish.html
dom/animation/test/css-animations/test_animation-oncancel.html
dom/animation/test/css-animations/test_animation-onfinish.html
dom/manifest/test/test_ImageObjectProcessor_background_color.html
layout/base/tests/chrome/test_bug370436.html
modules/libpref/init/all.js
testing/web-platform/meta/XMLHttpRequest/send-redirect-bogus.htm.ini
testing/web-platform/meta/html/semantics/embedded-content/the-img-element/update-the-source-set.html.ini
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1186060 - Switch Windows compiler from VS2013 to VS2015
+no Bug - touch clobber to fix Bustage
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -230,16 +230,17 @@ EventTree::Process()
       }
     }
   }
 
   // Fire reorder event at last.
   if (mFireReorder) {
     MOZ_ASSERT(mContainer);
     nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_REORDER, mContainer);
+    mContainer->Document()->MaybeNotifyOfValueChange(mContainer);
   }
 
   mDependentEvents.Clear();
 }
 
 EventTree*
 EventTree::FindOrInsert(Accessible* aContainer)
 {
--- a/accessible/base/TextAttrs.cpp
+++ b/accessible/base/TextAttrs.cpp
@@ -697,17 +697,17 @@ TextAttrsMgr::TextDecorValue::
   TextDecorValue(nsIFrame* aFrame)
 {
   const nsStyleTextReset* textReset = aFrame->StyleTextReset();
   mStyle = textReset->GetDecorationStyle();
 
   bool isForegroundColor = false;
   textReset->GetDecorationColor(mColor, isForegroundColor);
   if (isForegroundColor)
-    mColor = aFrame->StyleColor()->mColor;
+    mColor = aFrame->StyleContext()->GetTextFillColor();
 
   mLine = textReset->mTextDecorationLine &
     (NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE |
      NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH);
 }
 
 TextAttrsMgr::TextDecorTextAttr::
   TextDecorTextAttr(nsIFrame* aRootFrame, nsIFrame* aFrame) :
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -133,17 +133,17 @@ nsCoreUtils::DispatchClickEvent(nsITreeB
 void
 nsCoreUtils::DispatchMouseEvent(EventMessage aMessage, int32_t aX, int32_t aY,
                                 nsIContent *aContent, nsIFrame *aFrame,
                                 nsIPresShell *aPresShell, nsIWidget *aRootWidget)
 {
   WidgetMouseEvent event(true, aMessage, aRootWidget,
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
 
-  event.refPoint = LayoutDeviceIntPoint(aX, aY);
+  event.mRefPoint = LayoutDeviceIntPoint(aX, aY);
 
   event.clickCount = 1;
   event.button = WidgetMouseEvent::eLeftButton;
   event.mTime = PR_IntervalNow();
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -534,17 +534,17 @@ Accessible::ChildAtPoint(int32_t aX, int
   nsIWidget* rootWidget = rootFrame->GetView()->GetNearestWidget(nullptr);
   NS_ENSURE_TRUE(rootWidget, nullptr);
 
   LayoutDeviceIntRect rootRect;
   rootWidget->GetScreenBounds(rootRect);
 
   WidgetMouseEvent dummyEvent(true, eMouseMove, rootWidget,
                               WidgetMouseEvent::eSynthesized);
-  dummyEvent.refPoint = LayoutDeviceIntPoint(aX - rootRect.x, aY - rootRect.y);
+  dummyEvent.mRefPoint = LayoutDeviceIntPoint(aX - rootRect.x, aY - rootRect.y);
 
   nsIFrame* popupFrame = nsLayoutUtils::
     GetPopupFrameForEventCoordinates(accDocument->PresContext()->GetRootPresContext(),
                                      &dummyEvent);
   if (popupFrame) {
     // If 'this' accessible is not inside the popup then ignore the popup when
     // searching an accessible at point.
     DocAccessible* popupDoc =
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1851,18 +1851,16 @@ DocAccessible::FireEventsOnInsertion(Acc
     do {
       if (ancestor->IsAlert()) {
         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
         break;
       }
     }
     while ((ancestor = ancestor->Parent()));
   }
-
-  MaybeNotifyOfValueChange(aContainer);
 }
 
 void
 DocAccessible::UpdateTreeOnRemoval(Accessible* aContainer, nsIContent* aChildNode)
 {
   // If child node is not accessible then look for its accessible children.
   Accessible* child = GetAccessible(aChildNode);
 #ifdef A11Y_LOG
@@ -1874,40 +1872,33 @@ DocAccessible::UpdateTreeOnRemoval(Acces
       logging::Address("child", child);
     else
       logging::MsgEntry("child accessible: null");
 
     logging::MsgEnd();
   }
 #endif
 
-  uint32_t updateFlags = eNoAccessible;
   TreeMutation mt(aContainer);
-
   if (child) {
     mt.BeforeRemoval(child);
-    updateFlags |= UpdateTreeInternal(child, false);
+    UpdateTreeInternal(child, false);
   }
   else {
     TreeWalker walker(aContainer, aChildNode, TreeWalker::eWalkCache);
     Accessible* child = walker.Next();
     if (child) {
       do {
         mt.BeforeRemoval(child);
-        updateFlags |= UpdateTreeInternal(child, false);
+        UpdateTreeInternal(child, false);
       }
       while ((child = walker.Next()));
     }
   }
   mt.Done();
-
-  // Content insertion/removal is not cause of accessible tree change.
-  if (updateFlags != eNoAccessible) {
-    MaybeNotifyOfValueChange(aContainer);
-  }
 }
 
 uint32_t
 DocAccessible::UpdateTreeInternal(Accessible* aChild, bool aIsInsert)
 {
   uint32_t updateFlags = eAccessible;
 
   // If a focused node has been shown then it could mean its frame was recreated
@@ -2167,19 +2158,17 @@ DocAccessible::MoveChild(Accessible* aCh
   // If the child was taken from from an ARIA owns element.
   if (aChild->IsRelocated()) {
     nsTArray<RefPtr<Accessible> >* children = mARIAOwnsHash.Get(curParent);
     children->RemoveElement(aChild);
   }
 
   if (curParent == aNewParent) {
     MOZ_ASSERT(aChild->IndexInParent() != aIdxInParent, "No move case");
-
     curParent->MoveChild(aIdxInParent, aChild);
-    MaybeNotifyOfValueChange(curParent);
 
 #ifdef A11Y_LOG
     logging::TreeInfo("move child: parent tree after",
                       logging::eVerbose, curParent);
 #endif
     return true;
   }
 
@@ -2187,30 +2176,26 @@ DocAccessible::MoveChild(Accessible* aCh
     return false;
   }
 
   TreeMutation rmut(curParent);
   rmut.BeforeRemoval(aChild, TreeMutation::kNoShutdown);
   curParent->RemoveChild(aChild);
   rmut.Done();
 
-  MaybeNotifyOfValueChange(curParent);
-
   // No insertion point for the child.
   if (aIdxInParent == -1) {
     return true;
   }
 
   TreeMutation imut(aNewParent);
   aNewParent->InsertChildAt(aIdxInParent, aChild);
   imut.AfterInsertion(aChild);
   imut.Done();
 
-  MaybeNotifyOfValueChange(aNewParent);
-
 #ifdef A11Y_LOG
   logging::TreeInfo("move child: old parent tree after",
                     logging::eVerbose, curParent);
   logging::TreeInfo("move child: new parent tree after",
                     logging::eVerbose, aNewParent);
 #endif
 
   return true;
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -1841,63 +1841,16 @@ DocAccessibleChild::RecvTakeFocus(const 
   if (acc) {
     acc->TakeFocus();
   }
 
   return true;
 }
 
 bool
-DocAccessibleChild::RecvEmbeddedChildCount(const uint64_t& aID,
-                                           uint32_t* aCount)
-{
-  *aCount = 0;
-
-  Accessible* acc = IdToAccessible(aID);
-  if (!acc) {
-    return true;
-  }
-
-  *aCount = acc->EmbeddedChildCount();
-
-  return true;
-}
-
-bool
-DocAccessibleChild::RecvIndexOfEmbeddedChild(const uint64_t& aID,
-                                             const uint64_t& aChildID,
-                                             uint32_t* aChildIdx)
-{
-  *aChildIdx = 0;
-
-  Accessible* parent = IdToAccessible(aID);
-  Accessible* child = IdToAccessible(aChildID);
-  if (!parent || !child)
-    return true;
-
-  *aChildIdx = parent->GetIndexOfEmbeddedChild(child);
-  return true;
-}
-
-bool
-DocAccessibleChild::RecvEmbeddedChildAt(const uint64_t& aID,
-                                        const uint32_t& aIdx,
-                                        uint64_t* aChildID)
-{
-  *aChildID = 0;
-
-  Accessible* acc = IdToAccessible(aID);
-  if (!acc)
-    return true;
-
-  *aChildID = reinterpret_cast<uintptr_t>(acc->GetEmbeddedChildAt(aIdx));
-  return true;
-}
-
-bool
 DocAccessibleChild::RecvFocusedChild(const uint64_t& aID,
                                        uint64_t* aChild,
                                        bool* aOk)
 {
   *aChild = 0;
   *aOk = false;
   Accessible* acc = IdToAccessible(aID);
   if (acc) {
--- a/accessible/ipc/DocAccessibleChild.h
+++ b/accessible/ipc/DocAccessibleChild.h
@@ -449,26 +449,16 @@ public:
   virtual bool RecvMaxValue(const uint64_t& aID,
                             double* aValue) override;
 
   virtual bool RecvStep(const uint64_t& aID,
                         double* aStep) override;
 
   virtual bool RecvTakeFocus(const uint64_t& aID) override;
 
-  virtual bool RecvEmbeddedChildCount(const uint64_t& aID, uint32_t* aCount)
-    override final;
-
-  virtual bool RecvIndexOfEmbeddedChild(const uint64_t& aID,
-                                        const uint64_t& aChildID,
-                                        uint32_t* aChildIdx) override final;
-
-  virtual bool RecvEmbeddedChildAt(const uint64_t& aID, const uint32_t& aIdx,
-                                   uint64_t* aChildID) override final;
-
   virtual bool RecvFocusedChild(const uint64_t& aID,
                                 uint64_t* aChild,
                                 bool* aOk) override;
 
   virtual bool RecvLanguage(const uint64_t& aID, nsString* aLocale) override;
   virtual bool RecvDocType(const uint64_t& aID, nsString* aType) override;
   virtual bool RecvTitle(const uint64_t& aID, nsString* aTitle) override;
   virtual bool RecvURL(const uint64_t& aID, nsString* aURL) override;
--- a/accessible/ipc/PDocAccessible.ipdl
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -232,21 +232,16 @@ child:
 
   prio(high) sync CurValue(uint64_t aID) returns(double aValue);
   prio(high) sync SetCurValue(uint64_t aID, double aValue) returns(bool aRetVal);
   prio(high) sync MinValue(uint64_t aID) returns(double aValue);
   prio(high) sync MaxValue(uint64_t aID) returns(double aValue);
   prio(high) sync Step(uint64_t aID) returns(double aStep);
 
   async TakeFocus(uint64_t aID);
-  prio(high) sync EmbeddedChildCount(uint64_t aID) returns(uint32_t aCount);
-  prio(high) sync IndexOfEmbeddedChild(uint64_t aID, uint64_t aChildID)
-    returns(uint32_t childIdx);
-  prio(high) sync EmbeddedChildAt(uint64_t aID, uint32_t aChildIdx)
-    returns(uint64_t aChild);
   prio(high) sync FocusedChild(uint64_t aID)
     returns(uint64_t aChild, bool aOk);
 
   prio(high) sync Language(uint64_t aID) returns(nsString aLocale);
   prio(high) sync DocType(uint64_t aID) returns(nsString aType);
   prio(high) sync Title(uint64_t aID) returns(nsString aTitle);
   prio(high) sync URL(uint64_t aID) returns(nsString aURL);
   prio(high) sync MimeType(uint64_t aID) returns(nsString aMime);
--- a/accessible/ipc/ProxyAccessible.cpp
+++ b/accessible/ipc/ProxyAccessible.cpp
@@ -1030,44 +1030,60 @@ void
 ProxyAccessible::TakeFocus()
 {
   Unused << mDoc->SendTakeFocus(mID);
 }
 
 uint32_t
 ProxyAccessible::EmbeddedChildCount() const
 {
-  uint32_t count;
-  Unused << mDoc->SendEmbeddedChildCount(mID, &count);
+  size_t count = 0, kids = mChildren.Length();
+  for (size_t i = 0; i < kids; i++) {
+    if (mChildren[i]->IsEmbeddedObject()) {
+      count++;
+    }
+  }
+
   return count;
 }
 
 int32_t
 ProxyAccessible::IndexOfEmbeddedChild(const ProxyAccessible* aChild)
 {
-  uint64_t childID = aChild->mID;
-  uint32_t childIdx;
-  Unused << mDoc->SendIndexOfEmbeddedChild(mID, childID, &childIdx);
-  return childIdx;
+  size_t index = 0, kids = mChildren.Length();
+  for (size_t i = 0; i < kids; i++) {
+    if (mChildren[i]->IsEmbeddedObject()) {
+      if (mChildren[i] == aChild) {
+        return index;
+      }
+
+      index++;
+    }
+  }
+
+  return -1;
 }
 
 ProxyAccessible*
 ProxyAccessible::EmbeddedChildAt(size_t aChildIdx)
 {
-  // For an outer doc the only child is a document, which is of course an
-  // embedded child.  Further asking the child process for the id of the child
-  // document won't work because the id of the child doc will be 0, which we
-  // would interpret as being our parent document.
-  if (mOuterDoc) {
-    return ChildAt(aChildIdx);
+  size_t index = 0, kids = mChildren.Length();
+  for (size_t i = 0; i < kids; i++) {
+    if (!mChildren[i]->IsEmbeddedObject()) {
+      continue;
+    }
+
+    if (index == aChildIdx) {
+      return mChildren[i];
+    }
+
+    index++;
   }
 
-  uint64_t childID;
-  Unused << mDoc->SendEmbeddedChildAt(mID, aChildIdx, &childID);
-  return mDoc->GetAccessible(childID);
+  return nullptr;
 }
 
 ProxyAccessible*
 ProxyAccessible::FocusedChild()
 {
   uint64_t childID = 0;
   bool ok = false;
   Unused << mDoc->SendFocusedChild(mID, &childID, &ok);
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -86,16 +86,27 @@ public:
 
   Accessible* OuterDocOfRemoteBrowser() const;
 
   /**
    * Get the role of the accessible we're proxying.
    */
   role Role() const { return mRole; }
 
+  /**
+   * Return true if this is an embedded object.
+   */
+  bool IsEmbeddedObject() const
+  {
+    role role = Role();
+    return role != roles::TEXT_LEAF &&
+           role != roles::WHITESPACE &&
+           role != roles::STATICTEXT;
+  }
+
   /*
    * Return the states for the proxied accessible.
    */
   uint64_t State() const;
 
   /*
    * Return the native states for the proxied accessible.
    */
--- a/b2g/config/mozconfigs/win32_gecko/debug
+++ b/b2g/config/mozconfigs/win32_gecko/debug
@@ -9,21 +9,17 @@ ac_add_options --enable-jemalloc
 ac_add_options --enable-signmar
 ac_add_options --enable-debug
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2013-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2013
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # B2G Options
 ac_add_options --enable-application=b2g
 ENABLE_MARIONETTE=1
 
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
--- a/b2g/config/mozconfigs/win32_gecko/nightly
+++ b/b2g/config/mozconfigs/win32_gecko/nightly
@@ -9,21 +9,17 @@ ac_add_options --enable-update-packaging
 ac_add_options --enable-jemalloc
 ac_add_options --enable-signmar
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2013-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2013
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # B2G Options
 ac_add_options --enable-application=b2g
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
--- a/b2g/graphene/config/horizon-mozconfigs/win32/debug
+++ b/b2g/graphene/config/horizon-mozconfigs/win32/debug
@@ -4,20 +4,16 @@ ac_add_options --enable-jemalloc
 ac_add_options --enable-debug
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2013-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2013
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # graphene Options
 ENABLE_MARIONETTE=1
 
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 . "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
--- a/b2g/graphene/config/horizon-mozconfigs/win32/nightly
+++ b/b2g/graphene/config/horizon-mozconfigs/win32/nightly
@@ -6,18 +6,14 @@ ac_add_options --enable-jemalloc
 ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2013-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2013
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # graphene Options
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 . "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
--- a/b2g/graphene/config/horizon-mozconfigs/win64/debug
+++ b/b2g/graphene/config/horizon-mozconfigs/win64/debug
@@ -21,13 +21,13 @@ ac_add_options --with-google-oauth-api-k
 export MOZILLA_OFFICIAL=1
 
 # Treat warnings as errors in directories with FAIL_ON_WARNINGS.
 ac_add_options --enable-warnings-as-errors
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
-. $topsrcdir/build/win64/mozconfig.vs2013
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.cache"
 
 . "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common.override"
--- a/b2g/graphene/config/horizon-mozconfigs/win64/nightly
+++ b/b2g/graphene/config/horizon-mozconfigs/win64/nightly
@@ -3,17 +3,17 @@ if [ "x$IS_NIGHTLY" = "xyes" ]; then
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/b2g/graphene/config/horizon-mozconfigs/common"
 
 ac_add_options --target=x86_64-pc-mingw32
 ac_add_options --host=x86_64-pc-mingw32
 
-. $topsrcdir/build/win64/mozconfig.vs2013
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-jemalloc
 ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
--- a/b2g/graphene/config/mozconfigs/win32/debug
+++ b/b2g/graphene/config/mozconfigs/win32/debug
@@ -4,20 +4,16 @@ ac_add_options --enable-jemalloc
 ac_add_options --enable-debug
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2013-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2013
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # graphene Options
 ENABLE_MARIONETTE=1
 
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 . "$topsrcdir/b2g/grapheneconfig/mozconfigs/common.override"
--- a/b2g/graphene/config/mozconfigs/win32/nightly
+++ b/b2g/graphene/config/mozconfigs/win32/nightly
@@ -6,18 +6,14 @@ ac_add_options --enable-jemalloc
 ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2013-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2013
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # graphene Options
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 . "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
--- a/b2g/graphene/config/mozconfigs/win64/debug
+++ b/b2g/graphene/config/mozconfigs/win64/debug
@@ -21,13 +21,13 @@ ac_add_options --with-google-oauth-api-k
 export MOZILLA_OFFICIAL=1
 
 # Treat warnings as errors in directories with FAIL_ON_WARNINGS.
 ac_add_options --enable-warnings-as-errors
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
-. $topsrcdir/build/win64/mozconfig.vs2013
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.cache"
 
 . "$topsrcdir/b2g/graphene/config/mozconfigs/common.override"
--- a/b2g/graphene/config/mozconfigs/win64/nightly
+++ b/b2g/graphene/config/mozconfigs/win64/nightly
@@ -3,17 +3,17 @@ if [ "x$IS_NIGHTLY" = "xyes" ]; then
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
 . "$topsrcdir/b2g/graphene/config/mozconfigs/common"
 
 ac_add_options --target=x86_64-pc-mingw32
 ac_add_options --host=x86_64-pc-mingw32
 
-. $topsrcdir/build/win64/mozconfig.vs2013
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --enable-update-packaging
 ac_add_options --enable-jemalloc
 ac_add_options --enable-signmar
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
--- a/browser/config/mozconfigs/win32/common-opt
+++ b/browser/config/mozconfigs/win32/common-opt
@@ -22,17 +22,17 @@ fi
 ac_add_options --with-google-oauth-api-keyfile=${_google_oauth_api_keyfile}
 ac_add_options --with-mozilla-api-keyfile=/c/builds/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
-. $topsrcdir/build/win32/mozconfig.vs2015-win64
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 # Enable Adobe Primetime and Widevine CDMs on 32-bit Windows in Mozilla builds.
 # Enabled here on the assumption that downstream vendors will not be using
 # these build configs.
 ac_add_options --enable-eme=adobe,widevine
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -16,17 +16,17 @@ fi
 ac_add_options --with-google-oauth-api-keyfile=${_google_oauth_api_keyfile}
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
-. $topsrcdir/build/win32/mozconfig.vs2015-win64
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 ac_add_options --with-branding=browser/branding/nightly
--- a/browser/config/mozconfigs/win32/debug-static-analysis
+++ b/browser/config/mozconfigs/win32/debug-static-analysis
@@ -5,17 +5,17 @@ MOZ_AUTOMATION_L10N_CHECK=0
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/common"
 
 ac_add_options --enable-debug
 ac_add_options --enable-dmd
 
 ac_add_options --enable-clang-plugin
 
-. $topsrcdir/build/win32/mozconfig.vs2013-win64
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 . "$topsrcdir/build/mozconfig.rust"
 
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/win32/l10n-mozconfig
+++ b/browser/config/mozconfigs/win32/l10n-mozconfig
@@ -6,15 +6,11 @@ ac_add_options --with-l10n-base=../../l1
 ac_add_options --with-windows-version=603
 ac_add_options --with-branding=browser/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
-if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
-  . $topsrcdir/build/win32/mozconfig.vs2015-win64
-else
-  . $topsrcdir/build/win32/mozconfig.vs2015
-fi
+. $topsrcdir/build/win32/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win64/common-opt
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -23,17 +23,17 @@ ac_add_options --with-mozilla-api-keyfil
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
-. $topsrcdir/build/win64/mozconfig.vs2015
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 # Enable Adobe Primetime and Widevine CDMs on 64-bit Windows in Mozilla builds.
 # Enabled here on the assumption that downstream vendors will not be using
 # these build configs.
 ac_add_options --enable-eme=adobe,widevine
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -26,14 +26,14 @@ export MOZ_TELEMETRY_REPORTING=1
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 ac_add_options --with-branding=browser/branding/nightly
 
-. $topsrcdir/build/win64/mozconfig.vs2015
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.rust"
 
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/win64/l10n-mozconfig
+++ b/browser/config/mozconfigs/win64/l10n-mozconfig
@@ -7,11 +7,11 @@ ac_add_options --with-l10n-base=../../l1
 ac_add_options --with-windows-version=603
 ac_add_options --with-branding=browser/branding/nightly
 
 export MOZILLA_OFFICIAL=1
 
 # Enable Telemetry
 export MOZ_TELEMETRY_REPORTING=1
 
-. $topsrcdir/build/win64/mozconfig.vs2015
+. $topsrcdir/build/win64/mozconfig.vs-latest
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -4,81 +4,50 @@ dnl file, You can obtain one at http://m
 
 AC_DEFUN([MOZ_TOOL_VARIABLES],
 [
 GNU_AS=
 GNU_LD=
 
 GNU_CC=
 GNU_CXX=
-dnl moz.configure ensures that the compilers have the same version
-CXX_VERSION=$CC_VERSION
 if test "$CC_TYPE" = "gcc"; then
     GNU_CC=1
     GNU_CXX=1
-    changequote(<<,>>)
-    GCC_VERSION_FULL="$CXX_VERSION"
-    GCC_VERSION=`echo "$GCC_VERSION_FULL" | $PERL -pe '(split(/\./))[0]>=4&&s/(^\d*\.\d*).*/<<$>>1/;'`
-
-    GCC_MAJOR_VERSION=`echo ${GCC_VERSION} | $AWK -F\. '{ print <<$>>1 }'`
-    GCC_MINOR_VERSION=`echo ${GCC_VERSION} | $AWK -F\. '{ print <<$>>2 }'`
-    changequote([,])
 fi
 
 if test "`echo | $AS -o conftest.out -v 2>&1 | grep -c GNU`" != "0"; then
     GNU_AS=1
 fi
 rm -f conftest.out
 if test "`echo | $LD -v 2>&1 | grep -c GNU`" != "0"; then
     GNU_LD=1
 fi
 
-if test "$CC_TYPE" = "msvc"; then
-     MSVC_VERSION_FULL="$CXX_VERSION"
-     CC_VERSION=`echo ${CC_VERSION} | cut -c 1-4`
-     CXX_VERSION=`echo ${CXX_VERSION} | cut -c 1-4`
-fi
-
 CLANG_CC=
 CLANG_CXX=
 CLANG_CL=
 if test "$CC_TYPE" = "clang"; then
     GNU_CC=1
     GNU_CXX=1
     CLANG_CC=1
     CLANG_CXX=1
 fi
 if test "$CC_TYPE" = "clang-cl"; then
     CLANG_CL=1
-    # We force clang-cl to emulate Visual C++ 2013 in configure.in, but that
-    # is based on the CLANG_CL variable defined here, so make sure that we're
-    # getting the right version here manually.
-    CC_VERSION=1800
-    CXX_VERSION=1800
-    MSVC_VERSION_FULL=180030723
-    # Build on clang-cl with MSVC 2013 Update 3 with fallback emulation.
-    CFLAGS="$CFLAGS -fms-compatibility-version=18.00.30723 -fallback"
-    CXXFLAGS="$CXXFLAGS -fms-compatibility-version=18.00.30723 -fallback"
 fi
 
 if test "$GNU_CC"; then
     if `$CC -print-prog-name=ld` -v 2>&1 | grep -c GNU >/dev/null; then
         GCC_USE_GNU_LD=1
     fi
 fi
 
 AC_SUBST(CLANG_CXX)
 AC_SUBST(CLANG_CL)
-
-if test -n "$GNU_CC" -a -z "$CLANG_CC" ; then
-    if test "$GCC_MAJOR_VERSION" -eq 4 -a "$GCC_MINOR_VERSION" -lt 8 ||
-       test "$GCC_MAJOR_VERSION" -lt 4; then
-        AC_MSG_ERROR([Only GCC 4.8 or newer supported])
-    fi
-fi
 ])
 
 AC_DEFUN([MOZ_CROSS_COMPILER],
 [
 echo "cross compiling from $host to $target"
 
 _SAVE_CC="$CC"
 _SAVE_CFLAGS="$CFLAGS"
@@ -135,33 +104,16 @@ PATH=$_SAVE_PATH
 ])
 
 AC_DEFUN([MOZ_CXX11],
 [
 dnl Updates to the test below should be duplicated further below for the
 dnl cross-compiling case.
 AC_LANG_CPLUSPLUS
 if test "$GNU_CXX"; then
-    CXXFLAGS="$CXXFLAGS -std=gnu++11"
-    _ADDED_CXXFLAGS="-std=gnu++11"
-
-    if test -n "$CLANG_CC"; then
-        dnl We'd normally just check for the version from CC_VERSION (fed
-        dnl from __clang_major__ and __clang_minor__), but the clang that
-        dnl comes with Xcode has a completely different version scheme
-        dnl despite exposing the version with the same defines.
-        dnl So instead of a version check, do a feature check. Normally,
-        dnl we'd use __has_feature, but there are unfortunately no C++11
-        dnl differences in clang 3.4. However, it supports the 2013-08-28
-        dnl draft of the ISO WG21 SG10 feature test macro recommendations.
-        AC_TRY_COMPILE([], [#if !__cpp_static_assert
-                            #error ISO WG21 SG10 feature test macros unsupported
-                            #endif],,AC_MSG_ERROR([Only clang/llvm 3.4 or newer supported]))
-    fi
-
     AC_CACHE_CHECK([whether 64-bits std::atomic requires -latomic],
         ac_cv_needs_atomic,
         AC_TRY_LINK(
             [#include <cstdint>
              #include <atomic>],
             [ std::atomic<uint64_t> foo; foo = 1; ],
             ac_cv_needs_atomic=no,
             _SAVE_LIBS="$LIBS"
@@ -177,49 +129,10 @@ if test "$GNU_CXX"; then
     )
     if test "$ac_cv_needs_atomic" = yes; then
       MOZ_NEEDS_LIBATOMIC=1
     else
       MOZ_NEEDS_LIBATOMIC=
     fi
     AC_SUBST(MOZ_NEEDS_LIBATOMIC)
 fi
-
-if test -n "$CROSS_COMPILE"; then
-    dnl moz.configure ensures that the compilers have the same version
-    HOST_CXX_VERSION=$HOST_CC_VERSION
-    if test "$HOST_CC_TYPE" = "gcc" ; then
-	changequote(<<,>>)
-	HOST_GCC_VERSION_FULL="$HOST_CXX_VERSION"
-	HOST_GCC_VERSION=`echo "$HOST_GCC_VERSION_FULL" | $PERL -pe '(split(/\./))[0]>=4&&s/(^\d*\.\d*).*/<<$>>1/;'`
-
-	HOST_GCC_MAJOR_VERSION=`echo ${HOST_GCC_VERSION} | $AWK -F\. '{ print <<$>>1 }'`
-	HOST_GCC_MINOR_VERSION=`echo ${HOST_GCC_VERSION} | $AWK -F\. '{ print <<$>>2 }'`
-	changequote([,])
-
-	if test "$HOST_GCC_MAJOR_VERSION" -eq 4 -a "$HOST_GCC_MINOR_VERSION" -lt 8 ||
-	   test "$HOST_GCC_MAJOR_VERSION" -lt 4; then
-	    AC_MSG_ERROR([Only GCC 4.8 or newer supported for host compiler])
-	fi
-    fi
-
-    HOST_CXXFLAGS="$HOST_CXXFLAGS -std=gnu++11"
-
-    _SAVE_CXXFLAGS="$CXXFLAGS"
-    _SAVE_CPPFLAGS="$CPPFLAGS"
-    _SAVE_CXX="$CXX"
-    CXXFLAGS="$HOST_CXXFLAGS"
-    CPPFLAGS="$HOST_CPPFLAGS"
-    CXX="$HOST_CXX"
-    if test "$HOST_CC_TYPE" = clang; then
-	AC_TRY_COMPILE([], [#if !__cpp_static_assert
-			    #error ISO WG21 SG10 feature test macros unsupported
-			    #endif],,AC_MSG_ERROR([Only clang/llvm 3.4 or newer supported]))
-    fi
-
-    CXXFLAGS="$_SAVE_CXXFLAGS"
-    CPPFLAGS="$_SAVE_CPPFLAGS"
-    CXX="$_SAVE_CXX"
-elif test "$GNU_CXX"; then
-    HOST_CXXFLAGS="$HOST_CXXFLAGS $_ADDED_CXXFLAGS"
-fi
 AC_LANG_C
 ])
--- a/build/moz.configure/android-ndk.configure
+++ b/build/moz.configure/android-ndk.configure
@@ -34,17 +34,17 @@ def android_toolchain(target, host, ndk,
         return
     if toolchain:
         return toolchain[0]
     else:
         if target.cpu == 'arm' and target.endianness == 'little':
             target_base = 'arm-linux-androideabi'
         elif target.cpu == 'x86':
             target_base = 'x86'
-        elif target.cpu == 'mips' and target.endianness == 'little':
+        elif target.cpu == 'mips32' and target.endianness == 'little':
             target_base = 'mipsel-linux-android'
         else:
             die('Target cpu is not supported.')
 
         toolchain_format = '%s/toolchains/%s-%s/prebuilt/%s-%s'
 
         for version in gnu_compiler_version or ['4.9', '4.8', '4.7']:
             toolchain = toolchain_format % (ndk, target_base, version,
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -8,17 +8,16 @@
 # ==============================================================
 
 # Declare some exceptions. This is cumbersome, but since we shouldn't need a
 # lot of them, let's stack them all here. When adding a new one, put it in the
 # _declare_exceptions template, and add it to the return statement. Then
 # destructure in the assignment below the function declaration.
 @template
 @imports(_from='__builtin__', _import='Exception')
-@imports(_from='__builtin__', _import='__name__')
 def _declare_exceptions():
     class FatalCheckError(Exception):
         '''An exception to throw from a function decorated with @checking.
         It will result in calling die() with the given message.
         Debugging messages emitted from the decorated function will also be
         printed out.'''
     return (FatalCheckError,)
 
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -142,17 +142,17 @@ def add_old_configure_assignment(var, va
             return
         if value is True:
             assignments.append('%s=1' % var)
         elif value is False:
             assignments.append('%s=' % var)
         else:
             if isinstance(value, (list, tuple)):
                 value = quote(*value)
-            assignments.append('%s=%s' % (var, quote(value)))
+            assignments.append('%s=%s' % (var, quote(str(value))))
 
 @template
 def add_old_configure_arg(arg):
     @depends(extra_old_configure_args, arg)
     def add_arg(args, arg):
         if arg:
             args.append(arg)
 
@@ -308,16 +308,20 @@ def shell(mozillabuild):
 # Host and target systems
 # ==============================================================
 option('--host', nargs=1, help='Define the system type performing the build')
 
 option('--target', nargs=1,
        help='Define the system type where the resulting executables will be '
             'used')
 
+@imports(_from='mozbuild.configure.constants', _import='CPU')
+@imports(_from='mozbuild.configure.constants', _import='Endianness')
+@imports(_from='mozbuild.configure.constants', _import='Kernel')
+@imports(_from='mozbuild.configure.constants', _import='OS')
 def split_triplet(triplet):
     # The standard triplet is defined as
     #   CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
     # There is also a quartet form:
     #   CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
     # But we can consider the "KERNEL-OPERATING_SYSTEM" as one.
     cpu, manufacturer, os = triplet.split('-', 2)
 
@@ -403,20 +407,20 @@ def split_triplet(triplet):
         canonical_cpu = 'aarch64'
         endianness = 'little'
     else:
         canonical_cpu = cpu
         endianness = 'unknown'
 
     return namespace(
         alias=triplet,
-        cpu=canonical_cpu,
-        kernel=canonical_kernel,
-        os=canonical_os,
-        endianness=endianness,
+        cpu=CPU(canonical_cpu),
+        kernel=Kernel(canonical_kernel),
+        os=OS(canonical_os),
+        endianness=Endianness(endianness),
         raw_cpu=cpu,
         raw_os=os,
         # Toolchains, most notably for cross compilation may use cpu-os
         # prefixes.
         toolchain='%s-%s' % (cpu, os),
     )
 
 
@@ -488,17 +492,17 @@ def target_variables(target):
     elif target.kernel == 'Darwin' or (target.kernel == 'Linux' and
                                        target.os == 'GNU'):
         os_target = target.kernel
         os_arch = target.kernel
     else:
         os_target = target.os
         os_arch = target.kernel
 
-    if target.os == 'Darwin' and target.cpu == 'x86':
+    if target.kernel == 'Darwin' and target.cpu == 'x86':
         os_test = 'i386'
     else:
         os_test = target.raw_cpu
 
     return namespace(
         OS_TARGET=os_target,
         OS_ARCH=os_arch,
         OS_TEST=os_test,
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -148,72 +148,194 @@ add_old_configure_assignment('TOOLCHAIN_
 
 # Compilers
 # ==============================================================
 @imports('os')
 @imports('subprocess')
 @imports(_from='mozbuild.configure.util', _import='LineIO')
 @imports(_from='mozbuild.shellutil', _import='quote')
 @imports(_from='tempfile', _import='mkstemp')
-@imports(_from='textwrap', _import='dedent')
-def check_compiler(compiler, language):
-    check = dedent('''\
-        #if defined(_MSC_VER)
-        #if defined(__clang__)
-        COMPILER clang-cl _MSC_VER
-        #else
-        COMPILER msvc _MSC_FULL_VER
-        #endif
-        #elif defined(__clang__)
-        COMPILER clang __clang_major__.__clang_minor__.__clang_patchlevel__
-        #elif defined(__GNUC__)
-        COMPILER gcc __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
-        #endif
-    ''')
-
+def try_preprocess(compiler, language, source):
     suffix = {
         'C': '.c',
         'C++': '.cpp',
     }[language]
 
     fd, path = mkstemp(prefix='conftest.', suffix=suffix)
     try:
-        source = check.encode('ascii', 'replace')
+        source = source.encode('ascii', 'replace')
 
         log.debug('Creating `%s` with content:', path)
         with LineIO(lambda l: log.debug('| %s', l)) as out:
             out.write(source)
 
         os.write(fd, source)
         os.close(fd)
 
         cmd = compiler + ['-E', path]
         log.debug('Executing: `%s`', quote(*cmd))
         proc = subprocess.Popen(cmd, stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
         stdout, stderr = proc.communicate()
         retcode = proc.wait()
         if retcode == 0:
-            for line in stdout.splitlines():
-                if line.startswith('COMPILER '):
-                    _, type, version = line.split(None, 2)
-                    version = version.replace(' ', '')
-                    return type, version
-            return
+            return stdout
 
         log.debug('The command returned non-zero exit status %d.', retcode)
         for out, desc in ((stdout, 'output'), (stderr, 'error output')):
             if out:
                 log.debug('Its %s was:', desc)
                 with LineIO(lambda l: log.debug('| %s', l)) as o:
                     o.write(out)
     finally:
         os.remove(path)
 
 
+@imports(_from='mozbuild.configure.constants', _import='CompilerType')
+@imports(_from='textwrap', _import='dedent')
+def get_compiler_info(compiler, language):
+    '''Returns information about the given `compiler` (command line in the
+    form of a list or tuple), in the given `language`.
+
+    The returned information includes:
+    - the compiler type (msvc, clang-cl, clang or gcc)
+    - the compiler version
+    - the compiler supported language
+    - the compiler supported language version
+    '''
+    # Note: MSVC doesn't expose __STDC_VERSION__. It does expose __STDC__,
+    # but only when given the -Za option, which disables compiler
+    # extensions.
+    # Note: We'd normally do a version check for clang, but versions of clang
+    # in Xcode have a completely different versioning scheme despite exposing
+    # the version with the same defines.
+    # So instead, we make things such that the version is missing when the
+    # clang used is below the minimum supported version (currently clang 3.4).
+    # Normally, we'd use __has_feature, but there are unfortunately no C++11
+    # differences in clang 3.4. However, it supports the 2013-08-28 draft of
+    # the ISO WG21 SG10 feature test macro recommendations, and thus exposes
+    # new __cpp_* macros that older clang versions didn't.
+    # We then only include the version information when the C++ compiler
+    # matches the feature check, so that an unsupported version of clang would
+    # have no version number.
+    check = dedent('''\
+        #if defined(_MSC_VER)
+        #if defined(__clang__)
+        %COMPILER clang-cl
+        %VERSION _MSC_FULL_VER
+        #else
+        %COMPILER msvc
+        %VERSION _MSC_FULL_VER
+        #endif
+        #elif defined(__clang__)
+        %COMPILER clang
+        #  if !__cplusplus || __cpp_static_assert
+        %VERSION __clang_major__.__clang_minor__.__clang_patchlevel__
+        #  endif
+        #elif defined(__GNUC__)
+        %COMPILER gcc
+        %VERSION __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
+        #endif
+
+        #if __cplusplus
+        %cplusplus __cplusplus
+        #elif __STDC_VERSION__
+        %STDC_VERSION __STDC_VERSION__
+        #elif __STDC__
+        %STDC_VERSION 198900L
+        #endif
+    ''')
+
+    result = try_preprocess(compiler, language, check)
+
+    if not result:
+        raise FatalCheckError(
+            'Unknown compiler or compiler not supported.')
+
+    data = {}
+    for line in result.splitlines():
+        if line.startswith('%'):
+            k, _, v = line.partition(' ')
+            k = k.lstrip('%')
+            data[k] = v.replace(' ', '')
+            log.debug('%s = %s', k, data[k])
+
+    try:
+        type = CompilerType(data['COMPILER'])
+    except:
+        raise FatalCheckError(
+            'Unknown compiler or compiler not supported.')
+
+    cplusplus = int(data.get('cplusplus', '0L').rstrip('L'))
+    stdc_version = int(data.get('STDC_VERSION', '0L').rstrip('L'))
+
+    version = data.get('VERSION')
+    if version and type in ('msvc', 'clang-cl'):
+        msc_ver = version
+        version = msc_ver[0:2]
+        if len(msc_ver) > 2:
+            version += '.' + msc_ver[2:4]
+        if len(msc_ver) > 4:
+            version += '.' + msc_ver[4:]
+
+    if version:
+        version = Version(version)
+
+    return namespace(
+        type=type,
+        version=version,
+        language='C++' if cplusplus else 'C',
+        language_version=cplusplus if cplusplus else stdc_version,
+    )
+
+
+@imports(_from='mozbuild.shellutil', _import='quote')
+def check_compiler(compiler, language):
+    info = get_compiler_info(compiler, language)
+
+    flags = []
+
+    def append_flag(flag):
+        if flag not in flags:
+            if info.type == 'clang-cl':
+                flags.append('-Xclang')
+            flags.append(flag)
+
+    # Check language standards
+    # --------------------------------------------------------------------
+    if language != info.language:
+        raise FatalCheckError(
+            '`%s` is not a %s compiler.' % (quote(*compiler), language))
+
+    # Note: We do a strict version check because there sometimes are backwards
+    # incompatible changes in the standard, and not all code that compiles as
+    # C99 compiles as e.g. C11 (as of writing, this is true of libnestegg, for
+    # example)
+    if info.language == 'C' and info.language_version != 199901:
+        if info.type in ('clang-cl', 'clang', 'gcc'):
+            append_flag('-std=gnu99')
+
+    # Note: MSVC, while supporting C++11, still reports 199711L for __cplusplus.
+    # Note: this is a strict version check because we used to always add
+    # -std=gnu++11.
+    if info.language == 'C++' and info.language_version != 201103:
+        if info.type in ('clang-cl', 'clang', 'gcc'):
+            append_flag('-std=gnu++11')
+
+    # We force clang-cl to emulate Visual C++ 2013 Update 3 with fallback to
+    # cl.exe.
+    if info.type == 'clang-cl' and info.version != '18.00.30723':
+        # Those flags are direct clang-cl flags that don't need -Xclang, add
+        # them directly.
+        flags.append('-fms-compatibility-version=18.00.30723')
+        flags.append('-fallback')
+
+    return info.type, info.version, flags
+
+
 @template
 def default_c_compilers(host_or_target):
     '''Template defining the set of default C compilers for the host and
     target platforms.
     `host_or_target` is either `host` or `target` (the @depends functions
     from init.configure.
     '''
     assert host_or_target in (host, target)
@@ -330,17 +452,17 @@ def compiler(language, host_or_target, c
     # Normally, we'd use `var` instead of `_var`, but the interaction with
     # old-configure complicates things, and for now, we a) can't take the plain
     # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
     # old-configure AC_SUBST it (because it's autoconf doing it, not us)
     compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
                           input=delayed_getattr(provided_compiler, 'compiler'))
 
     @depends(compiler, provided_compiler, compiler_wrapper)
-    @checking('%s version' % what, lambda x: x.version if x else 'not found')
+    @checking('whether %s can be used' % what, lambda x: bool(x))
     @imports(_from='mozbuild.shellutil', _import='quote')
     def valid_compiler(compiler, provided_compiler, compiler_wrapper):
         wrapper = list(compiler_wrapper or ())
         if provided_compiler:
             provided_wrapper = list(provided_compiler.wrapper)
             # When doing a subconfigure, the compiler is set by old-configure
             # and it contains the wrappers from --with-compiler-wrapper and
             # --with-ccache.
@@ -366,27 +488,68 @@ def compiler(language, host_or_target, c
                     % quote(os.path.dirname(full_path)))
             if os.path.normcase(find_program(compiler)) != os.path.normcase(
                     full_path):
                 die('Found `%s` before `%s` in your $PATH. '
                     'Please reorder your $PATH.',
                     quote(os.path.dirname(found_compiler)),
                     quote(os.path.dirname(full_path)))
 
-        result = check_compiler(wrapper + [compiler] + flags, language)
-        if result:
-            type, version = result
-            return namespace(
-                wrapper=wrapper,
-                compiler=compiler,
-                flags=flags,
-                type=type,
-                version=version,
-            )
-        die('Failed to get the compiler version')
+        type, version, more_flags = check_compiler(
+            wrapper + [compiler] + flags, language)
+
+        # Check that the additional flags we got are enough to not require any
+        # more flags.
+        if more_flags:
+            flags += more_flags
+            type, version, more_flags = check_compiler(
+                wrapper + [compiler] + flags, language)
+
+        if more_flags:
+            raise FatalCheckError(
+                'Unknown compiler or compiler not supported.')
+
+        # Compiler version checks
+        # ===================================================
+        # Check the compiler version here instead of in `compiler_version` so
+        # that the `checking` message doesn't pretend the compiler can be used
+        # to then bail out one line later.
+        if type == 'gcc' and version < '4.8.0':
+            raise FatalCheckError(
+                'Only GCC 4.8 or newer is supported (found version %s).'
+                % version)
+
+        # If you want to bump the version check here search for
+        # __cpp_static_assert above, and see the associated comment.
+        if type == 'clang' and not version:
+            raise FatalCheckError(
+                'Only clang/llvm 3.4 or newer is supported.')
+
+        if type == 'msvc':
+            if version < '18.00.30723' or ('19' < version < '19.00.23506'):
+                raise FatalCheckError(
+                    'This version (%s) of the MSVC compiler is not '
+                    'supported.\n'
+                    'You must install Visual C++ 2013 Update 3, Visual '
+                    'C++ 2015 Update 1, or newer in order to build.\n'
+                    'See https://developer.mozilla.org/en/'
+                    'Windows_Build_Prerequisites' % version)
+
+        return namespace(
+            wrapper=wrapper,
+            compiler=compiler,
+            flags=flags,
+            type=type,
+            version=version,
+        )
+
+    @depends(valid_compiler)
+    @checking('%s version' % what)
+    def compiler_version(compiler):
+        return compiler.version
 
     if language == 'C++':
         @depends(valid_compiler, c_compiler)
         def compiler_suite_consistency(compiler, c_compiler):
             if compiler.type != c_compiler.type:
                 die('The %s C compiler is %s, while the %s C++ compiler is '
                     '%s. Need to use the same compiler suite.',
                     host_or_target_str, c_compiler.type,
new file mode 100644
--- /dev/null
+++ b/build/win32/mozconfig.vs-latest
@@ -0,0 +1,1 @@
+. $topsrcdir/build/win32/mozconfig.vs2015-win64
deleted file mode 100644
--- a/build/win32/mozconfig.vs2010
+++ /dev/null
@@ -1,13 +0,0 @@
-export INCLUDE=/d/msvs10/vc/include:/d/msvs10/vc/atlmfc/include:/d/sdks/v7.0/include:/d/sdks/v7.0/include/atl:/d/msvs8/VC/PlatformSDK/include:/d/sdks/dx10/include
-export LIBPATH=/d/msvs10/vc/lib:/d/msvs10/vc/atlmfc/lib:/c/WINDOWS/Microsoft.NET/Framework/v2.0.50727
-export LIB=/d/msvs10/vc/lib:/d/msvs10/vc/atlmfc/lib:/d/sdks/v7.0/lib:/d/msvs8/VC/PlatformSDK/lib:/d/msvs8/SDK/v2.0/lib:/d/mozilla-build/atlthunk_compat:/d/sdks/dx10/lib/x86
-export PATH="/d/msvs10/VSTSDB/Deploy:/d/msvs10/Common7/IDE/:/d/msvs10/VC/BIN:/d/msvs10/Common7/Tools:/d/msvs10/VC/VCPackages:${PATH}"
-export WIN32_REDIST_DIR=/d/msvs10/VC/redist/x86/Microsoft.VC100.CRT
-
-. $topsrcdir/build/mozconfig.vs-common
-
-mk_export_correct_style LIB
-mk_export_correct_style LIBPATH
-mk_export_correct_style PATH
-mk_export_correct_style INCLUDE
-mk_export_correct_style WIN32_REDIST_DIR
new file mode 100644
--- /dev/null
+++ b/build/win64/mozconfig.vs-latest
@@ -0,0 +1,1 @@
+. $topsrcdir/build/win64/mozconfig.vs2015
--- a/db/sqlite3/src/sqlite3.c
+++ b/db/sqlite3/src/sqlite3.c
@@ -1,11 +1,11 @@
 /******************************************************************************
 ** This file is an amalgamation of many separate C source files from SQLite
-** version 3.12.1.  By combining all the individual C code files into this 
+** version 3.12.2.  By combining all the individual C code files into this 
 ** single large file, the entire code can be compiled as a single translation
 ** unit.  This allows many compilers to do optimizations that would not be
 ** possible if the files were compiled separately.  Performance improvements
 ** of 5% or more are commonly seen when SQLite is compiled as a single
 ** translation unit.
 **
 ** This file is all you need to compile SQLite.  To use SQLite in other
 ** programs, you need this file and the "sqlite3.h" header file that defines
@@ -331,19 +331,19 @@ extern "C" {
 ** within its configuration management system.  ^The SQLITE_SOURCE_ID
 ** string contains the date and time of the check-in (UTC) and an SHA1
 ** hash of the entire source tree.
 **
 ** See also: [sqlite3_libversion()],
 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 ** [sqlite_version()] and [sqlite_source_id()].
 */
-#define SQLITE_VERSION        "3.12.1"
-#define SQLITE_VERSION_NUMBER 3012001
-#define SQLITE_SOURCE_ID      "2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d"
+#define SQLITE_VERSION        "3.12.2"
+#define SQLITE_VERSION_NUMBER 3012002
+#define SQLITE_SOURCE_ID      "2016-04-18 17:30:31 92dc59fd5ad66f646666042eb04195e3a61a9e8e"
 
 /*
 ** CAPI3REF: Run-Time Library Version Numbers
 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
 **
 ** These interfaces provide the same information as the [SQLITE_VERSION],
 ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
 ** but are associated with the library instead of the header file.  ^(Cautious
@@ -63936,16 +63936,38 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(Bt
   assert( pCur->eState==CURSOR_VALID );
   assert( (flags & ~(BTREE_SAVEPOSITION | BTREE_AUXDELETE))==0 );
 
   iCellDepth = pCur->iPage;
   iCellIdx = pCur->aiIdx[iCellDepth];
   pPage = pCur->apPage[iCellDepth];
   pCell = findCell(pPage, iCellIdx);
 
+  /* If the bPreserve flag is set to true, then the cursor position must
+  ** be preserved following this delete operation. If the current delete
+  ** will cause a b-tree rebalance, then this is done by saving the cursor
+  ** key and leaving the cursor in CURSOR_REQUIRESEEK state before 
+  ** returning. 
+  **
+  ** Or, if the current delete will not cause a rebalance, then the cursor
+  ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately
+  ** before or after the deleted entry. In this case set bSkipnext to true.  */
+  if( bPreserve ){
+    if( !pPage->leaf 
+     || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
+    ){
+      /* A b-tree rebalance will be required after deleting this entry.
+      ** Save the cursor key.  */
+      rc = saveCursorKey(pCur);
+      if( rc ) return rc;
+    }else{
+      bSkipnext = 1;
+    }
+  }
+
   /* If the page containing the entry to delete is not a leaf page, move
   ** the cursor to the largest entry in the tree that is smaller than
   ** the entry being deleted. This cell will replace the cell being deleted
   ** from the internal node. The 'previous' entry is used for this instead
   ** of the 'next' entry, as the previous entry is always a part of the
   ** sub-tree headed by the child page of the cell being deleted. This makes
   ** balancing the tree following the delete operation easier.  */
   if( !pPage->leaf ){
@@ -63962,38 +63984,16 @@ SQLITE_PRIVATE int sqlite3BtreeDelete(Bt
   }
 
   /* If this is a delete operation to remove a row from a table b-tree,
   ** invalidate any incrblob cursors open on the row being deleted.  */
   if( pCur->pKeyInfo==0 ){
     invalidateIncrblobCursors(p, pCur->info.nKey, 0);
   }
 
-  /* If the bPreserve flag is set to true, then the cursor position must
-  ** be preserved following this delete operation. If the current delete
-  ** will cause a b-tree rebalance, then this is done by saving the cursor
-  ** key and leaving the cursor in CURSOR_REQUIRESEEK state before 
-  ** returning. 
-  **
-  ** Or, if the current delete will not cause a rebalance, then the cursor
-  ** will be left in CURSOR_SKIPNEXT state pointing to the entry immediately
-  ** before or after the deleted entry. In this case set bSkipnext to true.  */
-  if( bPreserve ){
-    if( !pPage->leaf 
-     || (pPage->nFree+cellSizePtr(pPage,pCell)+2)>(int)(pBt->usableSize*2/3)
-    ){
-      /* A b-tree rebalance will be required after deleting this entry.
-      ** Save the cursor key.  */
-      rc = saveCursorKey(pCur);
-      if( rc ) return rc;
-    }else{
-      bSkipnext = 1;
-    }
-  }
-
   /* Make the page containing the entry to be deleted writable. Then free any
   ** overflow pages associated with the entry and finally remove the cell
   ** itself from within the page.  */
   rc = sqlite3PagerWrite(pPage->pDbPage);
   if( rc ) return rc;
   rc = clearCell(pPage, pCell, &szCell);
   dropCell(pPage, iCellIdx, szCell, &rc);
   if( rc ) return rc;
@@ -82764,17 +82764,16 @@ static int vdbeSorterCompareInt(
 */
 SQLITE_PRIVATE int sqlite3VdbeSorterInit(
   sqlite3 *db,                    /* Database connection (for malloc()) */
   int nField,                     /* Number of key fields in each record */
   VdbeCursor *pCsr                /* Cursor that holds the new sorter */
 ){
   int pgsz;                       /* Page size of main database */
   int i;                          /* Used to iterate through aTask[] */
-  int mxCache;                    /* Cache size */
   VdbeSorter *pSorter;            /* The new sorter */
   KeyInfo *pKeyInfo;              /* Copy of pCsr->pKeyInfo with db==0 */
   int szKeyInfo;                  /* Size of pCsr->pKeyInfo in bytes */
   int sz;                         /* Size of pSorter in bytes */
   int rc = SQLITE_OK;
 #if SQLITE_MAX_WORKER_THREADS==0
 # define nWorker 0
 #else
@@ -82821,21 +82820,30 @@ SQLITE_PRIVATE int sqlite3VdbeSorterInit
     pSorter->bUseThreads = (pSorter->nTask>1);
     pSorter->db = db;
     for(i=0; i<pSorter->nTask; i++){
       SortSubtask *pTask = &pSorter->aTask[i];
       pTask->pSorter = pSorter;
     }
 
     if( !sqlite3TempInMemory(db) ){
+      i64 mxCache;                /* Cache size in bytes*/
       u32 szPma = sqlite3GlobalConfig.szPma;
       pSorter->mnPmaSize = szPma * pgsz;
+
       mxCache = db->aDb[0].pSchema->cache_size;
-      if( mxCache<(int)szPma ) mxCache = (int)szPma;
-      pSorter->mxPmaSize = MIN((i64)mxCache*pgsz, SQLITE_MAX_PMASZ);
+      if( mxCache<0 ){
+        /* A negative cache-size value C indicates that the cache is abs(C)
+        ** KiB in size.  */
+        mxCache = mxCache * -1024;
+      }else{
+        mxCache = mxCache * pgsz;
+      }
+      mxCache = MIN(mxCache, SQLITE_MAX_PMASZ);
+      pSorter->mxPmaSize = MAX(pSorter->mnPmaSize, (int)mxCache);
 
       /* EVIDENCE-OF: R-26747-61719 When the application provides any amount of
       ** scratch memory using SQLITE_CONFIG_SCRATCH, SQLite avoids unnecessary
       ** large heap allocations.
       */
       if( sqlite3GlobalConfig.pScratch==0 ){
         assert( pSorter->iMemory==0 );
         pSorter->nMemory = pgsz;
@@ -95558,16 +95566,17 @@ SQLITE_PRIVATE void sqlite3AddColumn(Par
     /* If there is no type specified, columns have the default affinity
     ** 'BLOB'. */
     pCol->affinity = SQLITE_AFF_BLOB;
     pCol->szEst = 1;
   }else{
     zType = z + sqlite3Strlen30(z) + 1;
     memcpy(zType, pType->z, pType->n);
     zType[pType->n] = 0;
+    sqlite3Dequote(zType);
     pCol->affinity = sqlite3AffinityType(zType, &pCol->szEst);
     pCol->colFlags |= COLFLAG_HASTYPE;
   }
   p->nCol++;
   pParse->constraintName.n = 0;
 }
 
 /*
@@ -121355,17 +121364,23 @@ SQLITE_PRIVATE Bitmask sqlite3WhereCodeO
             pRight->iTable = iReg+j+2;
             sqlite3ExprIfFalse(pParse, pCompare, pLevel->addrCont, 0);
           }
           pCompare->pLeft = 0;
           sqlite3ExprDelete(db, pCompare);
         }
       }
     }
-    sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
+    /* These registers need to be preserved in case there is an IN operator
+    ** loop.  So we could deallocate the registers here (and potentially
+    ** reuse them later) if (pLoop->wsFlags & WHERE_IN_ABLE)==0.  But it seems
+    ** simpler and safer to simply not reuse the registers.
+    **
+    **    sqlite3ReleaseTempRange(pParse, iReg, nConstraint+2);
+    */
     sqlite3ExprCachePop(pParse);
   }else
 #endif /* SQLITE_OMIT_VIRTUALTABLE */
 
   if( (pLoop->wsFlags & WHERE_IPK)!=0
    && (pLoop->wsFlags & (WHERE_COLUMN_IN|WHERE_COLUMN_EQ))!=0
   ){
     /* Case 2:  We can directly reference a single row using an
@@ -185453,17 +185468,17 @@ static void fts5Fts5Func(
 */
 static void fts5SourceIdFunc(
   sqlite3_context *pCtx,          /* Function call context */
   int nArg,                       /* Number of args */
   sqlite3_value **apUnused        /* Function arguments */
 ){
   assert( nArg==0 );
   UNUSED_PARAM2(nArg, apUnused);
-  sqlite3_result_text(pCtx, "fts5: 2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d", -1, SQLITE_TRANSIENT);
+  sqlite3_result_text(pCtx, "fts5: 2016-04-18 17:30:31 92dc59fd5ad66f646666042eb04195e3a61a9e8e", -1, SQLITE_TRANSIENT);
 }
 
 static int fts5Init(sqlite3 *db){
   static const sqlite3_module fts5Mod = {
     /* iVersion      */ 2,
     /* xCreate       */ fts5CreateMethod,
     /* xConnect      */ fts5ConnectMethod,
     /* xBestIndex    */ fts5BestIndexMethod,
--- a/db/sqlite3/src/sqlite3.h
+++ b/db/sqlite3/src/sqlite3.h
@@ -106,19 +106,19 @@ extern "C" {
 ** within its configuration management system.  ^The SQLITE_SOURCE_ID
 ** string contains the date and time of the check-in (UTC) and an SHA1
 ** hash of the entire source tree.
 **
 ** See also: [sqlite3_libversion()],
 ** [sqlite3_libversion_number()], [sqlite3_sourceid()],
 ** [sqlite_version()] and [sqlite_source_id()].
 */
-#define SQLITE_VERSION        "3.12.1"
-#define SQLITE_VERSION_NUMBER 3012001
-#define SQLITE_SOURCE_ID      "2016-04-08 15:09:49 fe7d3b75fe1bde41511b323925af8ae1b910bc4d"
+#define SQLITE_VERSION        "3.12.2"
+#define SQLITE_VERSION_NUMBER 3012002
+#define SQLITE_SOURCE_ID      "2016-04-18 17:30:31 92dc59fd5ad66f646666042eb04195e3a61a9e8e"
 
 /*
 ** CAPI3REF: Run-Time Library Version Numbers
 ** KEYWORDS: sqlite3_version, sqlite3_sourceid
 **
 ** These interfaces provide the same information as the [SQLITE_VERSION],
 ** [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros
 ** but are associated with the library instead of the header file.  ^(Cautious
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -9,17 +9,17 @@
   This stylesheet is loaded as a ua stylesheet via the addon sdk, so having this
   pseudo-class is important.
   Having bug 1086532 fixed would make it possible to load this stylesheet in a
   <style scoped> node instead, directly in the native anonymous container
   element.
 */
 
 :-moz-native-anonymous .highlighter-container {
-  position: absolute;
+  position: fixed;
   width: 100%;
   height: 100%;
   /* The container for all highlighters doesn't react to pointer-events by
      default. This is because most highlighters cover the whole viewport but
      don't contain UIs that need to be accessed.
      If your highlighter has UI that needs to be interacted with, add
      'pointer-events:auto;' on its container element. */
   pointer-events: none;
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -500,29 +500,16 @@ KeyframeUtils::GetAnimationPropertiesFro
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
               pair.mValue, /* aUseSVGMode */ false, values)) {
           continue;
         }
         MOZ_ASSERT(values.Length() == 1,
                    "Longhand properties should produce a single"
                    " StyleAnimationValue");
-
-        // 'visibility' requires special handling that is unique to CSS
-        // Transitions/CSS Animations/Web Animations (i.e. not SMIL) so we
-        // apply that here.
-        //
-        // Bug 1259285 - Move this code to StyleAnimationValue
-        if (pair.mProperty == eCSSProperty_visibility) {
-          MOZ_ASSERT(values[0].mValue.GetUnit() ==
-                      StyleAnimationValue::eUnit_Enumerated,
-                    "unexpected unit");
-          values[0].mValue.SetIntValue(values[0].mValue.GetIntValue(),
-                                       StyleAnimationValue::eUnit_Visibility);
-        }
       }
 
       for (auto& value : values) {
         // If we already got a value for this property on the keyframe,
         // skip this one.
         if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
           continue;
         }
--- a/dom/animation/test/css-animations/file_animation-computed-timing.html
+++ b/dom/animation/test/css-animations/file_animation-computed-timing.html
@@ -20,24 +20,24 @@ test(function(t) {
   var effect = div.getAnimations()[0].effect;
   assert_equals(effect.getComputedTiming().delay, 0,
                 'Initial value of delay');
 }, 'delay of a new animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s -10s'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().delay, -10000,
+  assert_equals(effect.getComputedTiming().delay, -10 * MS_PER_SEC,
                 'Initial value of delay');
 }, 'Negative delay of a new animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s 10s'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().delay, 10000,
+  assert_equals(effect.getComputedTiming().delay, 10 * MS_PER_SEC,
                 'Initial value of delay');
 }, 'Positive delay of a new animation');
 
 
 // --------------------
 // endDelay
 // --------------------
 test(function(t) {
@@ -109,17 +109,17 @@ test(function(t) {
 
 
 // --------------------
 // duration
 // --------------------
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s -10s infinite'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().duration, 100000,
+  assert_equals(effect.getComputedTiming().duration, 100 * MS_PER_SEC,
                 'Initial value of duration');
 }, 'duration of a new animation');
 
 
 // --------------------
 // direction
 // --------------------
 test(function(t) {
@@ -156,60 +156,60 @@ test(function(t) {
 
 // ------------------------------
 // endTime
 // = max(start delay + active duration + end delay, 0)
 // --------------------
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().endTime, 100000,
+  assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC,
                 'Initial value of endTime');
 }, 'endTime of an new animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s -5s'});
   var effect = div.getAnimations()[0].effect;
-  var answer = 100000 - 5000; // ms
+  var answer = (100 - 5) * MS_PER_SEC;
   assert_equals(effect.getComputedTiming().endTime, answer,
                 'Initial value of endTime');
 }, 'endTime of an animation with a negative delay');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 10s -100s infinite'});
   var effect = div.getAnimations()[0].effect;
   assert_equals(effect.getComputedTiming().endTime, Infinity,
                 'Initial value of endTime');
 }, 'endTime of an infinitely repeating animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 0s 100s infinite'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().endTime, 100000,
+  assert_equals(effect.getComputedTiming().endTime, 100 * MS_PER_SEC,
                 'Initial value of endTime');
 }, 'endTime of an infinitely repeating zero-duration animation');
 
 test(function(t) {
   // Fill forwards so div.getAnimations()[0] won't return an
   // undefined value.
   var div = addDiv(t, {style: 'animation: moveAnimation 10s -100s forwards'});
   var effect = div.getAnimations()[0].effect;
-  assert_equals(effect.getComputedTiming().endTime, -90000,
+  assert_equals(effect.getComputedTiming().endTime, -90 * MS_PER_SEC,
                 'Initial value of endTime');
 }, 'endTime of an animation that finishes before its startTime');
 
 
 // --------------------
 // activeDuration
 // = iteration duration * iteration count
 // --------------------
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s 5'});
   var effect = div.getAnimations()[0].effect;
-  var answer = 100000 * 5; // ms
+  var answer = 100 * MS_PER_SEC * 5;
   assert_equals(effect.getComputedTiming().activeDuration, answer,
                 'Initial value of activeDuration');
 }, 'activeDuration of a new animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s infinite'});
   var effect = div.getAnimations()[0].effect;
   assert_equals(effect.getComputedTiming().activeDuration, Infinity,
@@ -243,35 +243,35 @@ test(function(t) {
   var effect = div.getAnimations()[0].effect;
   assert_equals(effect.getComputedTiming().localTime, 0,
                 'Initial value of localTime');
 }, 'localTime of a new animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s'});
   var anim = div.getAnimations()[0];
-  anim.currentTime = 5000;
+  anim.currentTime = 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime,
                 'current localTime after setting currentTime');
 }, 'localTime of an animation is always equal to currentTime');
 
-async_test(function(t) {
+promise_test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s'});
 
   var anim = div.getAnimations()[0];
   anim.playbackRate = 2; // 2 times faster
 
-  anim.ready.then(t.step_func(function() {
+  return anim.ready.then(function() {
     assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime,
                   'localTime is equal to currentTime');
     return waitForFrame();
-  })).then(t.step_func_done(function() {
+  }).then(function() {
     assert_equals(anim.effect.getComputedTiming().localTime, anim.currentTime,
                   'localTime is equal to currentTime');
-  }));
+  });
 }, 'localTime reflects playbackRate immediately');
 
 test(function(t) {
   var div = addDiv(t);
   var effect = new KeyframeEffectReadOnly(div, {left: ["0px", "100px"]});
 
   assert_equals(effect.getComputedTiming().localTime, null,
                 'localTime for orphaned effect');
@@ -301,23 +301,23 @@ test(function(t) {
 }, 'progress of an animation with different fill modes');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 10s 10 both'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 0.0,
                 'Initial value of progress');
-  anim.currentTime += 2500;
+  anim.currentTime += 2.5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
-  anim.currentTime += 5000;
+  anim.currentTime += 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
-  anim.currentTime += 5000;
+  anim.currentTime += 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
   anim.finish()
   assert_equals(anim.effect.getComputedTiming().progress, 1.0,
                 'Value of progress');
 }, 'progress of an integral repeating animation with normal direction');
 
 test(function(t) {
@@ -329,31 +329,31 @@ test(function(t) {
   //    unresolved (null value).
   var div = addDiv(t, {style: 'animation: moveAnimation 0s infinite both'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 1.0,
                 'Initial value of progress in after phase');
 
   // Seek backwards
-  anim.currentTime -= 1000;
+  anim.currentTime -= 1 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.0,
                 'Value of progress before phase');
 }, 'progress of an infinitely repeating zero-duration animation');
 
 test(function(t) {
   // Default iterations = 1
   var div = addDiv(t, {style: 'animation: moveAnimation 0s both'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 1.0,
                 'Initial value of progress in after phase');
 
   // Seek backwards
-  anim.currentTime -= 1000;
+  anim.currentTime -= 1 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.0,
                 'Value of progress before phase');
 }, 'progress of a finitely repeating zero-duration animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 0s 5s 10.25 both'});
   var anim = div.getAnimations()[0];
 
@@ -382,80 +382,80 @@ test(function(t) {
    'with reversing direction');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 10s 10.25 both alternate'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 0.0,
                 'Initial value of progress');
-  anim.currentTime += 2500;
+  anim.currentTime += 2.5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
-  anim.currentTime += 5000;
+  anim.currentTime += 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
-  anim.currentTime += 5000;
+  anim.currentTime += 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
   anim.finish()
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
 }, 'progress of a non-integral repeating animation ' +
    'with alternate direction');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 10s 10.25 both alternate-reverse'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 1.0,
                 'Initial value of progress');
-  anim.currentTime += 2500;
+  anim.currentTime += 2.5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
-  anim.currentTime += 5000;
+  anim.currentTime += 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
-  anim.currentTime += 5000;
+  anim.currentTime += 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
   anim.finish()
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
 }, 'progress of a non-integral repeating animation ' +
    'with alternate-reversing direction');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 0s 10.25 both alternate'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Initial value of progress');
-  anim.currentTime += 2500;
+  anim.currentTime += 2.5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
-  anim.currentTime -= 5000;
+  anim.currentTime -= 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.0,
                 'Value of progress');
   anim.finish()
   assert_equals(anim.effect.getComputedTiming().progress, 0.25,
                 'Value of progress');
 }, 'progress of a non-integral repeating zero-duration animation ' +
    'with alternate direction');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 0s 10.25 both alternate-reverse'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Initial value of progress');
-  anim.currentTime += 2500;
+  anim.currentTime += 2.5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
-  anim.currentTime -= 5000;
+  anim.currentTime -= 5 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().progress, 1.0,
                 'Value of progress');
   anim.finish()
   assert_equals(anim.effect.getComputedTiming().progress, 0.75,
                 'Value of progress');
 }, 'progress of a non-integral repeating zero-duration animation ' +
    'with alternate-reverse direction');
 
@@ -486,44 +486,44 @@ test(function(t) {
   //    unresolved (null value).
   var div = addDiv(t, {style: 'animation: moveAnimation 0s infinite both'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().currentIteration, Infinity,
                 'Initial value of currentIteration in after phase');
 
   // Seek backwards
-  anim.currentTime -= 2000;
+  anim.currentTime -= 2 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
                 'Value of currentIteration count during before phase');
 }, 'currentIteration of an infinitely repeating zero-duration animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 0s 10.5 both'});
   var anim = div.getAnimations()[0];
 
   // Note: currentIteration = ceil(iteration start + iteration count) - 1
   assert_equals(anim.effect.getComputedTiming().currentIteration, 10,
                 'Initial value of currentIteration');
 
   // Seek backwards
-  anim.currentTime -= 2000;
+  anim.currentTime -= 2 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
                 'Value of currentIteration count during before phase');
 }, 'currentIteration of a finitely repeating zero-duration animation');
 
 test(function(t) {
   var div = addDiv(t, {style: 'animation: moveAnimation 100s 5.5 forwards'});
   var anim = div.getAnimations()[0];
 
   assert_equals(anim.effect.getComputedTiming().currentIteration, 0,
                 'Initial value of currentIteration');
   // The 3rd iteration
   // Note: currentIteration = floor(scaled active time / iteration duration)
-  anim.currentTime = 250000; // ms
+  anim.currentTime = 250 * MS_PER_SEC;
   assert_equals(anim.effect.getComputedTiming().currentIteration, 2,
                 'Value of currentIteration during the 3rd iteration');
   // Finish
   anim.finish();
   assert_equals(anim.effect.getComputedTiming().currentIteration, 5,
                 'Value of currentIteration in after phase');
 }, 'currentIteration of an animation with a non-integral iteration count');
 
--- a/dom/animation/test/css-animations/file_animation-currenttime.html
+++ b/dom/animation/test/css-animations/file_animation-currenttime.html
@@ -34,18 +34,18 @@
 // 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_DELAY_MS = 1000 * MS_PER_SEC;
+const ANIM_DUR_MS = 1000 * MS_PER_SEC;
 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) {
@@ -181,17 +181,16 @@ function checkStateAtActiveIntervalEndTi
   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];
 
@@ -219,52 +218,47 @@ test(function(t)
 
   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) {
+promise_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() {
+  return animation.ready.then(function() {
     checkStateOnReadyPromiseResolved(animation);
 
     animation.currentTime =
       currentTimeForStartOfActiveInterval(animation.timeline);
     return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
+  }).then(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() {
+  }).then(function() {
     checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
   });
 }, 'Skipping forward through animation');
 
 
-async_test(function(t) {
+promise_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:
@@ -273,18 +267,18 @@ async_test(function(t) {
   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() {
+  var retPromise =  eventWatcher.wait_for(['animationstart',
+                                           'animationend']).then(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:
@@ -299,291 +293,267 @@ async_test(function(t) {
     //
     // 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() {
+  }).then(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() {
+  }).then(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);
+  checkStateAtActiveIntervalEndTime(animation);
+
+  return retPromise;
 }, '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) {
+promise_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();
-  });
+  return waitForAnimationFrames(2);
 }, 'Redundant change, before -> active, then back');
 
-async_test(function(t) {
+promise_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();
-  });
+  return waitForAnimationFrames(2);
 }, 'Redundant change, before -> after, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise = eventWatcher.wait_for('animationstart').then(function() {
     animation.currentTime = currentTimeForBeforePhase(animation.timeline);
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, active -> before, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise =  eventWatcher.wait_for('animationstart').then(function() {
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, active -> after, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise =  eventWatcher.wait_for(['animationstart',
+                                           'animationend']).then(function() {
     animation.currentTime = currentTimeForBeforePhase(animation.timeline);
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, after -> before, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise =  eventWatcher.wait_for(['animationstart',
+                                           'animationend']).then(function() {
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, after -> active, then back');
 
-async_test(function(t) {
+promise_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.pause();
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
-  eventWatcher.wait_for(['animationstart',
-                         'animationend']).then(t.step_func(function() {
+  return eventWatcher.wait_for(['animationstart',
+                                'animationend']).then(function() {
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
     return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-
+  });
 }, 'Seeking finished -> paused dispatches animationstart');
 
-async_test(function(t) {
+promise_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() {
+  return animation.ready.then(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) {
+promise_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() {
+  return animation.ready.then(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() {
+  }).then(function() {
     pauseTime = animation.currentTime;
     return waitForFrame();
-  })).then(t.step_func(function() {
+  }).then(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) {
+promise_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() {
+  return 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() {
+  }).then(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) {
+promise_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() {
+  return 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() {
+  }).then(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');
 
-async_test(function(t) {
+promise_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() {
+  return animation.ready.then(function() {
     animation.finish();
 
     // Initiate a pause then abort it
     animation.pause();
     animation.play();
 
     // Wait to return to running state
     return animation.ready;
-  })).then(t.step_func(function() {
+  }).then(function() {
     assert_true(animation.currentTime < 100 * 1000,
                 'After aborting a pause when finished, the currentTime should'
                 + ' jump back towards the start of the animation');
-    t.done();
-  }));
+  });
 }, 'After aborting a pause when finished, the call to play() should rewind'
    + ' the current time');
 
 done();
     </script>
   </body>
 </html>
--- a/dom/animation/test/css-animations/file_animation-finished.html
+++ b/dom/animation/test/css-animations/file_animation-finished.html
@@ -7,540 +7,87 @@
 }
 @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);
-  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.finish();
+const ANIM_DURATION = 100 * MS_PER_SEC;
 
-  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.finish();
-
-  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) {
+promise_test(function(t) {
   var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.finish();
-
-  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];
-
-  // Setup callback to run if finished promise is resolved
-  var finishPromiseResolved = false;
-  animation.finished.then(function() {
-    finishPromiseResolved = true;
-  });
-
-  animation.ready.then(t.step_func(function() {
-    // Jump to mid-way in interval and pause
-    animation.currentTime = ANIM_DURATION / 2;
-    animation.pause();
-    return animation.ready;
-  })).then(t.step_func(function() {
-    // Jump to the end
-    // (But don't use finish() since that should unpause as well)
-    animation.currentTime = ANIM_DURATION;
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    assert_false(finishPromiseResolved,
-                 'Finished promise should not resolve when paused');
-    t.done();
-  }));
-}, 'Finished promise does not resolve when paused');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  // Setup callback to run if finished promise is resolved
-  var finishPromiseResolved = false;
-  animation.finished.then(function() {
-    finishPromiseResolved = true;
-  });
-
-  animation.ready.then(t.step_func(function() {
-    // Jump to mid-way in interval and pause
-    animation.currentTime = ANIM_DURATION / 2;
-    animation.pause();
-    // Jump to the end
-    animation.currentTime = ANIM_DURATION;
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    assert_false(finishPromiseResolved,
-                 'Finished promise should not resolve when pause-pending');
-    t.done();
-  }));
-}, 'Finished promise does not resolve when pause-pending');
-
-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() {
+  var retPromise = animation.finished.then(function() {
     assert_unreached('finished promise is fulfilled');
-  })).catch(t.step_func(function(err) {
+  }).catch(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;
 
+  return retPromise;
 }, 'finished promise is rejected when an animation is cancelled by resetting ' +
    'the animation property');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise = animation.finished.then(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) {
+  }).catch(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;
+  // Now update the animation and flush styles
+  div.style.animation = 'def 100s';
+  window.getComputedStyle(div).animation;
 
-  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 animation');
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'cancelling an already-finished animation 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();
+  return retPromise;
+}, 'finished promise is rejected when an animation is cancelled by changing ' +
+   'the animation property');
 
-  // 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 animation still replaces the finished promise');
-
-async_test(function(t) {
+promise_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;
-
-  var gotNextFrame = false;
-  var currentTimeBeforeShortening;
-
-  animation.currentTime = HALF_DUR;
-  animation.ready.then(t.step_func(function() {
-    currentTimeBeforeShortening = animation.currentTime;
-
-    div.style.animationDuration = QUARTER_DUR + 'ms';
-    window.getComputedStyle(div).animationDuration; // flush style
-    // 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.
-
-    waitForFrame().then(function() {
-      gotNextFrame = true;
-    });
-
-    return animation.finished;
-  })).then(t.step_func(function() {
-    assert_false(gotNextFrame, 'shortening of the animation duration should ' +
-                               'resolve the finished promise');
-    assert_equals(animation.currentTime, currentTimeBeforeShortening,
-                  '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() {
+  return animation.finished.then(function() {
     div.style.animationPlayState = 'running';
     return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
+  }).then(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');
 
-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 = 0;
-    assert_not_equals(animation.finished, previousFinishedPromise,
-                      'Finished promise should change once a prior ' +
-                      'finished promise resolved and the animation ' +
-                      'falls out finished state');
-    t.done();
-  }));
-}, 'Test finished promise changes when a prior finished promise resolved ' +
-   'and the animation falls out finished state');
-
-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.currentTime = ANIM_DURATION / 2;
-
-  assert_equals(animation.finished, previousFinishedPromise,
-                'No new finished promise generated when finished state ' +
-                'is checked asynchronously');
-  t.done();
-}, 'Test no new finished promise generated when finished state ' +
-   'is checked asynchronously');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var previousFinishedPromise = animation.finished;
-
-  animation.finish();
-  animation.currentTime = ANIM_DURATION / 2;
-
-  assert_not_equals(animation.finished, previousFinishedPromise,
-                    'New finished promise generated when finished state ' +
-                    'is checked synchronously');
-  t.done();
-}, 'Test new finished promise generated when finished state ' +
-   'is checked synchronously');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var resolvedFinished = false;
-  animation.finished.then(function() {
-    resolvedFinished = true;
-  });
-
-  animation.ready.then(function() {
-    animation.finish();
-    animation.currentTime = ANIM_DURATION / 2;
-  }).then(t.step_func(function() {
-    assert_true(resolvedFinished,
-      'Animation.finished should be resolved even if ' +
-      'the finished state is changed soon');
-    t.done();
-  }));
-
-}, 'Test synchronous finished promise resolved even if finished state ' +
-   'is changed soon');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var resolvedFinished = false;
-  animation.finished.then(function() {
-    resolvedFinished = true;
-  });
-
-  animation.ready.then(t.step_func(function() {
-    animation.currentTime = ANIM_DURATION;
-    animation.finish();
-  })).then(t.step_func(function() {
-    assert_true(resolvedFinished,
-      'Animation.finished should be resolved soon after finish() is ' +
-      'called even if there are other asynchronous promises just before it');
-    t.done();
-  }));
-}, 'Test synchronous finished promise resolved even if asynchronous ' +
-   'finished promise happens just before synchronous promise');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.finished.then(t.step_func(function() {
-    assert_unreached('Animation.finished should not be resolved');
-  }));
-
-  animation.ready.then(function() {
-    animation.currentTime = ANIM_DURATION;
-    animation.currentTime = ANIM_DURATION / 2;
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'Test finished promise is not resolved when the animation ' +
-   'falls out finished state immediately');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.ready.then(function() {
-    animation.currentTime = ANIM_DURATION;
-    animation.finished.then(t.step_func(function() {
-      assert_unreached('Animation.finished should not be resolved');
-    }));
-    animation.currentTime = 0;
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-
-}, 'Test finished promise is not resolved once the animation ' +
-   'falls out finished state even though the current finished ' +
-   'promise is generated soon after animation state became finished');
-
 done();
 </script>
 </body>
--- a/dom/animation/test/css-animations/file_animation-id.html
+++ b/dom/animation/test/css-animations/file_animation-id.html
@@ -3,25 +3,30 @@
 <script src="../testcommon.js"></script>
 <style>
 @keyframes abc { }
 </style>
 <body>
 <script>
 'use strict';
 
-
 test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
-
   assert_equals(animation.id, '', 'id for CSS Animation is initially empty');
-
   animation.id = 'anim'
 
   assert_equals(animation.id, 'anim', 'animation.id reflects the value set');
-  }, 'Animation.id for CSS Animations');
+}, 'Animation.id for CSS Animations');
 
+test(function(t) {
+  var div = addDiv(t);
+  var animation = div.animate({}, 100 * MS_PER_SEC);
+  assert_equals(animation.id, '', 'id for CSS Animation is initially empty');
+  animation.id = 'anim'
+
+  assert_equals(animation.id, 'anim', 'animation.id reflects the value set');
+}, 'Animation.id for CSS Animations');
 done();
 </script>
 </body>
 </html>
deleted file mode 100644
--- a/dom/animation/test/css-animations/file_animation-oncancel.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<script src="../testcommon.js"></script>
-<style>
-@keyframes abc {
-  to { transform: translate(10px) }
-}
-</style>
-<body>
-<script>
-'use strict';
-
-async_test(function(t) {
-  var div = addDiv(t, {'style': 'animation: abc 100s'});
-  var animation = div.getAnimations()[0];
-
-  var finishedTimelineTime;
-  animation.finished.then().catch(function() {
-    finishedTimelineTime = animation.timeline.currentTime;
-  });
-
-  animation.oncancel = t.step_func_done(function(event) {
-    assert_equals(event.currentTime, null,
-      'event.currentTime should be null');
-    assert_equals(event.timelineTime, finishedTimelineTime,
-      'event.timelineTime should equal to the animation timeline ' +
-      'when finished promise is rejected');
-  });
-
-  animation.cancel();
-}, 'oncancel event is fired when animation.cancel()');
-
-done();
-</script>
-</body>
deleted file mode 100644
--- a/dom/animation/test/css-animations/file_animation-onfinish.html
+++ /dev/null
@@ -1,142 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<script src="../testcommon.js"></script>
-<style>
-@keyframes abc {
-  to { transform: translate(10px) }
-}
-</style>
-<body>
-<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 finishedTimelineTime;
-  animation.finished.then(function() {
-    finishedTimelineTime = animation.timeline.currentTime;
-  });
-
-  animation.onfinish = t.step_func_done(function(event) {
-    assert_equals(event.currentTime, 0,
-      'event.currentTime should be zero');
-    assert_equals(event.timelineTime, finishedTimelineTime,
-      'event.timelineTime should equal to the animation timeline ' +
-      'when finished promise is resolved');
-  });
-
-  animation.playbackRate = -1;
-}, 'onfinish event is fired when the currentTime < 0 and ' +
-   'the playbackRate < 0');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var finishedTimelineTime;
-  animation.finished.then(function() {
-    finishedTimelineTime = animation.timeline.currentTime;
-  });
-
-  animation.onfinish = t.step_func_done(function(event) {
-    assert_equals(event.currentTime, ANIM_DURATION,
-      'event.currentTime should be the effect end');
-    assert_equals(event.timelineTime, finishedTimelineTime,
-      'event.timelineTime should equal to the animation timeline ' +
-      'when finished promise is resolved');
-  });
-
-  animation.currentTime = ANIM_DURATION;
-}, 'onfinish event is fired when the currentTime > 0 and ' +
-   'the playbackRate > 0');
-
-async_test(function(t) {
-  var div = addDiv(t, {'class': 'animated-div'});
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  var finishedTimelineTime;
-  animation.finished.then(function() {
-    finishedTimelineTime = animation.timeline.currentTime;
-  });
-
-  animation.onfinish = t.step_func_done(function(event) {
-    assert_equals(event.currentTime, ANIM_DURATION,
-      'event.currentTime should be the effect end');
-    assert_equals(event.timelineTime, finishedTimelineTime,
-      'event.timelineTime should equal to the animation timeline ' +
-      'when finished promise is resolved');
-  });
-
-  animation.finish();
-}, 'onfinish event is fired when animation.finish() is called');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.onfinish = t.step_func(function(event) {
-    assert_unreached('onfinish event should not be fired');
-  });
-
-  animation.currentTime = ANIM_DURATION / 2;
-  animation.pause();
-
-  animation.ready.then(t.step_func(function() {
-    animation.currentTime = ANIM_DURATION;
-    return waitForAnimationFrames(2);
-  })).then(t.step_func(function() {
-    t.done();
-  }));
-}, 'onfinish event is not fired when paused');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.onfinish = t.step_func(function(event) {
-    assert_unreached('onfinish event should not be fired');
-  });
-
-  animation.ready.then(function() {
-    animation.playbackRate = 0;
-    animation.currentTime = ANIM_DURATION;
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-
-}, 'onfinish event is not fired when the playbackRate is zero');
-
-async_test(function(t) {
-  var div = addDiv(t);
-  div.style.animation = ANIM_PROP_VAL;
-  var animation = div.getAnimations()[0];
-
-  animation.onfinish = t.step_func(function(event) {
-    assert_unreached('onfinish event should not be fired');
-  });
-
-  animation.ready.then(function() {
-    animation.currentTime = ANIM_DURATION;
-    animation.currentTime = ANIM_DURATION / 2;
-    return waitForAnimationFrames(2);
-  }).then(t.step_func(function() {
-    t.done();
-  }));
-
-}, 'onfinish event is not fired when the animation falls out ' +
-   'finished state immediately');
-
-done();
-</script>
-</body>
--- a/dom/animation/test/css-animations/file_animation-pausing.html
+++ b/dom/animation/test/css-animations/file_animation-pausing.html
@@ -10,147 +10,108 @@
 <body>
 <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) {
+promise_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();
 
+  return animation.ready.then(waitForFrame).then(function() {
+    assert_true(getMarginLeft(cs) > 0,
+                'Playing value of margin-left is greater than zero');
+  });
+}, 'play() overrides animation-play-state');
+
+promise_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() {
+  return animation.ready.then(waitForFrame).then(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) {
+promise_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() {
+  return animation.ready.then(function() {
     div.style.animationPlayState = 'running';
     cs.animationPlayState; // Trigger style resolution
     return waitForFrame();
-  }).then(t.step_func(function() {
+  }).then(function() {
     assert_equals(cs.animationPlayState, 'running',
                   'animation-play-state is running');
     div.style.animationPlayState = 'paused';
     return animation.ready;
-  })).then(t.step_func(function() {
+  }).then(function() {
     assert_equals(cs.animationPlayState, 'paused',
                   'animation-play-state is paused');
     previousAnimVal = getMarginLeft(cs);
     return waitForFrame();
-  })).then(t.step_func(function() {
+  }).then(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) {
+promise_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() {
+  return animation.ready.then(waitForFrame).then(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) {
+promise_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
@@ -159,117 +120,46 @@ async_test(function(t) {
   // (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() {
+  return animation.ready.then(waitForFrame).then(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) {
+promise_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() {
+  return animation.ready.then(function() {
     div.style.animationPlayState = 'paused';
     assert_equals(animation.playState, 'pending', 'Animation is pause pending');
 
     // Set current time
-    animation.currentTime = 5000;
+    animation.currentTime = 5 * MS_PER_SEC;
     assert_equals(animation.playState, 'paused',
                   'Animation is paused immediately after setting currentTime');
     assert_equals(animation.startTime, null,
                   'Animation startTime is unresolved immediately after ' +
                   'setting currentTime');
-    assert_equals(animation.currentTime, 5000,
+    assert_equals(animation.currentTime, 5 * MS_PER_SEC,
                   'Animation currentTime does not change when forcing a ' +
                   'pause operation to complete');
 
     // 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 completes a pending pause');
 
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  var animation = div.getAnimations()[0];
-
-  // Go to idle state then pause
-  animation.cancel();
-  animation.pause();
-
-  assert_equals(animation.currentTime, 0, 'currentTime is set to 0');
-  assert_equals(animation.startTime, null, 'startTime is not set');
-  assert_equals(animation.playState, 'pending', 'initially pause-pending');
-
-  // Check it still resolves as expected
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.playState, 'paused',
-                  'resolves to paused state asynchronously');
-    assert_equals(animation.currentTime, 0,
-                  'keeps the initially set currentTime');
-    t.done();
-  }));
-}, 'pause() from idle');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  var animation = div.getAnimations()[0];
-
-  animation.cancel();
-  animation.playbackRate = -1;
-  animation.pause();
-
-  assert_equals(animation.currentTime, 1000 * 1000,
-                'currentTime is set to the effect end');
-
-  animation.ready.then(t.step_func(function() {
-    assert_equals(animation.currentTime, 1000 * 1000,
-                  'keeps the initially set currentTime');
-    t.done();
-  }));
-}, 'pause() from idle with a negative playbackRate');
-
-test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s infinite' });
-  var animation = div.getAnimations()[0];
-
-  animation.cancel();
-  animation.playbackRate = -1;
-
-  assert_throws('InvalidStateError',
-                function () { animation.pause(); },
-                'Expect InvalidStateError exception on calling pause() ' +
-                'from idle with a negative playbackRate and ' +
-                'infinite-duration animation');
-}, 'pause() from idle with a negative playbackRate and endless effect');
-
-promise_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 1000s' });
-  return div.getAnimations()[0].ready
-    .then(function(animation) {
-      animation.finish();
-      animation.pause();
-      return animation.ready;
-    }).then(function(animation) {
-      assert_equals(animation.currentTime, 1000 * 1000,
-                    'currentTime after pausing finished animation');
-    });
-}, 'pause() on a finished animation');
-
 done();
 </script>
 </body>
--- a/dom/animation/test/css-animations/file_animation-ready.html
+++ b/dom/animation/test/css-animations/file_animation-ready.html
@@ -5,247 +5,145 @@
 @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];
-
-  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) {
+promise_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s paused';
   var animation = div.getAnimations()[0];
+  var originalReadyPromise = animation.ready;
 
-  var originalReadyPromise = animation.ready;
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(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) {
+promise_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() {
+  var retPromise = animation.ready.then(function() {
     assert_unreached('ready promise is fulfilled');
-  })).catch(t.step_func(function(err) {
+  }).catch(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;
 
+  return retPromise;
 }, 'ready promise is rejected when an animation is cancelled by resetting'
    + ' the animation property');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise = animation.ready.then(function() {
     assert_unreached('ready promise was fulfilled');
-  })).catch(t.step_func(function(err) {
+  }).catch(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;
 
+  return retPromise;
 }, '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) {
+promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: abc 100s' });
   var animation = div.getAnimations()[0];
+  var originalReadyPromise = animation.ready;
 
-  var originalReadyPromise = animation.ready;
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(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) {
+promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: abc 100s' });
   var animation = div.getAnimations()[0];
 
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(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) {
+promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: abc 100s' });
   var animation = div.getAnimations()[0];
 
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(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() {
+  }).then(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) {
+promise_test(function(t) {
   var div = addDiv(t, { style: 'animation: abc 100s' });
   var animation = div.getAnimations()[0];
 
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(function() {
     div.style.animationPlayState = 'paused';
     return animation.ready;
-  })).then(t.step_func(function(resolvedAnimation) {
+  }).then(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');
 
 done();
 </script>
 </body>
--- a/dom/animation/test/css-animations/file_animation-reverse.html
+++ b/dom/animation/test/css-animations/file_animation-reverse.html
@@ -5,169 +5,25 @@
 @keyframes anim {
   to { transform: translate(100px) }
 }
 </style>
 <body>
 <script>
 'use strict';
 
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s infinite' });
-  var animation = div.getAnimations()[0];
-
-  // Wait a frame because if currentTime is still 0 when we call
-  // reverse(), it will throw (per spec).
-  animation.ready.then(waitForFrame).then(t.step_func_done(function() {
-    assert_greater_than(animation.currentTime, 0,
-      'currentTime expected to be greater than 0, one frame after starting');
-    var previousPlaybackRate = animation.playbackRate;
-    animation.reverse();
-    assert_equals(animation.playbackRate, -previousPlaybackRate,
-      'playbackRate should be invetrted');
-  }));
-}, 'reverse() inverts playbackRate');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s infinite' });
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = 50000;
-  animation.pause();
-  animation.ready.then(t.step_func(function() {
-    animation.reverse();
-    return animation.ready;
-  })).then(t.step_func_done(function() {
-    assert_equals(animation.playState, 'running',
-      'Animation.playState should be "running" after reverse()');
-  }));
-}, 'reverse() starts to play when pausing animation');
-
-async_test(function(t) {
+test(function(t) {
   var div = addDiv(t, { style: 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
   div.style.animation = "";
   flushComputedStyle(div);
 
   assert_equals(animation.currentTime, null);
   animation.reverse();
 
-  assert_equals(animation.currentTime, 100000,
+  assert_equals(animation.currentTime, 100 * MS_PER_SEC,
     'animation.currentTime should be its effect end');
-  t.done();
 }, 'reverse() from idle state starts playing the animation');
 
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = 50000;
-  animation.reverse();
-
-  assert_equals(animation.currentTime, 50000,
-    'reverse() should not change the currentTime ' +
-    'if the currentTime is in the middle of animation duration');
-  t.done();
-}, 'reverse() maintains the same currentTime');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = 200000;
-  animation.reverse();
-
-  assert_equals(animation.currentTime, 100000,
-    'reverse() should start playing from the animation effect end ' +
-    'if the playbackRate > 0 and the currentTime > effect end');
-  t.done();
-}, 'reverse() when playbackRate > 0 and currentTime > effect end');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = -200000;
-  animation.reverse();
-
-  assert_equals(animation.currentTime, 100000,
-    'reverse() should start playing from the animation effect end ' +
-    'if the playbackRate > 0 and the currentTime < 0');
-  t.done();
-}, 'reverse() when playbackRate > 0 and currentTime < 0');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.playbackRate = -1;
-  animation.currentTime = -200000;
-  animation.reverse();
-
-  assert_equals(animation.currentTime, 0,
-    'reverse() should start playing from the start of animation time ' +
-    'if the playbackRate < 0 and the currentTime < 0');
-  t.done();
-}, 'reverse() when playbackRate < 0 and currentTime < 0');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.playbackRate = -1;
-  animation.currentTime = 200000;
-  animation.reverse();
-
-  assert_equals(animation.currentTime, 0,
-    'reverse() should start playing from the start of animation time ' +
-    'if the playbackRate < 0 and the currentTime > effect end');
-  t.done();
-}, 'reverse() when playbackRate < 0 and currentTime > effect end');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s infinite' });
-  var animation = div.getAnimations()[0];
-
-  animation.currentTime = -200000;
-
-  assert_throws('InvalidStateError',
-    function () { animation.reverse(); },
-    'reverse() should throw InvalidStateError ' +
-    'if the playbackRate > 0 and the currentTime < 0 ' +
-    'and the target effect is positive infinity');
-  t.done();
-}, 'reverse() when playbackRate > 0 and currentTime < 0 ' +
-   'and the target effect is positive infinity');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s infinite' });
-  var animation = div.getAnimations()[0];
-
-  animation.playbackRate = -1;
-  animation.currentTime = -200000;
-  animation.reverse();
-
-  assert_equals(animation.currentTime, 0,
-    'reverse() should start playing from the start of animation time ' +
-    'if the playbackRate < 0 and the currentTime < 0 ' +
-    'and the target effect is positive infinity');
-  t.done();
-}, 'reverse() when playbackRate < 0 and currentTime < 0 ' +
-   'and the target effect is positive infinity');
-
-async_test(function(t) {
-  var div = addDiv(t, { style: 'animation: anim 100s' });
-  var animation = div.getAnimations()[0];
-
-  animation.playbackRate = 0;
-  animation.currentTime = 50000;
-  animation.reverse();
-
-  assert_equals(animation.playbackRate, 0,
-    'reverse() should preserve playbackRate if the playbackRate == 0');
-  assert_equals(animation.currentTime, 50000,
-    'reverse() should not affect the currentTime if the playbackRate == 0');
-  t.done();
-}, 'reverse() when playbackRate == 0');
 
 done();
 </script>
 </body>
--- a/dom/animation/test/css-animations/file_animation-starttime.html
+++ b/dom/animation/test/css-animations/file_animation-starttime.html
@@ -34,18 +34,18 @@
 // 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_DELAY_MS = 1000 * MS_PER_SEC; // 1000s
+const ANIM_DUR_MS = 1000 * MS_PER_SEC; // 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) {
@@ -172,172 +172,161 @@ test(function(t)
 
 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)
+promise_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(function() {
     assert_true(animation.startTime > 0,
                 'startTime is resolved when running');
-    t.done();
-  }));
+  });
 }, 'startTime is resolved when running');
 
-async_test(function(t)
+promise_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s paused' });
   var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(function() {
     assert_equals(animation.startTime, null,
                   'startTime is unresolved when paused');
-    t.done();
-  }));
+  });
 }, 'startTime is unresolved when paused');
 
-async_test(function(t)
+promise_test(function(t)
 {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
-  animation.ready.then(t.step_func(function() {
+  return animation.ready.then(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) {
+promise_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
-  animation.currentTime = 100 * 1000;
-  animation.ready.then(t.step_func(function() {
+  animation.currentTime = 100 * MS_PER_SEC;
+  return animation.ready.then(function() {
     // Call play() which puts us back in the running state
     animation.play();
     assert_equals(animation.startTime, null, 'startTime is unresolved');
-    t.done();
-  }));
+  });
 }, 'startTime while play-pending from finished state');
 
 test(function(t) {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
   animation.finish();
   // Call play() which puts us back in the running state
   animation.play();
   assert_equals(animation.startTime, null, 'startTime is unresolved');
 }, 'startTime while play-pending from finished state using finish()');
 
-async_test(function(t) {
+promise_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() {
+  return animation.ready.then(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() {
+  }).then(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) {
+promise_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() {
+  return animation.ready.then(function() {
     checkStateOnReadyPromiseResolved(animation);
 
     animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
     return eventWatcher.wait_for('animationstart');
-  })).then(t.step_func(function() {
+  }).then(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() {
+  }).then(function() {
     checkStateAtActiveIntervalEndTime(animation);
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(function() {
-    t.done();
   });
 }, 'Skipping forward through animation');
 
-
-async_test(function(t) {
+promise_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() {
+  return eventWatcher.wait_for(['animationstart',
+                                'animationend']).then(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:
@@ -352,210 +341,190 @@ async_test(function(t) {
     //
     // 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() {
+  }).then(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() {
+  }).then(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);
+  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) {
+promise_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();
-  });
+  return waitForAnimationFrames(2);
 }, 'Redundant change, before -> active, then back');
 
-async_test(function(t) {
+promise_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();
-  });
+  return waitForAnimationFrames(2);
 }, 'Redundant change, before -> after, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise =  eventWatcher.wait_for('animationstart').then(function() {
     animation.startTime = startTimeForBeforePhase(animation.timeline);
     animation.startTime = startTimeForActivePhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.startTime = startTimeForActivePhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, active -> before, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise = eventWatcher.wait_for('animationstart').then(function() {
     animation.startTime = startTimeForAfterPhase(animation.timeline);
     animation.startTime = startTimeForActivePhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.startTime = startTimeForActivePhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, active -> after, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise = eventWatcher.wait_for(['animationstart',
+                                          'animationend']).then(function() {
     animation.startTime = startTimeForBeforePhase(animation.timeline);
     animation.startTime = startTimeForAfterPhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.startTime = startTimeForAfterPhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, after -> before, then back');
 
-async_test(function(t) {
+promise_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() {
+  var retPromise = eventWatcher.wait_for(['animationstart',
+                                          'animationend']).then(function() {
     animation.startTime = startTimeForActivePhase(animation.timeline);
     animation.startTime = startTimeForAfterPhase(animation.timeline);
 
-    waitForAnimationFrames(2).then(function() {
-      t.done();
-    });
+    return waitForAnimationFrames(2);
   });
   // get us into the initial state:
   animation.startTime = startTimeForAfterPhase(animation.timeline);
+
+  return retPromise;
 }, 'Redundant change, after -> active, then back');
 
-
-async_test(function(t) {
+promise_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() {
+  return animation.ready.then(function() {
     storedCurrentTime = animation.currentTime;
     animation.startTime = null;
     return animation.ready;
-  })).catch(t.step_func(function(reason) {
-    assert_unreached(reason);
-  })).then(t.step_func(function() {
+  }).then(function() {
     assert_equals(animation.currentTime, storedCurrentTime,
       'Test that hold time is correct');
-    t.done();
-  }));
+  });
 }, 'Setting startTime to null');
 
-
-async_test(function(t) {
+promise_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() {
+  return animation.ready.then(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() {
+  }).then(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) {
+promise_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() {
+  return animation.ready.then(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>
--- a/dom/animation/test/css-animations/file_animations-dynamic-changes.html
+++ b/dom/animation/test/css-animations/file_animations-dynamic-changes.html
@@ -6,48 +6,46 @@
   to { left: 100px }
 }
 @keyframes anim2 { }
 </style>
 <body>
 <script>
 'use strict';
 
-async_test(function(t) {
+promise_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() {
+  return 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() {
+  }).then(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();
@@ -58,102 +56,99 @@ test(function(t) {
   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) {
+promise_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() {
+  return waitForAllAnimations(animations).then(waitForFrame).then(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() {
+  }).then(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) {
+promise_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() {
+  return secondAnimation.ready.then(waitForFrame).then(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) {
+promise_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() {
+  return firstAddedAnimation.ready.then(waitForFrame).then(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() {
+  }).then(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() {
+  }).then(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');
 
 done();
 </script>
 </body>
--- a/dom/animation/test/css-animations/file_effect-target.html
+++ b/dom/animation/test/css-animations/file_effect-target.html
@@ -27,17 +27,17 @@ test(function(t) {
 }, 'effect.target should return the same CSSPseudoElement object each time');
 
 test(function(t) {
   addStyle(t, { '.after::after': 'animation: anim 10s;' });
   var div = addDiv(t, { class: 'after' });
   var pseudoTarget = document.getAnimations()[0].effect.target;
   var effect = new KeyframeEffectReadOnly(pseudoTarget,
                                           { background: ["blue", "red"] },
-                                          3000);
+                                          3 * MS_PER_SEC);
   var newAnim = new Animation(effect, document.timeline);
   newAnim.play();
   var anims = document.getAnimations();
   assert_equals(anims.length, 2,
                 'Got animations running on ::after pseudo element');
   assert_not_equals(anims[0], newAnim,
                     'The scriped-generated animation appears last');
   assert_equals(newAnim.effect.target, pseudoTarget,
--- a/dom/animation/test/css-animations/file_element-get-animations.html
+++ b/dom/animation/test/css-animations/file_element-get-animations.html
@@ -19,52 +19,51 @@
 
 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) {
+promise_test(function(t) {
   var div = addDiv(t);
 
   // FIXME: This test does too many things. It should be split up.
 
   // 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() {
+  return animations[0].ready.then(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() {
+  }).then(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() {
+  }).then(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, { style: 'animation: anim1 100s' });
   assert_class_string(div.getAnimations()[0], 'CSSAnimation',
                       'Interface of returned animation is CSSAnimation');
 }, 'getAnimations returns CSSAnimation objects for CSS Animations');
 
@@ -73,40 +72,39 @@ test(function(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) {
+promise_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() {
+  return animations[0].ready.then(waitForFrame).then(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');
     assert_class_string(animations[0], 'CSSTransition',
                         'First-returned animation is the CSS Transition');
     assert_class_string(animations[1], 'CSSAnimation',
                         'Second-returned animation is the CSS Animation');
-    t.done();
-  }));
+  });
 }, 'getAnimations for both CSS Animations and CSS 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,
@@ -161,38 +159,37 @@ test(function(t) {
 
   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) {
+promise_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() {
+  return animations[0].ready.then(waitForFrame).then(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() {
+  }).then(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'
@@ -202,29 +199,28 @@ test(function(t) {
 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) {
+promise_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() {
+  return animations[0].ready.then(waitForFrame).then(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');
@@ -269,21 +265,21 @@ 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');
 
-async_test(function(t) {
+promise_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'anim2 100s';
 
-  div.getAnimations()[0].ready.then(t.step_func(function() {
+  return div.getAnimations()[0].ready.then(function() {
     // Prepend to the list and test that even though anim1 was triggered
     // *after* anim2, it should come first because it appears first
     // in the animation-name property.
     div.style.animation = 'anim1 100s, anim2 100s';
     var anims = div.getAnimations();
     assert_equals(anims[0].animationName, 'anim1',
                   'animation order after prepending to list');
     assert_equals(anims[1].animationName, 'anim2',
@@ -295,18 +291,17 @@ async_test(function(t) {
     var anim1 = anims[0];
     anim1.cancel();
     anim1.play();
     anims = div.getAnimations();
     assert_equals(anims[0].animationName, 'anim1',
                   'animation order after cancelling and restarting');
     assert_equals(anims[1].animationName, 'anim2',
                   'animation order after cancelling and restarting');
-    t.done();
-  }));
+  });
 }, 'getAnimations for CSS Animations follows animation-name order');
 
 test(function(t) {
   addStyle(t, { '#target::after': 'animation: anim1 10s;',
                 '#target::before': 'animation: anim1 10s;' });
   var target = addDiv(t, { 'id': 'target' });
   target.style.animation = 'anim1 100s';
 
--- a/dom/animation/test/css-animations/file_pseudoElement-get-animations.html
+++ b/dom/animation/test/css-animations/file_pseudoElement-get-animations.html
@@ -37,17 +37,17 @@ test(function(t) {
   // Trigger transitions
   flushComputedStyle(div);
   div.classList.add('after-change');
 
   // Create additional animation on the pseudo-element from script
   var pseudoTarget = document.getAnimations()[0].effect.target;
   var effect = new KeyframeEffectReadOnly(pseudoTarget,
                                           { background: ["blue", "red"] },
-                                          3000);
+                                          3 * MS_PER_SEC);
   var newAnimation = new Animation(effect, document.timeline);
   newAnimation.id = 'scripted-anim';
   newAnimation.play();
 
   // Check order - the script-generated animation should appear later
   var anims = pseudoTarget.getAnimations();
   assert_equals(anims.length, 5,
                 'Got expected number of animations/trnasitions running on ' +
deleted file mode 100644
--- a/dom/animation/test/css-animations/test_animation-oncancel.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
-<script>
-'use strict';
-setup({explicit_done: true});
-SpecialPowers.pushPrefEnv(
-  { "set": [["dom.animations-api.core.enabled", true]]},
-  function() {
-    window.open("file_animation-oncancel.html");
-  });
-</script>
-</html>
deleted file mode 100644
--- a/dom/animation/test/css-animations/test_animation-onfinish.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!doctype html>
-<meta charset=utf-8>
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
-<script>
-'use strict';
-setup({explicit_done: true});
-SpecialPowers.pushPrefEnv(
-  { "set": [["dom.animations-api.core.enabled", true]]},
-  function() {
-    window.open("file_animation-onfinish.html");
-  });
-</script>
-</html>
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -4,18 +4,16 @@
 support-files =
   chrome/file_animate_xrays.html
   css-animations/file_animation-cancel.html
   css-animations/file_animation-computed-timing.html
   css-animations/file_animation-currenttime.html
   css-animations/file_animation-finish.html
   css-animations/file_animation-finished.html
   css-animations/file_animation-id.html
-  css-animations/file_animation-oncancel.html
-  css-animations/file_animation-onfinish.html
   css-animations/file_animation-pausing.html
   css-animations/file_animation-playstate.html
   css-animations/file_animation-ready.html
   css-animations/file_animation-reverse.html
   css-animations/file_animation-starttime.html
   css-animations/file_animations-dynamic-changes.html
   css-animations/file_cssanimation-animationname.html
   css-animations/file_document-get-animations.html
@@ -44,18 +42,16 @@ support-files =
 
 [css-animations/test_animations-dynamic-changes.html]
 [css-animations/test_animation-cancel.html]
 [css-animations/test_animation-computed-timing.html]
 [css-animations/test_animation-currenttime.html]
 [css-animations/test_animation-finish.html]
 [css-animations/test_animation-finished.html]
 [css-animations/test_animation-id.html]
-[css-animations/test_animation-oncancel.html]
-[css-animations/test_animation-onfinish.html]
 [css-animations/test_animation-pausing.html]
 [css-animations/test_animation-playstate.html]
 [css-animations/test_animation-ready.html]
 [css-animations/test_animation-reverse.html]
 [css-animations/test_animation-starttime.html]
 [css-animations/test_cssanimation-animationname.html]
 [css-animations/test_document-get-animations.html]
 [css-animations/test_effect-target.html]
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -52,17 +52,17 @@
 // console.trace().
 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
 
 // This tags are used in the Structured Clone Algorithm to move js values from
 // worker thread to main thread
 #define CONSOLE_TAG_BLOB   JS_SCTAG_USER_MIN
 
 // This value is taken from ConsoleAPIStorage.js
-#define STORAGE_MAX_EVENTS 200
+#define STORAGE_MAX_EVENTS 1000
 
 using namespace mozilla::dom::exceptions;
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
 struct
--- a/dom/base/ConsoleAPIStorage.js
+++ b/dom/base/ConsoleAPIStorage.js
@@ -9,17 +9,17 @@ var Ci = Components.interfaces;
 var Cc = Components.classes;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 // This constant tells how many messages to process in a single timer execution.
 const MESSAGES_IN_INTERVAL = 1500
 
-const STORAGE_MAX_EVENTS = 200;
+const STORAGE_MAX_EVENTS = 1000;
 
 var _consoleStorage = new Map();
 
 const CONSOLEAPISTORAGE_CID = Components.ID('{96cf7855-dfa9-4c6d-8276-f9705b4890f2}');
 
 /**
  * The ConsoleAPIStorage is meant to cache window.console API calls for later
  * reuse by other components when needed. For example, the Web Console code can
--- a/dom/base/DOMException.h
+++ b/dom/base/DOMException.h
@@ -70,16 +70,28 @@ public:
   nsISupports* GetParentObject() const { return nullptr; }
 
   void GetMessageMoz(nsString& retval);
 
   uint32_t Result() const;
 
   void GetName(nsString& retval);
 
+  virtual void GetErrorMessage(nsAString& aRetVal)
+  {
+    // Since GetName and GetMessageMoz are non-virtual and they deal with
+    // different member variables in Exception vs. DOMException, have a 
+    // virtual method to ensure the right error message creation.
+    nsAutoString name;
+    nsAutoString message;
+    GetName(name);
+    GetMessageMoz(message);
+    CreateErrorMessage(name, message, aRetVal);
+  }
+
   // The XPCOM GetFilename does the right thing.  It might throw, but we want to
   // return an empty filename in that case anyway, instead of throwing.
 
   uint32_t LineNumber(JSContext* aCx) const;
 
   uint32_t ColumnNumber() const;
 
   already_AddRefed<nsIStackFrame> GetLocation() const;
@@ -97,16 +109,33 @@ public:
             nsresult aResult,
             const nsACString& aName,
             nsIStackFrame *aLocation,
             nsISupports *aData);
 
 protected:
   virtual ~Exception();
 
+  void CreateErrorMessage(const nsAString& aName, const nsAString& aMessage,
+                          nsAString& aRetVal)
+  {
+    // Create similar error message as what ErrorReport::init does in jsexn.cpp.
+    if (!aName.IsEmpty() && !aMessage.IsEmpty()) {
+      aRetVal.Assign(aName);
+      aRetVal.AppendLiteral(": ");
+      aRetVal.Append(aMessage);
+    } else if (!aName.IsEmpty()) {
+      aRetVal.Assign(aName);
+    } else if (!aMessage.IsEmpty()) {
+      aRetVal.Assign(aMessage);
+    } else {
+      aRetVal.Truncate();
+    }
+  }
+
   nsCString       mMessage;
   nsresult        mResult;
   nsCString       mName;
   nsCOMPtr<nsIStackFrame> mLocation;
   nsCOMPtr<nsISupports> mData;
   nsString        mFilename;
   int             mLineNumber;
   bool            mInitialized;
@@ -146,16 +175,26 @@ public:
   uint16_t Code() const {
     return mCode;
   }
 
   // Intentionally shadow the nsXPCException version.
   void GetMessageMoz(nsString& retval);
   void GetName(nsString& retval);
 
+  virtual void GetErrorMessage(nsAString& aRetVal) override
+  {
+    // See the comment in Exception::GetErrorMessage.
+    nsAutoString name;
+    nsAutoString message;
+    GetName(name);
+    GetMessageMoz(message);
+    CreateErrorMessage(name, message, aRetVal);
+  }
+
   static already_AddRefed<DOMException>
   Create(nsresult aRv);
 
   static already_AddRefed<DOMException>
   Create(nsresult aRv, const nsACString& aMessage);
 
 protected:
 
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2078,17 +2078,17 @@ Element::DispatchClickEvent(nsPresContex
                             nsEventStatus* aStatus)
 {
   NS_PRECONDITION(aTarget, "Must have target");
   NS_PRECONDITION(aSourceEvent, "Must have source event");
   NS_PRECONDITION(aStatus, "Null out param?");
 
   WidgetMouseEvent event(aSourceEvent->IsTrusted(), eMouseClick,
                          aSourceEvent->mWidget, WidgetMouseEvent::eReal);
-  event.refPoint = aSourceEvent->refPoint;
+  event.mRefPoint = aSourceEvent->mRefPoint;
   uint32_t clickCount = 1;
   float pressure = 0;
   uint16_t inputSource = 0;
   WidgetMouseEvent* sourceMouseEvent = aSourceEvent->AsMouseEvent();
   if (sourceMouseEvent) {
     clickCount = sourceMouseEvent->clickCount;
     pressure = sourceMouseEvent->pressure;
     inputSource = sourceMouseEvent->inputSource;
@@ -3020,17 +3020,17 @@ Element::PostHandleEventForLinks(EventCh
           aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
         }
       }
     }
     break;
   }
   case eLegacyDOMActivate:
     {
-      if (aVisitor.mEvent->originalTarget == this) {
+      if (aVisitor.mEvent->mOriginalTarget == this) {
         nsAutoString target;
         GetLinkTarget(target);
         const InternalUIEvent* activeEvent = aVisitor.mEvent->AsUIEvent();
         MOZ_ASSERT(activeEvent);
         nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
                                     true, true, activeEvent->IsTrustable());
         aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
       }
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -687,17 +687,17 @@ nsIContent::PreHandleEvent(EventChainPre
   if ((aVisitor.mEvent->mMessage == eMouseOver ||
        aVisitor.mEvent->mMessage == eMouseOut ||
        aVisitor.mEvent->mMessage == ePointerOver ||
        aVisitor.mEvent->mMessage == ePointerOut) &&
       // Check if we should stop event propagation when event has just been
       // dispatched or when we're about to propagate from
       // chrome access only subtree or if we are about to propagate out of
       // a shadow root to a shadow root host.
-      ((this == aVisitor.mEvent->originalTarget &&
+      ((this == aVisitor.mEvent->mOriginalTarget &&
         !ChromeOnlyAccess()) || isAnonForEvents || GetShadowRoot())) {
      nsCOMPtr<nsIContent> relatedTarget =
        do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->relatedTarget);
     if (relatedTarget &&
         relatedTarget->OwnerDoc() == OwnerDoc()) {
 
       // In the web components case, we may need to stop propagation of events
       // at shadow root host.
@@ -712,17 +712,17 @@ nsIContent::PreHandleEvent(EventChainPre
       }
 
       // If current target is anonymous for events or we know that related
       // target is descendant of an element which is anonymous for events,
       // we may want to stop event propagation.
       // If this is the original target, aVisitor.mRelatedTargetIsInAnon
       // must be updated.
       if (isAnonForEvents || aVisitor.mRelatedTargetIsInAnon ||
-          (aVisitor.mEvent->originalTarget == this &&
+          (aVisitor.mEvent->mOriginalTarget == this &&
            (aVisitor.mRelatedTargetIsInAnon =
             relatedTarget->ChromeOnlyAccess()))) {
         nsIContent* anonOwner = FindChromeAccessOnlySubtreeOwner(this);
         if (anonOwner) {
           nsIContent* anonOwnerRelated =
             FindChromeAccessOnlySubtreeOwner(relatedTarget);
           if (anonOwnerRelated) {
             // Note, anonOwnerRelated may still be inside some other
@@ -731,17 +731,17 @@ nsIContent::PreHandleEvent(EventChainPre
             // propagates up in the DOM tree.
             while (anonOwner != anonOwnerRelated &&
                    anonOwnerRelated->ChromeOnlyAccess()) {
               anonOwnerRelated = FindChromeAccessOnlySubtreeOwner(anonOwnerRelated);
             }
             if (anonOwner == anonOwnerRelated) {
 #ifdef DEBUG_smaug
               nsCOMPtr<nsIContent> originalTarget =
-                do_QueryInterface(aVisitor.mEvent->originalTarget);
+                do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
               nsAutoString ot, ct, rt;
               if (originalTarget) {
                 originalTarget->NodeInfo()->NameAtom()->ToString(ot);
               }
               NodeInfo()->NameAtom()->ToString(ct);
               relatedTarget->NodeInfo()->NameAtom()->ToString(rt);
               printf("Stopping %s propagation:"
                      "\n\toriginalTarget=%s \n\tcurrentTarget=%s %s"
@@ -870,25 +870,26 @@ nsIContent::PreHandleEvent(EventChainPre
   }
 
   // Event may need to be retargeted if this is the root of a native
   // anonymous content subtree or event is dispatched somewhere inside XBL.
   if (isAnonForEvents) {
 #ifdef DEBUG
     // If a DOM event is explicitly dispatched using node.dispatchEvent(), then
     // all the events are allowed even in the native anonymous content..
-    nsCOMPtr<nsIContent> t = do_QueryInterface(aVisitor.mEvent->originalTarget);
+    nsCOMPtr<nsIContent> t =
+      do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     NS_ASSERTION(!t || !t->ChromeOnlyAccess() ||
                  aVisitor.mEvent->mClass != eMutationEventClass ||
                  aVisitor.mDOMEvent,
                  "Mutation event dispatched in native anonymous content!?!");
 #endif
     aVisitor.mEventTargetAtParent = parent;
   } else if (parent && aVisitor.mOriginalTargetIsInAnon) {
-    nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->target));
+    nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget));
     if (content && content->GetBindingParent() == parent) {
       aVisitor.mEventTargetAtParent = parent;
     }
   }
 
   // check for an anonymous parent
   // XXX XBL2/sXBL issue
   if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
--- a/dom/base/ResponsiveImageSelector.cpp
+++ b/dom/base/ResponsiveImageSelector.cpp
@@ -424,17 +424,16 @@ bool
 ResponsiveImageSelector::ComputeFinalWidthForCurrentViewport(double *aWidth)
 {
   unsigned int numSizes = mSizeQueries.Length();
   nsIDocument* doc = Document();
   nsIPresShell *presShell = doc ? doc->GetShell() : nullptr;
   nsPresContext *pctx = presShell ? presShell->GetPresContext() : nullptr;
 
   if (!pctx) {
-    MOZ_ASSERT(false, "Unable to find presContext for this content");
     return false;
   }
 
   MOZ_ASSERT(numSizes == mSizeValues.Length(),
              "mSizeValues length differs from mSizeQueries");
 
   unsigned int i;
   for (i = 0; i < numSizes; i++) {
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -569,17 +569,18 @@ AutoJSAPI::ReportException()
       errorGlobal = xpc::PrivilegedJunkScope();
     } else {
       errorGlobal = workers::GetCurrentThreadWorkerGlobal();
     }
   }
   JSAutoCompartment ac(cx(), errorGlobal);
   JS::Rooted<JS::Value> exn(cx());
   js::ErrorReport jsReport(cx());
-  if (StealException(&exn) && jsReport.init(cx(), exn)) {
+  if (StealException(&exn) &&
+      jsReport.init(cx(), exn, js::ErrorReport::WithSideEffects)) {
     if (mIsMainThread) {
       RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
 
       RefPtr<nsGlobalWindow> win = xpc::WindowGlobalOrNull(errorGlobal);
       if (!win) {
         // We run addons in a separate privileged compartment, but they still
         // expect to trigger the onerror handler of their associated DOM Window.
         win = xpc::AddonWindowOrNull(errorGlobal);
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -249,18 +249,19 @@ bool
 TextInputProcessor::IsValidEventTypeForComposition(
                       const WidgetKeyboardEvent& aKeyboardEvent) const
 {
   // The key event type of composition methods must be "" or "keydown".
   if (aKeyboardEvent.mMessage == eKeyDown) {
     return true;
   }
   if (aKeyboardEvent.mMessage == eUnidentifiedEvent &&
-      aKeyboardEvent.userType &&
-      nsDependentAtomString(aKeyboardEvent.userType).EqualsLiteral("on")) {
+      aKeyboardEvent.mSpecifiedEventType &&
+      nsDependentAtomString(
+        aKeyboardEvent.mSpecifiedEventType).EqualsLiteral("on")) {
     return true;
   }
   return false;
 }
 
 TextInputProcessor::EventDispatcherResult
 TextInputProcessor::MaybeDispatchKeydownForComposition(
                       const WidgetKeyboardEvent* aKeyboardEvent,
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1158412.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+    document.styleSheetSets.expando = null;
+    var otherDoc = document.implementation.createDocument("", "", null);
+    var otherSpan = otherDoc.createElementNS("http://www.w3.org/1999/xhtml", "span");
+
+    var img = document.createElementNS("http://www.w3.org/1999/xhtml", "img");
+    img.srcset = "data:,a 1w, data:,b 1w";
+    img.sizes = "1px";
+    otherSpan.appendChild(img);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
\ No newline at end of file
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -194,15 +194,16 @@ load 942979.html
 load 973401.html
 load 978646.html
 pref(dom.webcomponents.enabled,true) load 1024428-1.html
 load 1026714.html
 pref(dom.webcomponents.enabled,true) load 1027461-1.html
 pref(dom.webcomponents.enabled,true) load 1029710.html
 load 1154598.xhtml
 load 1157995.html
+load 1158412.html
 load 1181619.html
 load structured_clone_container_throws.html
 HTTP(..) load xhr_abortinprogress.html
 load xhr_empty_datauri.html
 load xhr_html_nullresponse.html
 load 1230422.html
 load 1251361.html
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -5280,30 +5280,30 @@ nsContentUtils::SetDataTransferInEvent(W
       return NS_ERROR_FAILURE;
     }
   } else {
     // A dataTransfer won't exist when a drag was started by some other
     // means, for instance calling the drag service directly, or a drag
     // from another application. In either case, a new dataTransfer should
     // be created that reflects the data.
     initialDataTransfer =
-      new DataTransfer(aDragEvent->target, aDragEvent->mMessage, true, -1);
+      new DataTransfer(aDragEvent->mTarget, aDragEvent->mMessage, true, -1);
 
     // now set it in the drag session so we don't need to create it again
     dragSession->SetDataTransfer(initialDataTransfer);
   }
 
   bool isCrossDomainSubFrameDrop = false;
   if (aDragEvent->mMessage == eDrop ||
       aDragEvent->mMessage == eLegacyDragDrop) {
     isCrossDomainSubFrameDrop = CheckForSubFrameDrop(dragSession, aDragEvent);
   }
 
   // each event should use a clone of the original dataTransfer.
-  initialDataTransfer->Clone(aDragEvent->target, aDragEvent->mMessage,
+  initialDataTransfer->Clone(aDragEvent->mTarget, aDragEvent->mMessage,
                              aDragEvent->mUserCancelled,
                              isCrossDomainSubFrameDrop,
                              getter_AddRefs(aDragEvent->mDataTransfer));
   if (NS_WARN_IF(!aDragEvent->mDataTransfer)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // for the dragenter and dragover events, initialize the drop effect
@@ -5364,17 +5364,17 @@ nsContentUtils::FilterDropEffect(uint32_
   return nsIDragService::DRAGDROP_ACTION_NONE;
 }
 
 /* static */
 bool
 nsContentUtils::CheckForSubFrameDrop(nsIDragSession* aDragSession,
                                      WidgetDragEvent* aDropEvent)
 {
-  nsCOMPtr<nsIContent> target = do_QueryInterface(aDropEvent->originalTarget);
+  nsCOMPtr<nsIContent> target = do_QueryInterface(aDropEvent->mOriginalTarget);
   if (!target) {
     return true;
   }
   
   nsIDocument* targetDoc = target->OwnerDoc();
   nsPIDOMWindowOuter* targetWin = targetDoc->GetWindow();
   if (!targetWin) {
     return true;
@@ -7721,17 +7721,17 @@ nsContentUtils::SendKeyEvent(nsIWidget* 
           break;
         default:
           event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
           break;
       }
       break;
   }
 
-  event.refPoint.x = event.refPoint.y = 0;
+  event.mRefPoint = LayoutDeviceIntPoint(0, 0);
   event.mTime = PR_IntervalNow();
   if (!(aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_NOT_SYNTHESIZED_FOR_TESTS)) {
     event.mFlags.mIsSynthesizedForTests = true;
   }
 
   if (aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_PREVENT_DEFAULT) {
     event.PreventDefaultBeforeDispatch();
   }
@@ -7803,17 +7803,17 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<
   event.clickCount = aClickCount;
   event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aIsSynthesized;
 
   nsPresContext* presContext = aPresShell->GetPresContext();
   if (!presContext)
     return NS_ERROR_FAILURE;
 
-  event.refPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+  event.mRefPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
   event.ignoreRootScrollFrame = aIgnoreRootScrollFrame;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   if (aToWindow) {
     nsCOMPtr<nsIPresShell> presShell;
     nsView* view = GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
     if (!presShell || !view) {
       return NS_ERROR_FAILURE;
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -684,17 +684,18 @@ nsDOMWindowUtils::SendPointerEventCommon
   event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aOptionalArgCount >= 10 ? aIsSynthesized : true;
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
     return NS_ERROR_FAILURE;
   }
 
-  event.refPoint = nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+  event.mRefPoint =
+    nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
   event.ignoreRootScrollFrame = aIgnoreRootScrollFrame;
 
   nsEventStatus status;
   if (aToWindow) {
     nsCOMPtr<nsIPresShell> presShell;
     nsView* view = nsContentUtils::GetViewToDispatchEvent(presContext, getter_AddRefs(presShell));
     if (!presShell || !view) {
       return NS_ERROR_FAILURE;
@@ -805,17 +806,18 @@ nsDOMWindowUtils::SendWheelEvent(float a
   wheelEvent.mLineOrPageDeltaX = aLineOrPageDeltaX;
   wheelEvent.mLineOrPageDeltaY = aLineOrPageDeltaY;
 
   wheelEvent.mTime = PR_Now() / 1000;
 
   nsPresContext* presContext = GetPresContext();
   NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
 
-  wheelEvent.refPoint = nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+  wheelEvent.mRefPoint =
+    nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
 
   widget->DispatchInputEvent(&wheelEvent);
 
   if (widget->AsyncPanZoomEnabled()) {
     // Computing overflow deltas is not compatible with APZ, so if APZ is
     // enabled, we skip testing it.
     return NS_OK;
   }
@@ -1294,17 +1296,18 @@ nsDOMWindowUtils::SendSimpleGestureEvent
   event.delta = aDelta;
   event.clickCount = aClickCount;
   event.mTime = PR_IntervalNow();
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext)
     return NS_ERROR_FAILURE;
 
-  event.refPoint = nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
+  event.mRefPoint =
+    nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
 
   nsEventStatus status;
   return widget->DispatchEvent(&event, status);
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ElementFromPoint(float aX, float aY,
                                    bool aIgnoreRootScrollFrame,
@@ -1792,17 +1795,17 @@ nsDOMWindowUtils::DispatchDOMEventViaPre
   *aRetVal = (status != nsEventStatus_eConsumeNoDefault);
   return NS_OK;
 }
 
 static void
 InitEvent(WidgetGUIEvent& aEvent, LayoutDeviceIntPoint* aPt = nullptr)
 {
   if (aPt) {
-    aEvent.refPoint = *aPt;
+    aEvent.mRefPoint = *aPt;
   }
   aEvent.mTime = PR_IntervalNow();
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendQueryContentEvent(uint32_t aType,
                                         uint32_t aOffset, uint32_t aLength,
                                         int32_t aX, int32_t aY,
@@ -2253,25 +2256,16 @@ ComputeAnimationValue(nsCSSProperty aPro
 
   RefPtr<nsStyleContext> styleContext =
     nsComputedDOMStyle::GetStyleContextForElement(aElement, nullptr, shell);
 
   if (!StyleAnimationValue::ComputeValue(aProperty, aElement, styleContext,
                                          aInput, false, aOutput)) {
     return false;
   }
-
-  // This matches TransExtractComputedValue in nsTransitionManager.cpp.
-  if (aProperty == eCSSProperty_visibility) {
-    MOZ_ASSERT(aOutput.GetUnit() == StyleAnimationValue::eUnit_Enumerated,
-               "unexpected unit");
-    aOutput.SetIntValue(aOutput.GetIntValue(),
-                        StyleAnimationValue::eUnit_Visibility);
-  }
-
   return true;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::AdvanceTimeAndRefresh(int64_t aMilliseconds)
 {
   // Before we advance the time, we should trigger any animations that are
   // waiting to start. This is because there are many tests that call this
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -269,16 +269,17 @@ GK_ATOM(datasources, "datasources")
 GK_ATOM(datetime, "datetime")
 GK_ATOM(dblclick, "dblclick")
 GK_ATOM(dd, "dd")
 GK_ATOM(debug, "debug")
 GK_ATOM(decimalFormat, "decimal-format")
 GK_ATOM(decimalSeparator, "decimal-separator")
 GK_ATOM(deck, "deck")
 GK_ATOM(declare, "declare")
+GK_ATOM(decoderDoctor, "decoder-doctor")
 GK_ATOM(decrement, "decrement")
 GK_ATOM(_default, "default")
 GK_ATOM(headerDefaultStyle, "default-style")
 GK_ATOM(defaultAction, "defaultAction")
 GK_ATOM(defaultchecked, "defaultchecked")
 GK_ATOM(defaultLabel, "defaultLabel")
 GK_ATOM(defaultselected, "defaultselected")
 GK_ATOM(defaultvalue, "defaultvalue")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3108,27 +3108,27 @@ nsGlobalWindow::PreHandleEvent(EventChai
     //Chances are this counter will overflow during the life of the
     //process, but that's OK for our case. Means we get a little
     //more entropy.
     if (count++ % 100 == 0) {
       //Since the high bits seem to be zero's most of the time,
       //let's only take the lowest half of the point structure.
       int16_t myCoord[2];
 
-      myCoord[0] = aVisitor.mEvent->refPoint.x;
-      myCoord[1] = aVisitor.mEvent->refPoint.y;
+      myCoord[0] = aVisitor.mEvent->mRefPoint.x;
+      myCoord[1] = aVisitor.mEvent->mRefPoint.y;
       gEntropyCollector->RandomUpdate((void*)myCoord, sizeof(myCoord));
       gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->mTime),
                                       sizeof(uint32_t));
     }
   } else if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
     // QIing to window so that we can keep the old behavior also in case
     // a child window is handling resize.
     nsCOMPtr<nsPIDOMWindowInner> window =
-      do_QueryInterface(aVisitor.mEvent->originalTarget);
+      do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     if (window) {
       mIsHandlingResizeEvent = true;
     }
   } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) {
     gMouseDown = true;
   } else if ((msg == eMouseUp || msg == eDragEnd) &&
              aVisitor.mEvent->IsTrusted()) {
     gMouseDown = false;
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -917,16 +917,20 @@ nsLocation::Replace(const nsAString& aUr
   NS_ENSURE_SUCCESS(rv, rv);
 
   return SetHrefWithBase(aUrl, oldUri, true);
 }
 
 NS_IMETHODIMP
 nsLocation::Assign(const nsAString& aUrl)
 {
+  if (JSContext *cx = nsContentUtils::GetCurrentJSContext()) {
+    return SetHrefWithContext(cx, aUrl, false);
+  }
+
   nsAutoString oldHref;
   nsresult result = NS_OK;
 
   result = GetHref(oldHref);
 
   if (NS_SUCCEEDED(result)) {
     nsCOMPtr<nsIURI> oldUri;
 
--- a/dom/camera/CameraPreviewMediaStream.cpp
+++ b/dom/camera/CameraPreviewMediaStream.cpp
@@ -24,17 +24,17 @@ static const TrackID TRACK_VIDEO = 2;
 void
 FakeMediaStreamGraph::DispatchToMainThreadAfterStreamStateUpdate(already_AddRefed<nsIRunnable> aRunnable)
 {
   nsCOMPtr<nsIRunnable> task = aRunnable;
   NS_DispatchToMainThread(task);
 }
 
 CameraPreviewMediaStream::CameraPreviewMediaStream(DOMMediaStream* aWrapper)
-  : MediaStream(aWrapper)
+  : ProcessedMediaStream(aWrapper)
   , mMutex("mozilla::camera::CameraPreviewMediaStream")
   , mInvalidatePending(0)
   , mDiscardedFrames(0)
   , mRateLimit(false)
   , mTrackCreated(false)
 {
   SetGraphImpl(
       MediaStreamGraph::GetInstance(
@@ -126,16 +126,23 @@ CameraPreviewMediaStream::Invalidate()
   --mInvalidatePending;
   for (nsTArray<RefPtr<VideoFrameContainer> >::size_type i = 0; i < mVideoOutputs.Length(); ++i) {
     VideoFrameContainer* output = mVideoOutputs[i];
     output->Invalidate();
   }
 }
 
 void
+CameraPreviewMediaStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
+                                       uint32_t aFlags)
+{
+  return;
+}
+
+void
 CameraPreviewMediaStream::RateLimit(bool aLimit)
 {
   mRateLimit = aLimit;
 }
 
 void
 CameraPreviewMediaStream::SetCurrentFrame(const gfx::IntSize& aIntrinsicSize, Image* aImage)
 {
--- a/dom/camera/CameraPreviewMediaStream.h
+++ b/dom/camera/CameraPreviewMediaStream.h
@@ -30,17 +30,17 @@ protected:
 
 /**
  * This is a stream for camera preview.
  *
  * XXX It is a temporary fix of SourceMediaStream.
  * A camera preview requests no delay and no buffering stream,
  * but the SourceMediaStream does not support it.
  */
-class CameraPreviewMediaStream : public MediaStream
+class CameraPreviewMediaStream : public ProcessedMediaStream
 {
   typedef mozilla::layers::Image Image;
 
 public:
   explicit CameraPreviewMediaStream(DOMMediaStream* aWrapper);
 
   virtual void AddAudioOutput(void* aKey) override;
   virtual void SetAudioOutputVolume(void* aKey, float aVolume) override;
@@ -51,16 +51,18 @@ public:
   virtual void Resume() override {}
   virtual void AddListener(MediaStreamListener* aListener) override;
   virtual void RemoveListener(MediaStreamListener* aListener) override;
   virtual void Destroy() override;
   void OnPreviewStateChange(bool aActive);
 
   void Invalidate();
 
+  void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
+
   // Call these on any thread.
   void SetCurrentFrame(const gfx::IntSize& aIntrinsicSize, Image* aImage);
   void ClearCurrentFrame();
   void RateLimit(bool aLimit);
 
 protected:
   // mMutex protects all the class' fields.
   // This class is not registered to MediaStreamGraph.
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -257,16 +257,17 @@ nsDOMCameraControl::nsDOMCameraControl(u
   , mWindow(aWindow)
   , mPreviewState(CameraControlListener::kPreviewStopped)
   , mRecording(false)
   , mRecordingStoppedDeferred(false)
   , mSetInitialConfig(false)
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   mInput = new CameraPreviewMediaStream(this);
+  mOwnedStream = mInput;
 
   BindToOwner(aWindow);
 
   RefPtr<DOMCameraConfiguration> initialConfig =
     new DOMCameraConfiguration(aInitialConfig);
 
   // Create and initialize the underlying camera.
   ICameraControl::Configuration config;
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -3768,17 +3768,17 @@ struct MOZ_STACK_CLASS CanvasBidiProcess
           target->Stroke(path, patForStyle, strokeOpts, drawOpts);
           buffer.mGlyphs++;
         }
       }
     }
   }
 
   // current text run
-  nsAutoPtr<gfxTextRun> mTextRun;
+  UniquePtr<gfxTextRun> mTextRun;
 
   // pointer to a screen reference context used to measure text and such
   RefPtr<DrawTarget> mDrawTarget;
 
   // Pointer to the draw target we should fill our text to
   CanvasRenderingContext2D *mCtx;
 
   // position of the left side of the string, alphabetic baseline
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -1542,19 +1542,19 @@ ContentEventHandler::OnQueryCharacterAtP
     NS_ENSURE_TRUE(rootFrame, NS_ERROR_FAILURE);
     rootWidget = rootFrame->GetNearestWidget();
     NS_ENSURE_TRUE(rootWidget, NS_ERROR_FAILURE);
   }
 
   WidgetQueryContentEvent eventOnRoot(true, eQueryCharacterAtPoint,
                                       rootWidget);
   eventOnRoot.mUseNativeLineBreak = aEvent->mUseNativeLineBreak;
-  eventOnRoot.refPoint = aEvent->refPoint;
+  eventOnRoot.mRefPoint = aEvent->mRefPoint;
   if (rootWidget != aEvent->mWidget) {
-    eventOnRoot.refPoint += aEvent->mWidget->WidgetToScreenOffset() -
+    eventOnRoot.mRefPoint += aEvent->mWidget->WidgetToScreenOffset() -
       rootWidget->WidgetToScreenOffset();
   }
   nsPoint ptInRoot =
     nsLayoutUtils::GetEventCoordinatesRelativeTo(&eventOnRoot, rootFrame);
 
   nsIFrame* targetFrame = nsLayoutUtils::GetFrameForPoint(rootFrame, ptInRoot);
   if (!targetFrame || !targetFrame->GetContent() ||
       !nsContentUtils::ContentIsDescendantOf(targetFrame->GetContent(),
@@ -1640,17 +1640,17 @@ ContentEventHandler::OnQueryDOMWidgetHit
   NS_ENSURE_TRUE(aEvent->mWidget, NS_ERROR_FAILURE);
 
   nsIDocument* doc = mPresShell->GetDocument();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
   nsIFrame* docFrame = mPresShell->GetRootFrame();
   NS_ENSURE_TRUE(docFrame, NS_ERROR_FAILURE);
 
   LayoutDeviceIntPoint eventLoc =
-    aEvent->refPoint + aEvent->mWidget->WidgetToScreenOffset();
+    aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
   nsIntRect docFrameRect = docFrame->GetScreenRect(); // Returns CSS pixels
   CSSIntPoint eventLocCSS(
     mPresContext->DevPixelsToIntCSSPixels(eventLoc.x) - docFrameRect.x,
     mPresContext->DevPixelsToIntCSSPixels(eventLoc.y) - docFrameRect.y);
 
   Element* contentUnderMouse =
     doc->ElementFromPointHelper(eventLocCSS.x, eventLocCSS.y, false, false);
   if (contentUnderMouse) {
--- a/dom/events/DragEvent.cpp
+++ b/dom/events/DragEvent.cpp
@@ -20,17 +20,17 @@ DragEvent::DragEvent(EventTarget* aOwner
                         new WidgetDragEvent(false, eVoidEvent, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
-    mEvent->refPoint.x = mEvent->refPoint.y = 0;
+    mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     mEvent->AsMouseEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(DragEvent, MouseEvent)
 NS_IMPL_RELEASE_INHERITED(DragEvent, MouseEvent)
 
 NS_INTERFACE_MAP_BEGIN(DragEvent)
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -148,19 +148,19 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Event)
 NS_IMPL_CYCLE_COLLECTION_CLASS(Event)
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Event)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event)
   if (tmp->mEventIsInternal) {
-    tmp->mEvent->target = nullptr;
-    tmp->mEvent->currentTarget = nullptr;
-    tmp->mEvent->originalTarget = nullptr;
+    tmp->mEvent->mTarget = nullptr;
+    tmp->mEvent->mCurrentTarget = nullptr;
+    tmp->mEvent->mOriginalTarget = nullptr;
     switch (tmp->mEvent->mClass) {
       case eMouseEventClass:
       case eMouseScrollEventClass:
       case eWheelEventClass:
       case eSimpleGestureEventClass:
       case ePointerEventClass:
         tmp->mEvent->AsMouseEventBase()->relatedTarget = nullptr;
         break;
@@ -186,19 +186,19 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Ev
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExplicitOriginalTarget);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event)
   if (tmp->mEventIsInternal) {
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->target)
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->currentTarget)
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->originalTarget)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mTarget)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mCurrentTarget)
+    NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalTarget)
     switch (tmp->mEvent->mClass) {
       case eMouseEventClass:
       case eMouseScrollEventClass:
       case eWheelEventClass:
       case eSimpleGestureEventClass:
       case ePointerEventClass:
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
         cb.NoteXPCOMChild(tmp->mEvent->AsMouseEventBase()->relatedTarget);
@@ -253,58 +253,60 @@ Event::IsChrome(JSContext* aCx) const
     xpc::AccessCheck::isChrome(js::GetContextCompartment(aCx)) :
     mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
 }
 
 // nsIDOMEventInterface
 NS_METHOD
 Event::GetType(nsAString& aType)
 {
-  if (!mIsMainThreadEvent || !mEvent->typeString.IsEmpty()) {
-    aType = mEvent->typeString;
+  if (!mIsMainThreadEvent || !mEvent->mSpecifiedEventTypeString.IsEmpty()) {
+    aType = mEvent->mSpecifiedEventTypeString;
     return NS_OK;
   }
   const char* name = GetEventName(mEvent->mMessage);
 
   if (name) {
     CopyASCIItoUTF16(name, aType);
     return NS_OK;
-  } else if (mEvent->mMessage == eUnidentifiedEvent && mEvent->userType) {
-    aType = Substring(nsDependentAtomString(mEvent->userType), 2); // Remove "on"
-    mEvent->typeString = aType;
+  } else if (mEvent->mMessage == eUnidentifiedEvent &&
+             mEvent->mSpecifiedEventType) {
+    // Remove "on"
+    aType = Substring(nsDependentAtomString(mEvent->mSpecifiedEventType), 2);
+    mEvent->mSpecifiedEventTypeString = aType;
     return NS_OK;
   }
 
   aType.Truncate();
   return NS_OK;
 }
 
 static EventTarget*
 GetDOMEventTarget(nsIDOMEventTarget* aTarget)
 {
   return aTarget ? aTarget->GetTargetForDOMEvent() : nullptr;
 }
 
 EventTarget*
 Event::GetTarget() const
 {
-  return GetDOMEventTarget(mEvent->target);
+  return GetDOMEventTarget(mEvent->mTarget);
 }
 
 NS_METHOD
 Event::GetTarget(nsIDOMEventTarget** aTarget)
 {
   NS_IF_ADDREF(*aTarget = GetTarget());
   return NS_OK;
 }
 
 EventTarget*
 Event::GetCurrentTarget() const
 {
-  return GetDOMEventTarget(mEvent->currentTarget);
+  return GetDOMEventTarget(mEvent->mCurrentTarget);
 }
 
 NS_IMETHODIMP
 Event::GetCurrentTarget(nsIDOMEventTarget** aCurrentTarget)
 {
   NS_IF_ADDREF(*aCurrentTarget = GetCurrentTarget());
   return NS_OK;
 }
@@ -312,17 +314,17 @@ Event::GetCurrentTarget(nsIDOMEventTarge
 //
 // Get the actual event target node (may have been retargeted for mouse events)
 //
 already_AddRefed<nsIContent>
 Event::GetTargetFromFrame()
 {
   if (!mPresContext) { return nullptr; }
 
-  // Get the target frame (have to get the ESM first)
+  // Get the mTarget frame (have to get the ESM first)
   nsIFrame* targetFrame = mPresContext->EventStateManager()->GetEventTarget();
   if (!targetFrame) { return nullptr; }
 
   // get the real content
   nsCOMPtr<nsIContent> realEventContent;
   targetFrame->GetContentForEvent(mEvent, getter_AddRefs(realEventContent));
   return realEventContent.forget();
 }
@@ -341,18 +343,18 @@ Event::GetExplicitOriginalTarget(nsIDOME
 {
   NS_IF_ADDREF(*aRealEventTarget = GetExplicitOriginalTarget());
   return NS_OK;
 }
 
 EventTarget*
 Event::GetOriginalTarget() const
 {
-  if (mEvent->originalTarget) {
-    return GetDOMEventTarget(mEvent->originalTarget);
+  if (mEvent->mOriginalTarget) {
+    return GetDOMEventTarget(mEvent->mOriginalTarget);
   }
 
   return GetTarget();
 }
 
 NS_IMETHODIMP
 Event::GetOriginalTarget(nsIDOMEventTarget** aOriginalTarget)
 {
@@ -416,18 +418,18 @@ Event::Constructor(const GlobalObject& a
   return e.forget();
 }
 
 uint16_t
 Event::EventPhase() const
 {
   // Note, remember to check that this works also
   // if or when Bug 235441 is fixed.
-  if ((mEvent->currentTarget &&
-       mEvent->currentTarget == mEvent->target) ||
+  if ((mEvent->mCurrentTarget &&
+       mEvent->mCurrentTarget == mEvent->mTarget) ||
        mEvent->mFlags.InTargetPhase()) {
     return nsIDOMEvent::AT_TARGET;
   }
   if (mEvent->mFlags.mInCapturePhase) {
     return nsIDOMEvent::CAPTURING_PHASE;
   }
   if (mEvent->mFlags.mInBubblingPhase) {
     return nsIDOMEvent::BUBBLING_PHASE;
@@ -525,41 +527,42 @@ Event::PreventDefaultInternal(bool aCall
     return;
   }
 
   WidgetDragEvent* dragEvent = mEvent->AsDragEvent();
   if (!dragEvent) {
     return;
   }
 
-  nsCOMPtr<nsINode> node = do_QueryInterface(mEvent->currentTarget);
+  nsCOMPtr<nsINode> node = do_QueryInterface(mEvent->mCurrentTarget);
   if (!node) {
-    nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(mEvent->currentTarget);
+    nsCOMPtr<nsPIDOMWindowOuter> win =
+      do_QueryInterface(mEvent->mCurrentTarget);
     if (!win) {
       return;
     }
     node = win->GetExtantDoc();
   }
   if (!nsContentUtils::IsChromeDoc(node->OwnerDoc())) {
     dragEvent->mDefaultPreventedOnContent = true;
   }
 }
 
 void
 Event::SetEventType(const nsAString& aEventTypeArg)
 {
   if (mIsMainThreadEvent) {
-    mEvent->typeString.Truncate();
-    mEvent->userType =
+    mEvent->mSpecifiedEventTypeString.Truncate();
+    mEvent->mSpecifiedEventType =
       nsContentUtils::GetEventMessageAndAtom(aEventTypeArg, mEvent->mClass,
                                              &(mEvent->mMessage));
   } else {
-    mEvent->userType = nullptr;
+    mEvent->mSpecifiedEventType = nullptr;
     mEvent->mMessage = eUnidentifiedEvent;
-    mEvent->typeString = aEventTypeArg;
+    mEvent->mSpecifiedEventTypeString = aEventTypeArg;
   }
 }
 
 void
 Event::InitEvent(const nsAString& aEventTypeArg,
                  bool aCanBubbleArg,
                  bool aCancelableArg)
 {
@@ -579,18 +582,18 @@ Event::InitEvent(const nsAString& aEvent
   mEvent->mFlags.mCancelable = aCancelableArg;
 
   mEvent->mFlags.mDefaultPrevented = false;
   mEvent->mFlags.mDefaultPreventedByContent = false;
   mEvent->mFlags.mDefaultPreventedByChrome = false;
 
   // Clearing the old targets, so that the event is targeted correctly when
   // re-dispatching it.
-  mEvent->target = nullptr;
-  mEvent->originalTarget = nullptr;
+  mEvent->mTarget = nullptr;
+  mEvent->mOriginalTarget = nullptr;
 }
 
 NS_IMETHODIMP
 Event::DuplicatePrivateData()
 {
   NS_ASSERTION(mEvent, "No WidgetEvent for Event duplication!");
   if (mEventIsInternal) {
     return NS_OK;
@@ -602,17 +605,17 @@ Event::DuplicatePrivateData()
   mPrivateDataDuplicated = true;
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Event::SetTarget(nsIDOMEventTarget* aTarget)
 {
-  mEvent->target = do_QueryInterface(aTarget);
+  mEvent->mTarget = do_QueryInterface(aTarget);
   return NS_OK;
 }
 
 NS_IMETHODIMP_(bool)
 Event::IsDispatchStopped()
 {
   return mEvent->PropagationStopped();
 }
@@ -984,20 +987,20 @@ Event::GetClientCoords(nsPresContext* aP
 
 // static
 CSSIntPoint
 Event::GetOffsetCoords(nsPresContext* aPresContext,
                        WidgetEvent* aEvent,
                        LayoutDeviceIntPoint aPoint,
                        CSSIntPoint aDefaultPoint)
 {
-  if (!aEvent->target) {
+  if (!aEvent->mTarget) {
     return GetPageCoords(aPresContext, aEvent, aPoint, aDefaultPoint);
   }
-  nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->target);
+  nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mTarget);
   if (!content || !aPresContext) {
     return CSSIntPoint(0, 0);
   }
   nsCOMPtr<nsIPresShell> shell = aPresContext->GetPresShell();
   if (!shell) {
     return CSSIntPoint(0, 0);
   }
   shell->FlushPendingNotifications(Flush_Layout);
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -298,17 +298,17 @@ public:
     : mEvent(aEvent->InternalDOMEvent()),
       mOrigMessage(mEvent->mEvent->mMessage)
   {
     MOZ_ASSERT(aOverridingMessage != mOrigMessage,
                "Don't use this class if you're not actually overriding");
     MOZ_ASSERT(aOverridingMessage != eUnidentifiedEvent,
                "Only use this class with a valid overriding EventMessage");
     MOZ_ASSERT(mOrigMessage != eUnidentifiedEvent &&
-               mEvent->mEvent->typeString.IsEmpty(),
+               mEvent->mEvent->mSpecifiedEventTypeString.IsEmpty(),
                "Only use this class on events whose overridden type is "
                "known (so we can restore it properly)");
 
     mEvent->mEvent->mMessage = aOverridingMessage;
   }
 
   ~EventMessageAutoOverride()
   {
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -254,23 +254,23 @@ public:
     }
     if (!mManager) {
       if (!MayHaveListenerManager() && !aCd.MayHaveNewListenerManager()) {
         return;
       }
       mManager = mTarget->GetExistingListenerManager();
     }
     if (mManager) {
-      NS_ASSERTION(aVisitor.mEvent->currentTarget == nullptr,
+      NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr,
                    "CurrentTarget should be null!");
       mManager->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent,
                             &aVisitor.mDOMEvent,
                             CurrentTarget(),
                             &aVisitor.mEventStatus);
-      NS_ASSERTION(aVisitor.mEvent->currentTarget == nullptr,
+      NS_ASSERTION(aVisitor.mEvent->mCurrentTarget == nullptr,
                    "CurrentTarget should be null!");
     }
   }
 
   /**
    * Copies mItemFlags and mItemData to aVisitor and calls PostHandleEvent.
    */
   void PostHandleEvent(EventChainPostVisitor& aVisitor);
@@ -328,17 +328,17 @@ EventTargetChainItem::PostHandleEvent(Ev
 void
 EventTargetChainItem::HandleEventTargetChain(
                         nsTArray<EventTargetChainItem>& aChain,
                         EventChainPostVisitor& aVisitor,
                         EventDispatchingCallback* aCallback,
                         ELMCreationDetector& aCd)
 {
   // Save the target so that it can be restored later.
-  nsCOMPtr<EventTarget> firstTarget = aVisitor.mEvent->target;
+  nsCOMPtr<EventTarget> firstTarget = aVisitor.mEvent->mTarget;
   uint32_t chainLength = aChain.Length();
 
   // Capture
   aVisitor.mEvent->mFlags.mInCapturePhase = true;
   aVisitor.mEvent->mFlags.mInBubblingPhase = false;
   for (uint32_t i = chainLength - 1; i > 0; --i) {
     EventTargetChainItem& item = aChain[i];
     if ((!aVisitor.mEvent->mFlags.mNoContentDispatch ||
@@ -348,17 +348,17 @@ EventTargetChainItem::HandleEventTargetC
     }
 
     if (item.GetNewTarget()) {
       // item is at anonymous boundary. Need to retarget for the child items.
       for (uint32_t j = i; j > 0; --j) {
         uint32_t childIndex = j - 1;
         EventTarget* newTarget = aChain[childIndex].GetNewTarget();
         if (newTarget) {
-          aVisitor.mEvent->target = newTarget;
+          aVisitor.mEvent->mTarget = newTarget;
           break;
         }
       }
     }
   }
 
   // Target
   aVisitor.mEvent->mFlags.mInBubblingPhase = true;
@@ -375,17 +375,17 @@ EventTargetChainItem::HandleEventTargetC
   // Bubble
   aVisitor.mEvent->mFlags.mInCapturePhase = false;
   for (uint32_t i = 1; i < chainLength; ++i) {
     EventTargetChainItem& item = aChain[i];
     EventTarget* newTarget = item.GetNewTarget();
     if (newTarget) {
       // Item is at anonymous boundary. Need to retarget for the current item
       // and for parent items.
-      aVisitor.mEvent->target = newTarget;
+      aVisitor.mEvent->mTarget = newTarget;
     }
 
     if (aVisitor.mEvent->mFlags.mBubbles || newTarget) {
       if ((!aVisitor.mEvent->mFlags.mNoContentDispatch ||
            item.ForceContentDispatch()) &&
           !aVisitor.mEvent->PropagationStopped()) {
         item.HandleEvent(aVisitor, aCd);
       }
@@ -398,27 +398,27 @@ EventTargetChainItem::HandleEventTargetC
 
   if (!aVisitor.mEvent->mFlags.mInSystemGroup) {
     // Dispatch to the system event group.  Make sure to clear the
     // STOP_DISPATCH flag since this resets for each event group.
     aVisitor.mEvent->mFlags.mPropagationStopped = false;
     aVisitor.mEvent->mFlags.mImmediatePropagationStopped = false;
 
     // Setting back the original target of the event.
-    aVisitor.mEvent->target = aVisitor.mEvent->originalTarget;
+    aVisitor.mEvent->mTarget = aVisitor.mEvent->mOriginalTarget;
 
     // Special handling if PresShell (or some other caller)
     // used a callback object.
     if (aCallback) {
       aCallback->HandleEvent(aVisitor);
     }
 
     // Retarget for system event group (which does the default handling too).
     // Setting back the target which was used also for default event group.
-    aVisitor.mEvent->target = firstTarget;
+    aVisitor.mEvent->mTarget = firstTarget;
     aVisitor.mEvent->mFlags.mInSystemGroup = true;
     HandleEventTargetChain(aChain,
                            aVisitor,
                            aCallback,
                            aCd);
     aVisitor.mEvent->mFlags.mInSystemGroup = false;
 
     // After dispatch, clear all the propagation flags so that
@@ -510,34 +510,34 @@ EventDispatcher::Dispatch(nsISupports* a
 
   if (aEvent->mFlags.mRetargetToNonNativeAnonymous) {
     nsCOMPtr<nsIContent> content = do_QueryInterface(target);
     if (content && content->IsInNativeAnonymousSubtree()) {
       nsCOMPtr<EventTarget> newTarget =
         do_QueryInterface(content->FindFirstNonChromeOnlyAccessContent());
       NS_ENSURE_STATE(newTarget);
 
-      aEvent->originalTarget = target;
+      aEvent->mOriginalTarget = target;
       target = newTarget;
       retargeted = true;
     }
   }
 
   if (aEvent->mFlags.mOnlyChromeDispatch) {
     nsCOMPtr<nsIDocument> doc;
     if (!IsEventTargetChrome(target, getter_AddRefs(doc)) && doc) {
       nsPIDOMWindowInner* win = doc->GetInnerWindow();
       // If we can't dispatch the event to chrome, do nothing.
       EventTarget* piTarget = win ? win->GetParentTarget() : nullptr;
       if (!piTarget) {
         return NS_OK;
       }
 
       // Set the target to be the original dispatch target,
-      aEvent->target = target;
+      aEvent->mTarget = target;
       // but use chrome event handler or TabChildGlobal for event target chain.
       target = piTarget;
     } else if (NS_WARN_IF(!doc)) {
       return NS_ERROR_UNEXPECTED;
     }
   }
 
 #ifdef DEBUG
@@ -585,40 +585,40 @@ EventDispatcher::Dispatch(nsISupports* a
   MOZ_ASSERT(&chain[0] == targetEtci);
   if (!targetEtci->IsValid()) {
     EventTargetChainItem::DestroyLast(chain, targetEtci);
     return NS_ERROR_FAILURE;
   }
 
   // Make sure that nsIDOMEvent::target and nsIDOMEvent::originalTarget
   // point to the last item in the chain.
-  if (!aEvent->target) {
+  if (!aEvent->mTarget) {
     // Note, CurrentTarget() points always to the object returned by
     // GetTargetForEventTargetChain().
-    aEvent->target = targetEtci->CurrentTarget();
+    aEvent->mTarget = targetEtci->CurrentTarget();
   } else {
     // XXX But if the target is already set, use that. This is a hack
     //     for the 'load', 'beforeunload' and 'unload' events,
     //     which are dispatched to |window| but have document as their target.
     //
     // Make sure that the event target points to the right object.
-    aEvent->target = aEvent->target->GetTargetForEventTargetChain();
-    NS_ENSURE_STATE(aEvent->target);
+    aEvent->mTarget = aEvent->mTarget->GetTargetForEventTargetChain();
+    NS_ENSURE_STATE(aEvent->mTarget);
   }
 
   if (retargeted) {
-    aEvent->originalTarget =
-      aEvent->originalTarget->GetTargetForEventTargetChain();
-    NS_ENSURE_STATE(aEvent->originalTarget);
+    aEvent->mOriginalTarget =
+      aEvent->mOriginalTarget->GetTargetForEventTargetChain();
+    NS_ENSURE_STATE(aEvent->mOriginalTarget);
   }
   else {
-    aEvent->originalTarget = aEvent->target;
+    aEvent->mOriginalTarget = aEvent->mTarget;
   }
 
-  nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->originalTarget);
+  nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mOriginalTarget);
   bool isInAnon = (content && (content->IsInAnonymousSubtree() ||
                                content->IsInShadowTree()));
 
   aEvent->mFlags.mIsBeingDispatched = true;
 
   // Create visitor object and start event dispatching.
   // PreHandleEvent for the original target.
   nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore;
@@ -632,17 +632,17 @@ EventDispatcher::Dispatch(nsISupports* a
     targetEtci = EventTargetChainItemForChromeTarget(chain, content);
     NS_ENSURE_STATE(targetEtci);
     MOZ_ASSERT(&chain[0] == targetEtci);
     targetEtci->PreHandleEvent(preVisitor);
   }
   if (preVisitor.mCanHandle) {
     // At least the original target can handle the event.
     // Setting the retarget to the |target| simplifies retargeting code.
-    nsCOMPtr<EventTarget> t = do_QueryInterface(aEvent->target);
+    nsCOMPtr<EventTarget> t = do_QueryInterface(aEvent->mTarget);
     targetEtci->SetNewTarget(t);
     EventTargetChainItem* topEtci = targetEtci;
     targetEtci = nullptr;
     while (preVisitor.mParentTarget) {
       EventTarget* parentTarget = preVisitor.mParentTarget;
       EventTargetChainItem* parentEtci =
         EventTargetChainItem::Create(chain, preVisitor.mParentTarget, topEtci);
       if (!parentEtci->IsValid()) {
@@ -650,17 +650,17 @@ EventDispatcher::Dispatch(nsISupports* a
         rv = NS_ERROR_FAILURE;
         break;
       }
 
       // Item needs event retargetting.
       if (preVisitor.mEventTargetAtParent) {
         // Need to set the target of the event
         // so that also the next retargeting works.
-        preVisitor.mEvent->target = preVisitor.mEventTargetAtParent;
+        preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent;
         parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent);
       }
 
       parentEtci->PreHandleEvent(preVisitor);
       if (preVisitor.mCanHandle) {
         topEtci = parentEtci;
       } else {
         EventTargetChainItem::DestroyLast(chain, parentEtci);
@@ -745,18 +745,18 @@ EventDispatcher::DispatchDOMEvent(nsISup
                                   nsEventStatus* aEventStatus)
 {
   if (aDOMEvent) {
     WidgetEvent* innerEvent = aDOMEvent->WidgetEventPtr();
     NS_ENSURE_TRUE(innerEvent, NS_ERROR_ILLEGAL_VALUE);
 
     bool dontResetTrusted = false;
     if (innerEvent->mFlags.mDispatchedAtLeastOnce) {
-      innerEvent->target = nullptr;
-      innerEvent->originalTarget = nullptr;
+      innerEvent->mTarget = nullptr;
+      innerEvent->mOriginalTarget = nullptr;
     } else {
       aDOMEvent->GetIsTrusted(&dontResetTrusted);
     }
 
     if (!dontResetTrusted) {
       //Check security state to determine if dispatcher is trusted
       bool trusted = NS_IsMainThread() ? nsContentUtils::LegacyIsCallerChromeOrNativeCode()
                                        : mozilla::dom::workers::IsCurrentThreadRunningChromeWorker();
--- a/dom/events/EventDispatcher.h
+++ b/dom/events/EventDispatcher.h
@@ -235,18 +235,18 @@ class EventDispatcher
 {
 public:
   /**
    * aTarget should QI to EventTarget.
    * If the target of aEvent is set before calling this method, the target of 
    * aEvent is used as the target (unless there is event
    * retargeting) and the originalTarget of the DOM Event.
    * aTarget is always used as the starting point for constructing the event
-   * target chain, no matter what the value of aEvent->target is.
-   * In other words, aEvent->target is only a property of the event and it has
+   * target chain, no matter what the value of aEvent->mTarget is.
+   * In other words, aEvent->mTarget is only a property of the event and it has
    * nothing to do with the construction of the event target chain.
    * Neither aTarget nor aEvent is allowed to be nullptr.
    *
    * If aTargets is non-null, event target chain will be created, but
    * event won't be handled. In this case aEvent->mMessage should be
    * eVoidEvent.
    * @note Use this method when dispatching a WidgetEvent.
    */
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -669,19 +669,19 @@ EventListenerManager::ListenerCanHandle(
   // true even when aEvent->mMessage == eUnidentifiedEvent and
   // aListener=>mEventMessage != eUnidentifiedEvent as long as the atoms are
   // the same
   if (aListener->mAllEvents) {
     return true;
   }
   if (aEvent->mMessage == eUnidentifiedEvent) {
     if (mIsMainThreadELM) {
-      return aListener->mTypeAtom == aEvent->userType;
+      return aListener->mTypeAtom == aEvent->mSpecifiedEventType;
     }
-    return aListener->mTypeString.Equals(aEvent->typeString);
+    return aListener->mTypeString.Equals(aEvent->mSpecifiedEventTypeString);
   }
   MOZ_ASSERT(mIsMainThreadELM);
   return aListener->mEventMessage == aEventMessage;
 }
 
 void
 EventListenerManager::AddEventListenerByType(
                         const EventListenerHolder& aListenerHolder,
@@ -1220,26 +1220,26 @@ EventListenerManager::HandleEventInterna
         hasListener = true;
         hasListenerForCurrentGroup = hasListenerForCurrentGroup ||
           listener->mFlags.mInSystemGroup == aEvent->mFlags.mInSystemGroup;
         if (listener->IsListening(aEvent) &&
             (aEvent->IsTrusted() || listener->mFlags.mAllowUntrustedEvents)) {
           if (!*aDOMEvent) {
             // This is tiny bit slow, but happens only once per event.
             nsCOMPtr<EventTarget> et =
-              do_QueryInterface(aEvent->originalTarget);
+              do_QueryInterface(aEvent->mOriginalTarget);
             RefPtr<Event> event = EventDispatcher::CreateEvent(et, aPresContext,
                                                                aEvent,
                                                                EmptyString());
             event.forget(aDOMEvent);
           }
           if (*aDOMEvent) {
-            if (!aEvent->currentTarget) {
-              aEvent->currentTarget = aCurrentTarget->GetTargetForDOMEvent();
-              if (!aEvent->currentTarget) {
+            if (!aEvent->mCurrentTarget) {
+              aEvent->mCurrentTarget = aCurrentTarget->GetTargetForDOMEvent();
+              if (!aEvent->mCurrentTarget) {
                 break;
               }
             }
             if (usingLegacyMessage && !legacyAutoOverride) {
               // Override the aDOMEvent's event-message (its .type) until we
               // finish traversing listeners (when legacyAutoOverride destructs)
               legacyAutoOverride.emplace(*aDOMEvent, eventMessage);
             }
@@ -1295,21 +1295,21 @@ EventListenerManager::HandleEventInterna
     MOZ_ASSERT(GetLegacyEventMessage(legacyEventMessage) == legacyEventMessage,
                "Legacy event messages should not themselves have legacy versions");
 
     // Recheck our listeners, using the legacy event message we just looked up:
     eventMessage = legacyEventMessage;
     usingLegacyMessage = true;
   }
 
-  aEvent->currentTarget = nullptr;
+  aEvent->mCurrentTarget = nullptr;
 
   if (mIsMainThreadELM && !hasListener) {
     mNoListenerForEvent = aEvent->mMessage;
-    mNoListenerForEventAtom = aEvent->userType;
+    mNoListenerForEventAtom = aEvent->mSpecifiedEventType;
   }
 
   if (aEvent->DefaultPrevented()) {
     *aEventStatus = nsEventStatus_eConsumeNoDefault;
   }
 }
 
 void
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -338,17 +338,17 @@ public:
 
     if (!mMayHaveSystemGroupListeners && aEvent->mFlags.mInSystemGroup) {
       return;
     }
 
     // Check if we already know that there is no event listener for the event.
     if (mNoListenerForEvent == aEvent->mMessage &&
         (mNoListenerForEvent != eUnidentifiedEvent ||
-         mNoListenerForEventAtom == aEvent->userType)) {
+         mNoListenerForEventAtom == aEvent->mSpecifiedEventType)) {
       return;
     }
     HandleEventInternal(aPresContext, aEvent, aDOMEvent, aCurrentTarget,
                         aEventStatus);
   }
 
   /**
    * Tells the event listener manager that its target (which owns it) is
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -577,19 +577,20 @@ EventStateManager::PreHandleEvent(nsPres
 #endif
   // Store last known screenPoint and clientPoint so pointer lock
   // can use these values as constants.
   if (aEvent->IsTrusted() &&
       ((mouseEvent && mouseEvent->IsReal()) ||
        aEvent->mClass == eWheelEventClass) &&
       !sIsPointerLocked) {
     sLastScreenPoint =
-      Event::GetScreenCoords(aPresContext, aEvent, aEvent->refPoint);
+      Event::GetScreenCoords(aPresContext, aEvent, aEvent->mRefPoint);
     sLastClientPoint =
-      Event::GetClientCoords(aPresContext, aEvent, aEvent->refPoint, CSSIntPoint(0, 0));
+      Event::GetClientCoords(aPresContext, aEvent, aEvent->mRefPoint,
+                             CSSIntPoint(0, 0));
   }
 
   *aStatus = nsEventStatus_eIgnore;
 
   if (aEvent->mClass == eQueryContentEventClass) {
     HandleQueryContentEvent(aEvent->AsQueryContentEvent());
     return NS_OK;
   }
@@ -1586,17 +1587,17 @@ EventStateManager::BeginTrackingDragGest
 {
   if (!inDownEvent->mWidget) {
     return;
   }
 
   // Note that |inDownEvent| could be either a mouse down event or a
   // synthesized mouse move event.
   mGestureDownPoint =
-    inDownEvent->refPoint + inDownEvent->mWidget->WidgetToScreenOffset();
+    inDownEvent->mRefPoint + inDownEvent->mWidget->WidgetToScreenOffset();
 
   if (inDownFrame) {
     inDownFrame->GetContentForEvent(inDownEvent,
                                     getter_AddRefs(mGestureDownContent));
 
     mGestureDownFrameOwner = inDownFrame->GetContent();
     if (!mGestureDownFrameOwner) {
       mGestureDownFrameOwner = mGestureDownContent;
@@ -1635,17 +1636,17 @@ void
 EventStateManager::FillInEventFromGestureDown(WidgetMouseEvent* aEvent)
 {
   NS_ASSERTION(aEvent->mWidget == mCurrentTarget->GetNearestWidget(),
                "Incorrect widget in event");
 
   // Set the coordinates in the new event to the coordinates of
   // the old event, adjusted for the fact that the widget might be
   // different
-  aEvent->refPoint =
+  aEvent->mRefPoint =
     mGestureDownPoint - aEvent->mWidget->WidgetToScreenOffset();
   aEvent->mModifiers = mGestureModifiers;
   aEvent->buttons = mGestureDownButtons;
 }
 
 //
 // GenerateDragGesture
 //
@@ -1693,17 +1694,17 @@ EventStateManager::GenerateDragGesture(n
       if (!pixelThresholdX)
         pixelThresholdX = 5;
       if (!pixelThresholdY)
         pixelThresholdY = 5;
     }
 
     // fire drag gesture if mouse has moved enough
     LayoutDeviceIntPoint pt =
-      aEvent->refPoint + aEvent->mWidget->WidgetToScreenOffset();
+      aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset();
     LayoutDeviceIntPoint distance = pt - mGestureDownPoint;
     if (Abs(distance.x) > AssertedCast<uint32_t>(pixelThresholdX) ||
         Abs(distance.y) > AssertedCast<uint32_t>(pixelThresholdY)) {
       if (Prefs::ClickHoldContextMenu()) {
         // stop the click-hold before we fire off the drag gesture, in case
         // it takes a long time
         KillClickHoldTimer();
       }
@@ -2296,17 +2297,17 @@ EventStateManager::SendLineScrollEvent(n
   while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
     targetContent = targetContent->GetParent();
   }
 
   WidgetMouseScrollEvent event(aEvent->IsTrusted(),
                                eLegacyMouseLineOrPageScroll, aEvent->mWidget);
   event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
   event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
-  event.refPoint = aEvent->refPoint;
+  event.mRefPoint = aEvent->mRefPoint;
   event.mTime = aEvent->mTime;
   event.mTimeStamp = aEvent->mTimeStamp;
   event.mModifiers = aEvent->mModifiers;
   event.buttons = aEvent->buttons;
   event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
   event.mDelta = aDelta;
   event.inputSource = aEvent->inputSource;
 
@@ -2335,17 +2336,17 @@ EventStateManager::SendPixelScrollEvent(
   while (targetContent->IsNodeOfType(nsINode::eTEXT)) {
     targetContent = targetContent->GetParent();
   }
 
   WidgetMouseScrollEvent event(aEvent->IsTrusted(),
                                eLegacyMousePixelScroll, aEvent->mWidget);
   event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
   event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
-  event.refPoint = aEvent->refPoint;
+  event.mRefPoint = aEvent->mRefPoint;
   event.mTime = aEvent->mTime;
   event.mTimeStamp = aEvent->mTimeStamp;
   event.mModifiers = aEvent->mModifiers;
   event.buttons = aEvent->buttons;
   event.mIsHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
   event.mDelta = aPixelDelta;
   event.inputSource = aEvent->inputSource;
 
@@ -2936,29 +2937,29 @@ EventStateManager::PostHandleEvent(nsPre
           // 2. Element with NS_EVENT_STATE_DISABLED
           //    (aka :disabled pseudo-class for HTML element);
           // 3. XUL control element has the disabled property set to 'true'.
           //
           // We can't use nsIFrame::IsFocusable() because we want to blur when
           // we click on a visibility: none element.
           // We can't use nsIContent::IsFocusable() because we want to blur when
           // we click on a non-focusable element like a <div>.
-          // We have to use |aEvent->target| to not make sure we do not check an
-          // anonymous node of the targeted element.
+          // We have to use |aEvent->mTarget| to not make sure we do not check
+          // an anonymous node of the targeted element.
           suppressBlur = (ui->mUserFocus == NS_STYLE_USER_FOCUS_IGNORE);
 
           if (!suppressBlur) {
-            nsCOMPtr<Element> element = do_QueryInterface(aEvent->target);
+            nsCOMPtr<Element> element = do_QueryInterface(aEvent->mTarget);
             suppressBlur = element &&
                            element->State().HasState(NS_EVENT_STATE_DISABLED);
           }
 
           if (!suppressBlur) {
             nsCOMPtr<nsIDOMXULControlElement> xulControl =
-              do_QueryInterface(aEvent->target);
+              do_QueryInterface(aEvent->mTarget);
             if (xulControl) {
               bool disabled;
               xulControl->GetDisabled(&disabled);
               suppressBlur = disabled;
             }
           }
         }
 
@@ -3419,21 +3420,21 @@ EventStateManager::PostHandleEvent(nsPre
         nsCOMPtr<nsIContent> targetContent;
         mCurrentTarget->GetContentForEvent(aEvent,
                                            getter_AddRefs(targetContent));
 
         nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
         WidgetDragEvent event(aEvent->IsTrusted(), eLegacyDragDrop, widget);
 
         WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
-        event.refPoint = mouseEvent->refPoint;
+        event.mRefPoint = mouseEvent->mRefPoint;
         if (mouseEvent->mWidget) {
-          event.refPoint += mouseEvent->mWidget->WidgetToScreenOffset();
+          event.mRefPoint += mouseEvent->mWidget->WidgetToScreenOffset();
         }
-        event.refPoint -= widget->WidgetToScreenOffset();
+        event.mRefPoint -= widget->WidgetToScreenOffset();
         event.mModifiers = mouseEvent->mModifiers;
         event.buttons = mouseEvent->buttons;
         event.inputSource = mouseEvent->inputSource;
 
         nsEventStatus status = nsEventStatus_eIgnore;
         nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
         if (presShell) {
           presShell->HandleEventWithTarget(&event, mCurrentTarget,
@@ -3860,17 +3861,17 @@ CreateMouseOrPointerWidgetEvent(WidgetMo
         : aRelatedContent;
     aNewEvent = newPointerEvent.forget();
   } else {
     aNewEvent =
       new WidgetMouseEvent(aMouseEvent->IsTrusted(), aMessage,
                            aMouseEvent->mWidget, WidgetMouseEvent::eReal);
     aNewEvent->relatedTarget = aRelatedContent;
   }
-  aNewEvent->refPoint = aMouseEvent->refPoint;
+  aNewEvent->mRefPoint = aMouseEvent->mRefPoint;
   aNewEvent->mModifiers = aMouseEvent->mModifiers;
   aNewEvent->button = aMouseEvent->button;
   aNewEvent->buttons = aMouseEvent->buttons;
   aNewEvent->pressure = aMouseEvent->pressure;
   aNewEvent->mPluginEvent = aMouseEvent->mPluginEvent;
   aNewEvent->inputSource = aMouseEvent->inputSource;
 }
 
@@ -4201,56 +4202,56 @@ EventStateManager::GenerateMouseEnterExi
   // Hold onto old target content through the event and reset after.
   nsCOMPtr<nsIContent> targetBeforeEvent = mCurrentTargetContent;
 
   switch(aMouseEvent->mMessage) {
   case eMouseMove:
     {
       // Mouse movement is reported on the MouseEvent.movement{X,Y} fields.
       // Movement is calculated in UIEvent::GetMovementPoint() as:
-      //   previous_mousemove_refPoint - current_mousemove_refPoint.
+      //   previous_mousemove_mRefPoint - current_mousemove_mRefPoint.
       if (sIsPointerLocked && aMouseEvent->mWidget) {
         // The pointer is locked. If the pointer is not located at the center of
         // the window, dispatch a synthetic mousemove to return the pointer there.
         // Doing this between "real" pointer moves gives the impression that the
         // (locked) pointer can continue moving and won't stop at the screen
         // boundary. We cancel the synthetic event so that we don't end up
         // dispatching the centering move event to content.
         LayoutDeviceIntPoint center =
           GetWindowClientRectCenter(aMouseEvent->mWidget);
-        aMouseEvent->lastRefPoint = center;
-        if (aMouseEvent->refPoint != center) {
+        aMouseEvent->mLastRefPoint = center;
+        if (aMouseEvent->mRefPoint != center) {
           // Mouse move doesn't finish at the center of the window. Dispatch a
           // synthetic native mouse event to move the pointer back to the center
           // of the window, to faciliate more movement. But first, record that
           // we've dispatched a synthetic mouse movement, so we can cancel it
           // in the other branch here.
           sSynthCenteringPoint = center;
           aMouseEvent->mWidget->SynthesizeNativeMouseMove(
             center + aMouseEvent->mWidget->WidgetToScreenOffset(), nullptr);
-        } else if (aMouseEvent->refPoint == sSynthCenteringPoint) {
+        } else if (aMouseEvent->mRefPoint == sSynthCenteringPoint) {
           // This is the "synthetic native" event we dispatched to re-center the
           // pointer. Cancel it so we don't expose the centering move to content.
           aMouseEvent->StopPropagation();
           // Clear sSynthCenteringPoint so we don't cancel other events
           // targeted at the center.
           sSynthCenteringPoint = kInvalidRefPoint;
         }
       } else if (sLastRefPoint == kInvalidRefPoint) {
-        // We don't have a valid previous mousemove refPoint. This is either
+        // We don't have a valid previous mousemove mRefPoint. This is either
         // the first move we've encountered, or the mouse has just re-entered
         // the application window. We should report (0,0) movement for this
-        // case, so make the current and previous refPoints the same.
-        aMouseEvent->lastRefPoint = aMouseEvent->refPoint;
+        // case, so make the current and previous mRefPoints the same.
+        aMouseEvent->mLastRefPoint = aMouseEvent->mRefPoint;
       } else {
-        aMouseEvent->lastRefPoint = sLastRefPoint;
+        aMouseEvent->mLastRefPoint = sLastRefPoint;
       }
 
-      // Update the last known refPoint with the current refPoint.
-      sLastRefPoint = aMouseEvent->refPoint;
+      // Update the last known mRefPoint with the current mRefPoint.
+      sLastRefPoint = aMouseEvent->mRefPoint;
     }
     MOZ_FALLTHROUGH;
   case ePointerMove:
   case ePointerDown:
     {
       // Get the target content target (mousemove target == mouseover target)
       nsCOMPtr<nsIContent> targetElement = GetEventTargetContent(aMouseEvent);
       if (!targetElement) {
@@ -4469,17 +4470,17 @@ EventStateManager::FireDragEnterOrExit(n
                                        WidgetDragEvent* aDragEvent,
                                        EventMessage aMessage,
                                        nsIContent* aRelatedTarget,
                                        nsIContent* aTargetContent,
                                        nsWeakFrame& aTargetFrame)
 {
   nsEventStatus status = nsEventStatus_eIgnore;
   WidgetDragEvent event(aDragEvent->IsTrusted(), aMessage, aDragEvent->mWidget);
-  event.refPoint = aDragEvent->refPoint;
+  event.mRefPoint = aDragEvent->mRefPoint;
   event.mModifiers = aDragEvent->mModifiers;
   event.buttons = aDragEvent->buttons;
   event.relatedTarget = aRelatedTarget;
   event.inputSource = aDragEvent->inputSource;
 
   mCurrentTargetContent = aTargetContent;
 
   if (aTargetContent != aRelatedTarget) {
@@ -4633,17 +4634,17 @@ EventStateManager::CheckForAndDispatchCl
     }
     //fire click
     bool notDispatchToContents =
      (aEvent->button == WidgetMouseEvent::eMiddleButton ||
       aEvent->button == WidgetMouseEvent::eRightButton);
 
     WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick,
                            aEvent->mWidget, WidgetMouseEvent::eReal);
-    event.refPoint = aEvent->refPoint;
+    event.mRefPoint = aEvent->mRefPoint;
     event.clickCount = aEvent->clickCount;
     event.mModifiers = aEvent->mModifiers;
     event.buttons = aEvent->buttons;
     event.mTime = aEvent->mTime;
     event.mTimeStamp = aEvent->mTimeStamp;
     event.mFlags.mNoContentDispatch = notDispatchToContents;
     event.button = aEvent->button;
     event.inputSource = aEvent->inputSource;
@@ -4667,17 +4668,17 @@ EventStateManager::CheckForAndDispatchCl
       nsWeakFrame currentTarget = mCurrentTarget;
       ret = presShell->HandleEventWithTarget(&event, currentTarget,
                                              mouseContent, aStatus);
       if (NS_SUCCEEDED(ret) && aEvent->clickCount == 2 &&
           mouseContent && mouseContent->IsInComposedDoc()) {
         //fire double click
         WidgetMouseEvent event2(aEvent->IsTrusted(), eMouseDoubleClick,
                                 aEvent->mWidget, WidgetMouseEvent::eReal);
-        event2.refPoint = aEvent->refPoint;
+        event2.mRefPoint = aEvent->mRefPoint;
         event2.clickCount = aEvent->clickCount;
         event2.mModifiers = aEvent->mModifiers;
         event2.buttons = aEvent->buttons;
         event2.mFlags.mNoContentDispatch = notDispatchToContents;
         event2.button = aEvent->button;
         event2.inputSource = aEvent->inputSource;
 
         ret = presShell->HandleEventWithTarget(&event2, currentTarget,
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -902,33 +902,33 @@ private:
                                   bool aAddState);
   static void ResetLastOverForContent(const uint32_t& aIdx,
                                       RefPtr<OverOutElementsWrapper>& aChunk,
                                       nsIContent* aClosure);
 
   int32_t     mLockCursor;
   bool mLastFrameConsumedSetCursor;
 
-  // Last mouse event refPoint (the offset from the widget's origin in
+  // Last mouse event mRefPoint (the offset from the widget's origin in
   // device pixels) when mouse was locked, used to restore mouse position
   // after unlocking.
   LayoutDeviceIntPoint mPreLockPoint;
 
-  // Stores the refPoint of the last synthetic mouse move we dispatched
+  // Stores the mRefPoint of the last synthetic mouse move we dispatched
   // to re-center the mouse when we were pointer locked. If this is (-1,-1) it
   // means we've not recently dispatched a centering event. We use this to
   // detect when we receive the synth event, so we can cancel and not send it
   // to content.
   static LayoutDeviceIntPoint sSynthCenteringPoint;
 
   nsWeakFrame mCurrentTarget;
   nsCOMPtr<nsIContent> mCurrentTargetContent;
   static nsWeakFrame sLastDragOverFrame;
 
-  // Stores the refPoint (the offset from the widget's origin in device
+  // Stores the mRefPoint (the offset from the widget's origin in device
   // pixels) of the last mouse event.
   static LayoutDeviceIntPoint sLastRefPoint;
 
   // member variables for the d&d gesture state machine
   LayoutDeviceIntPoint mGestureDownPoint; // screen coordinates
   // The content to use as target if we start a d&d (what we drag).
   nsCOMPtr<nsIContent> mGestureDownContent;
   // The content of the frame where the mouse-down event occurred. It's the same
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -772,17 +772,17 @@ IMEContentObserver::OnMouseButtonEvent(n
   if (NS_WARN_IF(!mWidget) || NS_WARN_IF(mWidget->Destroyed())) {
     return false;
   }
 
   RefPtr<IMEContentObserver> kungFuDeathGrip(this);
 
   WidgetQueryContentEvent charAtPt(true, eQueryCharacterAtPoint,
                                    aMouseEvent->mWidget);
-  charAtPt.refPoint = aMouseEvent->refPoint;
+  charAtPt.mRefPoint = aMouseEvent->mRefPoint;
   ContentEventHandler handler(aPresContext);
   handler.OnQueryCharacterAtPoint(&charAtPt);
   if (NS_WARN_IF(!charAtPt.mSucceeded) ||
       charAtPt.mReply.mOffset == WidgetQueryContentEvent::NOT_FOUND) {
     return false;
   }
 
   // The widget might be destroyed during querying the content since it
@@ -797,25 +797,25 @@ IMEContentObserver::OnMouseButtonEvent(n
   if (topLevelWidget && topLevelWidget != mWidget) {
     charAtPt.mReply.mRect.MoveBy(
       topLevelWidget->WidgetToScreenOffset() -
         mWidget->WidgetToScreenOffset());
   }
   // The refPt is relative to its widget.
   // We should notify it with offset in the widget.
   if (aMouseEvent->mWidget != mWidget) {
-    charAtPt.refPoint += aMouseEvent->mWidget->WidgetToScreenOffset() -
+    charAtPt.mRefPoint += aMouseEvent->mWidget->WidgetToScreenOffset() -
       mWidget->WidgetToScreenOffset();
   }
 
   IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT);
   notification.mMouseButtonEventData.mEventMessage = aMouseEvent->mMessage;
   notification.mMouseButtonEventData.mOffset = charAtPt.mReply.mOffset;
   notification.mMouseButtonEventData.mCursorPos.Set(
-    charAtPt.refPoint.ToUnknownPoint());
+    charAtPt.mRefPoint.ToUnknownPoint());
   notification.mMouseButtonEventData.mCharRect.Set(
     charAtPt.mReply.mRect.ToUnknownRect());
   notification.mMouseButtonEventData.mButton = aMouseEvent->button;
   notification.mMouseButtonEventData.mButtons = aMouseEvent->buttons;
   notification.mMouseButtonEventData.mModifiers = aMouseEvent->mModifiers;
 
   nsresult rv = IMEStateManager::NotifyIME(notification, mWidget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -27,17 +27,17 @@ MouseEvent::MouseEvent(EventTarget* aOwn
 
   WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
-    mEvent->refPoint.x = mEvent->refPoint.y = 0;
+    mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
   if (mouseEvent) {
     MOZ_ASSERT(mouseEvent->reason != WidgetMouseEvent::eSynthesized,
                "Don't dispatch DOM events from synthesized mouse events");
     mDetail = mouseEvent->clickCount;
   }
@@ -77,18 +77,18 @@ MouseEvent::InitMouseEvent(const nsAStri
     case ePointerEventClass:
     case eSimpleGestureEventClass: {
       WidgetMouseEventBase* mouseEventBase = mEvent->AsMouseEventBase();
       mouseEventBase->relatedTarget = aRelatedTarget;
       mouseEventBase->button = aButton;
       mouseEventBase->InitBasicModifiers(aCtrlKey, aAltKey, aShiftKey, aMetaKey);
       mClientPoint.x = aClientX;
       mClientPoint.y = aClientY;
-      mouseEventBase->refPoint.x = aScreenX;
-      mouseEventBase->refPoint.y = aScreenY;
+      mouseEventBase->mRefPoint.x = aScreenX;
+      mouseEventBase->mRefPoint.y = aScreenY;
 
       WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
       if (mouseEvent) {
         mouseEvent->clickCount = aDetail;
       }
       break;
     }
     default:
@@ -293,17 +293,18 @@ MouseEvent::GetRelatedTarget()
         do_QueryInterface(mEvent->AsMouseEventBase()->relatedTarget);
       break;
     default:
       break;
   }
 
   if (relatedTarget) {
     nsCOMPtr<nsIContent> content = do_QueryInterface(relatedTarget);
-    nsCOMPtr<nsIContent> currentTarget = do_QueryInterface(mEvent->currentTarget);
+    nsCOMPtr<nsIContent> currentTarget =
+      do_QueryInterface(mEvent->mCurrentTarget);
 
     nsIContent* shadowRelatedTarget = GetShadowRelatedTarget(currentTarget, content);
     if (shadowRelatedTarget) {
       relatedTarget = shadowRelatedTarget;
     }
 
     if (content && content->ChromeOnlyAccess() &&
         !nsContentUtils::CanAccessNativeAnon()) {
@@ -352,75 +353,75 @@ MouseEvent::GetScreenX(int32_t* aScreenX
   NS_ENSURE_ARG_POINTER(aScreenX);
   *aScreenX = ScreenX();
   return NS_OK;
 }
 
 int32_t
 MouseEvent::ScreenX()
 {
-  return Event::GetScreenCoords(mPresContext, mEvent, mEvent->refPoint).x;
+  return Event::GetScreenCoords(mPresContext, mEvent, mEvent->mRefPoint).x;
 }
 
 NS_IMETHODIMP
 MouseEvent::GetScreenY(int32_t* aScreenY)
 {
   NS_ENSURE_ARG_POINTER(aScreenY);
   *aScreenY = ScreenY();
   return NS_OK;
 }
 
 int32_t
 MouseEvent::ScreenY()
 {
-  return Event::GetScreenCoords(mPresContext, mEvent, mEvent->refPoint).y;
+  return Event::GetScreenCoords(mPresContext, mEvent, mEvent->mRefPoint).y;
 }
 
 
 NS_IMETHODIMP
 MouseEvent::GetClientX(int32_t* aClientX)
 {
   NS_ENSURE_ARG_POINTER(aClientX);
   *aClientX = ClientX();
   return NS_OK;
 }
 
 int32_t
 MouseEvent::ClientX()
 {
-  return Event::GetClientCoords(mPresContext, mEvent, mEvent->refPoint,
+  return Event::GetClientCoords(mPresContext, mEvent, mEvent->mRefPoint,
                                 mClientPoint).x;
 }
 
 NS_IMETHODIMP
 MouseEvent::GetClientY(int32_t* aClientY)
 {
   NS_ENSURE_ARG_POINTER(aClientY);
   *aClientY = ClientY();
   return NS_OK;
 }
 
 int32_t
 MouseEvent::ClientY()
 {
-  return Event::GetClientCoords(mPresContext, mEvent, mEvent->refPoint,
+  return Event::GetClientCoords(mPresContext, mEvent, mEvent->mRefPoint,
                                 mClientPoint).y;
 }
 
 int32_t
 MouseEvent::OffsetX()
 {
-  return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->refPoint,
+  return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->mRefPoint,
                                 mClientPoint).x;
 }
 
 int32_t
 MouseEvent::OffsetY()
 {
-  return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->refPoint,
+  return Event::GetOffsetCoords(mPresContext, mEvent, mEvent->mRefPoint,
                                 mClientPoint).y;
 }
 
 bool
 MouseEvent::AltKey()
 {
   return mEvent->AsInputEvent()->IsAlt();
 }
--- a/dom/events/MouseScrollEvent.cpp
+++ b/dom/events/MouseScrollEvent.cpp
@@ -19,17 +19,17 @@ MouseScrollEvent::MouseScrollEvent(Event
                aEvent ? aEvent :
                         new WidgetMouseScrollEvent(false, eVoidEvent, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
-    mEvent->refPoint.x = mEvent->refPoint.y = 0;
+    mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
   mDetail = mEvent->AsMouseScrollEvent()->mDelta;
 }
 
 NS_IMPL_ADDREF_INHERITED(MouseScrollEvent, MouseEvent)
--- a/dom/events/PointerEvent.cpp
+++ b/dom/events/PointerEvent.cpp
@@ -24,17 +24,17 @@ PointerEvent::PointerEvent(EventTarget* 
                "event type mismatch ePointerEventClass");
 
   WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
-    mEvent->refPoint.x = mEvent->refPoint.y = 0;
+    mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 static uint16_t
 ConvertStringToPointerType(const nsAString& aPointerTypeArg)
 {
   if (aPointerTypeArg.EqualsLiteral("mouse")) {
--- a/dom/events/SimpleGestureEvent.cpp
+++ b/dom/events/SimpleGestureEvent.cpp
@@ -22,17 +22,17 @@ SimpleGestureEvent::SimpleGestureEvent(E
   NS_ASSERTION(mEvent->mClass == eSimpleGestureEventClass,
                "event type mismatch");
 
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
-    mEvent->refPoint.x = mEvent->refPoint.y = 0;
+    mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(SimpleGestureEvent, MouseEvent)
 NS_IMPL_RELEASE_INHERITED(SimpleGestureEvent, MouseEvent)
 
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -141,17 +141,17 @@ TouchEvent::TargetTouches()
     WidgetTouchEvent::AutoTouchArray targetTouches;
     WidgetTouchEvent* touchEvent = mEvent->AsTouchEvent();
     const WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
     for (uint32_t i = 0; i < touches.Length(); ++i) {
       // for touchend/cancel events, don't append to the target list if this is a
       // touch that is ending
       if ((mEvent->mMessage != eTouchEnd && mEvent->mMessage != eTouchCancel) ||
           !touches[i]->mChanged) {
-        if (touches[i]->mTarget == mEvent->originalTarget) {
+        if (touches[i]->mTarget == mEvent->mOriginalTarget) {
           targetTouches.AppendElement(touches[i]);
         }
       }
     }
     mTargetTouches = new TouchList(ToSupports(this), targetTouches);
   }
   return mTargetTouches;
 }
--- a/dom/events/UIEvent.cpp
+++ b/dom/events/UIEvent.cpp
@@ -125,18 +125,18 @@ UIEvent::GetMovementPoint()
        mEvent->mClass != eDragEventClass &&
        mEvent->mClass != ePointerEventClass &&
        mEvent->mClass != eSimpleGestureEventClass) ||
        !mEvent->AsGUIEvent()->mWidget) {
     return nsIntPoint(0, 0);
   }
 
   // Calculate the delta between the last screen point and the current one.
-  nsIntPoint current = DevPixelsToCSSPixels(mEvent->refPoint, mPresContext);
-  nsIntPoint last = DevPixelsToCSSPixels(mEvent->lastRefPoint, mPresContext);
+  nsIntPoint current = DevPixelsToCSSPixels(mEvent->mRefPoint, mPresContext);
+  nsIntPoint last = DevPixelsToCSSPixels(mEvent->mLastRefPoint, mPresContext);
   return current - last;
 }
 
 NS_IMETHODIMP
 UIEvent::GetView(mozIDOMWindowProxy** aView)
 {
   *aView = mView;
   NS_IF_ADDREF(*aView);
@@ -187,17 +187,17 @@ UIEvent::GetPageX(int32_t* aPageX)
 
 int32_t
 UIEvent::PageX() const
 {
   if (mPrivateDataDuplicated) {
     return mPagePoint.x;
   }
 
-  return Event::GetPageCoords(mPresContext, mEvent, mEvent->refPoint,
+  return Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint,
                               mClientPoint).x;
 }
 
 NS_IMETHODIMP
 UIEvent::GetPageY(int32_t* aPageY)
 {
   NS_ENSURE_ARG_POINTER(aPageY);
   *aPageY = PageY();
@@ -206,17 +206,17 @@ UIEvent::GetPageY(int32_t* aPageY)
 
 int32_t
 UIEvent::PageY() const
 {
   if (mPrivateDataDuplicated) {
     return mPagePoint.y;
   }
 
-  return Event::GetPageCoords(mPresContext, mEvent, mEvent->refPoint,
+  return Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint,
                               mClientPoint).y;
 }
 
 NS_IMETHODIMP
 UIEvent::GetWhich(uint32_t* aWhich)
 {
   NS_ENSURE_ARG_POINTER(aWhich);
   *aWhich = Which();
@@ -360,30 +360,30 @@ UIEvent::AsEvent(void)
 {
   return this;
 }
 
 NS_IMETHODIMP
 UIEvent::DuplicatePrivateData()
 {
   mClientPoint =
-    Event::GetClientCoords(mPresContext, mEvent, mEvent->refPoint,
+    Event::GetClientCoords(mPresContext, mEvent, mEvent->mRefPoint,
                            mClientPoint);
   mMovementPoint = GetMovementPoint();
   mLayerPoint = GetLayerPoint();
   mPagePoint =
-    Event::GetPageCoords(mPresContext, mEvent, mEvent->refPoint, mClientPoint);
-  // GetScreenPoint converts mEvent->refPoint to right coordinates.
+    Event::GetPageCoords(mPresContext, mEvent, mEvent->mRefPoint, mClientPoint);
+  // GetScreenPoint converts mEvent->mRefPoint to right coordinates.
   CSSIntPoint screenPoint =
-    Event::GetScreenCoords(mPresContext, mEvent, mEvent->refPoint);
+    Event::GetScreenCoords(mPresContext, mEvent, mEvent->mRefPoint);
   nsresult rv = Event::DuplicatePrivateData();
   if (NS_SUCCEEDED(rv)) {
     CSSToLayoutDeviceScale scale = mPresContext ? mPresContext->CSSToDevPixelScale()
                                                 : CSSToLayoutDeviceScale(1);
-    mEvent->refPoint = RoundedToInt(screenPoint * scale);
+    mEvent->mRefPoint = RoundedToInt(screenPoint * scale);
   }
   return rv;
 }
 
 NS_IMETHODIMP_(void)
 UIEvent::Serialize(IPC::Message* aMsg, bool aSerializeInterfaceType)
 {
   if (aSerializeInterfaceType) {
--- a/dom/events/UIEvent.h
+++ b/dom/events/UIEvent.h
@@ -105,17 +105,17 @@ protected:
 
   // Internal helper functions
   nsIntPoint GetMovementPoint();
   nsIntPoint GetLayerPoint() const;
 
   nsCOMPtr<nsPIDOMWindowOuter> mView;
   int32_t mDetail;
   CSSIntPoint mClientPoint;
-  // Screenpoint is mEvent->refPoint.
+  // Screenpoint is mEvent->mRefPoint.
   nsIntPoint mLayerPoint;
   CSSIntPoint mPagePoint;
   nsIntPoint mMovementPoint;
   bool mIsPointerLocked;
   CSSIntPoint mLastClientPoint;
 
   static Modifiers ComputeModifierState(const nsAString& aModifiersList);
   bool GetModifierStateInternal(const nsAString& aKey);
--- a/dom/events/WheelEvent.cpp
+++ b/dom/events/WheelEvent.cpp
@@ -26,17 +26,17 @@ WheelEvent::WheelEvent(EventTarget* aOwn
     // We should store the value of mAppUnitsPerDevPixel here because
     // it might be changed by changing zoom or something.
     if (aWheelEvent->mDeltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
       mAppUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
     }
   } else {
     mEventIsInternal = true;
     mEvent->mTime = PR_Now();
-    mEvent->refPoint.x = mEvent->refPoint.y = 0;
+    mEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
     mEvent->AsWheelEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(WheelEvent, MouseEvent)
 NS_IMPL_RELEASE_INHERITED(WheelEvent, MouseEvent)
 
 NS_INTERFACE_MAP_BEGIN(WheelEvent)
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -334,17 +334,17 @@ WheelTransaction::SetTimeout()
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "nsITimer::InitWithFuncCallback failed");
 }
 
 /* static */ nsIntPoint
 WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
 {
   NS_ASSERTION(aEvent, "aEvent is null");
   NS_ASSERTION(aEvent->mWidget, "aEvent-mWidget is null");
-  return (aEvent->refPoint + aEvent->mWidget->WidgetToScreenOffset())
+  return (aEvent->mRefPoint + aEvent->mWidget->WidgetToScreenOffset())
       .ToUnknownPoint();
 }
 
 /* static */ uint32_t
 WheelTransaction::GetTimeoutTime()
 {
   return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
 }
--- a/dom/events/test/test_legacy_event.html
+++ b/dom/events/test/test_legacy_event.html
@@ -5,18 +5,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1236979 (events that have legacy alternative versions)</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <style>
     @keyframes anim1 {
-      0%   { margin-left: 0px }
-      100% { margin-left: 100px }
+      0%   { margin-left: 200px }
+      100% { margin-left: 300px }
     }
   </style>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1236979">Mozilla Bug 1236979</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -27,31 +27,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 /** Test for Bug 1236979 **/
 
 'use strict';
 SimpleTest.waitForExplicitFinish();
 
 // Array of info-bundles about each legacy event to be tested:
 var gLegacyEventInfo = [
   {
-    legacy_name: "webkitTransitionEnd",
-    modern_name: "transitionend",
-    trigger_event: triggerShortTransition,
-  },
-  {
-    legacy_name: "webkitAnimationStart",
-    modern_name: "animationstart",
-    trigger_event: triggerShortAnimation,
-  },
-  {
-    legacy_name: "webkitAnimationEnd",
-    modern_name: "animationend",
-    trigger_event: triggerShortAnimation,
-  },
-  {
     legacy_name: "webkitAnimationIteration",
     modern_name: "animationiteration",
     trigger_event: triggerAnimationIteration,
   }
 ];
 
 // EVENT-TRIGGERING FUNCTIONS
 // --------------------------
@@ -83,17 +68,18 @@ function triggerShortAnimation(node) {
 // any animationiteration events -- the CSS Animations spec says this event
 // must not be fired "...when an animationend event would fire at the same time"
 // (which would be the case in this example with a 1ms duration). So, to make
 // sure our event does fire, we use a long duration and a nearly-as-long
 // negative delay. This ensures we hit the end of the first iteration right
 // away, and that we don't risk hitting the end of the second iteration at the
 // same time.
 function triggerAnimationIteration(node) {
-  node.style.animation = "anim1 300s -299.999s linear 2";
+  dump("************ Add Animation! **********");
+  node.style.animation = "anim1 1s -0.999s linear 2";
 }
 
 // GENERAL UTILITY FUNCTIONS
 // -------------------------
 // Creates a new div and appends it as a child of the specified parentNode, or
 // (if no parent is specified) as a child of the element with ID 'display'.
 function createChildDiv(parentNode) {
   if (!parentNode) {
@@ -107,16 +93,17 @@ function createChildDiv(parentNode) {
   return div;
 }
 
 // Returns an event-handler function, which (when invoked) simply checks that
 // the event's type matches what's expected. If a callback is passed in, then
 // the event-handler will invoke that callback as well.
 function createHandlerWithTypeCheck(expectedEventType, extraHandlerLogic) {
   var handler = function(e) {
+    dump("********** event fired [" + e.type + "] ******\n");
     is(e.type, expectedEventType,
        "When an event handler for '" + expectedEventType + "' is invoked, " +
        "the event's type field should be '" + expectedEventType + "'.");
     if (extraHandlerLogic) {
       extraHandlerLogic(e);
     }
   }
   return handler;
@@ -132,26 +119,29 @@ function createHandlerWithTypeCheck(expe
 
 // Tests that the legacy event type is sent, when only a legacy handler is
 // registered.
 function mpTestLegacyEventSent(eventInfo) {
   return new Promise(
     function(resolve, reject) {
       // Create a node & register an event-handler for the legacy event:
       var div = createChildDiv();
+      div.innerHTML = "<h2>Clobber</h2>";
 
       var handler = createHandlerWithTypeCheck(eventInfo.legacy_name,
                                                function() {
         // When event-handler is done, clean up & resolve:
         div.parentNode.removeChild(div);
+        dump("************ Resolved **********\n");
         resolve();
       });
       div.addEventListener(eventInfo.legacy_name, handler);
 
       // Trigger the event:
+      dump("************ trigger[" + eventInfo.legacy_name + "] **********\n");
       eventInfo.trigger_event(div);
     }
   );
 }
 
 // Test that the modern event type (and only the modern event type) is fired,
 // when listeners of both modern & legacy types are registered. The legacy
 // listener should not be invoked.
@@ -169,16 +159,17 @@ function mpTestModernBeatsLegacy(eventIn
       var modernHandler = createHandlerWithTypeCheck(eventInfo.modern_name,
                                                      function() {
         // Indicate that the test has passed (we invoked the modern handler):
         ok(true, "Handler for modern event '" + eventInfo.modern_name +
            "' should be invoked when there's a handler registered for " +
            "both modern & legacy event type on the same node");
         // When event-handler is done, clean up & resolve:
         div.parentNode.removeChild(div);
+        dump("************ Resolved **********\n");
         resolve();
       });
 
       div.addEventListener(eventInfo.legacy_name, legacyHandler);
       div.addEventListener(eventInfo.modern_name, modernHandler);
       eventInfo.trigger_event(div);
     }
   );
@@ -216,16 +207,17 @@ function mpTestDiffListenersEventBubblin
         ok(didEventFireOnTarget,
            "Event should have fired on child");
         ok(didEventFireOnParent,
            "Event should have fired on parent");
         is(e, eventSentToTarget,
            "Same event object should bubble, despite difference in type");
         // Clean up.
         grandparent.parentNode.removeChild(grandparent);
+        dump("************ Resolved **********\n");
         resolve();
       }));
 
       eventInfo.trigger_event(target);
     }
   );
 }
 
@@ -265,35 +257,41 @@ function mpTestDiffListenersEventCapturi
           is(e.eventPhase, Event.AT_TARGET,
              "event should be at target phase");
           is(e, eventSentToGrandparent,
              "Same event object should capture, despite difference in type");
           ok(didEventFireOnParent,
              "Event should have fired on parent");
           // Clean up.
           grandparent.parentNode.removeChild(grandparent);
+          dump("************ Resolved **********\n");
           resolve();
       }), true);
 
       eventInfo.trigger_event(target);
     }
   );
 }
 
 // MAIN FUNCTION: Kick off the tests.
 function main() {
   Promise.resolve().then(function() {
+    dump("part1 clear\n");
     return Promise.all(gLegacyEventInfo.map(mpTestLegacyEventSent))
   }).then(function() {
+    dump("part2 clear\n");
     return Promise.all(gLegacyEventInfo.map(mpTestModernBeatsLegacy));
   }).then(function() {
+    dump("part3 clear");
     return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventCapturing));
   }).then(function() {
+    dump("part4 clear");
     return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventBubbling));
   }).then(function() {
+    dump("part5 clear");
     SimpleTest.finish();
   }).catch(function(reason) {
     ok(false, "Test failed: " + reason);
     SimpleTest.finish();
   });
 }
 
 main();
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -446,17 +446,17 @@ ExtractFromUSVString(const nsString& aSt
 nsresult
 ExtractFromURLSearchParams(const URLSearchParams& aParams,
                            nsIInputStream** aStream,
                            nsCString& aContentType)
 {
   nsAutoString serialized;
   aParams.Stringify(serialized);
   aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8");
-  return NS_NewStringInputStream(aStream, serialized);
+  return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized));
 }
 } // namespace
 
 nsresult
 ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit,
                           nsIInputStream** aStream,
                           nsCString& aContentType)
 {
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -486,17 +486,17 @@ HTMLFormElement::UnbindFromTree(bool aDe
   }
   ForgetCurrentSubmission();
 }
 
 nsresult
 HTMLFormElement::PreHandleEvent(EventChainPreVisitor& aVisitor)
 {
   aVisitor.mWantsWillHandleEvent = true;
-  if (aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this)) {
+  if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
     uint32_t msg = aVisitor.mEvent->mMessage;
     if (msg == eFormSubmit) {
       if (mGeneratingSubmit) {
         aVisitor.mCanHandle = false;
         return NS_OK;
       }
       mGeneratingSubmit = true;
 
@@ -519,26 +519,26 @@ nsresult
 HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor)
 {
   // If this is the bubble stage and there is a nested form below us which
   // received a submit event we do *not* want to handle the submit event
   // for this form too.
   if ((aVisitor.mEvent->mMessage == eFormSubmit ||
        aVisitor.mEvent->mMessage == eFormReset) &&
       aVisitor.mEvent->mFlags.mInBubblingPhase &&
-      aVisitor.mEvent->originalTarget != static_cast<nsIContent*>(this)) {
+      aVisitor.mEvent->mOriginalTarget != static_cast<nsIContent*>(this)) {
     aVisitor.mEvent->StopPropagation();
   }
   return NS_OK;
 }
 
 nsresult
 HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
 {
-  if (aVisitor.mEvent->originalTarget == static_cast<nsIContent*>(this)) {
+  if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this)) {
     EventMessage msg = aVisitor.mEvent->mMessage;
     if (msg == eFormSubmit) {
       // let the form know not to defer subsequent submissions
       mDeferSubmission = false;
     }
 
     if (aVisitor.mEventStatus == nsEventStatus_eIgnore) {
       switch (msg) {
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -1152,18 +1152,26 @@ HTMLImageElement::UpdateResponsiveSource
   }
 
   return !hadSelector || mResponsiveSelector;
 }
 
 /*static */ bool
 HTMLImageElement::SupportedPictureSourceType(const nsAString& aType)
 {
+  nsAutoString type;
+  nsAutoString params;
+
+  nsContentUtils::SplitMimeType(aType, type, params);
+  if (type.IsEmpty()) {
+    return true;
+  }
+
   return
-    imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(aType).get(),
+    imgLoader::SupportImageWithMimeType(NS_ConvertUTF16toUTF8(type).get(),
                                         AcceptedMimeTypes::IMAGES_AND_DOCUMENTS);
 }
 
 bool
 HTMLImageElement::SourceElementMatches(nsIContent* aSourceNode)
 {
   MOZ_ASSERT(aSourceNode->IsHTMLElement(nsGkAtoms::source));
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -3424,27 +3424,27 @@ HTMLInputElement::PreHandleEvent(EventCh
   }
 
   nsresult rv = nsGenericHTMLFormElementWithState::PreHandleEvent(aVisitor);
 
   // We do this after calling the base class' PreHandleEvent so that
   // nsIContent::PreHandleEvent doesn't reset any change we make to mCanHandle.
   if (mType == NS_FORM_INPUT_NUMBER &&
       aVisitor.mEvent->IsTrusted()  &&
-      aVisitor.mEvent->originalTarget != this) {
+      aVisitor.mEvent->mOriginalTarget != this) {
     // <input type=number> has an anonymous <input type=text> descendant. If
     // 'input' or 'change' events are fired at that text control then we need
     // to do some special handling here.
     HTMLInputElement* textControl = nullptr;
     nsNumberControlFrame* numberControlFrame =
       do_QueryFrame(GetPrimaryFrame());
     if (numberControlFrame) {
       textControl = numberControlFrame->GetAnonTextControl();
     }
-    if (textControl && aVisitor.mEvent->originalTarget == textControl) {
+    if (textControl && aVisitor.mEvent->mOriginalTarget == textControl) {
       if (aVisitor.mEvent->mMessage == eEditorInput) {
         // Propogate the anon text control's new value to our HTMLInputElement:
         nsAutoString value;
         numberControlFrame->GetValueOfAnonTextControl(value);
         numberControlFrame->HandlingInputEvent(true);
         nsWeakFrame weakNumberControlFrame(numberControlFrame);
         rv = SetValueInternal(value,
                               nsTextEditorState::eSetValue_BySetUserInput |
@@ -3710,17 +3710,17 @@ HTMLInputElement::MaybeInitPickers(Event
   if (!(mouseEvent && mouseEvent->IsLeftClickEvent())) {
     return NS_OK;
   }
   if (mType == NS_FORM_INPUT_FILE) {
     // If the user clicked on the "Choose folder..." button we open the
     // directory picker, else we open the file picker.
     FilePickerType type = FILE_PICKER_FILE;
     nsCOMPtr<nsIContent> target =
-      do_QueryInterface(aVisitor.mEvent->originalTarget);
+      do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     if (target &&
         target->GetParent() == this &&
         target->IsRootOfNativeAnonymousSubtree() &&
         target->HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) {
       MOZ_ASSERT(Preferences::GetBool("dom.input.dirpicker", false),
                  "No API or UI should have been exposed to allow this code to "
                  "be reached");
       type = FILE_PICKER_DIRECTORY;
@@ -3777,17 +3777,17 @@ HTMLInputElement::PostHandleEvent(EventC
   // when space is pressed.  So, we just nest the firing of DOMActivate inside
   // the click event handling, and allow cancellation of DOMActivate to cancel
   // the click.
   if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault &&
       !IsSingleLineTextControl(true) &&
       mType != NS_FORM_INPUT_NUMBER) {
     WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
     if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
-        !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->originalTarget)) {
+        !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->mOriginalTarget)) {
       // DOMActive event should be trusted since the activation is actually
       // occurred even if the cause is an untrusted click event.
       InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
       actEvent.mDetail = 1;
 
       nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
       if (shell) {
         nsEventStatus status = nsEventStatus_eIgnore;
--- a/dom/html/HTMLLabelElement.cpp
+++ b/dom/html/HTMLLabelElement.cpp
@@ -107,33 +107,33 @@ HTMLLabelElement::PostHandleEvent(EventC
        aVisitor.mEvent->mMessage != eMouseDown) ||
       aVisitor.mEventStatus == nsEventStatus_eConsumeNoDefault ||
       !aVisitor.mPresContext ||
       // Don't handle the event if it's already been handled by another label
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->target);
+  nsCOMPtr<nsIContent> target = do_QueryInterface(aVisitor.mEvent->mTarget);
   if (InInteractiveHTMLContent(target, this)) {
     return NS_OK;
   }
 
   // Strong ref because event dispatch is going to happen.
   RefPtr<Element> content = GetLabeledElement();
 
   if (content) {
     mHandlingEvent = true;
     switch (aVisitor.mEvent->mMessage) {
       case eMouseDown:
         if (mouseEvent->button == WidgetMouseEvent::eLeftButton) {
           // We reset the mouse-down point on every event because there is
           // no guarantee we will reach the eMouseClick code below.
           LayoutDeviceIntPoint* curPoint =
-            new LayoutDeviceIntPoint(mouseEvent->refPoint);
+            new LayoutDeviceIntPoint(mouseEvent->mRefPoint);
           SetProperty(nsGkAtoms::labelMouseDownPtProperty,
                       static_cast<void*>(curPoint),
                       nsINode::DeleteProperty<LayoutDeviceIntPoint>);
         }
         break;
 
       case eMouseClick:
         if (mouseEvent->IsLeftClickEvent()) {
@@ -141,17 +141,17 @@ HTMLLabelElement::PostHandleEvent(EventC
             static_cast<LayoutDeviceIntPoint*>(
               GetProperty(nsGkAtoms::labelMouseDownPtProperty));
 
           bool dragSelect = false;
           if (mouseDownPoint) {
             LayoutDeviceIntPoint dragDistance = *mouseDownPoint;
             DeleteProperty(nsGkAtoms::labelMouseDownPtProperty);
 
-            dragDistance -= mouseEvent->refPoint;
+            dragDistance -= mouseEvent->mRefPoint;
             const int CLICK_DISTANCE = 2;
             dragSelect = dragDistance.x > CLICK_DISTANCE ||
                          dragDistance.x < -CLICK_DISTANCE ||
                          dragDistance.y > CLICK_DISTANCE ||
                          dragDistance.y < -CLICK_DISTANCE;
           }
           // Don't click the for-content if we did drag-select text or if we
           // have a kbd modifier (which adjusts a selection).
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -81,16 +81,17 @@
 
 #include "mozilla/dom/AudioTrack.h"
 #include "mozilla/dom/AudioTrackList.h"
 #include "mozilla/dom/VideoTrack.h"
 #include "mozilla/dom/VideoTrackList.h"
 #include "mozilla/dom/TextTrack.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/Telemetry.h"
+#include "DecoderDoctorDiagnostics.h"
 
 #include "ImageContainer.h"
 #include "nsRange.h"
 #include <algorithm>
 #include <cmath>
 
 static mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
 static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
@@ -1056,22 +1057,29 @@ void HTMLMediaElement::LoadFromSourceChi
     if (!child->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
       ReportLoadError("MediaLoadSourceMissingSrc");
       DispatchAsyncSourceError(child);
       continue;
     }
 
     // If we have a type attribute, it must be a supported type.
     nsAutoString type;
-    if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
-        GetCanPlay(type) == CANPLAY_NO) {
-      DispatchAsyncSourceError(child);
-      const char16_t* params[] = { type.get(), src.get() };
-      ReportLoadError("MediaLoadUnsupportedTypeAttribute", params, ArrayLength(params));
-      continue;
+    if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
+      DecoderDoctorDiagnostics diagnostics;
+      CanPlayStatus canPlay = GetCanPlay(type, &diagnostics);
+      if (canPlay != CANPLAY_NO) {
+        diagnostics.SetCanPlay();
+      }
+      diagnostics.StoreDiagnostics(OwnerDoc(), type, __func__);
+      if (canPlay == CANPLAY_NO) {
+        DispatchAsyncSourceError(child);
+        const char16_t* params[] = { type.get(), src.get() };
+        ReportLoadError("MediaLoadUnsupportedTypeAttribute", params, ArrayLength(params));
+        continue;
+      }
     }
     nsAutoString media;
     HTMLSourceElement *childSrc = HTMLSourceElement::FromContent(child);
     MOZ_ASSERT(childSrc, "Expect child to be HTMLSourceElement");
     if (childSrc && !childSrc->MatchesCurrentMedia()) {
       DispatchAsyncSourceError(child);
       const char16_t* params[] = { media.get(), src.get() };
       ReportLoadError("MediaLoadSourceMediaNotMatched", params, ArrayLength(params));
@@ -2879,37 +2887,45 @@ void HTMLMediaElement::UnbindFromTree(bo
   if (mDecoder) {
     MOZ_ASSERT(IsHidden());
     mDecoder->NotifyOwnerActivityChanged();
   }
 }
 
 /* static */
 CanPlayStatus
-HTMLMediaElement::GetCanPlay(const nsAString& aType)
+HTMLMediaElement::GetCanPlay(const nsAString& aType,
+                             DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   nsresult rv = parser.GetType(mimeType);
   if (NS_FAILED(rv))
     return CANPLAY_NO;
 
   nsAutoString codecs;
   rv = parser.GetParameter("codecs", codecs);
 
   NS_ConvertUTF16toUTF8 mimeTypeUTF8(mimeType);
   return DecoderTraits::CanHandleMediaType(mimeTypeUTF8.get(),
                                            NS_SUCCEEDED(rv),
-                                           codecs);
+                                           codecs,
+                                           aDiagnostics);
 }
 
 NS_IMETHODIMP
 HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
 {
-  switch (GetCanPlay(aType)) {
+  DecoderDoctorDiagnostics diagnostics;
+  CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
+  if (canPlay != CANPLAY_NO) {
+    diagnostics.SetCanPlay();
+  }
+  diagnostics.StoreDiagnostics(OwnerDoc(), aType, __func__);
+  switch (canPlay) {
   case CANPLAY_NO:
     aResult.Truncate();
     break;
   case CANPLAY_YES:
     aResult.AssignLiteral("probably");
     break;
   default:
   case CANPLAY_MAYBE:
@@ -2959,17 +2975,25 @@ nsresult HTMLMediaElement::InitializeDec
   NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
   NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
 
   nsAutoCString mimeType;
 
   aChannel->GetContentType(mimeType);
   NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
 
-  RefPtr<MediaDecoder> decoder = DecoderTraits::CreateDecoder(mimeType, this);
+  DecoderDoctorDiagnostics diagnostics;
+  RefPtr<MediaDecoder> decoder =
+    DecoderTraits::CreateDecoder(mimeType, this, &diagnostics);
+  if (decoder) {
+    diagnostics.SetCanPlay();
+  }
+  diagnostics.StoreDiagnostics(OwnerDoc(),
+                               NS_ConvertASCIItoUTF16(mimeType),
+                               __func__);
   if (!decoder) {
     nsAutoString src;
     GetCurrentSrc(src);
     NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
     const char16_t* params[] = { mimeUTF16.get(), src.get() };
     ReportLoadError("MediaLoadUnsupportedMimeType", params, ArrayLength(params));
     return NS_ERROR_FAILURE;
   }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -34,16 +34,17 @@
 
 // Define to output information on decoding and painting framerate
 /* #define DEBUG_FRAME_RATE 1 */
 
 typedef uint16_t nsMediaNetworkState;
 typedef uint16_t nsMediaReadyState;
 
 namespace mozilla {
+class DecoderDoctorDiagnostics;
 class DOMMediaStream;
 class ErrorResult;
 class MediaResource;
 class MediaDecoder;
 class VideoFrameContainer;
 namespace dom {
 class MediaKeys;
 class TextTrack;
@@ -316,17 +317,18 @@ public:
   // main thread when/if the size changes.
   void UpdateMediaSize(const nsIntSize& aSize);
   // Like UpdateMediaSize, but only updates the size if no size has yet
   // been set.
   void UpdateInitialMediaSize(const nsIntSize& aSize);
 
   // Returns the CanPlayStatus indicating if we can handle the
   // full MIME type including the optional codecs parameter.
-  static CanPlayStatus GetCanPlay(const nsAString& aType);
+  static CanPlayStatus GetCanPlay(const nsAString& aType,
+                                  DecoderDoctorDiagnostics* aDiagnostics);
 
   /**
    * Called when a child source element is added to this media element. This
    * may queue a task to run the select resource algorithm if appropriate.
    */
   void NotifyAddedSource();
 
   /**
--- a/dom/html/nsGenericHTMLElement.cpp
+++ b/dom/html/nsGenericHTMLElement.cpp
@@ -2272,17 +2272,17 @@ nsGenericHTMLFormElement::PreHandleEvent
   if (aVisitor.mEvent->IsTrusted()) {
     switch (aVisitor.mEvent->mMessage) {
       case eFocus: {
         // Check to see if focus has bubbled up from a form control's
         // child textfield or button.  If that's the case, don't focus
         // this parent file control -- leave focus on the child.
         nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
         if (formControlFrame &&
-            aVisitor.mEvent->originalTarget == static_cast<nsINode*>(this))
+            aVisitor.mEvent->mOriginalTarget == static_cast<nsINode*>(this))
           formControlFrame->SetFocus(true, true);
         break;
       }
       case eBlur: {
         nsIFormControlFrame* formControlFrame = GetFormControlFrame(true);
         if (formControlFrame)
           formControlFrame->SetFocus(false, false);
         break;
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -2651,17 +2651,17 @@ nsHTMLDocument::TearingDownEditor(nsIEdi
     auto cache = nsLayoutStylesheetCache::For(GetStyleBackendType());
 
     agentSheets.RemoveElement(cache->ContentEditableSheet());
     if (oldState == eDesignMode)
       agentSheets.RemoveElement(cache->DesignModeSheet());
 
     presShell->SetAgentStyleSheets(agentSheets);
 
-    presShell->ReconstructStyleData();
+    presShell->RestyleForCSSRuleChanges();
   }
 }
 
 nsresult
 nsHTMLDocument::TurnEditingOff()
 {
   NS_ASSERTION(mEditingState != eOff, "Editing is already off.");
 
@@ -2815,17 +2815,17 @@ nsHTMLDocument::EditingStateChanged()
       // designMode is being turned off (contentEditable is still on).
       agentSheets.RemoveElement(cache->DesignModeSheet());
       updateState = true;
     }
 
     rv = presShell->SetAgentStyleSheets(agentSheets);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    presShell->ReconstructStyleData();
+    presShell->RestyleForCSSRuleChanges();
 
     // Adjust focused element with new style but blur event shouldn't be fired
     // until mEditingState is modified with newState.
     nsAutoScriptBlocker scriptBlocker;
     if (designMode) {
       nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
       nsIContent* focusedContent =
         nsFocusManager::GetFocusedDescendant(window, false,
--- a/dom/ipc/AppProcessChecker.cpp
+++ b/dom/ipc/AppProcessChecker.cpp
@@ -31,18 +31,18 @@ class PContentParent;
 } // namespace dom
 } // namespace mozilla
 
 class nsIPrincipal;
 #endif
 
 namespace mozilla {
 
-#if DEUBG
-  #define LOG(args...) printf_stderr(args)
+#if DEBUG
+  #define LOG(...) printf_stderr(__VA_ARGS__)
 #else
   #define LOG(...)
 #endif
 
 #ifdef MOZ_CHILD_PERMISSIONS
 
 static bool
 CheckAppTypeHelper(mozIApplication* aApp,
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -442,17 +442,17 @@ NS_IMETHODIMP
 ConsoleListener::Observe(nsIConsoleMessage* aMessage)
 {
   if (!mChild) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIScriptError> scriptError = do_QueryInterface(aMessage);
   if (scriptError) {
-    nsString msg, sourceName, sourceLine;
+    nsAutoString msg, sourceName, sourceLine;
     nsXPIDLCString category;
     uint32_t lineNum, colNum, flags;
 
     nsresult rv = scriptError->GetErrorMessage(msg);
     NS_ENSURE_SUCCESS(rv, rv);
     TruncateString(msg);
     rv = scriptError->GetSourceName(sourceName);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1163,17 +1163,17 @@ TabParent::SendKeyEvent(const nsAString&
   }
 }
 
 bool TabParent::SendRealMouseEvent(WidgetMouseEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  event.refPoint += GetChildProcessOffset();
+  event.mRefPoint += GetChildProcessOffset();
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (widget) {
     // When we mouseenter the tab, the tab's cursor should
     // become the current cursor.  When we mouseexit, we stop.
     if (eMouseEnterIntoWidget == event.mMessage) {
       mTabSetsCursor = true;
       if (mCustomCursor) {
@@ -1215,17 +1215,17 @@ TabParent::GetLayoutDeviceToCSSScale()
 
 bool
 TabParent::SendRealDragEvent(WidgetDragEvent& event, uint32_t aDragAction,
                              uint32_t aDropEffect)
 {
   if (mIsDestroyed) {
     return false;
   }
-  event.refPoint += GetChildProcessOffset();
+  event.mRefPoint += GetChildProcessOffset();
   return PBrowserParent::SendRealDragEvent(event, aDragAction, aDropEffect);
 }
 
 CSSPoint TabParent::AdjustTapToChildWidget(const CSSPoint& aPoint)
 {
   return aPoint + (LayoutDevicePoint(GetChildProcessOffset()) * GetLayoutDeviceToCSSScale());
 }
 
@@ -1233,62 +1233,62 @@ bool TabParent::SendMouseWheelEvent(Widg
 {
   if (mIsDestroyed) {
     return false;
   }
 
   ScrollableLayerGuid guid;
   uint64_t blockId;
   ApzAwareEventRoutingToChild(&guid, &blockId, nullptr);
-  event.refPoint += GetChildProcessOffset();
+  event.mRefPoint += GetChildProcessOffset();
   return PBrowserParent::SendMouseWheelEvent(event, guid, blockId);
 }
 
 bool TabParent::RecvDispatchWheelEvent(const mozilla::WidgetWheelEvent& aEvent)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return true;
   }
 
   WidgetWheelEvent localEvent(aEvent);
   localEvent.mWidget = widget;
-  localEvent.refPoint -= GetChildProcessOffset();
+  localEvent.mRefPoint -= GetChildProcessOffset();
 
   widget->DispatchInputEvent(&localEvent);
   return true;
 }
 
 bool
 TabParent::RecvDispatchMouseEvent(const mozilla::WidgetMouseEvent& aEvent)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return true;
   }
 
   WidgetMouseEvent localEvent(aEvent);
   localEvent.mWidget = widget;
-  localEvent.refPoint -= GetChildProcessOffset();
+  localEvent.mRefPoint -= GetChildProcessOffset();
 
   widget->DispatchInputEvent(&localEvent);
   return true;
 }
 
 bool
 TabParent::RecvDispatchKeyboardEvent(const mozilla::WidgetKeyboardEvent& aEvent)
 {
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return true;
   }
 
   WidgetKeyboardEvent localEvent(aEvent);
   localEvent.mWidget = widget;
-  localEvent.refPoint -= GetChildProcessOffset();
+  localEvent.mRefPoint -= GetChildProcessOffset();
 
   widget->DispatchInputEvent(&localEvent);
   return true;
 }
 
 static void
 DoCommandCallback(mozilla::Command aCommand, void* aData)
 {
@@ -1500,17 +1500,17 @@ TabParent::RecvClearNativeTouchSequence(
   return true;
 }
 
 bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
-  event.refPoint += GetChildProcessOffset();
+  event.mRefPoint += GetChildProcessOffset();
 
   MaybeNativeKeyBinding bindings;
   bindings = void_t();
   if (event.mMessage == eKeyPress) {
     nsCOMPtr<nsIWidget> widget = GetWidget();
 
     AutoTArray<mozilla::CommandInt, 4> singleLine;
     AutoTArray<mozilla::CommandInt, 4> multiLine;
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -102,16 +102,22 @@ MediaLoadInvalidURI=Invalid URI. Load of
 # LOCALIZATION NOTE: %1$S is the media resource's format/codec type (basically equivalent to the file type, e.g. MP4,AVI,WMV,MOV etc), %2$S is the URL of the media resource which failed to load.
 MediaLoadUnsupportedTypeAttribute=Specified "type" attribute of "%1$S" is not supported. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %1$S is the "media" attribute value of the <source> element. It is a media query. %2$S is the URL of the media resource which failed to load.
 MediaLoadSourceMediaNotMatched=Specified "media" attribute of "%1$S" does not match the environment. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %1$S is the MIME type HTTP header being sent by the web server, %2$S is the URL of the media resource which failed to load.
 MediaLoadUnsupportedMimeType=HTTP "Content-Type" of "%1$S" is not supported. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %S is the URL of the media resource which failed to load because of error in decoding.
 MediaLoadDecodeError=Media resource %S could not be decoded.
+# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
+MediaCannotPlayNoDecoders=Cannot play media. No decoders for requested formats: %S
+# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
+MediaPlatformDecoderNotFound=The video on this page can't be played. Your system may not have the required video codecs for: %S
+# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
+MediaNoDecoders=No decoders for some of the requested formats: %S
 # LOCALIZATION NOTE: Do not translate "MediaRecorder".
 MediaRecorderMultiTracksNotSupported=MediaRecorder does not support recording multiple tracks of the same type at this time.
 # LOCALIZATION NOTE: %S is the ID of the MediaStreamTrack passed to MediaStream.addTrack(). Do not translate "MediaStreamTrack" and "AudioChannel".
 MediaStreamAddTrackDifferentAudioChannel=MediaStreamTrack %S could not be added since it belongs to a different AudioChannel.
 # LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack"
 MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead.
 # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
 DOMExceptionCodeWarning=Use of DOMException's code attribute is deprecated. Use name instead.
--- a/dom/manifest/ImageObjectProcessor.jsm
+++ b/dom/manifest/ImageObjectProcessor.jsm
@@ -70,17 +70,16 @@ ImageObjectProcessor.prototype.process =
   }
   return images;
 
   function toImageObject(aImageSpec) {
     return {
       'src': processSrcMember(aImageSpec, aBaseURL),
       'type': processTypeMember(aImageSpec),
       'sizes': processSizesMember(aImageSpec),
-      'background_color': processBackgroundColorMember(aImageSpec)
     };
   }
 
   function processTypeMember(aImage) {
     const charset = {};
     const hadCharset = {};
     const spec = {
       objectName: 'image',
@@ -123,19 +122,19 @@ ImageObjectProcessor.prototype.process =
       expectedType: 'string',
       trim: true
     };
     const value = extractor.extractValue(spec);
     if (value) {
       // Split on whitespace and filter out invalid values.
       value.split(/\s+/)
         .filter(isValidSizeValue)
-        .forEach(size => sizes.add(size));
+        .reduce((collector, size) => collector.add(size), sizes);
     }
-    return sizes;
+    return (sizes.size) ? Array.from(sizes).join(" ") : undefined;
     // Implementation of HTML's link@size attribute checker.
     function isValidSizeValue(aSize) {
       const size = aSize.toLowerCase();
       if (ImageObjectProcessor.anyRegEx.test(aSize)) {
         return true;
       }
       if (!size.includes('x') || size.indexOf('x') !== size.lastIndexOf('x')) {
         return false;
@@ -144,22 +143,11 @@ ImageObjectProcessor.prototype.process =
       const widthAndHeight = size.split('x');
       const w = widthAndHeight.shift();
       const h = widthAndHeight.join('x');
       const validStarts = !w.startsWith('0') && !h.startsWith('0');
       const validDecimals = ImageObjectProcessor.decimals.test(w + h);
       return (validStarts && validDecimals);
     }
   }
-
-  function processBackgroundColorMember(aImage) {
-    const spec = {
-      objectName: 'image',
-      object: aImage,
-      property: 'background_color',
-      expectedType: 'string',
-      trim: true
-    };
-    return extractor.extractColorValue(spec);
-  }
 };
 this.ImageObjectProcessor = ImageObjectProcessor; // jshint ignore:line
 this.EXPORTED_SYMBOLS = ['ImageObjectProcessor']; // jshint ignore:line
--- a/dom/manifest/test/mochitest.ini
+++ b/dom/manifest/test/mochitest.ini
@@ -1,14 +1,13 @@
 [DEFAULT]
 support-files =
 	common.js
 	resource.sjs
 	manifestLoader.html
-[test_ImageObjectProcessor_background_color.html]
 [test_ImageObjectProcessor_sizes.html]
 [test_ImageObjectProcessor_src.html]
 [test_ImageObjectProcessor_type.html]
 [test_ManifestProcessor_background_color.html]
 [test_ManifestProcessor_dir.html]
 [test_ManifestProcessor_display.html]
 [test_ManifestProcessor_icons.html]
 [test_ManifestProcessor_JSON.html]
deleted file mode 100644
--- a/dom/manifest/test/test_ImageObjectProcessor_background_color.html
+++ /dev/null
@@ -1,84 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1162808
--->
-
-<head>
-    <meta charset="utf-8">
-    <title>Test for Bug 1162808</title>
-    <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-    <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-    <script src="common.js"></script>
-    <script type="application/javascript">
-/**
- * Image object's type member
- * https://w3c.github.io/manifest/#type-member
- *
- * Checks that invalid and valid colors are processed correctly.
- **/
- /*globals data, processor, is*/
-'use strict';
-var testIcon = {
-  icons: [{
-    src: 'test',
-    'background_color': undefined
-  }]
-};
-
-// Test invalid colors.
-var invalidColors = [
-  'marooon',
-  'f000000',
-  '#ff00000',
-  'rgb(255 0 0)',
-  'rgb(100, 0%, 0%)',
-  'rgb(255,0)',
-  'rgb(300 0 0)',
-  'rbg(255,-10,0)',
-  'rgb(110, 0%, 0%)',
-  '(255,0,0) }',
-  'rgba(255)',
-  ' rgb(100%,0%,0%) }',
-  'hsl 120, 100%, 50%',
-  'hsla{120, 100%, 50%, 1}'
-];
-invalidColors.forEach((invalidColor) => {
-  var expected = `Treat invalid color (${invalidColor}) as undefined.`;
-  testIcon.icons[0].background_color = invalidColor;
-  data.jsonText = JSON.stringify(testIcon);
-  var result = processor.process(data);
-  is(result.icons[0].background_color, undefined, expected);
-});
-
-// Test valid colors.
-var validColors = [
-  'maroon',
-  '#f00',
-  '#ff0000',
-  'rgb(255,0,0)',
-  'rgb(100%, 0%, 0%)',
-  'rgb(255,0,0)',
-  'rgb(300,0,0)',
-  'rgb(255,-10,0)',
-  'rgb(110%, 0%, 0%)',
-  'rgb(255,0,0)',
-  'rgba(255,0,0,1)',
-  'rgb(100%,0%,0%)',
-  'rgba(100%,0%,0%,1)',
-  'rgba(0,0,255,0.5)',
-  'rgba(100%, 50%, 0%, 0.1)',
-  'hsl(120, 100%, 50%)',
-  'hsla(120, 100%, 50%, 1)'
-];
-
-validColors.forEach((color) => {
-  var expected = `Treat valid CSS color (${color}) as valid input.`;
-  testIcon.icons[0].background_color = color;
-  data.jsonText = JSON.stringify(testIcon);
-  var result = processor.process(data);
-  is(result.icons[0].background_color, color, expected);
-});
-</script>
-</head>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1162808">Mozilla Bug 1162808</a>
--- a/dom/manifest/test/test_ImageObjectProcessor_sizes.html
+++ b/dom/manifest/test/test_ImageObjectProcessor_sizes.html
@@ -52,50 +52,44 @@ var validSizes = [{
 
 var testIcon = {
   icons: [{
     src: 'test',
     sizes: undefined
   }]
 };
 
-validSizes.forEach(({
-  test, expect
-}) => {
+validSizes.forEach(({test, expect}) => {
   testIcon.icons[0].sizes = test;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
   var sizes = result.icons[0].sizes;
-  for (var expectedSize of expect) {
-    var expected = `Expect sizes to contain ${expectedSize}`;
-    is(sizes.has(expectedSize), true, expected);
-  }
-  expected = `Expect the size of the set to be ${expect.length}.`;
-  is(sizes.size, expect.length, expected);
+  var expected = `Expect sizes to equal ${expect.join(" ")}`;
+  is(sizes, expect.join(" "), expected);
 });
 
 var testIcon = {
   icons: [{
     src: 'test',
     sizes: undefined
   }]
 };
 
 var invalidSizes = ['invalid', '', ' ', '16 x 16', '32', '21', '16xx16', '16 x x 6'];
 invalidSizes.forEach((invalidSize) => {
-  var expected = 'Expect invalid sizes to return an empty set.';
+  var expected = 'Expect invalid sizes to return undefined.';
   testIcon.icons[0].sizes = invalidSize;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
   var sizes = result.icons[0].sizes;
-  is(sizes.size, 0, expected);
+  is(sizes, undefined, expected);
 });
 
 typeTests.forEach((type) => {
-  var expected = `Expect non-string sizes to be empty set: ${typeof type}.`;
+  var expected = `Expect non-string sizes ${typeof type} to be undefined.`;
   testIcon.icons[0].sizes = type;
   data.jsonText = JSON.stringify(testIcon);
   var result = processor.process(data);
   var sizes = result.icons[0].sizes;
-  is(sizes.size, 0, expected);
+  is(sizes, undefined, expected);
 });
   </script>
 </head>
--- a/dom/media/ADTSDecoder.cpp
+++ b/dom/media/ADTSDecoder.cpp
@@ -29,17 +29,18 @@ ADTSDecoder::CreateStateMachine()
   return new MediaDecoderStateMachine(this, reader);
 }
 
 /* static */ bool
 ADTSDecoder::IsEnabled()
 {
   PDMFactory::Init();
   RefPtr<PDMFactory> platform = new PDMFactory();
-  return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mp4a-latm"));
+  return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mp4a-latm"),
+                                    /* DecoderDoctorDiagnostics* */ nullptr);
 }
 
 /* static */ bool
 ADTSDecoder::CanHandleMediaType(const nsACString& aType,
                                 const nsAString& aCodecs)
 {
   if (aType.EqualsASCII("audio/aac") || aType.EqualsASCII("audio/aacp")) {
     return IsEnabled() && (aCodecs.IsEmpty() || aCodecs.EqualsASCII("aac"));
--- a/dom/media/AudioChannelFormat.h
+++ b/dom/media/AudioChannelFormat.h
@@ -60,16 +60,18 @@ GetAudioChannelsSuperset(uint32_t aChann
 
 /**
  * DownMixMatrix represents a conversion matrix efficiently by exploiting the
  * fact that each input channel contributes to at most one output channel,
  * except possibly for the C input channel in layouts that have one. Also,
  * every input channel is multiplied by the same coefficient for every output
  * channel it contributes to.
  */
+const float SQRT_ONE_HALF = sqrt(0.5);
+
 struct DownMixMatrix {
   // Every input channel c is copied to output channel mInputDestination[c]
   // after multiplying by mInputCoefficient[c].
   uint8_t mInputDestination[CUSTOM_CHANNEL_LAYOUTS];
   // If not IGNORE, then the C channel is copied to this output channel after
   // multiplying by its coefficient.
   uint8_t mCExtraDestination;
   float mInputCoefficient[CUSTOM_CHANNEL_LAYOUTS];
@@ -78,29 +80,29 @@ struct DownMixMatrix {
 static const DownMixMatrix
 gDownMixMatrices[CUSTOM_CHANNEL_LAYOUTS*(CUSTOM_CHANNEL_LAYOUTS - 1)/2] =
 {
   // Downmixes to mono
   { { 0, 0 }, IGNORE, { 0.5f, 0.5f } },
   { { 0, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F } },
   { { 0, 0, 0, 0 }, IGNORE, { 0.25f, 0.25f, 0.25f, 0.25f } },
   { { 0, IGNORE, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F, IGNORE_F, IGNORE_F } },
-  { { 0, 0, 0, IGNORE, 0, 0 }, IGNORE, { 0.7071f, 0.7071f, 1.0f, IGNORE_F, 0.5f, 0.5f } },
+  { { 0, 0, 0, IGNORE, 0, 0 }, IGNORE, { SQRT_ONE_HALF, SQRT_ONE_HALF, 1.0f, IGNORE_F, 0.5f, 0.5f } },
   // Downmixes to stereo
   { { 0, 1, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F } },
   { { 0, 1, 0, 1 }, IGNORE, { 0.5f, 0.5f, 0.5f, 0.5f } },
   { { 0, 1, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } },
-  { { 0, 1, 0, IGNORE, 0, 1 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 0.7071f, 0.7071f } },
+  { { 0, 1, 0, IGNORE, 0, 1 }, 1, { 1.0f, 1.0f, SQRT_ONE_HALF, IGNORE_F, SQRT_ONE_HALF, SQRT_ONE_HALF } },
   // Downmixes to 3-channel
   { { 0, 1, 2, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F } },
   { { 0, 1, 2, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F } },
   { { 0, 1, 2, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } },
   // Downmixes to quad
   { { 0, 1, 2, 3, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } },
-  { { 0, 1, 0, IGNORE, 2, 3 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 1.0f, 1.0f } },
+  { { 0, 1, 0, IGNORE, 2, 3 }, 1, { 1.0f, 1.0f, SQRT_ONE_HALF, IGNORE_F, 1.0f, 1.0f } },
   // Downmixes to 5-channel
   { { 0, 1, 2, 3, 4, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } }
 };
 
 /**
  * Given an array of input channels, downmix to aOutputChannelCount, and copy
  * the results to the channel buffers in aOutputChannels.  Don't call this with
  * input count <= output count.
--- a/dom/media/AudioConverter.cpp
+++ b/dom/media/AudioConverter.cpp
@@ -2,16 +2,17 @@
 /* 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 "AudioConverter.h"
 #include <string.h>
 #include <speex/speex_resampler.h>
+#include <cmath>
 
 /*
  *  Parts derived from MythTV AudioConvert Class
  *  Created by Jean-Yves Avenard.
  *
  *  Copyright (C) Bubblestuff Pty Ltd 2013
  *  Copyright (C) foobum@gmail.com 2010
  */
@@ -21,69 +22,59 @@ namespace mozilla {
 AudioConverter::AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut)
   : mIn(aIn)
   , mOut(aOut)
   , mResampler(nullptr)
 {
   MOZ_DIAGNOSTIC_ASSERT(aIn.Format() == aOut.Format() &&
                         aIn.Interleaved() == aOut.Interleaved(),
                         "No format or rate conversion is supported at this stage");
-  MOZ_DIAGNOSTIC_ASSERT((aIn.Channels() > aOut.Channels() && aOut.Channels() <= 2) ||
+  MOZ_DIAGNOSTIC_ASSERT(aOut.Channels() <= 2 ||
                         aIn.Channels() == aOut.Channels(),
-                        "Only downmixing to mono or stereo is supported at this stage");
+                        "Only down/upmixing to mono or stereo is supported at this stage");
   MOZ_DIAGNOSTIC_ASSERT(aOut.Interleaved(), "planar audio format not supported");
   mIn.Layout().MappingTable(mOut.Layout(), mChannelOrderMap);
   if (aIn.Rate() != aOut.Rate()) {
-    int error;
-    mResampler = speex_resampler_init(aOut.Channels(),
-                                      aIn.Rate(),
-                                      aOut.Rate(),
-                                      SPEEX_RESAMPLER_QUALITY_DEFAULT,
-                                      &error);
-
-    if (error == RESAMPLER_ERR_SUCCESS) {
-      speex_resampler_skip_zeros(mResampler);
-    } else {
-      NS_WARNING("Failed to initialize resampler.");
-      mResampler = nullptr;
-    }
+    RecreateResampler();
   }
 }
 
 AudioConverter::~AudioConverter()
 {
   if (mResampler) {
     speex_resampler_destroy(mResampler);
     mResampler = nullptr;
   }
 }
 
 bool
 AudioConverter::CanWorkInPlace() const
 {
   bool needDownmix = mIn.Channels() > mOut.Channels();
+  bool needUpmix = mIn.Channels() < mOut.Channels();
   bool canDownmixInPlace =
     mIn.Channels() * AudioConfig::SampleSize(mIn.Format()) >=
     mOut.Channels() * AudioConfig::SampleSize(mOut.Format());
   bool needResample = mIn.Rate() != mOut.Rate();
   bool canResampleInPlace = mIn.Rate() >= mOut.Rate();
   // We should be able to work in place if 1s of audio input takes less space
   // than 1s of audio output. However, as we downmix before resampling we can't
   // perform any upsampling in place (e.g. if incoming rate >= outgoing rate)
-  return (!needDownmix || canDownmixInPlace) &&
+  return !needUpmix && (!needDownmix || canDownmixInPlace) &&
          (!needResample || canResampleInPlace);
 }
 
 size_t
 AudioConverter::ProcessInternal(void* aOut, const void* aIn, size_t aFrames)
 {
   if (mIn.Channels() > mOut.Channels()) {
     return DownmixAudio(aOut, aIn, aFrames);
-  } else if (mIn.Layout() != mOut.Layout() &&
-      CanReorderAudio()) {
+  } else if (mIn.Channels() < mOut.Channels()) {
+    return UpmixAudio(aOut, aIn, aFrames);
+  } else if (mIn.Layout() != mOut.Layout() && CanReorderAudio()) {
     ReOrderInterleavedChannels(aOut, aIn, aFrames);
   } else if (aIn != aOut) {
     memmove(aOut, aIn, FramesOutToBytes(aFrames));
   }
   return aFrames;
 }
 
 // Reorder interleaved channels.
@@ -109,20 +100,17 @@ void
 }
 
 void
 AudioConverter::ReOrderInterleavedChannels(void* aOut, const void* aIn,
                                            size_t aFrames) const
 {
   MOZ_DIAGNOSTIC_ASSERT(mIn.Channels() == mOut.Channels());
 
-  if (mOut.Layout() == mIn.Layout()) {
-    return;
-  }
-  if (mOut.Channels() == 1) {
+  if (mOut.Channels() == 1 || mOut.Layout() == mIn.Layout()) {
     // If channel count is 1, planar and non-planar formats are the same and
     // there's nothing to reorder.
     if (aOut != aIn) {
       memmove(aOut, aIn, FramesOutToBytes(aFrames));
     }
     return;
   }
 
@@ -226,26 +214,26 @@ AudioConverter::DownmixAudio(void* aOut,
     aIn = aOut;
     channels = 2;
   }
 
   if (mOut.Channels() == 1) {
     if (mIn.Format() == AudioConfig::FORMAT_FLT) {
       const float* in = static_cast<const float*>(aIn);
       float* out = static_cast<float*>(aOut);
-      for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
+      for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
         float sample = 0.0;
         // The sample of the buffer would be interleaved.
         sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
         *out++ = sample;
       }
     } else if (mIn.Format() == AudioConfig::FORMAT_S16) {
       const int16_t* in = static_cast<const int16_t*>(aIn);
       int16_t* out = static_cast<int16_t*>(aOut);
-      for (uint32_t fIdx = 0; fIdx < aFrames; ++fIdx) {
+      for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
         int32_t sample = 0.0;
         // The sample of the buffer would be interleaved.
         sample = (in[fIdx*channels] + in[fIdx*channels + 1]) * 0.5;
         *out++ = sample;
       }
     } else {
       MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
     }
@@ -257,37 +245,136 @@ size_t
 AudioConverter::ResampleAudio(void* aOut, const void* aIn, size_t aFrames)
 {
   if (!mResampler) {
     return 0;
   }
   uint32_t outframes = ResampleRecipientFrames(aFrames);
   uint32_t inframes = aFrames;
 
+  int error;
   if (mOut.Format() == AudioConfig::FORMAT_FLT) {
     const float* in = reinterpret_cast<const float*>(aIn);
     float* out = reinterpret_cast<float*>(aOut);
-    speex_resampler_process_interleaved_float(mResampler, in, &inframes,
-                                              out, &outframes);
+    error =
+      speex_resampler_process_interleaved_float(mResampler, in, &inframes,
+                                                out, &outframes);
   } else if (mOut.Format() == AudioConfig::FORMAT_S16) {
     const int16_t* in = reinterpret_cast<const int16_t*>(aIn);
     int16_t* out = reinterpret_cast<int16_t*>(aOut);
-    speex_resampler_process_interleaved_int(mResampler, in, &inframes,
-                                            out, &outframes);
+    error =
+      speex_resampler_process_interleaved_int(mResampler, in, &inframes,
+                                              out, &outframes);
   } else {
     MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
   }
+  MOZ_ASSERT(error == RESAMPLER_ERR_SUCCESS);
+  if (error != RESAMPLER_ERR_SUCCESS) {
+    speex_resampler_destroy(mResampler);
+    mResampler = nullptr;
+    return 0;
+  }
   MOZ_ASSERT(inframes == aFrames, "Some frames will be dropped");
   return outframes;
 }
 
+void
+AudioConverter::RecreateResampler()
+{
+  if (mResampler) {
+    speex_resampler_destroy(mResampler);
+  }
+  int error;
+  mResampler = speex_resampler_init(mOut.Channels(),
+                                    mIn.Rate(),
+                                    mOut.Rate(),
+                                    SPEEX_RESAMPLER_QUALITY_DEFAULT,
+                                    &error);
+
+  if (error == RESAMPLER_ERR_SUCCESS) {
+    speex_resampler_skip_zeros(mResampler);
+  } else {
+    NS_WARNING("Failed to initialize resampler.");
+    mResampler = nullptr;
+  }
+}
+
+size_t
+AudioConverter::DrainResampler(void* aOut)
+{
+  if (!mResampler) {
+    return 0;
+  }
+  int frames = speex_resampler_get_input_latency(mResampler);
+  AlignedByteBuffer buffer(FramesOutToBytes(frames));
+  if (!buffer) {
+    // OOM
+    return 0;
+  }
+  frames = ResampleAudio(aOut, buffer.Data(), frames);
+  // Tore down the resampler as it's easier than handling follow-up.
+  RecreateResampler();
+  return frames;
+}
+
+size_t
+AudioConverter::UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const
+{
+  MOZ_ASSERT(mIn.Format() == AudioConfig::FORMAT_S16 ||
+             mIn.Format() == AudioConfig::FORMAT_FLT);
+  MOZ_ASSERT(mIn.Channels() < mOut.Channels());
+  MOZ_ASSERT(mIn.Channels() == 1, "Can only upmix mono for now");
+  MOZ_ASSERT(mOut.Channels() == 2, "Can only upmix to stereo for now");
+
+  if (mOut.Channels() != 2) {
+    return 0;
+  }
+
+  // Upmix mono to stereo.
+  // This is a very dumb mono to stereo upmixing, power levels are preserved
+  // following the calculation: left = right = -3dB*mono.
+  if (mIn.Format() == AudioConfig::FORMAT_FLT) {
+    const float m3db = std::sqrt(0.5); // -3dB = sqrt(1/2)
+    const float* in = static_cast<const float*>(aIn);
+    float* out = static_cast<float*>(aOut);
+    for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
+      float sample = in[fIdx] * m3db;
+      // The samples of the buffer would be interleaved.
+      *out++ = sample;
+      *out++ = sample;
+    }
+  } else if (mIn.Format() == AudioConfig::FORMAT_S16) {
+    const int16_t* in = static_cast<const int16_t*>(aIn);
+    int16_t* out = static_cast<int16_t*>(aOut);
+    for (size_t fIdx = 0; fIdx < aFrames; ++fIdx) {
+      int16_t sample = ((int32_t)in[fIdx] * 11585) >> 14; // close enough to i*sqrt(0.5)
+      // The samples of the buffer would be interleaved.
+      *out++ = sample;
+      *out++ = sample;
+    }
+  } else {
+    MOZ_DIAGNOSTIC_ASSERT(false, "Unsupported data type");
+  }
+
+  return aFrames;
+}
+
 size_t
 AudioConverter::ResampleRecipientFrames(size_t aFrames) const
 {
-  return (uint64_t)aFrames * mOut.Rate() / mIn.Rate() + 1;
+  if (!aFrames && mIn.Rate() != mOut.Rate()) {
+    // The resampler will be drained, account for frames currently buffered
+    // in the resampler.
+    if (!mResampler) {
+      return 0;
+    }
+    return speex_resampler_get_output_latency(mResampler);
+  } else {
+    return (uint64_t)aFrames * mOut.Rate() / mIn.Rate() + 1;
+  }
 }
 
 size_t
 AudioConverter::FramesOutToSamples(size_t aFrames) const
 {
   return aFrames * mOut.Channels();
 }
 
--- a/dom/media/AudioConverter.h
+++ b/dom/media/AudioConverter.h
@@ -118,16 +118,18 @@ typedef AudioDataBuffer<AudioConfig::FOR
 class AudioConverter {
 public:
   AudioConverter(const AudioConfig& aIn, const AudioConfig& aOut);
   ~AudioConverter();
 
   // Convert the AudioDataBuffer.
   // Conversion will be done in place if possible. Otherwise a new buffer will
   // be returned.
+  // Providing an empty buffer and resampling is expected, the resampler
+  // will be drained.
   template <AudioConfig::SampleFormat Format, typename Value>
   AudioDataBuffer<Format, Value> Process(AudioDataBuffer<Format, Value>&& aBuffer)
   {
     MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && mIn.Format() == Format);
     AudioDataBuffer<Format, Value> buffer = Move(aBuffer);
     if (CanWorkInPlace()) {
       size_t frames = SamplesInToFrames(buffer.Length());
       frames = ProcessInternal(buffer.Data(), buffer.Data(), frames);
@@ -147,32 +149,36 @@ public:
     MOZ_DIAGNOSTIC_ASSERT(mIn.Format() == mOut.Format() && mIn.Format() == Format);
     // Perform the downmixing / reordering in temporary buffer.
     size_t frames = SamplesInToFrames(aBuffer.Length());
     AlignedBuffer<Value> temp1;
     if (!temp1.SetLength(FramesOutToSamples(frames))) {
       return AudioDataBuffer<Format, Value>(Move(temp1));
     }
     frames = ProcessInternal(temp1.Data(), aBuffer.Data(), frames);
-    if (!frames || mIn.Rate() == mOut.Rate()) {
+    if (mIn.Rate() == mOut.Rate()) {
       temp1.SetLength(FramesOutToSamples(frames));
       return AudioDataBuffer<Format, Value>(Move(temp1));
     }
 
     // At this point, temp1 contains the buffer reordered and downmixed.
     // If we are downsampling we can re-use it.
     AlignedBuffer<Value>* outputBuffer = &temp1;
     AlignedBuffer<Value> temp2;
-    if (mOut.Rate() > mIn.Rate()) {
-      // We are upsampling, we can't work in place. Allocate another temporary
-      // buffer where the upsampling will occur.
+    if (!frames || mOut.Rate() > mIn.Rate()) {
+      // We are upsampling or about to drain, we can't work in place.
+      // Allocate another temporary buffer where the upsampling will occur.
       temp2.SetLength(FramesOutToSamples(ResampleRecipientFrames(frames)));
       outputBuffer = &temp2;
     }
-    frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
+    if (!frames) {
+      frames = DrainResampler(outputBuffer->Data());
+    } else {
+      frames = ResampleAudio(outputBuffer->Data(), temp1.Data(), frames);
+    }
     outputBuffer->SetLength(FramesOutToSamples(frames));
     return AudioDataBuffer<Format, Value>(Move(*outputBuffer));
   }
 
   // Attempt to convert the AudioDataBuffer in place.
   // Will return 0 if the conversion wasn't possible.
   template <typename Value>
   size_t Process(Value* aBuffer, size_t aFrames)
@@ -208,22 +214,25 @@ private:
    * aIn   : source buffer
    * aSamples: number of frames in source buffer
    *
    * Return Value: number of frames converted or 0 if error
    */
   size_t ProcessInternal(void* aOut, const void* aIn, size_t aFrames);
   void ReOrderInterleavedChannels(void* aOut, const void* aIn, size_t aFrames) const;
   size_t DownmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
+  size_t UpmixAudio(void* aOut, const void* aIn, size_t aFrames) const;
 
   size_t FramesOutToSamples(size_t aFrames) const;
   size_t SamplesInToFrames(size_t aSamples) const;
   size_t FramesOutToBytes(size_t aFrames) const;
 
   // Resampler context.
   SpeexResamplerState* mResampler;
   size_t ResampleAudio(void* aOut, const void* aIn, size_t aFrames);
   size_t ResampleRecipientFrames(size_t aFrames) const;
+  void RecreateResampler();
+  size_t DrainResampler(void* aOut);
 };
 
 } // namespace mozilla
 
 #endif /* AudioConverter_h */
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -124,17 +124,16 @@ AudioStream::AudioStream(DataSource& aSo
   , mInRate(0)
   , mOutRate(0)
   , mChannels(0)
   , mOutChannels(0)
   , mAudioClock(this)
   , mTimeStretcher(nullptr)
   , mDumpFile(nullptr)
   , mState(INITIALIZED)
-  , mIsMonoAudioEnabled(gfxPrefs::MonoAudio())
   , mDataSource(aSource)
 {
 }
 
 AudioStream::~AudioStream()
 {
   LOG("deleted, state %d", mState);
   MOZ_ASSERT(mState == SHUTDOWN && !mCubebStream,
@@ -326,17 +325,17 @@ AudioStream::Init(uint32_t aNumChannels,
   if (!CubebUtils::GetCubebContext()) {
     return NS_ERROR_FAILURE;
   }
 
   MOZ_LOG(gAudioStreamLog, LogLevel::Debug,
     ("%s  channels: %d, rate: %d for %p", __FUNCTION__, aNumChannels, aRate, this));
   mInRate = mOutRate = aRate;
   mChannels = aNumChannels;
-  mOutChannels = mIsMonoAudioEnabled ? 1 : aNumChannels;
+  mOutChannels = aNumChannels;
 
   mDumpFile = OpenDumpFile(this);
 
   cubeb_stream_params params;
   params.rate = aRate;
   params.channels = mOutChannels;
 #if defined(__ANDROID__)
 #if defined(MOZ_B2G)
@@ -348,21 +347,16 @@ AudioStream::Init(uint32_t aNumChannels,
   if (params.stream_type == CUBEB_STREAM_TYPE_MAX) {
     return NS_ERROR_INVALID_ARG;
   }
 #endif
 
   params.format = ToCubebFormat<AUDIO_OUTPUT_FORMAT>::value;
   mAudioClock.Init();
 
-  if (mIsMonoAudioEnabled) {
-    AudioConfig inConfig(mChannels, mInRate);
-    AudioConfig outConfig(mOutChannels, mOutRate);
-    mAudioConverter = MakeUnique<AudioConverter>(inConfig, outConfig);
-  }
   return OpenCubeb(params);
 }
 
 // This code used to live inside AudioStream::Init(), but on Mac (others?)
 // it has been known to take 300-800 (or even 8500) ms to execute(!)
 nsresult
 AudioStream::OpenCubeb(cubeb_stream_params &aParams)
 {
@@ -547,31 +541,27 @@ AudioStream::GetPositionInFramesUnlocked
 bool
 AudioStream::IsPaused()
 {
   MonitorAutoLock mon(mMonitor);
   return mState == STOPPED;
 }
 
 bool
-AudioStream::Downmix(Chunk* aChunk)
+AudioStream::IsValidAudioFormat(Chunk* aChunk)
 {
   if (aChunk->Rate() != mInRate) {
     LOGW("mismatched sample %u, mInRate=%u", aChunk->Rate(), mInRate);
     return false;
   }
 
   if (aChunk->Channels() > 8) {
     return false;
   }
 
-  if (mAudioConverter) {
-    mAudioConverter->Process(aChunk->GetWritable(), aChunk->Frames());
-  }
-
   return true;
 }
 
 void
 AudioStream::GetUnprocessed(AudioBufferWriter& aWriter)
 {
   mMonitor.AssertCurrentThreadOwns();
 
@@ -590,20 +580,20 @@ AudioStream::GetUnprocessed(AudioBufferW
   }
 
   while (aWriter.Available() > 0) {
     UniquePtr<Chunk> c = mDataSource.PopFrames(aWriter.Available());
     if (c->Frames() == 0) {
       break;
     }
     MOZ_ASSERT(c->Frames() <= aWriter.Available());
-    if (Downmix(c.get())) {
+    if (IsValidAudioFormat(c.get())) {
       aWriter.Write(c->Data(), c->Frames());
     } else {
-      // Write silence if downmixing fails.
+      // Write silence if invalid format.
       aWriter.WriteZeros(c->Frames());
     }
   }
 }
 
 void
 AudioStream::GetTimeStretched(AudioBufferWriter& aWriter)
 {
@@ -618,20 +608,20 @@ AudioStream::GetTimeStretched(AudioBuffe
   uint32_t toPopFrames = ceil(aWriter.Available() * playbackRate);
 
   while (mTimeStretcher->numSamples() < aWriter.Available()) {
     UniquePtr<Chunk> c = mDataSource.PopFrames(toPopFrames);
     if (c->Frames() == 0) {
       break;
     }
     MOZ_ASSERT(c->Frames() <= toPopFrames);
-    if (Downmix(c.get())) {
+    if (IsValidAudioFormat(c.get())) {
       mTimeStretcher->putSamples(c->Data(), c->Frames());
     } else {
-      // Write silence if downmixing fails.
+      // Write silence if invalid format.
       AutoTArray<AudioDataValue, 1000> buf;
       buf.SetLength(mOutChannels * c->Frames());
       memset(buf.Elements(), 0, buf.Length() * sizeof(AudioDataValue));
       mTimeStretcher->putSamples(buf.Elements(), c->Frames());
     }
   }
 
   auto timeStretcher = mTimeStretcher;
--- a/dom/media/AudioStream.h
+++ b/dom/media/AudioStream.h
@@ -281,16 +281,21 @@ public:
 
   // Return the position, measured in audio frames played since the stream
   // was opened, of the audio hardware.  Thread-safe.
   int64_t GetPositionInFrames();
 
   // Returns true when the audio stream is paused.
   bool IsPaused();
 
+  static uint32_t GetPreferredRate()
+  {
+    CubebUtils::InitPreferredSampleRate();
+    return CubebUtils::PreferredSampleRate();
+  }
   uint32_t GetRate() { return mOutRate; }
   uint32_t GetChannels() { return mChannels; }
   uint32_t GetOutChannels() { return mOutChannels; }
 
   // Set playback rate as a multiple of the intrinsic playback rate. This is to
   // be called only with aPlaybackRate > 0.0.
   nsresult SetPlaybackRate(double aPlaybackRate);
   // Switch between resampling (if false) and time stretching (if true, default).
@@ -323,18 +328,19 @@ private:
   }
 
 
   long DataCallback(void* aBuffer, long aFrames);
   void StateCallback(cubeb_state aState);
 
   nsresult EnsureTimeStretcherInitializedUnlocked();
 
-  // Return true if downmixing succeeds otherwise false.
-  bool Downmix(Chunk* aChunk);
+  // Return true if audio frames are valid (correct sampling rate and valid
+  // channel count) otherwise false.
+  bool IsValidAudioFormat(Chunk* aChunk);
 
   void GetUnprocessed(AudioBufferWriter& aWriter);
   void GetTimeStretched(AudioBufferWriter& aWriter);
 
   void StartUnlocked();
 
   // The monitor is held to protect all access to member variables.
   Monitor mMonitor;
@@ -364,19 +370,15 @@ private:
     STOPPED,     // Stopped by a call to Pause().
     DRAINED,     // StateCallback has indicated that the drain is complete.
     ERRORED,     // Stream disabled due to an internal error.
     SHUTDOWN     // Shutdown has been called
   };
 
   StreamState mState;
   bool mIsFirst;
-  // Get this value from the preference, if true, we would downmix the stereo.
-  bool mIsMonoAudioEnabled;
 
   DataSource& mDataSource;
-
-  UniquePtr<AudioConverter> mAudioConverter;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/Benchmark.cpp
+++ b/dom/media/Benchmark.cpp
@@ -19,16 +19,17 @@ namespace mozilla {
 // Update this version number to force re-running the benchmark. Such as when
 // an improvement to FFVP9 or LIBVPX is deemed worthwhile.
 const uint32_t VP9Benchmark::sBenchmarkVersionID = 1;
 
 const char* VP9Benchmark::sBenchmarkFpsPref = "media.benchmark.vp9.fps";
 const char* VP9Benchmark::sBenchmarkFpsVersionCheck = "media.benchmark.vp9.versioncheck";
 bool VP9Benchmark::sHasRunTest = false;
 
+// static
 bool
 VP9Benchmark::IsVP9DecodeFast()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   bool hasPref = Preferences::HasUserValue(sBenchmarkFpsPref);
   uint32_t hadRecentUpdate = Preferences::GetUint(sBenchmarkFpsVersionCheck, 0U);
 
@@ -195,17 +196,18 @@ BenchmarkPlayback::DemuxNextSample()
 }
 
 void
 BenchmarkPlayback::InitDecoder(TrackInfo&& aInfo)
 {
   MOZ_ASSERT(OnThread());
 
   RefPtr<PDMFactory> platform = new PDMFactory();
-  mDecoder = platform->CreateDecoder(aInfo, mDecoderTaskQueue, this);
+  mDecoder = platform->CreateDecoder(aInfo, mDecoderTaskQueue, this,
+     /* DecoderDoctorDiagnostics* */ nullptr);
   if (!mDecoder) {
     MainThreadShutdown();
     return;
   }
   RefPtr<Benchmark> ref(mMainThreadState);
   mDecoder->Init()->Then(
     Thread(), __func__,
     [this, ref](TrackInfo::TrackType aTrackType) {
new file mode 100644
--- /dev/null
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -0,0 +1,435 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "DecoderDoctorDiagnostics.h"
+
+#include "mozilla/dom/DecoderDoctorNotificationBinding.h"
+#include "mozilla/Logging.h"
+#include "nsGkAtoms.h"
+#include "nsIDocument.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIWeakReference.h"
+
+static mozilla::LazyLogModule sDecoderDoctorLog("DecoderDoctor");
+#define DD_LOG(level, arg, ...) MOZ_LOG(sDecoderDoctorLog, level, (arg, ##__VA_ARGS__))
+#define DD_DEBUG(arg, ...) DD_LOG(mozilla::LogLevel::Debug, arg, ##__VA_ARGS__)
+#define DD_INFO(arg, ...) DD_LOG(mozilla::LogLevel::Info, arg, ##__VA_ARGS__)
+#define DD_WARN(arg, ...) DD_LOG(mozilla::LogLevel::Warning, arg, ##__VA_ARGS__)
+
+namespace mozilla
+{
+
+// Class that collects a sequence of diagnostics from the same document over a
+// small period of time, in order to provide a synthesized analysis.
+//
+// Referenced by the document through a nsINode property, mTimer, and
+// inter-task captures.
+// When notified that the document is dead, or when the timer expires but
+// nothing new happened, StopWatching() will remove the document property and
+// timer (if present), so no more work will happen and the watcher will be
+// destroyed once all references are gone.
+class DecoderDoctorDocumentWatcher : public nsITimerCallback
+{
+public:
+  static RefPtr<DecoderDoctorDocumentWatcher>
+  RetrieveOrCreate(nsIDocument* aDocument);
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSITIMERCALLBACK
+
+  void AddDiagnostics(const nsAString& aFormat,
+                      const char* aCallSite,
+                      DecoderDoctorDiagnostics&& aDiagnostics);
+
+private:
+  explicit DecoderDoctorDocumentWatcher(nsIDocument* aDocument);
+  virtual ~DecoderDoctorDocumentWatcher();
+
+  // This will prevent further work from happening, watcher will deregister
+  // itself from document (if requested) and cancel any timer, and soon die.
+  void StopWatching(bool aRemoveProperty);
+
+  // Remove property from document; will call DestroyPropertyCallback.
+  void RemovePropertyFromDocument();
+  // Callback for property destructor, will be automatically called when the
+  // document (in aObject) is being destroyed.
+  static void DestroyPropertyCallback(void* aObject,
+                                      nsIAtom* aPropertyName,
+                                      void* aPropertyValue,
+                                      void* aData);
+
+  static const uint32_t sAnalysisPeriod_ms = 1000;
+  void EnsureTimerIsStarted();
+
+  void ReportAnalysis(dom::DecoderDoctorNotificationType aNotificationType,
+                      const char* aReportStringId,
+                      const nsAString& aFormats);
+
+  void SynthesizeAnalysis();
+
+  // Raw pointer to an nsIDocument.
+  // Must be non-null during construction.
+  // Nulled when we want to stop watching, because either:
+  // 1. The document has been destroyed (notified through
+  //    DestroyPropertyCallback).
+  // 2. We have not received new diagnostic information within a short time
+  //    period, so we just stop watching.
+  // Once nulled, no more actual work will happen, and the watcher will be
+  // destroyed soon.
+  nsIDocument* mDocument;
+
+  struct Diagnostics
+  {
+    Diagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
+                const nsAString& aFormat,
+                const char* aCallSite)
+      : mDecoderDoctorDiagnostics(Move(aDiagnostics))
+      , mFormat(aFormat)
+      , mCallSite(aCallSite)
+    {}
+    Diagnostics(const Diagnostics&) = delete;
+    Diagnostics(Diagnostics&& aOther)
+      : mDecoderDoctorDiagnostics(Move(aOther.mDecoderDoctorDiagnostics))
+      , mFormat(Move(aOther.mFormat))
+      , mCallSite(Move(aOther.mCallSite))
+    {}
+
+    const DecoderDoctorDiagnostics mDecoderDoctorDiagnostics;
+    const nsString mFormat;
+    const nsCString mCallSite;
+  };
+  typedef nsTArray<Diagnostics> DiagnosticsSequence;
+  DiagnosticsSequence mDiagnosticsSequence;
+
+  nsCOMPtr<nsITimer> mTimer; // Keep timer alive until we run.
+  DiagnosticsSequence::size_type mDiagnosticsHandled = 0;
+};
+
+
+NS_IMPL_ISUPPORTS(DecoderDoctorDocumentWatcher, nsITimerCallback)
+
+// static
+RefPtr<DecoderDoctorDocumentWatcher>
+DecoderDoctorDocumentWatcher::RetrieveOrCreate(nsIDocument* aDocument)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aDocument);
+  RefPtr<DecoderDoctorDocumentWatcher> watcher =
+    static_cast<DecoderDoctorDocumentWatcher*>(
+      aDocument->GetProperty(nsGkAtoms::decoderDoctor));
+  if (!watcher) {
+    watcher = new DecoderDoctorDocumentWatcher(aDocument);
+    if (NS_WARN_IF(NS_FAILED(
+          aDocument->SetProperty(nsGkAtoms::decoderDoctor,
+                                 watcher.get(),
+                                 DestroyPropertyCallback,
+                                 /*transfer*/ false)))) {
+      DD_WARN("DecoderDoctorDocumentWatcher::RetrieveOrCreate(doc=%p) - Could not set property in document, will destroy new watcher[%p]",
+              aDocument, watcher.get());
+      return nullptr;
+    }
+    // Document owns watcher through this property.
+    // Released in DestroyPropertyCallback().
+    NS_ADDREF(watcher.get());
+  }
+  return watcher;
+}
+
+DecoderDoctorDocumentWatcher::DecoderDoctorDocumentWatcher(nsIDocument* aDocument)
+  : mDocument(aDocument)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mDocument);
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p]::DecoderDoctorDocumentWatcher(doc=%p)",
+           this, mDocument);
+}
+
+DecoderDoctorDocumentWatcher::~DecoderDoctorDocumentWatcher()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p <- expect 0]::~DecoderDoctorDocumentWatcher()",
+           this, mDocument);
+  // mDocument should have been reset through StopWatching()!
+  MOZ_ASSERT(!mDocument);
+}
+
+void
+DecoderDoctorDocumentWatcher::RemovePropertyFromDocument()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  DecoderDoctorDocumentWatcher* watcher =
+    static_cast<DecoderDoctorDocumentWatcher*>(
+      mDocument->GetProperty(nsGkAtoms::decoderDoctor));
+  if (!watcher) {
+    return;
+  }
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::RemovePropertyFromDocument()\n",
+           watcher, watcher->mDocument);
+  // This will remove the property and call our DestroyPropertyCallback.
+  mDocument->DeleteProperty(nsGkAtoms::decoderDoctor);
+}
+
+// Callback for property destructors. |aObject| is the object
+// the property is being removed for, |aPropertyName| is the property
+// being removed, |aPropertyValue| is the value of the property, and |aData|
+// is the opaque destructor data that was passed to SetProperty().
+// static
+void
+DecoderDoctorDocumentWatcher::DestroyPropertyCallback(void* aObject,
+                                                      nsIAtom* aPropertyName,
+                                                      void* aPropertyValue,
+                                                      void*)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+#ifdef DEBUG
+  nsIDocument* document = static_cast<nsIDocument*>(aObject);
+#endif
+  MOZ_ASSERT(aPropertyName == nsGkAtoms::decoderDoctor);
+  DecoderDoctorDocumentWatcher* watcher =
+    static_cast<DecoderDoctorDocumentWatcher*>(aPropertyValue);
+  MOZ_ASSERT(watcher);
+  MOZ_ASSERT(watcher->mDocument == document);
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::DestroyPropertyCallback()\n",
+           watcher, watcher->mDocument);
+  // 'false': StopWatching should not try and remove the property.
+  watcher->StopWatching(false);
+  NS_RELEASE(watcher);
+}
+
+void
+DecoderDoctorDocumentWatcher::StopWatching(bool aRemoveProperty)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // StopWatching() shouldn't be called twice.
+  MOZ_ASSERT(mDocument);
+
+  if (aRemoveProperty) {
+    RemovePropertyFromDocument();
+  }
+
+  // Forget document now, this will prevent more work from being started.
+  mDocument = nullptr;
+
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+}
+
+void
+DecoderDoctorDocumentWatcher::EnsureTimerIsStarted()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mTimer) {
+    mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
+    if (NS_WARN_IF(!mTimer)) {
+      return;
+    }
+    if (NS_WARN_IF(NS_FAILED(
+          mTimer->InitWithCallback(
+            this, sAnalysisPeriod_ms, nsITimer::TYPE_ONE_SHOT)))) {
+      mTimer = nullptr;
+    }
+  }
+}
+
+static void
+DispatchNotification(nsISupports* aSubject,
+                     dom::DecoderDoctorNotificationType aNotificationType,
+                     const nsAString& aFormats)
+{
+  if (!aSubject) {
+    return;
+  }
+  dom::DecoderDoctorNotification data;
+  data.mType = aNotificationType;
+  if (!aFormats.IsEmpty()) {
+    data.mFormats.Construct(aFormats);
+  }
+  nsAutoString json;
+  data.ToJSON(json);
+  if (json.IsEmpty()) {
+    DD_WARN("DecoderDoctorDiagnostics/DispatchEvent() - Could not create json for dispatch");
+    // No point in dispatching this notification without data, the front-end
+    // wouldn't know what to display.
+    return;
+  }
+  DD_DEBUG("DecoderDoctorDiagnostics/DispatchEvent() %s", NS_ConvertUTF16toUTF8(json).get());
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(aSubject, "decoder-doctor-notification", json.get());
+  }
+}
+
+void
+DecoderDoctorDocumentWatcher::ReportAnalysis(
+  dom::DecoderDoctorNotificationType aNotificationType,
+  const char* aReportStringId,
+  const nsAString& aFormats)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mDocument) {
+    return;
+  }
+
+  const char16_t* params[] = { aFormats.Data() };
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::ReportAnalysis() ReportToConsole - aMsg='%s' params[0]='%s'",
+           this, mDocument, aReportStringId,
+           NS_ConvertUTF16toUTF8(params[0]).get());
+  nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                  NS_LITERAL_CSTRING("Media"),
+                                  mDocument,
+                                  nsContentUtils::eDOM_PROPERTIES,
+                                  aReportStringId,
+                                  params,
+                                  ArrayLength(params));
+
+  // For now, disable all front-end notifications by default.
+  // TODO: Future bugs will use finer-grained filtering instead.
+  if (Preferences::GetBool("media.decoderdoctor.enable-notification-bar", false)) {
+    DispatchNotification(
+      mDocument->GetInnerWindow(), aNotificationType, aFormats);
+  }
+}
+
+void
+DecoderDoctorDocumentWatcher::SynthesizeAnalysis()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool canPlay = false;
+#if defined(MOZ_FFMPEG)
+  bool PlatformDecoderNeeded = false;
+#endif
+  nsAutoString formats;
+  for (auto& diag : mDiagnosticsSequence) {
+    if (diag.mDecoderDoctorDiagnostics.CanPlay()) {
+      canPlay = true;
+    } else {
+#if defined(MOZ_FFMPEG)
+      if (diag.mDecoderDoctorDiagnostics.DidFFmpegFailToLoad()) {
+        PlatformDecoderNeeded = true;
+      }
+#endif
+      if (!formats.IsEmpty()) {
+        formats += NS_LITERAL_STRING(", ");
+      }
+      formats += diag.mFormat;
+    }
+  }
+  if (!canPlay) {
+#if defined(MOZ_FFMPEG)
+    if (PlatformDecoderNeeded) {
+      DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - formats: %s -> Cannot play media because platform decoder was not found",
+               this, mDocument, NS_ConvertUTF16toUTF8(formats).get());
+      ReportAnalysis(dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
+                     "MediaPlatformDecoderNotFound", formats);
+    } else
+#endif
+    {
+      DD_WARN("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - Cannot play media, formats: %s",
+              this, mDocument, NS_ConvertUTF16toUTF8(formats).get());
+      ReportAnalysis(dom::DecoderDoctorNotificationType::Cannot_play,
+                     "MediaCannotPlayNoDecoders", formats);
+    }
+  } else if (!formats.IsEmpty()) {
+    DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - Can play media, but no decoders for some requested formats: %s",
+            this, mDocument, NS_ConvertUTF16toUTF8(formats).get());
+    if (Preferences::GetBool("media.decoderdoctor.verbose", false)) {
+      ReportAnalysis(
+        dom::DecoderDoctorNotificationType::Can_play_but_some_missing_decoders,
+        "MediaNoDecoders", formats);
+    }
+  } else {
+    DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - Can play media, decoders available for all requested formats",
+             this, mDocument);
+  }
+}
+
+void
+DecoderDoctorDocumentWatcher::AddDiagnostics(const nsAString& aFormat,
+                                            const char* aCallSite,
+                                            DecoderDoctorDiagnostics&& aDiagnostics)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mDocument) {
+    return;
+  }
+
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics(format='%s', call site '%s', can play=%d, platform lib failed to load=%d)",
+           this, mDocument, NS_ConvertUTF16toUTF8(aFormat).get(),
+           aCallSite, aDiagnostics.CanPlay(), aDiagnostics.DidFFmpegFailToLoad());
+  mDiagnosticsSequence.AppendElement(
+    Diagnostics(Move(aDiagnostics), aFormat, aCallSite));
+  EnsureTimerIsStarted();
+}
+
+NS_IMETHODIMP
+DecoderDoctorDocumentWatcher::Notify(nsITimer* timer)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(timer == mTimer);
+
+  // Forget timer. (Assuming timer keeps itself and us alive during this call.)
+  mTimer = nullptr;
+
+  if (!mDocument) {
+    return NS_OK;
+  }
+
+  if (mDiagnosticsSequence.Length() > mDiagnosticsHandled) {
+    // We have new diagnostic data.
+    mDiagnosticsHandled = mDiagnosticsSequence.Length();
+
+    SynthesizeAnalysis();
+
+    // Restart timer, to redo analysis or stop watching this document,
+    // depending on whether anything new happens.
+    EnsureTimerIsStarted();
+  } else {
+    DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - No new diagnostics to analyze -> Stop watching",
+             this, mDocument);
+    // Stop watching this document, we don't expect more diagnostics for now.
+    // If more diagnostics come in, we'll treat them as another burst, separately.
+    // 'true' to remove the property from the document.
+    StopWatching(true);
+  }
+
+  return NS_OK;
+}
+
+
+void
+DecoderDoctorDiagnostics::StoreDiagnostics(nsIDocument* aDocument,
+                                           const nsAString& aFormat,
+                                           const char* aCallSite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (NS_WARN_IF(!aDocument)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDiagnostics(nsIDocument* aDocument=nullptr, format='%s', call site '%s')",
+            this, NS_ConvertUTF16toUTF8(aFormat).get(), aCallSite);
+    return;
+  }
+
+  RefPtr<DecoderDoctorDocumentWatcher> watcher =
+    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
+
+  if (NS_WARN_IF(!watcher)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDiagnostics(nsIDocument* aDocument=nullptr, format='%s', call site '%s') - Could not create document watcher",
+            this, NS_ConvertUTF16toUTF8(aFormat).get(), aCallSite);
+    return;
+  }
+
+  // StoreDiagnostics should only be called once, after all data is available,
+  // so it is safe to Move() from this object.
+  watcher->AddDiagnostics(aFormat, aCallSite, Move(*this));
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef DecoderDoctorDiagnostics_h_
+#define DecoderDoctorDiagnostics_h_
+
+class nsIDocument;
+class nsAString;
+
+namespace mozilla {
+
+// DecoderDoctorDiagnostics class, used to gather data from PDMs/DecoderTraits,
+// and then notify the user about issues preventing (or worsening) playback.
+//
+// The expected usage is:
+// 1. Instantiate a DecoderDoctorDiagnostics in a function (close to the point
+//    where a webpage is trying to know whether some MIME types can be played,
+//    or trying to play a media file).
+// 2. Pass a pointer to the DecoderDoctorDiagnostics structure to one of the
+//    CanPlayStatus/IsTypeSupported/(others?). During that call, some PDMs may
+//    add relevant diagnostic information.
+// 3. Analyze the collected diagnostics, and optionally dispatch an event to the
+//    UX, to notify the user about potential playback issues and how to resolve
+//    them.
+//
+// This class' methods must be called from the main thread.
+
+class DecoderDoctorDiagnostics
+{
+public:
+  // Store the diagnostic information collected so far on a document for a
+  // given format. All diagnostics for a document will be analyzed together
+  // within a short timeframe.
+  // Should only be called once.
+  void StoreDiagnostics(nsIDocument* aDocument,
+                        const nsAString& aFormat,
+                        const char* aCallSite);
+
+  // Methods to record diagnostic information:
+
+  void SetCanPlay() { mCanPlay = true; }
+  bool CanPlay() const { return mCanPlay; }
+
+  void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; }
+  bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; }
+
+private:
+  // True if there is at least one decoder that can play the media.
+  bool mCanPlay = false;
+
+  bool mFFmpegFailedToLoad = false;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -299,27 +299,29 @@ IsDirectShowSupportedType(const nsACStri
 {
   return DirectShowDecoder::GetSupportedCodecs(aType, nullptr);
 }
 #endif
 
 #ifdef MOZ_FMP4
 static bool
 IsMP4SupportedType(const nsACString& aType,
+                   DecoderDoctorDiagnostics* aDiagnostics,
                    const nsAString& aCodecs = EmptyString())
 {
-  return MP4Decoder::CanHandleMediaType(aType, aCodecs);
+  return MP4Decoder::CanHandleMediaType(aType, aCodecs, aDiagnostics);
 }
 #endif
 
 /* static */ bool
-DecoderTraits::IsMP4TypeAndEnabled(const nsACString& aType)
+DecoderTraits::IsMP4TypeAndEnabled(const nsACString& aType,
+                                   DecoderDoctorDiagnostics* aDiagnostics)
 {
 #ifdef MOZ_FMP4
-  return IsMP4SupportedType(aType);
+  return IsMP4SupportedType(aType, aDiagnostics);
 #else
   return false;
 #endif
 }
 
 static bool
 IsMP3SupportedType(const nsACString& aType,
                    const nsAString& aCodecs = EmptyString())
@@ -341,17 +343,18 @@ IsAACSupportedType(const nsACString& aTy
 static bool
 IsWAVSupportedType(const nsACString& aType,
                    const nsAString& aCodecs = EmptyString())
 {
   return WaveDecoder::CanHandleMediaType(aType, aCodecs);
 }
 
 /* static */
-bool DecoderTraits::ShouldHandleMediaType(const char* aMIMEType)
+bool DecoderTraits::ShouldHandleMediaType(const char* aMIMEType,
+                                          DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (IsWaveType(nsDependentCString(aMIMEType))) {
     // We should not return true for Wave types, since there are some
     // Wave codecs actually in use in the wild that we don't support, and
     // we should allow those to be handled by plugins or helper apps.
     // Furthermore people can play Wave files on most platforms by other
     // means.
     return false;
@@ -363,23 +366,25 @@ bool DecoderTraits::ShouldHandleMediaTyp
   if (nsDependentCString(aMIMEType).EqualsASCII("video/quicktime")) {
     RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
     if (pluginHost &&
         pluginHost->HavePluginForType(nsDependentCString(aMIMEType))) {
       return false;
     }
   }
 
-  return CanHandleMediaType(aMIMEType, false, EmptyString()) != CANPLAY_NO;
+  return CanHandleMediaType(aMIMEType, false, EmptyString(), aDiagnostics)
+         != CANPLAY_NO;
 }
 
 /* static */
 CanPlayStatus
 DecoderTraits::CanHandleCodecsType(const char* aMIMEType,
-                                   const nsAString& aRequestedCodecs)
+                                   const nsAString& aRequestedCodecs,
+                                   DecoderDoctorDiagnostics* aDiagnostics)
 {
   char const* const* codecList = nullptr;
 #ifdef MOZ_RAW
   if (IsRawType(nsDependentCString(aMIMEType))) {
     codecList = gRawCodecs;
   }
 #endif
   if (IsOggType(nsDependentCString(aMIMEType))) {
@@ -395,18 +400,18 @@ DecoderTraits::CanHandleCodecsType(const
     } else {
       // We can only reach this position if a particular codec was requested,
       // webm is supported and working: the codec must be invalid.
       return CANPLAY_NO;
     }
   }
 #endif
 #ifdef MOZ_FMP4
-  if (IsMP4TypeAndEnabled(nsDependentCString(aMIMEType))) {
-    if (IsMP4SupportedType(nsDependentCString(aMIMEType), aRequestedCodecs)) {
+  if (IsMP4TypeAndEnabled(nsDependentCString(aMIMEType), aDiagnostics)) {
+    if (IsMP4SupportedType(nsDependentCString(aMIMEType), aDiagnostics, aRequestedCodecs)) {
       return CANPLAY_YES;
     } else {
       // We can only reach this position if a particular codec was requested,
       // fmp4 is supported and working: the codec must be invalid.
       return CANPLAY_NO;
     }
   }
 #endif
@@ -462,38 +467,41 @@ DecoderTraits::CanHandleCodecsType(const
 
   return CANPLAY_YES;
 }
 
 /* static */
 CanPlayStatus
 DecoderTraits::CanHandleMediaType(const char* aMIMEType,
                                   bool aHaveRequestedCodecs,
-                                  const nsAString& aRequestedCodecs)
+                                  const nsAString& aRequestedCodecs,
+                                  DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (aHaveRequestedCodecs) {
-    CanPlayStatus result = CanHandleCodecsType(aMIMEType, aRequestedCodecs);
+    CanPlayStatus result = CanHandleCodecsType(aMIMEType,
+                                               aRequestedCodecs,
+                                               aDiagnostics);
     if (result == CANPLAY_NO || result == CANPLAY_YES) {
       return result;
     }
   }
 #ifdef MOZ_RAW
   if (IsRawType(nsDependentCString(aMIMEType))) {
     return CANPLAY_MAYBE;
   }
 #endif
   if (IsOggType(nsDependentCString(aMIMEType))) {
     return CANPLAY_MAYBE;
   }
   if (IsWaveType(nsDependentCString(aMIMEType))) {
     return CANPLAY_MAYBE;
   }
-  if (IsMP4TypeAndEnabled(nsDependentCString(aMIMEType))) {
+  if (IsMP4TypeAndEnabled(nsDependentCString(aMIMEType), aDiagnostics)) {
     return CANPLAY_MAYBE;
   }
 #if !defined(MOZ_OMX_WEBM_DECODER)
   if (IsWebMTypeAndEnabled(nsDependentCString(aMIMEType))) {
     return CANPLAY_MAYBE;
   }
 #endif
   if (IsMP3SupportedType(nsDependentCString(aMIMEType))) {
@@ -524,23 +532,25 @@ DecoderTraits::CanHandleMediaType(const 
   }
 #endif
   return CANPLAY_NO;
 }
 
 // Instantiates but does not initialize decoder.
 static
 already_AddRefed<MediaDecoder>
-InstantiateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner)
+InstantiateDecoder(const nsACString& aType,
+                   MediaDecoderOwner* aOwner,
+                   DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<MediaDecoder> decoder;
 
 #ifdef MOZ_FMP4
-  if (IsMP4SupportedType(aType)) {
+  if (IsMP4SupportedType(aType, aDiagnostics)) {
     decoder = new MP4Decoder(aOwner);
     return decoder.forget();
   }
 #endif
   if (IsMP3SupportedType(aType)) {
     decoder = new MP3Decoder(aOwner);
     return decoder.forget();
   }
@@ -611,33 +621,35 @@ InstantiateDecoder(const nsACString& aTy
   }
 #endif
 
   return nullptr;
 }
 
 /* static */
 already_AddRefed<MediaDecoder>
-DecoderTraits::CreateDecoder(const nsACString& aType, MediaDecoderOwner* aOwner)
+DecoderTraits::CreateDecoder(const nsACString& aType,
+                             MediaDecoderOwner* aOwner,
+                             DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  return InstantiateDecoder(aType, aOwner);
+  return InstantiateDecoder(aType, aOwner, aDiagnostics);
 }
 
 /* static */
 MediaDecoderReader* DecoderTraits::CreateReader(const nsACString& aType, AbstractMediaDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MediaDecoderReader* decoderReader = nullptr;
 
   if (!aDecoder) {
     return decoderReader;
   }
 #ifdef MOZ_FMP4
-  if (IsMP4SupportedType(aType)) {
+  if (IsMP4SupportedType(aType, /* DecoderDoctorDiagnostics* */ nullptr)) {
     decoderReader = new MediaFormatReader(aDecoder, new MP4Demuxer(aDecoder->GetResource()));
   } else
 #endif
   if (IsMP3SupportedType(aType)) {
     decoderReader = new MediaFormatReader(aDecoder, new mp3::MP3Demuxer(aDecoder->GetResource()));
   } else
   if (IsAACSupportedType(aType)) {
     decoderReader = new MediaFormatReader(aDecoder, new ADTSDemuxer(aDecoder->GetResource()));
@@ -701,17 +713,17 @@ bool DecoderTraits::IsSupportedInVideoDo
     (IsOmxSupportedType(aType) &&
      !IsB2GSupportOnlyType(aType)) ||
 #endif
     IsWebMSupportedType(aType) ||
 #ifdef MOZ_ANDROID_OMX
     (MediaDecoder::IsAndroidMediaPluginEnabled() && IsAndroidMediaType(aType)) ||
 #endif
 #ifdef MOZ_FMP4
-    IsMP4SupportedType(aType) ||
+    IsMP4SupportedType(aType, /* DecoderDoctorDiagnostics* */ nullptr) ||
 #endif
     IsMP3SupportedType(aType) ||
     IsAACSupportedType(aType) ||
 #ifdef MOZ_DIRECTSHOW
     IsDirectShowSupportedType(aType) ||
 #endif
 #ifdef NECKO_PROTOCOL_rtsp
     IsRtspSupportedType(aType) ||
--- a/dom/media/DecoderTraits.h
+++ b/dom/media/DecoderTraits.h
@@ -10,16 +10,17 @@
 #include "nsCOMPtr.h"
 
 class nsAString;
 class nsACString;
 
 namespace mozilla {
 
 class AbstractMediaDecoder;
+class DecoderDoctorDiagnostics;
 class MediaDecoder;
 class MediaDecoderOwner;
 class MediaDecoderReader;
 
 enum CanPlayStatus {
   CANPLAY_NO,
   CANPLAY_MAYBE,
   CANPLAY_YES
@@ -30,36 +31,40 @@ public:
   // Returns the CanPlayStatus indicating if we can handle this
   // MIME type. The MIME type should not include the codecs parameter.
   // That parameter should be passed in aRequestedCodecs, and will only be
   // used if whether a given MIME type being handled depends on the
   // codec that will be used.  If the codecs parameter has not been
   // specified, pass false in aHaveRequestedCodecs.
   static CanPlayStatus CanHandleMediaType(const char* aMIMEType,
                                           bool aHaveRequestedCodecs,
-                                          const nsAString& aRequestedCodecs);
+                                          const nsAString& aRequestedCodecs,
+                                          DecoderDoctorDiagnostics* aDiagnostics);
 
   // Returns the CanPlayStatus indicating if we can handle this MIME type and
   // codecs type natively, excluding any plugins-based reader (such as
   // GStreamer)
   // The MIME type should not include the codecs parameter.
   // That parameter is passed in aRequestedCodecs.
   static CanPlayStatus CanHandleCodecsType(const char* aMIMEType,
-                                           const nsAString& aRequestedCodecs);
+                                           const nsAString& aRequestedCodecs,
+                                           DecoderDoctorDiagnostics* aDiagnostics);
 
   // Returns true if we should handle this MIME type when it appears
   // as an <object> or as a toplevel page. If, in practice, our support
   // for the type is more limited than appears in the wild, we should return
   // false here even if CanHandleMediaType would return true.
-  static bool ShouldHandleMediaType(const char* aMIMEType);
+  static bool ShouldHandleMediaType(const char* aMIMEType,
+                                    DecoderDoctorDiagnostics* aDiagnostics);
 
   // Create a decoder for the given aType. Returns null if we
   // were unable to create the decoder.
   static already_AddRefed<MediaDecoder> CreateDecoder(const nsACString& aType,
-                                                      MediaDecoderOwner* aOwner);
+                                                      MediaDecoderOwner* aOwner,
+                                                      DecoderDoctorDiagnostics* aDiagnostics);
 
   // Create a reader for thew given MIME type aType. Returns null
   // if we were unable to create the reader.
   static MediaDecoderReader* CreateReader(const nsACString& aType,
                                           AbstractMediaDecoder* aDecoder);
 
   // Returns true if MIME type aType is supported in video documents,
   // or false otherwise. Not all platforms support all MIME types, and
@@ -67,15 +72,16 @@ public:
   static bool IsSupportedInVideoDocument(const nsACString& aType);
 
   // Returns true if we should not start decoder until we receive
   // OnConnected signal. (currently RTSP only)
   static bool DecoderWaitsForOnConnected(const nsACString& aType);
 
   static bool IsWebMTypeAndEnabled(const nsACString& aType);
   static bool IsWebMAudioType(const nsACString& aType);
-  static bool IsMP4TypeAndEnabled(const nsACString& aType);
+  static bool IsMP4TypeAndEnabled(const nsACString& aType,
+                                  DecoderDoctorDiagnostics* aDiagnostics);
 };
 
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/MP3Decoder.cpp
+++ b/dom/media/MP3Decoder.cpp
@@ -29,17 +29,18 @@ MP3Decoder::CreateStateMachine() {
   return new MediaDecoderStateMachine(this, reader);
 }
 
 /* static */
 bool
 MP3Decoder::IsEnabled() {
   PDMFactory::Init();
   RefPtr<PDMFactory> platform = new PDMFactory();
-  return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mpeg"));
+  return platform->SupportsMimeType(NS_LITERAL_CSTRING("audio/mpeg"),
+                                    /* DecoderDoctorDiagnostics* */ nullptr);
 }
 
 /* static */
 bool MP3Decoder::CanHandleMediaType(const nsACString& aType,
                                     const nsAString& aCodecs)
 {
   if (aType.EqualsASCII("audio/mp3") || aType.EqualsASCII("audio/mpeg")) {
     return IsEnabled() &&
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -148,17 +148,17 @@ public:
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
   virtual RefPtr<VideoDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
   // By default, the state machine polls the reader once per second when it's
   // 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 bool IsWaitForDataSupported() const { return false; }
 
   virtual RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType)
   {
     MOZ_CRASH();
   }
 
   // By default, the reader return the decoded data. Some readers support
   // retuning demuxed data.
@@ -208,17 +208,17 @@ public:
 
   // MediaSourceReader opts out of the start-time-guessing mechanism.
   virtual bool ForceZeroStartTime() const { return false; }
 
   // The MediaDecoderStateMachine uses various heuristics that assume that
   // raw media data is arriving sequentially from a network channel. This
   // makes sense in the <video src="foo"> case, but not for more advanced use
   // cases like MSE.
-  virtual bool UseBufferingHeuristics() { return true; }
+  virtual bool UseBufferingHeuristics() const { return true; }
 
   // Returns the number of bytes of memory allocated by structures/frames in
   // the video queue.
   size_t SizeOfVideoQueueInBytes() const;
 
   // Returns the number of bytes of memory allocated by structures/frames in
   // the audio queue.
   size_t SizeOfAudioQueueInBytes() const;
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -241,25 +241,70 @@ MediaDecoderReaderWrapper::Seek(SeekTarg
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   aTarget.SetTime(aTarget.GetTime() + StartTime());
   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                      &MediaDecoderReader::Seek, aTarget,
                      aEndTime.ToMicroseconds());
 }
 
+RefPtr<MediaDecoderReaderWrapper::WaitForDataPromise>
+MediaDecoderReaderWrapper::WaitForData(MediaData::Type aType)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
+                     &MediaDecoderReader::WaitForData, aType);
+}
+
+RefPtr<MediaDecoderReaderWrapper::BufferedUpdatePromise>
+MediaDecoderReaderWrapper::UpdateBufferedWithPromise()
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
+                     &MediaDecoderReader::UpdateBufferedWithPromise);
+}
+
 void
+MediaDecoderReaderWrapper::ReleaseMediaResources()
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources);
+  mReader->OwnerThread()->Dispatch(r.forget());
+}
+
+void
+MediaDecoderReaderWrapper::SetIdle()
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle);
+  mReader->OwnerThread()->Dispatch(r.forget());
+}
+
+void
+MediaDecoderReaderWrapper::ResetDecode()
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  nsCOMPtr<nsIRunnable> r =
+    NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
+  mReader->OwnerThread()->Dispatch(r.forget());
+}
+
+RefPtr<ShutdownPromise>
 MediaDecoderReaderWrapper::Shutdown()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   mShutdown = true;
   if (mStartTimeRendezvous) {
     mStartTimeRendezvous->Destroy();
     mStartTimeRendezvous = nullptr;
   }
+  return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
+                     &MediaDecoderReader::Shutdown);
 }
 
 void
 MediaDecoderReaderWrapper::OnMetadataRead(MetadataHolder* aMetadata)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   if (mShutdown) {
     return;
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -25,31 +25,68 @@ typedef MozPromise<bool, bool, /* isExcl
  * It also adjusts the seek target passed to Seek() to ensure correct seek time
  * is passed to the underlying reader.
  */
 class MediaDecoderReaderWrapper {
   typedef MediaDecoderReader::MetadataPromise MetadataPromise;
   typedef MediaDecoderReader::AudioDataPromise AudioDataPromise;
   typedef MediaDecoderReader::VideoDataPromise VideoDataPromise;
   typedef MediaDecoderReader::SeekPromise SeekPromise;
+  typedef MediaDecoderReader::WaitForDataPromise WaitForDataPromise;
+  typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper);
 
 public:
   MediaDecoderReaderWrapper(bool aIsRealTime,
                             AbstractThread* aOwnerThread,
                             MediaDecoderReader* aReader);
 
   media::TimeUnit StartTime() const;
   RefPtr<MetadataPromise> ReadMetadata();
   RefPtr<HaveStartTimePromise> AwaitStartTime();
   RefPtr<AudioDataPromise> RequestAudioData();
   RefPtr<VideoDataPromise> RequestVideoData(bool aSkipToNextKeyframe,
                                             media::TimeUnit aTimeThreshold);
   RefPtr<SeekPromise> Seek(SeekTarget aTarget, media::TimeUnit aEndTime);
-  void Shutdown();
+  RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType);
+  RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise();
+  RefPtr<ShutdownPromise> Shutdown();
+
+  void ReleaseMediaResources();
+  void SetIdle();
+  void ResetDecode();
+
+  nsresult Init() { return mReader->Init(); }
+  bool IsWaitForDataSupported() const { return mReader->IsWaitForDataSupported(); }
+  bool IsAsync() const { return mReader->IsAsync(); }
+  bool UseBufferingHeuristics() const { return mReader->UseBufferingHeuristics(); }
+  bool ForceZeroStartTime() const { return mReader->ForceZeroStartTime(); }
+
+  bool VideoIsHardwareAccelerated() const {
+    return mReader->VideoIsHardwareAccelerated();
+  }
+  TimedMetadataEventSource& TimedMetadataEvent() {
+    return mReader->TimedMetadataEvent();
+  }
+  size_t SizeOfAudioQueueInFrames() const {
+    return mReader->SizeOfAudioQueueInFrames();
+  }
+  size_t SizeOfVideoQueueInFrames() const {
+    return mReader->SizeOfVideoQueueInFrames();
+  }
+  void ReadUpdatedMetadata(MediaInfo* aInfo) {
+    mReader->ReadUpdatedMetadata(aInfo);
+  }
+  AbstractCanonical<media::TimeIntervals>* CanonicalBuffered() {
+    return mReader->CanonicalBuffered();
+  }
+
+#ifdef MOZ_EME
+  void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
+#endif
 
 private:
   ~MediaDecoderReaderWrapper();
 
   void OnMetadataRead(MetadataHolder* aMetadata);
   void OnMetadataNotRead() {}
   void OnSampleDecoded(MediaData* aSample);
   void OnNotDecoded() {}
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -318,17 +318,17 @@ MediaDecoderStateMachine::~MediaDecoderS
 }
 
 void
 MediaDecoderStateMachine::InitializationTask(MediaDecoder* aDecoder)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Connect mirrors.
-  mBuffered.Connect(mReader->CanonicalBuffered());
+  mBuffered.Connect(mReaderWrapper->CanonicalBuffered());
   mEstimatedDuration.Connect(aDecoder->CanonicalEstimatedDuration());
   mExplicitDuration.Connect(aDecoder->CanonicalExplicitDuration());
   mPlayState.Connect(aDecoder->CanonicalPlayState());
   mNextPlayState.Connect(aDecoder->CanonicalNextPlayState());
   mLogicallySeeking.Connect(aDecoder->CanonicalLogicallySeeking());
   mVolume.Connect(aDecoder->CanonicalVolume());
   mLogicalPlaybackRate.Connect(aDecoder->CanonicalPlaybackRate());
   mPreservesPitch.Connect(aDecoder->CanonicalPreservesPitch());
@@ -357,17 +357,17 @@ MediaDecoderStateMachine::Initialization
 
 media::MediaSink*
 MediaDecoderStateMachine::CreateAudioSink()
 {
   RefPtr<MediaDecoderStateMachine> self = this;
   auto audioSinkCreator = [self] () {
     MOZ_ASSERT(self->OnTaskQueue());
     return new DecodedAudioDataSink(
-      self->mAudioQueue, self->GetMediaTime(),
+      self->mTaskQueue, self->mAudioQueue, self->GetMediaTime(),
       self->mInfo.mAudio, self->mAudioChannel);
   };
   return new AudioSinkWrapper(mTaskQueue, audioSinkCreator);
 }
 
 already_AddRefed<media::MediaSink>
 MediaDecoderStateMachine::CreateMediaSink(bool aAudioCaptured)
 {
@@ -517,28 +517,28 @@ MediaDecoderStateMachine::NeedToSkipToNe
   // We'll skip the video decode to the next keyframe if we're low on
   // audio, or if we're low on video, provided we're not running low on
   // data to decode. If we're running low on downloaded data to decode,
   // we won't start keyframe skipping, as we'll be pausing playback to buffer
   // soon anyway and we'll want to be able to display frames immediately
   // after buffering finishes. We ignore the low audio calculations for
   // readers that are async, as since their audio decode runs on a different
   // task queue it should never run low and skipping won't help their decode.
-  bool isLowOnDecodedAudio = !mReader->IsAsync() &&
+  bool isLowOnDecodedAudio = !mReaderWrapper->IsAsync() &&
                              !mIsAudioPrerolling && IsAudioDecoding() &&
                              (GetDecodedAudioDuration() <
                               mLowAudioThresholdUsecs * mPlaybackRate);
   bool isLowOnDecodedVideo = !mIsVideoPrerolling &&
                              ((GetClock() - mDecodedVideoEndTime) * mPlaybackRate >
                               LOW_VIDEO_THRESHOLD_USECS);
   bool lowUndecoded = HasLowUndecodedData();
 
   if ((isLowOnDecodedAudio || isLowOnDecodedVideo) && !lowUndecoded) {
     DECODER_LOG("Skipping video decode to the next keyframe lowAudio=%d lowVideo=%d lowUndecoded=%d async=%d",
-                isLowOnDecodedAudio, isLowOnDecodedVideo, lowUndecoded, mReader->IsAsync());
+                isLowOnDecodedAudio, isLowOnDecodedVideo, lowUndecoded, mReaderWrapper->IsAsync());
     return true;
   }
 
   return false;
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeAudio()
@@ -689,21 +689,21 @@ MediaDecoderStateMachine::OnNotDecoded(M
   if (aReason == MediaDecoderReader::DECODE_ERROR) {
     DecodeError();
     return;
   }
 
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
   if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
-    MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+    MOZ_ASSERT(mReaderWrapper->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
     RefPtr<MediaDecoderStateMachine> self = this;
-    WaitRequestRef(aType).Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
-                                            &MediaDecoderReader::WaitForData, aType)
+    WaitRequestRef(aType).Begin(
+      mReaderWrapper->WaitForData(aType)
       ->Then(OwnerThread(), __func__,
              [self] (MediaData::Type aType) -> void {
                self->WaitRequestRef(aType).Complete();
                if (aType == MediaData::AUDIO_DATA) {
                  self->EnsureAudioDecodeTaskQueued();
                } else {
                  self->EnsureVideoDecodeTaskQueued();
                }
@@ -822,17 +822,17 @@ MediaDecoderStateMachine::OnVideoDecoded
         StopPrerollingVideo();
       }
 
       // For non async readers, if the requested video sample was slow to
       // arrive, increase the amount of audio we buffer to ensure that we
       // don't run out of audio. This is unnecessary for async readers,
       // since they decode audio and video on different threads so they
       // are unlikely to run out of decoded audio.
-      if (mReader->IsAsync()) {
+      if (mReaderWrapper->IsAsync()) {
         return;
       }
       TimeDuration decodeTime = TimeStamp::Now() - aDecodeStartTime;
       if (!IsDecodingFirstFrame() &&
           THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
           !HasLowUndecodedData())
       {
         mLowAudioThresholdUsecs =
@@ -903,28 +903,28 @@ nsresult MediaDecoderStateMachine::Init(
     this, &MediaDecoderStateMachine::InitializationTask, aDecoder);
   mTaskQueue->Dispatch(r.forget());
 
   mAudioQueueListener = AudioQueue().PopEvent().Connect(
     mTaskQueue, this, &MediaDecoderStateMachine::OnAudioPopped);
   mVideoQueueListener = VideoQueue().PopEvent().Connect(
     mTaskQueue, this, &MediaDecoderStateMachine::OnVideoPopped);
 
-  mMetadataManager.Connect(mReader->TimedMetadataEvent(), OwnerThread());
+  mMetadataManager.Connect(mReaderWrapper->TimedMetadataEvent(), OwnerThread());
 
   mMediaSink = CreateMediaSink(mAudioCaptured);
 
 #ifdef MOZ_EME
   mCDMProxyPromise.Begin(aDecoder->RequestCDMProxy()->Then(
     OwnerThread(), __func__, this,
     &MediaDecoderStateMachine::OnCDMProxyReady,
     &MediaDecoderStateMachine::OnCDMProxyNotReady));
 #endif
 
-  nsresult rv = mReader->Init();
+  nsresult rv = mReaderWrapper->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   r = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::ReadMetadata);
   OwnerThread()->Dispatch(r.forget());
 
   return NS_OK;
 }
 
@@ -981,21 +981,21 @@ void
 MediaDecoderStateMachine::MaybeStartBuffering()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   if (mState == DECODER_STATE_DECODING &&
       mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
       mResource->IsExpectingMoreData()) {
     bool shouldBuffer;
-    if (mReader->UseBufferingHeuristics()) {
+    if (mReaderWrapper->UseBufferingHeuristics()) {
       shouldBuffer = HasLowDecodedData(EXHAUSTED_DATA_MARGIN_USECS) &&
                      (JustExitedQuickBuffering() || HasLowUndecodedData());
     } else {
-      MOZ_ASSERT(mReader->IsWaitForDataSupported());
+      MOZ_ASSERT(mReaderWrapper->IsWaitForDataSupported());
       shouldBuffer = (OutOfDecodedAudio() && mAudioWaitRequest.Exists()) ||
                      (OutOfDecodedVideo() && mVideoWaitRequest.Exists());
     }
     if (shouldBuffer) {
       StartBuffering();
       // Don't go straight back to the state machine loop since that might
       // cause us to start decoding again and we could flip-flop between
       // decoding and quick-buffering.
@@ -1163,18 +1163,17 @@ MediaDecoderStateMachine::SetDormant(boo
     Reset();
 
     // Note that we do not wait for the decode task queue to go idle before
     // queuing the ReleaseMediaResources task - instead, we disconnect promises,
     // reset state, and put a ResetDecode in the decode task queue. Any tasks
     // that run after ResetDecode are supposed to run with a clean slate. We rely
     // on that in other places (i.e. seeking), so it seems reasonable to rely on
     // it here as well.
-    nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(mReader, &MediaDecoderReader::ReleaseMediaResources);
-    DecodeTaskQueue()->Dispatch(r.forget());
+    mReaderWrapper->ReleaseMediaResources();
   } else if ((aDormant != true) && (mState == DECODER_STATE_DORMANT)) {
     mDecodingFirstFrame = true;
     SetState(DECODER_STATE_DECODING_METADATA);
     ReadMetadata();
   }
 }
 
 RefPtr<ShutdownPromise>
@@ -1202,24 +1201,22 @@ MediaDecoderStateMachine::Shutdown()
 
   if (IsPlaying()) {
     StopPlayback();
   }
 
   Reset();
 
   mMediaSink->Shutdown();
-  mReaderWrapper->Shutdown();
 
   DECODER_LOG("Shutdown started");
 
   // Put a task in the decode queue to shutdown the reader.
   // the queue to spin down.
-  return InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
-                     &MediaDecoderReader::Shutdown)
+  return mReaderWrapper->Shutdown()
     ->Then(OwnerThread(), __func__, this,
            &MediaDecoderStateMachine::FinishShutdown,
            &MediaDecoderStateMachine::FinishShutdown)
     ->CompletionPromise();
 }
 
 void MediaDecoderStateMachine::StartDecoding()
 {
@@ -1364,17 +1361,17 @@ MediaDecoderStateMachine::Seek(SeekTarge
     DECODER_WARN("Seek() function should not be called on a non-seekable state machine");
     return MediaDecoder::SeekPromise::CreateAndReject(/* aIgnored = */ true, __func__);
   }
 
   MOZ_ASSERT(mState > DECODER_STATE_DECODING_METADATA,
                "We should have got duration already");
 
   if (mState < DECODER_STATE_DECODING ||
-      (IsDecodingFirstFrame() && !mReader->ForceZeroStartTime())) {
+      (IsDecodingFirstFrame() && !mReaderWrapper->ForceZeroStartTime())) {
     DECODER_LOG("Seek() Not Enough Data to continue at this stage, queuing seek");
     mQueuedSeek.RejectIfExists(__func__);
     mQueuedSeek.mTarget = aTarget;
     return mQueuedSeek.mPromise.Ensure(__func__);
   }
   mQueuedSeek.RejectIfExists(__func__);
 
   DECODER_LOG("Changed state to SEEKING (to %lld)", aTarget.GetTime().ToMicroseconds());
@@ -1452,35 +1449,34 @@ MediaDecoderStateMachine::DispatchDecode
   if (needToDecodeVideo) {
     EnsureVideoDecodeTaskQueued();
   }
 
   if (needIdle) {
     DECODER_LOG("Dispatching SetIdle() audioQueue=%lld videoQueue=%lld",
                 GetDecodedAudioDuration(),
                 VideoQueue().Duration());
-    nsCOMPtr<nsIRunnable> task = NS_NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle);
-    DecodeTaskQueue()->Dispatch(task.forget());
+    mReaderWrapper->SetIdle();
   }
 }
 
 void
 MediaDecoderStateMachine::InitiateSeek(SeekJob aSeekJob)
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Discard the existing seek task.
   if (mSeekTask) {
     mSeekTask->Discard();
   }
 
   mSeekTaskRequest.DisconnectIfExists();
 
   // Create a new SeekTask instance for the incoming seek task.
-  mSeekTask = SeekTask::CreateSeekTask(mDecoderID, OwnerThread(), mReader.get(),
+  mSeekTask = SeekTask::CreateSeekTask(mDecoderID, OwnerThread(),
                                        mReaderWrapper.get(), Move(aSeekJob),
                                        mInfo, Duration(), GetMediaTime());
 
   // Stop playback now to ensure that while we're outside the monitor
   // dispatching SeekingStarted, playback doesn't advance and mess with
   // mCurrentPosition that we've setting to seekTime here.
   StopPlayback();
   UpdatePlaybackPositionInternal(mSeekTask->GetSeekJob().mTarget.GetTime().ToMicroseconds());
@@ -1614,17 +1610,17 @@ MediaDecoderStateMachine::EnsureAudioDec
 
 void
 MediaDecoderStateMachine::RequestAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState != DECODER_STATE_SEEKING);
 
   SAMPLE_LOG("Queueing audio task - queued=%i, decoder-queued=%o",
-             AudioQueue().GetSize(), mReader->SizeOfAudioQueueInFrames());
+             AudioQueue().GetSize(), mReaderWrapper->SizeOfAudioQueueInFrames());
 
   mAudioDataRequest.Begin(
     mReaderWrapper->RequestAudioData()
     ->Then(OwnerThread(), __func__, this,
            &MediaDecoderStateMachine::OnAudioDecoded,
            &MediaDecoderStateMachine::OnAudioNotDecoded));
 }
 
@@ -1679,17 +1675,17 @@ MediaDecoderStateMachine::RequestVideoDa
   TimeStamp videoDecodeStartTime = TimeStamp::Now();
 
   bool skipToNextKeyFrame = mSentFirstFrameLoadedEvent &&
     NeedToSkipToNextKeyframe();
 
   media::TimeUnit currentTime = media::TimeUnit::FromMicroseconds(GetMediaTime());
 
   SAMPLE_LOG("Queueing video task - queued=%i, decoder-queued=%o, skip=%i, time=%lld",
-             VideoQueue().GetSize(), mReader->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
+             VideoQueue().GetSize(), mReaderWrapper->SizeOfVideoQueueInFrames(), skipToNextKeyFrame,
              currentTime.ToMicroseconds());
 
   RefPtr<MediaDecoderStateMachine> self = this;
   mVideoDataRequest.Begin(
     mReaderWrapper->RequestVideoData(skipToNextKeyFrame, currentTime)
     ->Then(OwnerThread(), __func__,
            [self, videoDecodeStartTime] (MediaData* aVideoSample) {
              self->OnVideoDecoded(aVideoSample, videoDecodeStartTime);
@@ -1723,17 +1719,17 @@ MediaDecoderStateMachine::StartMediaSink
         &MediaDecoderStateMachine::OnMediaSinkVideoError));
     }
   }
 }
 
 bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs)
 {
   MOZ_ASSERT(OnTaskQueue());
-  MOZ_ASSERT(mReader->UseBufferingHeuristics());
+  MOZ_ASSERT(mReaderWrapper->UseBufferingHeuristics());
   // We consider ourselves low on decoded data if we're low on audio,
   // provided we've not decoded to the end of the audio stream, or
   // if we're low on video frames, provided
   // we've not decoded to the end of the video stream.
   return ((IsAudioDecoding() && GetDecodedAudioDuration() < aAudioUsecs) ||
          (IsVideoDecoding() &&
           static_cast<uint32_t>(VideoQueue().GetSize()) < LOW_VIDEO_FRAMES));
 }
@@ -1840,18 +1836,18 @@ MediaDecoderStateMachine::OnMetadataRead
         self->mInfo.mMetadataDuration.emplace(unadjusted - adjustment);
         self->RecomputeDuration();
       }, [] () -> void { NS_WARNING("Adjusting metadata end time failed"); }
     );
   }
 
   if (HasVideo()) {
     DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
-                mReader->IsAsync(),
-                mReader->VideoIsHardwareAccelerated(),
+                mReaderWrapper->IsAsync(),
+                mReaderWrapper->VideoIsHardwareAccelerated(),
                 GetAmpleVideoFrames());
   }
 
   // In general, we wait until we know the duration before notifying the decoder.
   // However, we notify  unconditionally in this case without waiting for the start
   // time, since the caller might be waiting on metadataloaded to be fired before
   // feeding in the CDM, which we need to decode the first frame (and
   // thus get the metadata). We could fix this if we could compute the start
@@ -1905,18 +1901,18 @@ MediaDecoderStateMachine::EnqueueLoadedM
 void
 MediaDecoderStateMachine::EnqueueFirstFrameLoadedEvent()
 {
   MOZ_ASSERT(OnTaskQueue());
   // Track value of mSentFirstFrameLoadedEvent from before updating it
   bool firstFrameBeenLoaded = mSentFirstFrameLoadedEvent;
   mSentFirstFrameLoadedEvent = true;
   RefPtr<MediaDecoderStateMachine> self = this;
-  mBufferedUpdateRequest.Begin(InvokeAsync(DecodeTaskQueue(), mReader.get(), __func__,
-    &MediaDecoderReader::UpdateBufferedWithPromise)
+  mBufferedUpdateRequest.Begin(
+    mReaderWrapper->UpdateBufferedWithPromise()
     ->Then(OwnerThread(),
     __func__,
     // Resolve
     [self, firstFrameBeenLoaded]() {
       self->mBufferedUpdateRequest.Complete();
       MediaDecoderEventVisibility visibility =
         firstFrameBeenLoaded ? MediaDecoderEventVisibility::Suppressed
                              : MediaDecoderEventVisibility::Observable;
@@ -1948,17 +1944,17 @@ MediaDecoderStateMachine::FinishDecodeFi
     mDuration = Some(TimeUnit::FromInfinity());
   }
 
   DECODER_LOG("Media duration %lld, "
               "transportSeekable=%d, mediaSeekable=%d",
               Duration().ToMicroseconds(), mResource->IsTransportSeekable(), mMediaSeekable.Ref());
 
   // Get potentially updated metadata
-  mReader->ReadUpdatedMetadata(&mInfo);
+  mReaderWrapper->ReadUpdatedMetadata(&mInfo);
 
   if (!mNotifyMetadataBeforeFirstFrame) {
     // If we didn't have duration and/or start time before, we should now.
     EnqueueLoadedMetadataEvent();
   }
   EnqueueFirstFrameLoadedEvent();
 
   mDecodingFirstFrame = false;
@@ -2147,33 +2143,33 @@ nsresult MediaDecoderStateMachine::RunSt
 
     case DECODER_STATE_BUFFERING: {
       TimeStamp now = TimeStamp::Now();
       NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
 
       // With buffering heuristics we will remain in the buffering state if
       // we've not decoded enough data to begin playback, or if we've not
       // downloaded a reasonable amount of data inside our buffering time.
-      if (mReader->UseBufferingHeuristics()) {
+      if (mReaderWrapper->UseBufferingHeuristics()) {
         TimeDuration elapsed = now - mBufferingStart;
         bool isLiveStream = resource->IsLiveStream();
         if ((isLiveStream || !CanPlayThrough()) &&
               elapsed < TimeDuration::FromSeconds(mBufferingWait * mPlaybackRate) &&
               (mQuickBuffering ? HasLowDecodedData(mQuickBufferingLowDataThresholdUsecs)
                                : HasLowUndecodedData(mBufferingWait * USECS_PER_S)) &&
               mResource->IsExpectingMoreData())
         {
           DECODER_LOG("Buffering: wait %ds, timeout in %.3lfs %s",
                       mBufferingWait, mBufferingWait - elapsed.ToSeconds(),
                       (mQuickBuffering ? "(quick exit)" : ""));
           ScheduleStateMachineIn(USECS_PER_S);
           return NS_OK;
         }
       } else if (OutOfDecodedAudio() || OutOfDecodedVideo()) {
-        MOZ_ASSERT(mReader->IsWaitForDataSupported(),
+        MOZ_ASSERT(mReaderWrapper->IsWaitForDataSupported(),
                    "Don't yet have a strategy for non-heuristic + non-WaitForData");
         DispatchDecodeTasksIfNeeded();
         MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedAudio(), mAudioDataRequest.Exists() || mAudioWaitRequest.Exists());
         MOZ_ASSERT_IF(!mMinimizePreroll && OutOfDecodedVideo(), mVideoDataRequest.Exists() || mVideoWaitRequest.Exists());
         DECODER_LOG("In buffering mode, waiting to be notified: outOfAudio: %d, "
                     "mAudioStatus: %s, outOfVideo: %d, mVideoStatus: %s",
                     OutOfDecodedAudio(), AudioRequestStatus(),
                     OutOfDecodedVideo(), VideoRequestStatus());
@@ -2270,19 +2266,17 @@ MediaDecoderStateMachine::Reset()
   mAudioDataRequest.DisconnectIfExists();
   mAudioWaitRequest.DisconnectIfExists();
   mVideoDataRequest.DisconnectIfExists();
   mVideoWaitRequest.DisconnectIfExists();
   mSeekTaskRequest.DisconnectIfExists();
 
   mPlaybackOffset = 0;
 
-  nsCOMPtr<nsIRunnable> resetTask =
-    NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
-  DecodeTaskQueue()->Dispatch(resetTask.forget());
+  mReaderWrapper->ResetDecode();
 }
 
 int64_t
 MediaDecoderStateMachine::GetClock(TimeStamp* aTimeStamp) const
 {
   MOZ_ASSERT(OnTaskQueue());
   int64_t clockTime = mMediaSink->GetPosition(aTimeStamp);
   NS_ASSERTION(GetMediaTime() <= clockTime, "Clock should go forwards.");
@@ -2599,17 +2593,17 @@ void MediaDecoderStateMachine::OnMediaSi
 
 #ifdef MOZ_EME
 void
 MediaDecoderStateMachine::OnCDMProxyReady(RefPtr<CDMProxy> aProxy)
 {
   MOZ_ASSERT(OnTaskQueue());
   mCDMProxyPromise.Complete();
   mCDMProxy = aProxy;
-  mReader->SetCDMProxy(aProxy);
+  mReaderWrapper->SetCDMProxy(aProxy);
   if (mState == DECODER_STATE_WAIT_FOR_CDM) {
     StartDecoding();
   }
 }
 
 void
 MediaDecoderStateMachine::OnCDMProxyNotReady()
 {
@@ -2661,17 +2655,17 @@ MediaDecoderStateMachine::SetAudioCaptur
   if (mIsAudioPrerolling && DonePrerollingAudio()) {
     StopPrerollingAudio();
   }
 }
 
 uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const
 {
   MOZ_ASSERT(OnTaskQueue());
-  return (mReader->IsAsync() && mReader->VideoIsHardwareAccelerated())
+  return (mReaderWrapper->IsAsync() && mReaderWrapper->VideoIsHardwareAccelerated())
     ? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
     : std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
 }
 
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
                                                bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -390,27 +390,29 @@ MediaFormatReader::EnsureDecoderCreated(
 
   switch (aTrack) {
     case TrackType::kAudioTrack:
       decoder.mDecoder =
         mPlatform->CreateDecoder(decoder.mInfo ?
                                    *decoder.mInfo->GetAsAudioInfo() :
                                    mInfo.mAudio,
                                  decoder.mTaskQueue,
-                                 decoder.mCallback);
+                                 decoder.mCallback,
+                                 /* DecoderDoctorDiagnostics* */ nullptr);
       break;
     case TrackType::kVideoTrack:
       // Decoders use the layers backend to decide if they can use hardware decoding,
       // so specify LAYERS_NONE if we want to forcibly disable it.
       decoder.mDecoder =
         mPlatform->CreateDecoder(mVideo.mInfo ?
                                    *mVideo.mInfo->GetAsVideoInfo() :
                                    mInfo.mVideo,
                                  decoder.mTaskQueue,
                                  decoder.mCallback,
+                                 /* DecoderDoctorDiagnostics* */ nullptr,
                                  mLayersBackendType,
                                  GetImageContainer());
       break;
     default:
       break;
   }
   if (decoder.mDecoder ) {
     decoder.mDescription = decoder.mDecoder->GetDescriptionName();