Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 08 Mar 2017 14:28:34 +0100
changeset 346527 791a43e25a1e56bfad5a062bd7f94a6d2f0957d5
parent 346455 3b8c0a06aedd01ae231764854a47e64a453411a4 (current diff)
parent 346526 becff35a0bed14b536bb0a141b0e9640e9cb063d (diff)
child 346528 dc0814eb54f8e9507572cc80c58c5ceb344b8907
child 346552 800ba54a4bd52628833c4db005ddd182586666c4
push id87825
push usercbook@mozilla.com
push dateWed, 08 Mar 2017 13:28:47 +0000
treeherdermozilla-inbound@791a43e25a1e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.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 mozilla-inbound
dom/animation/KeyframeUtils.cpp
gfx/layers/ipc/CompositorBridgeParent.cpp
layout/base/nsCSSFrameConstructor.cpp
old-configure.in
services/sync/tests/unit/test_service_migratePrefs.js
third_party/rust/bitreader/LICENSE
toolkit/components/search/tests/xpcshell/data/engine-update.xml
toolkit/components/search/tests/xpcshell/test_update_telemetry.js
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -111,16 +111,25 @@ const TRANSPARENT_SELECT =
   "<html><head><style>" +
   "  #one { background-color: transparent; }" +
   "</style>" +
   "<body><select id='one'>" +
   '  <option value="One">{"unstyled": "true"}</option>' +
   '  <option value="Two" selected="true">{"end": "true"}</option>' +
   "</select></body></html>";
 
+const OPTION_COLOR_EQUAL_TO_UABACKGROUND_COLOR_SELECT =
+  "<html><head><style>" +
+  "  #one { background-color: black; color: white; }" +
+  "</style>" +
+  "<body><select id='one'>" +
+  '  <option value="One" style="background-color: white; color: black;">{"color": "rgb(0, 0, 0)", "backgroundColor": "rgb(255, 255, 255)"}</option>' +
+  '  <option value="Two" selected="true">{"end": "true"}</option>' +
+  "</select></body></html>";
+
 function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
 
   if (mode == "click" || mode == "mousedown") {
     let mousePromise;
     if (mode == "click") {
       mousePromise = BrowserTestUtils.synthesizeMouseAtCenter(selector, { }, win.gBrowser.selectedBrowser);
     } else {
@@ -925,8 +934,46 @@ add_task(function* test_blur_hides_popup
   });
 
   yield popupHiddenPromise;
 
   ok(true, "Blur closed popup");
 
   yield BrowserTestUtils.removeTab(tab);
 });
+
+// This test checks when a <select> element has a background set, and the
+// options have their own background set which is equal to the default
+// user-agent background color, but should be used because the select
+// background color has been changed.
+add_task(function* test_options_inverted_from_select_background() {
+  const pageUrl = "data:text/html," + escape(OPTION_COLOR_EQUAL_TO_UABACKGROUND_COLOR_SELECT);
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+  let menulist = document.getElementById("ContentSelectDropdown");
+  let selectPopup = menulist.menupopup;
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
+  yield popupShownPromise;
+
+  is(selectPopup.parentNode.itemCount, 2, "Correct number of items");
+  let child = selectPopup.firstChild;
+  let idx = 1;
+
+  // The popup has a black background and white text, but the
+  // options inside of it have flipped the colors.
+  is(getComputedStyle(selectPopup).color, "rgb(255, 255, 255)",
+    "popup has expected foreground color");
+  is(getComputedStyle(selectPopup).backgroundColor, "rgb(0, 0, 0)",
+    "popup has expected background color");
+
+  ok(!child.selected, "The first child should not be selected");
+  while (child) {
+    testOptionColors(idx, child, menulist);
+    idx++;
+    child = child.nextSibling;
+  }
+
+  yield hideSelectPopup(selectPopup, "escape");
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -58,21 +58,20 @@
       <method name="_onUnderflow">
         <body></body>
       </method>
 
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
           let outerBoxRect = this.parentNode.getBoundingClientRect();
-          let value = this.getAttribute("ac-value");
-          let comment = this.getAttribute("ac-comment");
+          let {primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
 
-          this._comment.textContent = comment;
-          this._label.textContent = value;
+          this._label.textContent = primary;
+          this._comment.textContent = secondary;
 
           // Make item fit in popup as XUL box could not constrain
           // item's width
           this._itemBox.style.width =  outerBoxRect.width + "px";
           // Use two-lines layout when width is smaller than 150px
           if (outerBoxRect.width <= 150) {
             this._itemBox.setAttribute("size", "small");
           } else {
--- a/browser/modules/AboutNewTab.jsm
+++ b/browser/modules/AboutNewTab.jsm
@@ -19,25 +19,42 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/NewTabUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RemotePages",
   "resource://gre/modules/RemotePageManager.jsm");
 
 var AboutNewTab = {
 
   pageListener: null,
 
+  isOverridden: false,
+
   init() {
+    if (this.isOverridden) {
+      return;
+    }
     this.pageListener = new RemotePages("about:newtab");
     this.pageListener.addMessageListener("NewTab:Customize", this.customize.bind(this));
     this.pageListener.addMessageListener("NewTab:MaybeShowAutoMigrationUndoNotification",
       (msg) => AutoMigrate.maybeShowUndoNotification(msg.target.browser));
   },
 
   customize(message) {
     NewTabUtils.allPages.enabled = message.data.enabled;
     NewTabUtils.allPages.enhanced = message.data.enhanced;
   },
 
   uninit() {
-    this.pageListener.destroy();
-    this.pageListener = null;
+    if (this.pageListener) {
+      this.pageListener.destroy();
+      this.pageListener = null;
+    }
   },
+
+  override() {
+    this.uninit();
+    this.isOverridden = true;
+  },
+
+  reset() {
+    this.isOverridden = false;
+    this.init();
+  }
 };
--- a/build/mozconfig.cache
+++ b/build/mozconfig.cache
@@ -103,20 +103,16 @@ fi
 
 if test -z "$bucket"; then
     case "$platform" in
     win*) : ;;
     *)
         ac_add_options --with-ccache
     esac
 else
-    if ! test -e $topsrcdir/sccache2/sccache${suffix}; then
-        echo "sccache2 missing in the tooltool manifest" >&2
-        exit 1
-    fi
     mk_add_options "export SCCACHE_BUCKET=$bucket"
     case "$master" in
     *us[ew][12].mozilla.com*|*euc1.mozilla.com*)
         mk_add_options "export SCCACHE_NAMESERVER=169.254.169.253"
         ;;
     esac
     ac_add_options "--with-ccache=$topsrcdir/sccache2/sccache${suffix}"
     export SCCACHE_VERBOSE_STATS=1
--- a/build/mozconfig.clang-cl
+++ b/build/mozconfig.clang-cl
@@ -1,7 +1,9 @@
-CLANG_DIR=`cd "$topsrcdir/clang/bin" ; pwd`
-export PATH="${CLANG_DIR}:${PATH}"
+if test -d "$topsrcdir/clang/bin"; then
+    CLANG_DIR=`cd "$topsrcdir/clang/bin" ; pwd`
+    export PATH="${CLANG_DIR}:${PATH}"
 
-mk_export_correct_style PATH
+    mk_export_correct_style PATH
+fi
 
 export CC=clang-cl
 export CXX=clang-cl
--- a/build/win32/mozconfig.vs2015-win64
+++ b/build/win32/mozconfig.vs2015-win64
@@ -1,24 +1,26 @@
 if [ -z "${VSPATH}" ]; then
     TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
     VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3"
 fi
 
-VSWINPATH="$(cd ${VSPATH} && pwd -W)"
+if [ -d "${VSPATH}" ]; then
+    VSWINPATH="$(cd ${VSPATH} && pwd -W)"
 
-export WINDOWSSDKDIR="${VSWINPATH}/SDK"
-export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT"
-export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
+    export WINDOWSSDKDIR="${VSWINPATH}/SDK"
+    export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT"
+    export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
 
-export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}"
-export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}"
+    export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}"
+    export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}"
 
-export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
-export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib"
+    export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
+    export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib"
+fi
 
 . $topsrcdir/build/mozconfig.vs-common
 
 mk_export_correct_style WINDOWSSDKDIR
 mk_export_correct_style INCLUDE
 mk_export_correct_style LIB
 mk_export_correct_style PATH
 mk_export_correct_style WIN32_REDIST_DIR
--- a/build/win64/mozconfig.vs2015
+++ b/build/win64/mozconfig.vs2015
@@ -1,23 +1,25 @@
 if [ -z "${VSPATH}" ]; then
     TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
     VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3"
 fi
 
-VSWINPATH="$(cd ${VSPATH} && pwd -W)"
+if [ -d "${VSPATH}" ]; then
+    VSWINPATH="$(cd ${VSPATH} && pwd -W)"
 
-export WINDOWSSDKDIR="${VSWINPATH}/SDK"
-export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT
-export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64"
+    export WINDOWSSDKDIR="${VSWINPATH}/SDK"
+    export WIN32_REDIST_DIR=${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT
+    export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x64"
 
-export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}"
+    export PATH="${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x64:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${VSPATH}/DIA SDK/bin/amd64:${PATH}"
 
-export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
-export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64:${VSPATH}/DIA SDK/lib/amd64"
+    export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
+    export LIB="${VSPATH}/VC/lib/amd64:${VSPATH}/VC/atlmfc/lib/amd64:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x64:${VSPATH}/SDK/lib/10.0.14393.0/um/x64:${VSPATH}/DIA SDK/lib/amd64"
+fi
 
 . $topsrcdir/build/mozconfig.vs-common
 
 mk_export_correct_style WINDOWSSDKDIR
 mk_export_correct_style INCLUDE
 mk_export_correct_style LIB
 mk_export_correct_style PATH
 mk_export_correct_style WIN32_REDIST_DIR
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -162,17 +162,17 @@ ifndef GNU_CC
 # The final PDB for libraries and programs is created by the linker and uses
 # a different name from the single PDB file created by the compiler. See
 # bug 462740.
 #
 
 ifdef SIMPLE_PROGRAMS
 COMPILE_PDB_FLAG ?= -Fd$(basename $(@F)).pdb
 else
-COMPILE_PDB_FLAG ?= -Fdgenerated.pdb
+COMPILE_PDB_FLAG ?= -Fdgenerated.pdb -FS
 endif
 COMPILE_CFLAGS += $(COMPILE_PDB_FLAG)
 COMPILE_CXXFLAGS += $(COMPILE_PDB_FLAG)
 
 LINK_PDBFILE ?= $(basename $(@F)).pdb
 ifdef MOZ_DEBUG
 CODFILE=$(basename $(@F)).cod
 endif
--- a/dom/animation/ComputedTimingFunction.cpp
+++ b/dom/animation/ComputedTimingFunction.cpp
@@ -13,17 +13,17 @@ namespace mozilla {
 void
 ComputedTimingFunction::Init(const nsTimingFunction &aFunction)
 {
   mType = aFunction.mType;
   if (nsTimingFunction::IsSplineType(mType)) {
     mTimingFunction.Init(aFunction.mFunc.mX1, aFunction.mFunc.mY1,
                          aFunction.mFunc.mX2, aFunction.mFunc.mY2);
   } else {
-    mSteps = aFunction.mSteps;
+    mStepsOrFrames = aFunction.mStepsOrFrames;
   }
 }
 
 static inline double
 StepTiming(uint32_t aSteps,
            double aPortion,
            ComputedTimingFunction::BeforeFlag aBeforeFlag,
            nsTimingFunction::Type aType)
@@ -53,17 +53,32 @@ StepTiming(uint32_t aSteps,
   // input outside that range. This takes care of steps that would otherwise
   // occur at boundaries.
   if (result < 0.0 && aPortion >= 0.0) {
     return 0.0;
   }
   if (result > 1.0 && aPortion <= 1.0) {
     return 1.0;
   }
+  return result;
+}
 
+static inline double
+FramesTiming(uint32_t aFrames, double aPortion)
+{
+  MOZ_ASSERT(aFrames > 1, "the number of frames must be greater than 1");
+  int32_t currentFrame = floor(aPortion * aFrames);
+  double result = double(currentFrame) / double(aFrames - 1);
+
+  // Don't overshoot the natural range of the animation (by producing an output
+  // progress greater than 1.0) when we are at the exact end of its interval
+  // (i.e. the input progress is 1.0).
+  if (result > 1.0 && aPortion <= 1.0) {
+    return 1.0;
+  }
   return result;
 }
 
 double
 ComputedTimingFunction::GetValue(
     double aPortion,
     ComputedTimingFunction::BeforeFlag aBeforeFlag) const
 {
@@ -108,35 +123,38 @@ ComputedTimingFunction::GetValue(
       }
       // If we can't calculate a sensible tangent, don't extrapolate at all.
       return 1.0;
     }
 
     return mTimingFunction.GetSplineValue(aPortion);
   }
 
-  return StepTiming(mSteps, aPortion, aBeforeFlag, mType);
+  return mType == nsTimingFunction::Type::Frames
+         ? FramesTiming(mStepsOrFrames, aPortion)
+         : StepTiming(mStepsOrFrames, aPortion, aBeforeFlag, mType);
 }
 
 int32_t
 ComputedTimingFunction::Compare(const ComputedTimingFunction& aRhs) const
 {
   if (mType != aRhs.mType) {
     return int32_t(mType) - int32_t(aRhs.mType);
   }
 
   if (mType == nsTimingFunction::Type::CubicBezier) {
     int32_t order = mTimingFunction.Compare(aRhs.mTimingFunction);
     if (order != 0) {
       return order;
     }
   } else if (mType == nsTimingFunction::Type::StepStart ||
-             mType == nsTimingFunction::Type::StepEnd) {
-    if (mSteps != aRhs.mSteps) {
-      return int32_t(mSteps) - int32_t(aRhs.mSteps);
+             mType == nsTimingFunction::Type::StepEnd ||
+             mType == nsTimingFunction::Type::Frames) {
+    if (mStepsOrFrames != aRhs.mStepsOrFrames) {
+      return int32_t(mStepsOrFrames) - int32_t(aRhs.mStepsOrFrames);
     }
   }
 
   return 0;
 }
 
 void
 ComputedTimingFunction::AppendToString(nsAString& aResult) const
@@ -146,17 +164,20 @@ ComputedTimingFunction::AppendToString(n
       nsStyleUtil::AppendCubicBezierTimingFunction(mTimingFunction.X1(),
                                                    mTimingFunction.Y1(),
                                                    mTimingFunction.X2(),
                                                    mTimingFunction.Y2(),
                                                    aResult);
       break;
     case nsTimingFunction::Type::StepStart:
     case nsTimingFunction::Type::StepEnd:
-      nsStyleUtil::AppendStepsTimingFunction(mType, mSteps, aResult);
+      nsStyleUtil::AppendStepsTimingFunction(mType, mStepsOrFrames, aResult);
+      break;
+    case nsTimingFunction::Type::Frames:
+      nsStyleUtil::AppendFramesTimingFunction(mStepsOrFrames, aResult);
       break;
     default:
       nsStyleUtil::AppendCubicBezierKeywordTimingFunction(mType, aResult);
       break;
   }
 }
 
 /* static */ int32_t
--- a/dom/animation/ComputedTimingFunction.h
+++ b/dom/animation/ComputedTimingFunction.h
@@ -25,23 +25,33 @@ public:
   double GetValue(double aPortion, BeforeFlag aBeforeFlag) const;
   const nsSMILKeySpline* GetFunction() const
   {
     NS_ASSERTION(HasSpline(), "Type mismatch");
     return &mTimingFunction;
   }
   nsTimingFunction::Type GetType() const { return mType; }
   bool HasSpline() const { return nsTimingFunction::IsSplineType(mType); }
-  uint32_t GetSteps() const { return mSteps; }
+  uint32_t GetSteps() const
+  {
+    MOZ_ASSERT(mType == nsTimingFunction::Type::StepStart ||
+               mType == nsTimingFunction::Type::StepEnd);
+    return mStepsOrFrames;
+  }
+  uint32_t GetFrames() const
+  {
+    MOZ_ASSERT(mType == nsTimingFunction::Type::Frames);
+    return mStepsOrFrames;
+  }
   bool operator==(const ComputedTimingFunction& aOther) const
   {
     return mType == aOther.mType &&
            (HasSpline() ?
             mTimingFunction == aOther.mTimingFunction :
-            mSteps == aOther.mSteps);
+            mStepsOrFrames == aOther.mStepsOrFrames);
   }
   bool operator!=(const ComputedTimingFunction& aOther) const
   {
     return !(*this == aOther);
   }
   int32_t Compare(const ComputedTimingFunction& aRhs) const;
   void AppendToString(nsAString& aResult) const;
 
@@ -52,14 +62,14 @@ public:
     return aFunction ? aFunction->GetValue(aPortion, aBeforeFlag) : aPortion;
   }
   static int32_t Compare(const Maybe<ComputedTimingFunction>& aLhs,
                          const Maybe<ComputedTimingFunction>& aRhs);
 
 private:
   nsTimingFunction::Type mType;
   nsSMILKeySpline mTimingFunction;
-  uint32_t mSteps;
+  uint32_t mStepsOrFrames;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ComputedTimingFunction_h
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -373,25 +373,26 @@ KeyframeEffectReadOnly::DoUpdateProperti
 
 /* static */ StyleAnimationValue
 KeyframeEffectReadOnly::CompositeValue(
   nsCSSPropertyID aProperty,
   const StyleAnimationValue& aValueToComposite,
   const StyleAnimationValue& aUnderlyingValue,
   CompositeOperation aCompositeOperation)
 {
+  // Just return the underlying value if |aValueToComposite| is null
+  // (i.e. missing keyframe).
+  if (aValueToComposite.IsNull()) {
+    return aUnderlyingValue;
+  }
+
   switch (aCompositeOperation) {
     case dom::CompositeOperation::Replace:
       return aValueToComposite;
     case dom::CompositeOperation::Add: {
-      // Just return the underlying value if |aValueToComposite| is null (i.e.
-      // missing keyframe).
-      if (aValueToComposite.IsNull()) {
-        return aUnderlyingValue;
-      }
       StyleAnimationValue result(aValueToComposite);
       return StyleAnimationValue::Add(aProperty,
                                       aUnderlyingValue,
                                       Move(result));
     }
     case dom::CompositeOperation::Accumulate: {
       StyleAnimationValue result(aValueToComposite);
       return StyleAnimationValue::Accumulate(aProperty,
@@ -458,57 +459,44 @@ StyleAnimationValue
 KeyframeEffectReadOnly::CompositeValue(
   nsCSSPropertyID aProperty,
   const RefPtr<AnimValuesStyleRule>& aAnimationRule,
   const StyleAnimationValue& aValueToComposite,
   CompositeOperation aCompositeOperation)
 {
   MOZ_ASSERT(mTarget, "CompositeValue should be called with target element");
 
-  StyleAnimationValue result = aValueToComposite;
-
-  if (aCompositeOperation == CompositeOperation::Replace) {
-    MOZ_ASSERT(!aValueToComposite.IsNull(),
-      "Input value should be valid in case of replace composite");
-    // Just copy the input value in case of 'Replace'.
-    return result;
+  // FIXME: Bug 1311257: Get the base value for the servo backend.
+  if (mDocument->IsStyledByServo()) {
+    return aValueToComposite;
   }
 
-  // FIXME: Bug 1311257: Get the base value for the servo backend.
-  if (mDocument->IsStyledByServo()) {
-    return result;
-  }
-
-  MOZ_ASSERT(!aValueToComposite.IsNull() ||
-             aCompositeOperation == CompositeOperation::Add,
-             "InputValue should be null only if additive composite");
-
-  result = GetUnderlyingStyle(aProperty, aAnimationRule);
+  StyleAnimationValue underlyingValue =
+    GetUnderlyingStyle(aProperty, aAnimationRule);
 
   return CompositeValue(aProperty,
                         aValueToComposite,
-                        result,
+                        underlyingValue,
                         aCompositeOperation);
 }
 
 void
 KeyframeEffectReadOnly::EnsureBaseStyles(
   nsStyleContext* aStyleContext,
   const nsTArray<AnimationProperty>& aProperties)
 {
   if (!mTarget) {
     return;
   }
 
   mBaseStyleValues.Clear();
 
   for (const AnimationProperty& property : aProperties) {
     for (const AnimationPropertySegment& segment : property.mSegments) {
-      if (segment.mFromComposite == dom::CompositeOperation::Replace &&
-          segment.mToComposite == dom::CompositeOperation::Replace) {
+      if (segment.HasReplacableValues()) {
         continue;
       }
 
       Unused << ResolveBaseStyle(property.mProperty, aStyleContext);
       break;
     }
   }
 }
@@ -1226,26 +1214,43 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
 
     JS::Rooted<JSObject*> keyframeObject(aCx, &keyframeJSValue.toObject());
     for (const PropertyValuePair& propertyValue : keyframe.mPropertyValues) {
       nsAutoString stringValue;
       if (propertyValue.mServoDeclarationBlock) {
         Servo_DeclarationBlock_SerializeOneValue(
           propertyValue.mServoDeclarationBlock,
           propertyValue.mProperty, &stringValue);
+      } else if (nsCSSProps::IsShorthand(propertyValue.mProperty)) {
+         // nsCSSValue::AppendToString does not accept shorthands properties but
+         // works with token stream values if we pass eCSSProperty_UNKNOWN as
+         // the property.
+         propertyValue.mValue.AppendToString(
+           eCSSProperty_UNKNOWN, stringValue, nsCSSValue::eNormalized);
       } else {
-        // nsCSSValue::AppendToString does not accept shorthands properties but
-        // works with token stream values if we pass eCSSProperty_UNKNOWN as
-        // the property.
-        nsCSSPropertyID propertyForSerializing =
-          nsCSSProps::IsShorthand(propertyValue.mProperty)
-          ? eCSSProperty_UNKNOWN
-          : propertyValue.mProperty;
-        propertyValue.mValue.AppendToString(
-          propertyForSerializing, stringValue, nsCSSValue::eNormalized);
+        nsCSSValue cssValue = propertyValue.mValue;
+        if (cssValue.GetUnit() == eCSSUnit_Null) {
+          // We use an uninitialized nsCSSValue to represent the
+          // "neutral value". We currently only do this for keyframes generated
+          // from CSS animations with missing 0%/100% keyframes. Furthermore,
+          // currently (at least until bug 1339334) keyframes generated from
+          // CSS animations only contain longhand properties so we only need to
+          // handle null nsCSSValues for longhand properties.
+          DebugOnly<bool> uncomputeResult =
+            StyleAnimationValue::UncomputeValue(
+              propertyValue.mProperty, Move(BaseStyle(propertyValue.mProperty)),
+              cssValue);
+
+          MOZ_ASSERT(uncomputeResult,
+                     "Unable to get specified value from computed value");
+          MOZ_ASSERT(cssValue.GetUnit() != eCSSUnit_Null,
+                     "Got null computed value");
+        }
+        cssValue.AppendToString(propertyValue.mProperty,
+                                stringValue, nsCSSValue::eNormalized);
       }
 
       const char* name = nsCSSProps::PropertyIDLName(propertyValue.mProperty);
       JS::Rooted<JS::Value> value(aCx);
       if (!ToJSValue(aCx, stringValue, &value) ||
           !JS_DefineProperty(aCx, keyframeObject, name, value,
                              JSPROP_ENUMERATE)) {
         aRv.Throw(NS_ERROR_FAILURE);
@@ -1620,22 +1625,21 @@ KeyframeEffectReadOnly::CalculateCumulat
   if (mDocument->IsStyledByServo()) {
     // FIXME (bug 1303235): Do this for Servo too
     return;
   }
   mCumulativeChangeHint = nsChangeHint(0);
 
   for (const AnimationProperty& property : mProperties) {
     for (const AnimationPropertySegment& segment : property.mSegments) {
-      // In case composite operation is not 'replace', we can't throttle
-      // animations which will not cause any layout changes on invisible
-      // elements because we can't calculate the change hint for such properties
-      // until we compose it.
-      if (segment.mFromComposite != CompositeOperation::Replace ||
-          segment.mToComposite != CompositeOperation::Replace) {
+      // In case composite operation is not 'replace' or value is null,
+      // we can't throttle animations which will not cause any layout changes
+      // on invisible elements because we can't calculate the change hint for
+      // such properties until we compose it.
+      if (!segment.HasReplacableValues()) {
         mCumulativeChangeHint = ~nsChangeHint_Hints_CanIgnoreIfNotVisible;
         return;
       }
       RefPtr<nsStyleContext> fromContext =
         CreateStyleContextForAnimationValue(property.mProperty,
                                             segment.mFromValue.mGecko,
                                             aStyleContext);
 
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -61,16 +61,33 @@ struct AnimationPropertySegment
   // NOTE: In the case that no keyframe for 0 or 1 offset is specified
   // the unit of mFromValue or mToValue is eUnit_Null.
   AnimationValue mFromValue, mToValue;
 
   Maybe<ComputedTimingFunction> mTimingFunction;
   dom::CompositeOperation mFromComposite = dom::CompositeOperation::Replace;
   dom::CompositeOperation mToComposite = dom::CompositeOperation::Replace;
 
+  bool HasReplacableValues() const
+  {
+    return HasReplacableFromValue() && HasReplacableToValue();
+  }
+
+  bool HasReplacableFromValue() const
+  {
+    return !mFromValue.IsNull() &&
+           mFromComposite == dom::CompositeOperation::Replace;
+  }
+
+  bool HasReplacableToValue() const
+  {
+    return !mToValue.IsNull() &&
+           mToComposite == dom::CompositeOperation::Replace;
+  }
+
   bool operator==(const AnimationPropertySegment& aOther) const
   {
     return mFromKey == aOther.mFromKey &&
            mToKey == aOther.mToKey &&
            mFromValue == aOther.mFromValue &&
            mToValue == aOther.mToValue &&
            mTimingFunction == aOther.mTimingFunction &&
            mFromComposite == aOther.mFromComposite &&
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -649,16 +649,22 @@ KeyframeUtils::GetComputedKeyframeValues
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aElement, aStyleContext,
               tokenStream->mTokenStream, /* aUseSVGMode */ false, values) ||
             IsComputeValuesFailureKey(pair)) {
           continue;
         }
+      } else if (pair.mValue.GetUnit() == eCSSUnit_Null) {
+        // An uninitialized nsCSSValue represents the underlying value which
+        // we represent as an uninitialized AnimationValue so we just leave
+        // neutralPair->mValue as-is.
+        PropertyStyleAnimationValuePair* neutralPair = values.AppendElement();
+        neutralPair->mProperty = pair.mProperty;
       } else {
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aElement, aStyleContext,
               pair.mValue, /* aUseSVGMode */ false, values)) {
           continue;
         }
         MOZ_ASSERT(values.Length() == 1,
                   "Longhand properties should produce a single"
@@ -1158,33 +1164,31 @@ IsComputeValuesFailureKey(const Property
 
 static void
 AppendInitialSegment(AnimationProperty* aAnimationProperty,
                      const KeyframeValueEntry& aFirstEntry)
 {
   AnimationPropertySegment* segment =
     aAnimationProperty->mSegments.AppendElement();
   segment->mFromKey        = 0.0f;
-  segment->mFromComposite  = dom::CompositeOperation::Add;
   segment->mToKey          = aFirstEntry.mOffset;
   segment->mToValue        = aFirstEntry.mValue;
   segment->mToComposite    = aFirstEntry.mComposite;
 }
 
 static void
 AppendFinalSegment(AnimationProperty* aAnimationProperty,
                    const KeyframeValueEntry& aLastEntry)
 {
   AnimationPropertySegment* segment =
     aAnimationProperty->mSegments.AppendElement();
   segment->mFromKey        = aLastEntry.mOffset;
   segment->mFromValue      = aLastEntry.mValue;
   segment->mFromComposite  = aLastEntry.mComposite;
   segment->mToKey          = 1.0f;
-  segment->mToComposite    = dom::CompositeOperation::Add;
   segment->mTimingFunction = aLastEntry.mTimingFunction;
 }
 
 // Returns a newly created AnimationProperty if one was created to fill-in the
 // missing keyframe, nullptr otherwise (if we decided not to fill the keyframe
 // becase we don't support additive animation).
 static AnimationProperty*
 HandleMissingInitialKeyframe(nsTArray<AnimationProperty>& aResult,
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -133,16 +133,17 @@ TimingParams::ParseEasing(const nsAStrin
         case eCSSUnit_Enumerated:
           // Return Nothing() if "linear" is passed in.
           if (list->mValue.GetIntValue() ==
               NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR) {
             return Nothing();
           }
           MOZ_FALLTHROUGH;
         case eCSSUnit_Cubic_Bezier:
+        case eCSSUnit_Function:
         case eCSSUnit_Steps: {
           nsTimingFunction timingFunction;
           nsRuleNode::ComputeTimingFunction(list->mValue, timingFunction);
           ComputedTimingFunction computedTimingFunction;
           computedTimingFunction.Init(timingFunction);
           return Some(computedTimingFunction);
         }
         default:
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -7,14 +7,15 @@ support-files =
   chrome/file_animation_performance_warning.html
 
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
+[chrome/test_cssanimation_missing_keyframes.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_observers_for_sync_api.html]
 [chrome/test_restyles.html]
 skip-if = os == 'android' && processor == 'x86' # bug 1335986
 [chrome/test_running_on_compositor.html]
 [chrome/test_simulate_compute_values_failure.html]
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -19,877 +19,851 @@
 }
 div {
   font-size: 10px; /* For calculating em-based units */
 }
 </style>
 <script>
 'use strict';
 
-function assert_properties_equal(actual, expected) {
-  assert_equals(actual.length, expected.length);
-
-  var compareProperties = (a, b) =>
-    a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
-
-  var sortedActual   = actual.sort(compareProperties);
-  var sortedExpected = expected.sort(compareProperties);
-
-  var serializeValues = values =>
-    values.map(value =>
-      '{ ' +
-        [ 'offset', 'value', 'easing', 'composite' ].map(
-          member => `${member}: ${value[member]}`
-        ).join(', ') +
-      ' }')
-    .join(', ');
-
-  for (var i = 0; i < sortedActual.length; i++) {
-    assert_equals(sortedActual[i].property,
-                  sortedExpected[i].property,
-                  'CSS property name should match');
-    assert_equals(serializeValues(sortedActual[i].values),
-                  serializeValues(sortedExpected[i].values),
-                  `Values arrays do not match for `
-                  + `${sortedActual[i].property} property`);
-  }
-}
-
-// Shorthand for constructing a value object
-function value(offset, value, composite, easing) {
-  return { offset: offset, value: value, easing: easing, composite: composite };
-}
-
 var gTests = [
 
   // ---------------------------------------------------------------------
   //
   // Tests for property-indexed specifications
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a one-property two-value property-indexed specification',
     frames:   { left: ['10px', '20px'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] } ]
   },
   { desc:     'a one-shorthand-property two-value property-indexed'
               + ' specification',
     frames:   { margin: ['10px', '10px 20px 30px 40px'] },
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '10px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] } ]
   },
   { desc:     'a two-property (one shorthand and one of its longhand'
               + ' components) two-value property-indexed specification',
     frames:   { marginTop: ['50px', '60px'],
                 margin: ['10px', '10px 20px 30px 40px'] },
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '50px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] },
+                  values: [ valueFormat(0, '50px', 'replace', 'linear'),
+                            valueFormat(1, '60px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] } ]
   },
   { desc:     'a two-property property-indexed specification with different'
               + ' numbers of values',
     frames:   { left: ['10px', '20px', '30px'],
                 top: ['40px', '50px'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a property-indexed specification with an invalid value',
     frames:   { left: ['10px', '20px', '30px', '40px', '50px'],
                 top:  ['15px', '25px', 'invalid', '45px', '55px'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '20px', 'replace', 'linear'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(0.75, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '15px', 'replace', 'linear'),
-                            value(0.25, '25px', 'replace', 'linear'),
-                            value(0.75, '45px', 'replace', 'linear'),
-                            value(1, '55px', 'replace') ] } ]
+                  values: [ valueFormat(0, '15px', 'replace', 'linear'),
+                            valueFormat(0.25, '25px', 'replace', 'linear'),
+                            valueFormat(0.75, '45px', 'replace', 'linear'),
+                            valueFormat(1, '55px', 'replace') ] } ]
   },
   { desc:     'a one-property two-value property-indexed specification that'
               + ' needs to stringify its values',
     frames:   { opacity: [0, 1] },
     expected: [ { property: 'opacity',
-                  values: [ value(0, '0', 'replace', 'linear'),
-                            value(1, '1', 'replace') ] } ]
+                  values: [ valueFormat(0, '0', 'replace', 'linear'),
+                            valueFormat(1, '1', 'replace') ] } ]
   },
   { desc:     'a property-indexed keyframe where a lesser shorthand precedes'
               + ' a greater shorthand',
     frames:   { borderLeft: [ '1px solid rgb(1, 2, 3)',
                               '2px solid rgb(4, 5, 6)' ],
                 border:     [ '3px dotted rgb(7, 8, 9)',
                               '4px dashed rgb(10, 11, 12)' ] },
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(4, 5, 6)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '2px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '2px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'solid', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'solid', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
   { desc:     'a property-indexed keyframe where a greater shorthand precedes'
               + ' a lesser shorthand',
     frames:   { border:     [ '3px dotted rgb(7, 8, 9)',
                               '4px dashed rgb(10, 11, 12)' ],
                 borderLeft: [ '1px solid rgb(1, 2, 3)',
                               '2px solid rgb(4, 5, 6)' ] },
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(4, 5, 6)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(4, 5, 6)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
-                            value(1, 'rgb(10, 11, 12)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(7, 8, 9)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(10, 11, 12)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '2px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '2px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '3px', 'replace', 'linear'),
-                            value(1, '4px', 'replace') ] },
+                  values: [ valueFormat(0, '3px', 'replace', 'linear'),
+                            valueFormat(1, '4px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'solid', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'solid', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for keyframe sequences
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a keyframe sequence specification with repeated values at'
               + ' offset 0/1 with different easings',
     frames:   [ { offset: 0.0, left: '100px', easing: 'ease' },
                 { offset: 0.0, left: '200px', easing: 'ease' },
                 { offset: 0.5, left: '300px', easing: 'linear' },
                 { offset: 1.0, left: '400px', easing: 'ease-out' },
                 { offset: 1.0, left: '500px', easing: 'step-end' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '100px', 'replace'),
-                            value(0, '200px', 'replace', 'ease'),
-                            value(0.5, '300px', 'replace', 'linear'),
-                            value(1, '400px', 'replace'),
-                            value(1, '500px', 'replace') ] } ]
+                  values: [ valueFormat(0, '100px', 'replace'),
+                            valueFormat(0, '200px', 'replace', 'ease'),
+                            valueFormat(0.5, '300px', 'replace', 'linear'),
+                            valueFormat(1, '400px', 'replace'),
+                            valueFormat(1, '500px', 'replace') ] } ]
   },
   { desc:     'a one-property two-keyframe sequence',
     frames:   [ { offset: 0, left: '10px' },
                 { offset: 1, left: '20px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] } ]
   },
   { desc:     'a two-property two-keyframe sequence',
     frames:   [ { offset: 0, left: '10px', top: '30px' },
                 { offset: 1, left: '20px', top: '40px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '30px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] } ]
+                  values: [ valueFormat(0, '30px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] } ]
   },
   { desc:     'a one shorthand property two-keyframe sequence',
     frames:   [ { offset: 0, margin: '10px' },
                 { offset: 1, margin: '20px 30px 40px 50px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a two-property (a shorthand and one of its component longhands)'
               + ' two-keyframe sequence',
     frames:   [ { offset: 0, margin: '10px', marginTop: '20px' },
                 { offset: 1, marginTop: '70px',
                              margin: '30px 40px 50px 60px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '70px', 'replace') ] },
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '70px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '60px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence with duplicate values for a given interior'
               + ' offset',
     frames:   [ { offset: 0.0, left: '10px' },
                 { offset: 0.5, left: '20px' },
                 { offset: 0.5, left: '30px' },
                 { offset: 0.5, left: '40px' },
                 { offset: 1.0, left: '50px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.5, '20px', 'replace'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.5, '20px', 'replace'),
+                            valueFormat(0.5, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence with duplicate values for offsets 0 and 1',
     frames:   [ { offset: 0, left: '10px' },
                 { offset: 0, left: '20px' },
                 { offset: 0, left: '30px' },
                 { offset: 1, left: '40px' },
                 { offset: 1, left: '50px' },
                 { offset: 1, left: '60px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace'),
-                            value(0, '30px', 'replace', 'linear'),
-                            value(1, '40px', 'replace'),
-                            value(1, '60px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace'),
+                            valueFormat(0, '30px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace'),
+                            valueFormat(1, '60px', 'replace') ] } ]
   },
   { desc:     'a two-property four-keyframe sequence',
     frames:   [ { offset: 0, left: '10px' },
                 { offset: 0, top: '20px' },
                 { offset: 1, top: '30px' },
                 { offset: 1, left: '40px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '40px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '40px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] } ]
   },
   { desc:     'a one-property keyframe sequence with some omitted offsets',
     frames:   [ { offset: 0.00, left: '10px' },
                 { offset: 0.25, left: '20px' },
                 { left: '30px' },
                 { left: '40px' },
                 { offset: 1.00, left: '50px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '20px', 'replace', 'linear'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(0.75, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a two-property keyframe sequence with some omitted offsets',
     frames:   [ { offset: 0.00, left: '10px', top: '20px' },
                 { offset: 0.25, left: '30px' },
                 { left: '40px' },
                 { left: '50px', top: '60px' },
                 { offset: 1.00, left: '70px', top: '80px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '30px', 'replace', 'linear'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(0.75, '50px', 'replace', 'linear'),
-                            value(1, '70px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '30px', 'replace', 'linear'),
+                            valueFormat(0.5, '40px', 'replace', 'linear'),
+                            valueFormat(0.75, '50px', 'replace', 'linear'),
+                            valueFormat(1, '70px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(0.75, '60px', 'replace', 'linear'),
-                            value(1, '80px', 'replace') ] } ]
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(0.75, '60px', 'replace', 'linear'),
+                            valueFormat(1, '80px', 'replace') ] } ]
   },
   { desc:     'a one-property keyframe sequence with all omitted offsets',
     frames:   [ { left: '10px' },
                 { left: '20px' },
                 { left: '30px' },
                 { left: '40px' },
                 { left: '50px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(0.25, '20px', 'replace', 'linear'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(0.75, '40px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(0.25, '20px', 'replace', 'linear'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(0.75, '40px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence with different easing values, but the'
               + ' same easing value for a given offset',
     frames:   [ { offset: 0.0, easing: 'ease',     left: '10px'},
                 { offset: 0.0, easing: 'ease',     top: '20px'},
                 { offset: 0.5, easing: 'linear',   left: '30px' },
                 { offset: 0.5, easing: 'linear',   top: '40px' },
                 { offset: 1.0, easing: 'step-end', left: '50px' },
                 { offset: 1.0, easing: 'step-end', top: '60px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'ease'),
-                            value(0.5, '30px', 'replace', 'linear'),
-                            value(1, '50px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'ease'),
+                            valueFormat(0.5, '30px', 'replace', 'linear'),
+                            valueFormat(1, '50px', 'replace') ] },
                 { property: 'top',
-                  values: [ value(0, '20px', 'replace', 'ease'),
-                            value(0.5, '40px', 'replace', 'linear'),
-                            value(1, '60px', 'replace') ] } ]
+                  values: [ valueFormat(0, '20px', 'replace', 'ease'),
+                            valueFormat(0.5, '40px', 'replace', 'linear'),
+                            valueFormat(1, '60px', 'replace') ] } ]
   },
   { desc:     'a one-property two-keyframe sequence that needs to'
               + ' stringify its values',
     frames:   [ { offset: 0, opacity: 0 },
                 { offset: 1, opacity: 1 } ],
     expected: [ { property: 'opacity',
-                  values: [ value(0, '0', 'replace', 'linear'),
-                            value(1, '1', 'replace') ] } ]
+                  values: [ valueFormat(0, '0', 'replace', 'linear'),
+                            valueFormat(1, '1', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where shorthand precedes longhand',
     frames:   [ { offset: 0, margin: '10px', marginRight: '20px' },
                 { offset: 1, margin: '30px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where longhand precedes shorthand',
     frames:   [ { offset: 0, marginRight: '20px', margin: '10px' },
                 { offset: 1, margin: '30px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '20px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '20px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '30px', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where lesser shorthand precedes greater'
               + ' shorthand',
     frames:   [ { offset: 0, borderLeft: '1px solid rgb(1, 2, 3)',
                              border: '2px dotted rgb(4, 5, 6)' },
                 { offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
   { desc:     'a keyframe sequence where greater shorthand precedes'
               + ' lesser shorthand',
     frames:   [ { offset: 0, border: '2px dotted rgb(4, 5, 6)',
                              borderLeft: '1px solid rgb(1, 2, 3)' },
                 { offset: 1, border: '3px dashed rgb(7, 8, 9)' } ],
     expected: [ { property: 'border-bottom-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-left-color',
-                  values: [ value(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(1, 2, 3)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-right-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-top-color',
-                  values: [ value(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
-                            value(1, 'rgb(7, 8, 9)', 'replace') ] },
+                  values: [ valueFormat(0, 'rgb(4, 5, 6)', 'replace', 'linear'),
+                            valueFormat(1, 'rgb(7, 8, 9)', 'replace') ] },
                 { property: 'border-bottom-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-left-width',
-                  values: [ value(0, '1px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '1px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-right-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-top-width',
-                  values: [ value(0, '2px', 'replace', 'linear'),
-                            value(1, '3px', 'replace') ] },
+                  values: [ valueFormat(0, '2px', 'replace', 'linear'),
+                            valueFormat(1, '3px', 'replace') ] },
                 { property: 'border-bottom-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-left-style',
-                  values: [ value(0, 'solid', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'solid', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-right-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-top-style',
-                  values: [ value(0, 'dotted', 'replace', 'linear'),
-                            value(1, 'dashed', 'replace') ] },
+                  values: [ valueFormat(0, 'dotted', 'replace', 'linear'),
+                            valueFormat(1, 'dashed', 'replace') ] },
                 { property: 'border-image-outset',
-                  values: [ value(0, '0 0 0 0', 'replace', 'linear'),
-                            value(1, '0 0 0 0', 'replace') ] },
+                  values: [ valueFormat(0, '0 0 0 0', 'replace', 'linear'),
+                            valueFormat(1, '0 0 0 0', 'replace') ] },
                 { property: 'border-image-repeat',
-                  values: [ value(0, 'stretch stretch', 'replace', 'linear'),
-                            value(1, 'stretch stretch', 'replace') ] },
+                  values: [ valueFormat(0,
+                                        'stretch stretch', 'replace', 'linear'),
+                            valueFormat(1, 'stretch stretch', 'replace') ] },
                 { property: 'border-image-slice',
-                  values: [ value(0, '100% 100% 100% 100%',
+                  values: [ valueFormat(0, '100% 100% 100% 100%',
                                   'replace', 'linear'),
-                            value(1, '100% 100% 100% 100%', 'replace') ] },
+                            valueFormat(1,
+                                        '100% 100% 100% 100%', 'replace') ] },
                 { property: 'border-image-source',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: 'border-image-width',
-                  values: [ value(0, '1 1 1 1', 'replace', 'linear'),
-                            value(1, '1 1 1 1', 'replace') ] },
+                  values: [ valueFormat(0, '1 1 1 1', 'replace', 'linear'),
+                            valueFormat(1, '1 1 1 1', 'replace') ] },
                 { property: '-moz-border-bottom-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-left-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-right-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] },
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] },
                 { property: '-moz-border-top-colors',
-                  values: [ value(0, 'none', 'replace', 'linear'),
-                            value(1, 'none', 'replace') ] } ]
+                  values: [ valueFormat(0, 'none', 'replace', 'linear'),
+                            valueFormat(1, 'none', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for unit conversion
   //
   // ---------------------------------------------------------------------
 
   { desc:     'em units are resolved to px values',
     frames:   { left: ['10em', '20em'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '100px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] } ]
+                  values: [ valueFormat(0, '100px', 'replace', 'linear'),
+                            valueFormat(1, '200px', 'replace') ] } ]
   },
   { desc:     'calc() expressions are resolved to the equivalent units',
     frames:   { left: ['calc(10em + 10px)', 'calc(10em + 10%)'] },
     expected: [ { property: 'left',
-                  values: [ value(0, 'calc(110px)', 'replace', 'linear'),
-                            value(1, 'calc(100px + 10%)', 'replace') ] } ]
+                  values: [ valueFormat(0, 'calc(110px)', 'replace', 'linear'),
+                            valueFormat(1, 'calc(100px + 10%)', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for CSS variable handling conversion
   //
   // ---------------------------------------------------------------------
 
   { desc:     'CSS variables are resolved to their corresponding values',
     frames:   { left: ['10px', 'var(--var-100px)'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '100px', 'replace') ] } ]
   },
   { desc:     'CSS variables in calc() expressions are resolved',
     frames:   { left: ['10px', 'calc(var(--var-100px) / 2 - 10%)'] },
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, 'calc(50px + -10%)', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, 'calc(50px + -10%)', 'replace') ] } ]
   },
   { desc:     'CSS variables in shorthands are resolved to their corresponding'
               + ' values',
     frames:   { margin: ['10px', 'var(--var-100px-200px)'] },
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '100px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '200px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '100px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '100px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '200px', 'replace') ] } ]
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '200px', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for properties that parse correctly but which we fail to
   // convert to computed values.
   //
   // ---------------------------------------------------------------------
 
   { desc:     'a missing property in initial keyframe',
     frames:   [ { },
                 { margin: '5px' } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] } ]
   },
   { desc:     'a missing property in initial keyframe and there are some ' +
               'keyframes with the same offset',
     frames:   [ { },
                 { margin: '10px', offset: 0.5 },
                 { margin: '20px', offset: 0.5 },
                 { margin: '30px'} ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   '30px', 'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '10px',  'replace'),
+                            valueFormat(0.5, '20px',  'replace', 'linear'),
+                            valueFormat(1,   '30px',  'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe',
     frames:   [ { margin: '5px' },
                 { } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe and there are some ' +
               'keyframes with the same offsets',
     frames:   [ { margin: '5px' },
                 { margin: '10px', offset: 0.5 },
                 { margin: '20px', offset: 0.5 },
                 { } ],
     expected: [ { property: 'margin-top',
-                  values: [ value(0,   '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1,   undefined, 'add') ] },
+                  values: [ valueFormat(0,   '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1,   undefined, 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(0.5, '10px', 'replace'),
-                            value(0.5, '20px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(0.5, '10px', 'replace'),
+                            valueFormat(0.5, '20px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe where it forms the last'
               + ' segment in the series',
     frames:   [ { margin: '5px' },
                 { marginLeft: '5px',
                   marginRight: '5px',
                   marginBottom: '5px' } ],
     expected: [ { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'a missing property in initial keyframe along with other values',
     frames:   [ {                left: '10px' },
                 { margin: '5px', left: '20px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-top',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] },
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '5px', 'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '5px', 'replace') ] } ]
   },
   { desc:     'a missing property in final keyframe along with other values',
     frames:   [ { margin: '5px', left: '10px' },
                 {                left: '20px' } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '20px', 'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '20px', 'replace') ] },
                 { property: 'margin-top',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-right',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-bottom',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] },
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] },
                 { property: 'margin-left',
-                  values: [ value(0, '5px', 'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px', 'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
   { desc:     'missing properties in both of initial and final keyframe',
     frames:   [ { left: '5px', offset: 0.5 } ],
     expected: [ { property: 'left',
-                  values: [ value(0,   undefined, 'add',     'linear'),
-                            value(0.5, '5px',       'replace', 'linear'),
-                            value(1,   undefined, 'add') ] } ]
+                  values: [ valueFormat(0,   undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '5px',     'replace', 'linear'),
+                            valueFormat(1,   undefined, 'replace') ] } ]
   },
   { desc:     'missing propertes in both of initial and final keyframe along '
               + 'with other values',
     frames:   [ { left:  '5px',  offset: 0 },
                 { right: '5px',  offset: 0.5 },
                 { left:  '10px', offset: 1 } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '5px',  'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
+                  values: [ valueFormat(0, '5px',  'replace', 'linear'),
+                            valueFormat(1, '10px', 'replace') ] },
                 { property: 'right',
-                  values: [ value(0,   undefined, 'add',     'linear'),
-                            value(0.5, '5px',     'replace', 'linear'),
-                            value(1,   undefined, 'add') ] } ]
+                  values: [ valueFormat(0,   undefined, 'replace', 'linear'),
+                            valueFormat(0.5, '5px',     'replace', 'linear'),
+                            valueFormat(1,   undefined, 'replace') ] } ]
   },
 
   { desc:     'a missing property in final keyframe with duplicate offset ' +
               + 'along with other values',
     frames:   [ { left: '5px',  right: '5px', offset: 0 },
                 { left: '8px',  right: '8px', offset: 0 },
                 { left: '10px',               offset: 1 } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '5px',  'replace'),
-                            value(0, '8px',  'replace', 'linear'),
-                            value(1, '10px', 'replace') ] },
+                  values: [ valueFormat(0, '5px',  'replace'),
+                            valueFormat(0, '8px',  'replace', 'linear'),
+                            valueFormat(1, '10px', 'replace') ] },
                 { property: 'right',
-                  values: [ value(0, '5px',     'replace'),
-                            value(0, '8px',     'replace', 'linear'),
-                            value(1, undefined, 'add') ] } ]
+                  values: [ valueFormat(0, '5px',     'replace'),
+                            valueFormat(0, '8px',     'replace', 'linear'),
+                            valueFormat(1, undefined, 'replace') ] } ]
   },
 
   { desc:     'a missing property in initial keyframe with duplicate offset '
               + 'along with other values',
     frames:   [ { left: '10px',              offset: 0 },
                 { left: '8px', right: '8px', offset: 1 },
                 { left: '5px', right: '5px', offset: 1 } ],
     expected: [ { property: 'left',
-                  values: [ value(0, '10px', 'replace', 'linear'),
-                            value(1, '8px',  'replace'),
-                            value(1, '5px',  'replace') ] },
+                  values: [ valueFormat(0, '10px', 'replace', 'linear'),
+                            valueFormat(1, '8px',  'replace'),
+                            valueFormat(1, '5px',  'replace') ] },
                 { property: 'right',
-                  values: [ value(0, undefined, 'add', 'linear'),
-                            value(1, '8px',     'replace'),
-                            value(1, '5px',     'replace') ] } ]
+                  values: [ valueFormat(0, undefined, 'replace', 'linear'),
+                            valueFormat(1, '8px',     'replace'),
+                            valueFormat(1, '5px',     'replace') ] } ]
   },
 ];
 
 SpecialPowers.pushPrefEnv(
   { set: [["dom.animations-api.core.enabled", true]] },
   function() {
     gTests.forEach(function(subtest) {
       test(function(t) {
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_cssanimation_missing_keyframes.html
@@ -0,0 +1,73 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<title>Bug 1339332 - Test for missing keyframes in CSS Animation</title>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1339332"
+   target="_blank">Mozilla Bug 1339332</a>
+<div id="log"></div>
+<style>
+@keyframes missingFrom {
+  to {
+    text-align: right;
+  }
+}
+@keyframes missingBoth {
+  50% {
+    text-align: right;
+  }
+}
+@keyframes missingTo {
+  from {
+    text-align: right;
+  }
+}
+</style>
+<script>
+'use strict';
+
+const gTests = [
+  { desc: 'missing "from" keyframe',
+    animationName: 'missingFrom',
+    expected: [{ property: 'text-align',
+                 values: [valueFormat(0, undefined, 'replace', 'ease'),
+                          valueFormat(1, 'right',   'replace')] } ]
+  },
+  { desc: 'missing "to" keyframe',
+    animationName: 'missingTo',
+    expected: [{ property: 'text-align',
+                 values: [valueFormat(0, 'right',   'replace', 'ease'),
+                          valueFormat(1, undefined, 'replace')] } ]
+  },
+  { desc: 'missing "from" and "to" keyframes',
+    animationName: 'missingBoth',
+    expected: [{ property: 'text-align',
+                 values: [valueFormat(0,  undefined, 'replace', 'ease'),
+                          valueFormat(.5, 'right',   'replace', 'ease'),
+                          valueFormat(1,  undefined, 'replace')] } ]
+  },
+];
+
+SpecialPowers.pushPrefEnv(
+  { set: [["dom.animations-api.core.enabled", true]] },
+  function() {
+    gTests.forEach(function(subtest) {
+      test(function(t) {
+        const div = addDiv(t);
+        div.style.animation = `${ subtest.animationName } 1000s`;
+        const animation = div.getAnimations()[0];
+        assert_properties_equal(animation.effect.getProperties(),
+                                subtest.expected);
+      }, subtest.desc);
+    });
+
+    done();
+  }
+);
+
+</script>
+</body>
--- a/dom/animation/test/chrome/test_restyles.html
+++ b/dom/animation/test/chrome/test_restyles.html
@@ -846,12 +846,28 @@ waitForAllPaints(function() {
     var markers = yield observeStyling(5);
 
     is(markers.length, 0,
        'Discrete animation running on the main-thread in an out-of-view ' +
        'element should never cause restyles');
     yield ensureElementRemoval(div);
   });
 
+  add_task(function* no_restyling_while_computed_timing_is_not_changed() {
+    var div = addDiv(null);
+    var animation = div.animate({ backgroundColor: [ 'red', 'blue' ] },
+                                { duration: 100 * MS_PER_SEC,
+                                  easing: 'step-end' });
+
+    yield animation.ready;
+
+    var markers = yield observeStyling(5);
+
+    is(markers.length, 0,
+       'Animation running on the main-thread while computed timing is not ' +
+       'changed should never cause restyles');
+    yield ensureElementRemoval(div);
+  });
+
 });
 
 </script>
 </body>
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -50,16 +50,65 @@ function assert_matrix_equals(actual, ex
   for (var i = 0; i < actualMatrixArray.length; i++) {
     assert_approx_equals(actualMatrixArray[i], expectedMatrixArray[i], 0.01,
       'Matrix array should be equal (got \'' + expected + '\' and \'' + actual +
       '\'): ' + description);
   }
 }
 
 /**
+ * Compare given values which are same format of
+ * KeyframeEffectReadonly::GetProperties.
+ */
+function assert_properties_equal(actual, expected) {
+  assert_equals(actual.length, expected.length);
+
+  const compareProperties = (a, b) =>
+    a.property == b.property ? 0 : (a.property < b.property ? -1 : 1);
+
+  const sortedActual   = actual.sort(compareProperties);
+  const sortedExpected = expected.sort(compareProperties);
+
+  const serializeValues = values =>
+    values.map(value =>
+      '{ ' +
+          [ 'offset', 'value', 'easing', 'composite' ].map(
+            member => `${member}: ${value[member]}`
+          ).join(', ') +
+                      ' }')
+          .join(', ');
+
+  for (let i = 0; i < sortedActual.length; i++) {
+    assert_equals(sortedActual[i].property,
+                  sortedExpected[i].property,
+                  'CSS property name should match');
+    assert_equals(serializeValues(sortedActual[i].values),
+                  serializeValues(sortedExpected[i].values),
+                  `Values arrays do not match for `
+                  + `${sortedActual[i].property} property`);
+  }
+}
+
+/**
+ * Construct a object which is same to a value of
+ * KeyframeEffectReadonly::GetProperties().
+ * The method returns undefined as a value in case of missing keyframe.
+ * Therefor, we can use undefined for |value| and |easing| parameter.
+ * @param offset - keyframe offset. e.g. 0.1
+ * @param value - any keyframe value. e.g. undefined '1px', 'center', 0.5
+ * @param composite - 'replace', 'add', 'accumulate'
+ * @param easing - e.g. undefined, 'linear', 'ease' and so on
+ * @return Object -
+ *   e.g. { offset: 0.1, value: '1px', composite: 'replace', easing: 'ease'}
+ */
+function valueFormat(offset, value, composite, easing) {
+  return { offset: offset, value: value, easing: easing, composite: composite };
+}
+
+/**
  * Appends a div to the document body and creates an animation on the div.
  * NOTE: This function asserts when trying to create animations with durations
  * shorter than 100s because the shorter duration may cause intermittent
  * failures.  If you are not sure how long it is suitable, use 100s; it's
  * long enough but shorter than our test framework timeout (330s).
  * If you really need to use shorter durations, use animate() function directly.
  *
  * @param t  The testharness.js Test object. If provided, this will be used
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3635,36 +3635,37 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMEle
       Layer* layer =
         FrameLayerBuilder::GetDedicatedLayer(frame,
                                              nsDisplayItem::TYPE_OPACITY);
       if (layer) {
         ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
         if (forwarder && forwarder->HasShadowManager()) {
           float value;
           bool hadAnimatedOpacity;
-          forwarder->GetShadowManager()->SendGetAnimationOpacity(
-            layer->AsShadowableLayer()->GetShadow(),
-            &value, &hadAnimatedOpacity);
+          forwarder->GetShadowManager()->
+            SendGetAnimationOpacity(layer->GetCompositorAnimationsId(),
+                                    &value,
+                                    &hadAnimatedOpacity);
 
           if (hadAnimatedOpacity) {
             cssValue = new nsROCSSPrimitiveValue;
             cssValue->SetNumber(value);
           }
         }
       }
     } else if (aProperty.EqualsLiteral("transform")) {
       Layer* layer =
         FrameLayerBuilder::GetDedicatedLayer(frame,
                                              nsDisplayItem::TYPE_TRANSFORM);
       if (layer) {
         ShadowLayerForwarder* forwarder = layer->Manager()->AsShadowForwarder();
         if (forwarder && forwarder->HasShadowManager()) {
           MaybeTransform transform;
-          forwarder->GetShadowManager()->SendGetAnimationTransform(
-            layer->AsShadowableLayer()->GetShadow(), &transform);
+          forwarder->GetShadowManager()->
+            SendGetAnimationTransform(layer->GetCompositorAnimationsId(), &transform);
           if (transform.type() == MaybeTransform::TMatrix4x4) {
             Matrix4x4 matrix = transform.get_Matrix4x4();
             cssValue = nsComputedDOMStyle::MatrixToCSSValue(matrix);
           }
         }
       }
     }
   }
--- a/dom/base/nsIContent.h
+++ b/dom/base/nsIContent.h
@@ -954,16 +954,24 @@ public:
         if (hasAttr) {
           return true;
         }
       }
     }
     return false;
   }
 
+  // Returns true if this element is native-anonymous scrollbar content.
+  bool IsNativeScrollbarContent() const {
+    return IsNativeAnonymous() &&
+           IsAnyOfXULElements(nsGkAtoms::scrollbar,
+                              nsGkAtoms::resizer,
+                              nsGkAtoms::scrollcorner);
+  }
+
   // Overloaded from nsINode
   virtual already_AddRefed<nsIURI> GetBaseURI(bool aTryUseXHRDocBaseURI = false) const override;
 
   virtual nsresult GetEventTargetParent(
                      mozilla::EventChainPreVisitor& aVisitor) override;
 
   virtual bool IsPurple() = 0;
   virtual void RemovePurple() = 0;
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -392,17 +392,17 @@ SimpleTimer::Create(nsIRunnable* aTask, 
 
 void
 LogToBrowserConsole(const nsAString& aMsg)
 {
   if (!NS_IsMainThread()) {
     nsString msg(aMsg);
     nsCOMPtr<nsIRunnable> task =
       NS_NewRunnableFunction([msg]() { LogToBrowserConsole(msg); });
-    NS_DispatchToMainThread(task.forget(), NS_DISPATCH_NORMAL);
+    SystemGroup::Dispatch("LogToBrowserConsole", TaskCategory::Other, task.forget());
     return;
   }
   nsCOMPtr<nsIConsoleService> console(
     do_GetService("@mozilla.org/consoleservice;1"));
   if (!console) {
     NS_WARNING("Failed to log message to console.");
     return;
   }
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -89,17 +89,18 @@ public:
   {}
 
   // Main thread only.
   // Loads the CDM corresponding to mKeySystem.
   // Calls MediaKeys::OnCDMCreated() when the CDM is created.
   virtual void Init(PromiseId aPromiseId,
                     const nsAString& aOrigin,
                     const nsAString& aTopLevelOrigin,
-                    const nsAString& aName) = 0;
+                    const nsAString& aName,
+                    nsIEventTarget* aMainThread) = 0;
 
   virtual void OnSetDecryptorId(uint32_t aId) {}
 
   // Main thread only.
   // Uses the CDM to create a key session.
   // Calls MediaKeys::OnSessionActivated() when session is created.
   // Assumes ownership of (Move()s) aInitData's contents.
   virtual void CreateSession(uint32_t aCreateSessionToken,
@@ -254,16 +255,19 @@ protected:
 
   // Our reference back to the MediaKeys object.
   // WARNING: This is a non-owning reference that is cleared by MediaKeys
   // destructor. only use on main thread, and always nullcheck before using!
   MainThreadOnlyRawPtr<dom::MediaKeys> mKeys;
 
   const nsString mKeySystem;
 
+  // The main thread associated with the root document. Must be set in Init().
+  nsCOMPtr<nsIEventTarget> mMainThread;
+
   // Onwer specified thread. e.g. Gecko Media Plugin thread.
   // All interactions with the out-of-process EME plugin must come from this thread.
   RefPtr<nsIThread> mOwnerThread;
 
   nsCString mNodeId;
 
   CDMCaps mCapabilites;
 
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -424,17 +424,18 @@ MediaKeys::Init(ErrorResult& aRv)
   // here, and hold a self-reference until that promise is resolved or
   // rejected.
   MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
   mCreatePromiseId = StorePromise(promise);
   AddRef();
   mProxy->Init(mCreatePromiseId,
                NS_ConvertUTF8toUTF16(origin),
                NS_ConvertUTF8toUTF16(topLevelOrigin),
-               KeySystemToGMPName(mKeySystem));
+               KeySystemToGMPName(mKeySystem),
+               top->GetExtantDoc()->EventTargetFor(TaskCategory::Other));
 
   return promise.forget();
 }
 
 void
 MediaKeys::OnCDMCreated(PromiseId aId, const uint32_t aPluginId)
 {
   RefPtr<DetailedPromise> promise(RetrievePromise(aId));
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -41,26 +41,29 @@ MediaDrmCDMProxy::~MediaDrmCDMProxy()
 {
   MOZ_COUNT_DTOR(MediaDrmCDMProxy);
 }
 
 void
 MediaDrmCDMProxy::Init(PromiseId aPromiseId,
                        const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
-                       const nsAString& aName)
+                       const nsAString& aName,
+                       nsIEventTarget* aMainThread)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
   EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
           NS_ConvertUTF16toUTF8(aOrigin).get(),
           NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
           NS_ConvertUTF16toUTF8(aName).get());
 
+  mMainThread = aMainThread;
+
   // Create a thread to work with cdm.
   if (!mOwnerThread) {
     nsresult rv = NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
     if (NS_FAILED(rv)) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                     NS_LITERAL_CSTRING("Couldn't create CDM thread MediaDrmCDMProxy::Init"));
       return;
     }
@@ -303,17 +306,17 @@ MediaDrmCDMProxy::RejectPromise(PromiseI
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
       mKeys->RejectPromise(aId, aCode, aReason);
     }
   } else {
     nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
                                                      aReason));
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 void
 MediaDrmCDMProxy::ResolvePromise(PromiseId aId)
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
@@ -321,17 +324,17 @@ MediaDrmCDMProxy::ResolvePromise(Promise
     } else {
       NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
     }
   } else {
     nsCOMPtr<nsIRunnable> task;
     task = NewRunnableMethod<PromiseId>(this,
                                         &MediaDrmCDMProxy::ResolvePromise,
                                         aId);
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 const nsString&
 MediaDrmCDMProxy::KeySystem() const
 {
   return mKeySystem;
 }
@@ -403,17 +406,17 @@ MediaDrmCDMProxy::md_Init(uint32_t aProm
   MOZ_ASSERT(mCDM);
 
   mCallback.reset(new MediaDrmCDMCallbackProxy(this));
   mCDM->Init(mCallback.get());
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<uint32_t>(this,
                                 &MediaDrmCDMProxy::OnCDMCreated,
                                 aPromiseId));
-  NS_DispatchToMainThread(task);
+  mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
 }
 
 void
 MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData)
 {
   MOZ_ASSERT(IsOnOwnerThread());
 
   if (!mCDM) {
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -32,17 +32,18 @@ public:
   MediaDrmCDMProxy(dom::MediaKeys* aKeys,
                    const nsAString& aKeySystem,
                    bool aDistinctiveIdentifierRequired,
                    bool aPersistentStateRequired);
 
   void Init(PromiseId aPromiseId,
             const nsAString& aOrigin,
             const nsAString& aTopLevelOrigin,
-            const nsAString& aGMPName) override;
+            const nsAString& aGMPName,
+            nsIEventTarget* aMainThread) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
                      MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
 
   void LoadSession(PromiseId aPromiseId,
--- a/dom/media/gmp/GMPCDMProxy.cpp
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -47,25 +47,28 @@ GMPCDMProxy::~GMPCDMProxy()
 {
   MOZ_COUNT_DTOR(GMPCDMProxy);
 }
 
 void
 GMPCDMProxy::Init(PromiseId aPromiseId,
                   const nsAString& aOrigin,
                   const nsAString& aTopLevelOrigin,
-                  const nsAString& aGMPName)
+                  const nsAString& aGMPName,
+                  nsIEventTarget* aMainThread)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
   EME_LOG("GMPCDMProxy::Init (%s, %s)",
           NS_ConvertUTF16toUTF8(aOrigin).get(),
           NS_ConvertUTF16toUTF8(aTopLevelOrigin).get());
 
+  mMainThread = aMainThread;
+
   nsCString pluginVersion;
   if (!mOwnerThread) {
     nsCOMPtr<mozIGeckoMediaPluginService> mps =
       do_GetService("@mozilla.org/gecko-media-plugin-service;1");
     if (!mps) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                     NS_LITERAL_CSTRING("Couldn't get MediaPluginService in GMPCDMProxy::Init"));
       return;
@@ -136,17 +139,17 @@ GMPCDMProxy::gmp_InitDone(GMPDecryptorPr
 void GMPCDMProxy::OnSetDecryptorId(uint32_t aId)
 {
   MOZ_ASSERT(mCreatePromiseId);
   mDecryptorId = aId;
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<uint32_t>(this,
                                 &GMPCDMProxy::OnCDMCreated,
                                 mCreatePromiseId));
-  NS_DispatchToMainThread(task);
+  mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
 }
 
 class gmp_InitDoneCallback : public GetGMPDecryptorCallback
 {
 public:
   gmp_InitDoneCallback(GMPCDMProxy* aGMPCDMProxy,
                        UniquePtr<GMPCDMProxy::InitData>&& aData)
     : mGMPCDMProxy(aGMPCDMProxy),
@@ -513,17 +516,17 @@ GMPCDMProxy::RejectPromise(PromiseId aId
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
       mKeys->RejectPromise(aId, aCode, aReason);
     }
   } else {
     nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
                                                      aReason));
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 void
 GMPCDMProxy::ResolvePromise(PromiseId aId)
 {
   if (NS_IsMainThread()) {
     if (!mKeys.IsNull()) {
@@ -531,17 +534,17 @@ GMPCDMProxy::ResolvePromise(PromiseId aI
     } else {
       NS_WARNING("GMPCDMProxy unable to resolve promise!");
     }
   } else {
     nsCOMPtr<nsIRunnable> task;
     task = NewRunnableMethod<PromiseId>(this,
                                         &GMPCDMProxy::ResolvePromise,
                                         aId);
-    NS_DispatchToMainThread(task);
+    mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
   }
 }
 
 const nsCString&
 GMPCDMProxy::GetNodeId() const
 {
   return mNodeId;
 }
@@ -604,17 +607,20 @@ GMPCDMProxy::OnExpirationChange(const ns
                                 GMPTimestamp aExpiryTime)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
     return;
   }
   RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
   if (session) {
-    session->SetExpiration(static_cast<double>(aExpiryTime));
+    // Expiry of 0 is interpreted as "never expire". See bug 1345341.
+    double t = (aExpiryTime == 0) ? std::numeric_limits<double>::quiet_NaN()
+                                  : static_cast<double>(aExpiryTime);
+    session->SetExpiration(t);
   }
 }
 
 void
 GMPCDMProxy::OnSessionClosed(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mKeys.IsNull()) {
--- a/dom/media/gmp/GMPCDMProxy.h
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -26,17 +26,18 @@ public:
               const nsAString& aKeySystem,
               GMPCrashHelper* aCrashHelper,
               bool aDistinctiveIdentifierRequired,
               bool aPersistentStateRequired);
 
   void Init(PromiseId aPromiseId,
             const nsAString& aOrigin,
             const nsAString& aTopLevelOrigin,
-            const nsAString& aGMPName) override;
+            const nsAString& aGMPName,
+            nsIEventTarget* aMainThread) override;
 
   void OnSetDecryptorId(uint32_t aId) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
                      dom::MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -409,21 +409,17 @@ WidevineDecryptor::OnExpirationChange(co
                                       Time aNewExpiryTime)
 {
   if (!mCallback) {
     CDM_LOG("Decryptor::OnExpirationChange(sid=%s) t=%lf FAIL; !mCallback",
             aSessionId, aNewExpiryTime);
     return;
   }
   CDM_LOG("Decryptor::OnExpirationChange(sid=%s) t=%lf", aSessionId, aNewExpiryTime);
-  GMPTimestamp expiry = ToGMPTime(aNewExpiryTime);
-  if (aNewExpiryTime == 0) {
-    return;
-  }
-  mCallback->ExpirationChange(aSessionId, aSessionIdSize, expiry);
+  mCallback->ExpirationChange(aSessionId, aSessionIdSize, ToGMPTime(aNewExpiryTime));
 }
 
 void
 WidevineDecryptor::OnSessionClosed(const char* aSessionId,
                                    uint32_t aSessionIdSize)
 {
   if (!mCallback) {
     CDM_LOG("Decryptor::OnSessionClosed(sid=%s) FAIL; !mCallback", aSessionId);
--- a/dom/media/gtest/GMPTestMonitor.h
+++ b/dom/media/gtest/GMPTestMonitor.h
@@ -31,17 +31,19 @@ private:
   {
     MOZ_ASSERT(NS_IsMainThread());
     mFinished = true;
   }
 
 public:
   void SetFinished()
   {
-    NS_DispatchToMainThread(mozilla::NewNonOwningRunnableMethod(this,
-                                                                &GMPTestMonitor::MarkFinished));
+    mozilla::SystemGroup::Dispatch(
+      "GMPTestMonitor::SetFinished",
+      mozilla::TaskCategory::Other,
+      mozilla::NewNonOwningRunnableMethod(this, &GMPTestMonitor::MarkFinished));
   }
 
 private:
   bool mFinished;
 };
 
 #endif // __GMPTestMonitor_h__
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -422,22 +422,23 @@ private:
 NS_IMPL_ISUPPORTS(ClearGMPStorageTask, nsIRunnable, nsIObserver)
 
 static void
 ClearGMPStorage(already_AddRefed<nsIRunnable> aContinuation,
                 nsIThread* aTarget, PRTime aSince = -1)
 {
   RefPtr<ClearGMPStorageTask> task(
     new ClearGMPStorageTask(Move(aContinuation), aTarget, aSince));
-  NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+  SystemGroup::Dispatch("ClearGMPStorage", TaskCategory::Other, task.forget());
 }
 
 static void
 SimulatePBModeExit()
 {
+  // SystemGroup::EventTargetFor() doesn't support NS_DISPATCH_SYNC.
   NS_DispatchToMainThread(new NotifyObserversTask("last-pb-context-exited"), NS_DISPATCH_SYNC);
 }
 
 class TestGetNodeIdCallback : public GetNodeIdCallback
 {
 public:
   TestGetNodeIdCallback(nsCString& aNodeId, nsresult& aResult)
     : mNodeId(aNodeId),
@@ -780,17 +781,20 @@ class GMPStorageTest : public GMPDecrypt
 
     UniquePtr<NodeInfo> siteInfo(
         new NodeInfo(NS_LITERAL_CSTRING("http://example1.com"),
                      pattern));
     // Collect nodeIds that are expected to remain for later comparison.
     EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"),
                            NodeIdCollector(siteInfo.get()));
     // Invoke "Forget this site" on the main thread.
-    NS_DispatchToMainThread(NewRunnableMethod<UniquePtr<NodeInfo>&&>(
+    SystemGroup::Dispatch(
+      "TestForgetThisSite_Forget",
+      TaskCategory::Other,
+      NewRunnableMethod<UniquePtr<NodeInfo>&&>(
         this, &GMPStorageTest::TestForgetThisSite_Forget, Move(siteInfo)));
   }
 
   void TestForgetThisSite_Forget(UniquePtr<NodeInfo>&& aSiteInfo) {
     RefPtr<GeckoMediaPluginServiceParent> service =
         GeckoMediaPluginServiceParent::GetSingleton();
     service->ForgetThisSiteNative(NS_ConvertUTF8toUTF16(aSiteInfo->siteToForget),
                                   aSiteInfo->mPattern);
@@ -1164,34 +1168,35 @@ class GMPStorageTest : public GMPDecrypt
     EXPECT_TRUE(!!mDecryptor);
     if (!mDecryptor) {
       return;
     }
     EXPECT_FALSE(mNodeId.IsEmpty());
     RefPtr<GMPShutdownObserver> task(
       new GMPShutdownObserver(NewRunnableMethod(this, &GMPStorageTest::Shutdown),
                               Move(aContinuation), mNodeId));
-    NS_DispatchToMainThread(task, NS_DISPATCH_NORMAL);
+    SystemGroup::Dispatch("GMPShutdownObserver", TaskCategory::Other, task.forget());
   }
 
   void Shutdown() {
     if (mDecryptor) {
       mDecryptor->Close();
       mDecryptor = nullptr;
       mNodeId = EmptyCString();
     }
   }
 
   void Dummy() {
   }
 
   void SetFinished() {
     mFinished = true;
     Shutdown();
-    NS_DispatchToMainThread(NewRunnableMethod(this, &GMPStorageTest::Dummy));
+    nsCOMPtr<nsIRunnable> task = NewRunnableMethod(this, &GMPStorageTest::Dummy);
+    SystemGroup::Dispatch("GMPStorageTest::Dummy", TaskCategory::Other, task.forget());
   }
 
   void SessionMessage(const nsCString& aSessionId,
                       mozilla::dom::MediaKeyMessageType aMessageType,
                       const nsTArray<uint8_t>& aMessage) override
   {
     MonitorAutoLock mon(mMonitor);
 
--- a/dom/media/test/test_buffered.html
+++ b/dom/media/test/test_buffered.html
@@ -9,60 +9,59 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript" src="manifest.js"></script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=462957">Mozilla Bug 462957</a>
 
 <p id="display"></p>
 <div id="content" style="display: none">
-  
+
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 // Test for Bug 462957; HTMLMediaElement.buffered.
 
 var manager = new MediaTestManager;
 
 function testBuffered(e) {
   var v = e.target;
-  v.removeEventListener('timeupdate', testBuffered);
-  
+
   // The whole media should be buffered...
   var b = v.buffered;
   is(b.length, 1, v._name + ": Should be buffered in one range");
   is(b.start(0), 0, v._name + ": First range start should be media start");
   ok(Math.abs(b.end(0) - v.duration) < 0.1, v._name + ": First range end should be media end");
 
   // Ensure INDEX_SIZE_ERR is thrown when we access outside the range
   var caught = false;
   try {
     b.start(-1);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under start bounds range");
-  
+
   caught = false;
   try {
     b.end(-1);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on under end bounds range");
 
   caught = false;
   try {
     b.start(b.length);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over start bounds range");
-  
+
   caught = false;
   try {
     b.end(b.length);
   } catch (e) {
     caught = e.name == "IndexSizeError" && e.code == DOMException.INDEX_SIZE_ERR;
   }
   is(caught, true, v._name + ": Should throw INDEX_SIZE_ERR on over end bounds range");
 
@@ -72,48 +71,47 @@ function testBuffered(e) {
 
 function fetch(url, fetched_callback) {
   var xhr = new XMLHttpRequest();
   xhr.open("GET", url, true);
   xhr.responseType = "blob";
 
   var loaded = function (event) {
     if (xhr.status == 200 || xhr.status == 206) {
+      ok(true, `${url}: Fetch succeeded, status=${xhr.status}`);
       // Request fulfilled. Note sometimes we get 206... Presumably because either
       // httpd.js or Necko cached the result.
       fetched_callback(window.URL.createObjectURL(xhr.response));
     } else {
-      ok(false, "Fetch failed headers=" + xhr.getAllResponseHeaders());
+      ok(false, `${url}: Fetch failed, headers=${xhr.getAllResponseHeaders()}`);
     }
   };
 
   xhr.addEventListener("load", loaded);
   xhr.send();
 }
 
 function startTest(test, token) {
   // Fetch the media resource using XHR so we can be sure the entire
   // resource is loaded before we test buffered ranges. This ensures
   // we have deterministic behaviour.
   var onfetched = function(uri) {
     var v = document.createElement('video');
-    v.autoplay = true;
     v._token = token;
     v.src = uri;
     v._name = test.name;
     v._test = test;
-    v.addEventListener("timeupdate", testBuffered);
+    v.addEventListener("loadeddata", testBuffered, {once: true});
     document.body.appendChild(v);
   };
 
   manager.started(token);
   fetch(test.name, onfetched);
 }
 
 // Note: No need to set media test prefs, since we're using XHR to fetch
 // media data.
-SimpleTest.waitForExplicitFinish();
 manager.runTests(gSeekTests, startTest);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/chrome/window_focus.xul
+++ b/dom/tests/mochitest/chrome/window_focus.xul
@@ -1339,19 +1339,18 @@ function switchWindowTest(otherWindow, f
   fm.activeWindow = topWindow;
   fm.activeWindow = otherWindow;
   is(otherTextbox.selectionStart, 2, "selectionStart after textbox focus and window raise");
   is(otherTextbox.selectionEnd, 3, "selectionEnd after textbox focus and window raise");
   is(fm.getLastFocusMethod(null), fm.FLAG_BYMOVEFOCUS, "last focus method after textbox focus and window raise");
 
   fm.clearFocus(otherWindow);
 
-  // test to ensure that a synthetic event works
-  var synevent = document.createEvent("Event");
-  synevent.initEvent("focus", false, false);
+  // test to ensure that a synthetic event won't move focus
+  var synevent = new FocusEvent("focus", {});
   otherTextbox.inputField.dispatchEvent(synevent);
   is(synevent.type, "focus", "event.type after synthetic focus event");
   is(synevent.target, otherTextbox, "event.target after synthetic focus event");
   is(fm.focusedElement, null, "focusedElement after synthetic focus event");
   is(otherWindow.document.activeElement, otherWindow.document.documentElement,
      "document.activeElement after synthetic focus event");
 
   // check accessing a focus event after the event has finishing firing
--- a/editor/libeditor/EditorEventListener.cpp
+++ b/editor/libeditor/EditorEventListener.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=4 sw=2 et tw=78: */
 /* 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 "EditorEventListener.h"
 
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT, etc.
+#include "mozilla/ContentEvents.h"      // for InternalFocusEvent
 #include "mozilla/EditorBase.h"         // for EditorBase, etc.
 #include "mozilla/EventListenerManager.h" // for EventListenerManager
 #include "mozilla/IMEStateManager.h"    // for IMEStateManager
 #include "mozilla/Preferences.h"        // for Preferences
 #include "mozilla/TextEvents.h"         // for WidgetCompositionEvent
 #include "mozilla/dom/Element.h"        // for Element
 #include "mozilla/dom/Event.h"          // for Event
 #include "mozilla/dom/EventTarget.h"    // for EventTarget
@@ -183,18 +184,21 @@ EditorEventListener::InstallToEditor()
                                NS_LITERAL_STRING("mousedown"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("mouseup"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("click"),
                                TrustedEventsAtCapture());
-// Focus event doesn't bubble so adding the listener to capturing phase.
-// Make sure this works after bug 235441 gets fixed.
+  // Focus event doesn't bubble so adding the listener to capturing phase.
+  // XXX Should we listen focus/blur events of system group too? Or should
+  //     editor notified focus/blur of the element from nsFocusManager
+  //     directly?  Because if the event propagation is stopped by JS,
+  //     editor cannot initialize selection as expected.
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("blur"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("focus"),
                                TrustedEventsAtCapture());
   elmP->AddEventListenerByType(this,
                                NS_LITERAL_STRING("text"),
@@ -461,45 +465,37 @@ EditorEventListener::HandleEvent(nsIDOME
         mMouseDownOrUpConsumedByIME = false;
         mouseEvent->AsEvent()->PreventDefault();
         return NS_OK;
       }
       return MouseClick(mouseEvent);
     }
     // focus
     case eFocus:
-      return Focus(internalEvent);
+      return Focus(internalEvent->AsFocusEvent());
     // blur
     case eBlur:
-      return Blur(internalEvent);
+      return Blur(internalEvent->AsFocusEvent());
     // text
     case eCompositionChange:
       return HandleChangeComposition(internalEvent->AsCompositionEvent());
     // compositionstart
     case eCompositionStart:
       return HandleStartComposition(internalEvent->AsCompositionEvent());
     // compositionend
     case eCompositionEnd:
       HandleEndComposition(internalEvent->AsCompositionEvent());
       return NS_OK;
     default:
       break;
   }
 
+#ifdef DEBUG
   nsAutoString eventType;
   aEvent->GetType(eventType);
-  // We should accept "focus" and "blur" event even if it's synthesized with
-  // wrong interface for compatibility with older Gecko.
-  if (eventType.EqualsLiteral("focus")) {
-    return Focus(internalEvent);
-  }
-  if (eventType.EqualsLiteral("blur")) {
-    return Blur(internalEvent);
-  }
-#ifdef DEBUG
   nsPrintfCString assertMessage("Editor doesn't handle \"%s\" event "
     "because its internal event doesn't have proper message",
     NS_ConvertUTF16toUTF8(eventType).get());
   NS_ASSERTION(false, assertMessage.get());
 #endif
 
   return NS_OK;
 }
@@ -1073,28 +1069,22 @@ EditorEventListener::HandleEndCompositio
     return;
   }
   MOZ_ASSERT(!aCompositionEndEvent->DefaultPrevented(),
              "eCompositionEnd shouldn't be cancelable");
   editorBase->EndIMEComposition();
 }
 
 nsresult
-EditorEventListener::Focus(WidgetEvent* aFocusEvent)
+EditorEventListener::Focus(InternalFocusEvent* aFocusEvent)
 {
   if (NS_WARN_IF(!aFocusEvent) || DetachedFromEditor()) {
     return NS_OK;
   }
 
-  // XXX If aFocusEvent was created by chrome script, its defaultPrevented
-  //     may be true, though.  We shouldn't handle such event but we don't
-  //     have a way to distinguish if coming event is created by chrome script.
-  NS_WARNING_ASSERTION(!aFocusEvent->DefaultPrevented(),
-                       "eFocus event shouldn't be cancelable");
-
   // Don't turn on selection and caret when the editor is disabled.
   RefPtr<EditorBase> editorBase(mEditorBase);
   if (editorBase->IsDisabled()) {
     return NS_OK;
   }
 
   // Spell check a textarea the first time that it is focused.
   SpellCheckIfNeeded();
@@ -1162,28 +1152,22 @@ EditorEventListener::Focus(WidgetEvent* 
   nsCOMPtr<nsIContent> focusedContent = editorBase->GetFocusedContentForIME();
   IMEStateManager::OnFocusInEditor(ps->GetPresContext(), focusedContent,
                                    editorBase);
 
   return NS_OK;
 }
 
 nsresult
-EditorEventListener::Blur(WidgetEvent* aBlurEvent)
+EditorEventListener::Blur(InternalFocusEvent* aBlurEvent)
 {
   if (NS_WARN_IF(!aBlurEvent) || DetachedFromEditor()) {
     return NS_OK;
   }
 
-  // XXX If aBlurEvent was created by chrome script, its defaultPrevented
-  //     may be true, though.  We shouldn't handle such event but we don't
-  //     have a way to distinguish if coming event is created by chrome script.
-  NS_WARNING_ASSERTION(!aBlurEvent->DefaultPrevented(),
-                       "eBlur event shouldn't be cancelable");
-
   // check if something else is focused. If another element is focused, then
   // we should not change the selection.
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
   NS_ENSURE_TRUE(fm, NS_OK);
 
   nsCOMPtr<nsIDOMElement> element;
   fm->GetFocusedElement(getter_AddRefs(element));
   if (!element) {
--- a/editor/libeditor/EditorEventListener.h
+++ b/editor/libeditor/EditorEventListener.h
@@ -63,18 +63,18 @@ protected:
 #endif
   nsresult KeyPress(WidgetKeyboardEvent* aKeyboardEvent);
   nsresult HandleChangeComposition(WidgetCompositionEvent* aCompositionEvent);
   nsresult HandleStartComposition(WidgetCompositionEvent* aCompositionEvent);
   void HandleEndComposition(WidgetCompositionEvent* aCompositionEvent);
   virtual nsresult MouseDown(nsIDOMMouseEvent* aMouseEvent);
   virtual nsresult MouseUp(nsIDOMMouseEvent* aMouseEvent) { return NS_OK; }
   virtual nsresult MouseClick(nsIDOMMouseEvent* aMouseEvent);
-  nsresult Focus(WidgetEvent* aFocusEvent);
-  nsresult Blur(WidgetEvent* aBlurEvent);
+  nsresult Focus(InternalFocusEvent* aFocusEvent);
+  nsresult Blur(InternalFocusEvent* aBlurEvent);
   nsresult DragEnter(nsIDOMDragEvent* aDragEvent);
   nsresult DragOver(nsIDOMDragEvent* aDragEvent);
   nsresult DragExit(nsIDOMDragEvent* aDragEvent);
   nsresult Drop(nsIDOMDragEvent* aDragEvent);
 
   bool CanDrop(nsIDOMDragEvent* aEvent);
   void CleanupDragDropCaret();
   already_AddRefed<nsIPresShell> GetPresShell();
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -4,28 +4,80 @@
  * 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 "AnimationHelper.h"
 #include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for dom::FillMode
 #include "mozilla/dom/KeyframeEffectBinding.h" // for dom::IterationComposite
 #include "mozilla/dom/KeyframeEffectReadOnly.h" // for dom::KeyFrameEffectReadOnly
+#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
 #include "mozilla/layers/LayerAnimationUtils.h" // for TimingFunctionToComputedTimingFunction
-#include "mozilla/layers/LayersMessages.h" // for TransformFunction, etc
 #include "mozilla/StyleAnimationValue.h" // for StyleAnimationValue, etc
 
 namespace mozilla {
 namespace layers {
 
 struct StyleAnimationValueCompositePair {
   StyleAnimationValue mValue;
   dom::CompositeOperation mComposite;
 };
 
+void
+CompositorAnimationStorage::Clear()
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+
+  mAnimatedValues.Clear();
+  mAnimations.Clear();
+
+}
+
+AnimatedValue*
+CompositorAnimationStorage::GetAnimatedValue(const uint64_t& aId) const
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  return mAnimatedValues.Get(aId);
+}
+
+void
+CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
+                                             gfx::Matrix4x4&& aTransformInDevSpace,
+                                             gfx::Matrix4x4&& aFrameTransform,
+                                             const TransformData& aData)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AnimatedValue* value = new AnimatedValue(Move(aTransformInDevSpace), Move(aFrameTransform), aData);
+  mAnimatedValues.Put(aId, value);
+}
+
+void
+CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
+                                             const float& aOpacity)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AnimatedValue* value = new AnimatedValue(aOpacity);
+  mAnimatedValues.Put(aId, value);
+}
+
+AnimationArray*
+CompositorAnimationStorage::GetAnimations(const uint64_t& aId) const
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  return mAnimations.Get(aId);
+}
+
+void
+CompositorAnimationStorage::SetAnimations(uint64_t aId, const AnimationArray& aValue)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  AnimationArray* value = new AnimationArray(aValue);
+  mAnimations.Put(aId, value);
+}
+
 static StyleAnimationValue
 SampleValue(float aPortion, const layers::Animation& aAnimation,
             const StyleAnimationValueCompositePair& aStart,
             const StyleAnimationValueCompositePair& aEnd,
             const StyleAnimationValue& aLastValue,
             uint64_t aCurrentIteration,
             const StyleAnimationValue& aUnderlyingValue)
 {
@@ -409,10 +461,22 @@ AnimationHelper::SetAnimations(Animation
     InfallibleTArray<StyleAnimationValue>& endValues = data->mEndValues;
     for (const AnimationSegment& segment : segments) {
       startValues.AppendElement(ToStyleAnimationValue(segment.startState()));
       endValues.AppendElement(ToStyleAnimationValue(segment.endState()));
     }
   }
 }
 
+uint64_t
+AnimationHelper::GetNextCompositorAnimationsId()
+{
+  static uint32_t sNextId = 0;
+  ++sNextId;
+
+  uint32_t procId = static_cast<uint32_t>(base::GetCurrentProcId());
+  uint64_t nextId = procId;
+  nextId = nextId << 32 | sNextId;
+  return nextId;
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -3,45 +3,145 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_AnimationHelper_h
 #define mozilla_layers_AnimationHelper_h
 
 #include "mozilla/ComputedTimingFunction.h" // for ComputedTimingFunction
+#include "mozilla/layers/LayersMessages.h" // for TransformData, etc
 #include "mozilla/TimeStamp.h"          // for TimeStamp
 
 
 namespace mozilla {
-  class StyleAnimationValue;
+class StyleAnimationValue;
 namespace layers {
 class Animation;
 
 typedef InfallibleTArray<layers::Animation> AnimationArray;
 
 struct AnimData {
   InfallibleTArray<mozilla::StyleAnimationValue> mStartValues;
   InfallibleTArray<mozilla::StyleAnimationValue> mEndValues;
   InfallibleTArray<Maybe<mozilla::ComputedTimingFunction>> mFunctions;
 };
 
+struct AnimationTransform {
+  /*
+   * This transform is calculated from sampleanimation in device pixel
+   * and used by compositor.
+   */
+  gfx::Matrix4x4 mTransformInDevSpace;
+  /*
+   * This transform is calculated from frame and used by getOMTAStyle()
+   * for OMTA testing.
+   */
+  gfx::Matrix4x4 mFrameTransform;
+  TransformData mData;
+};
+
+struct AnimatedValue {
+  enum {
+    TRANSFORM,
+    OPACITY,
+    NONE
+  } mType {NONE};
+
+  union {
+    AnimationTransform mTransform;
+    float mOpacity;
+  };
+
+  AnimatedValue(gfx::Matrix4x4&& aTransformInDevSpace,
+                gfx::Matrix4x4&& aFrameTransform,
+                const TransformData& aData)
+    : mType(AnimatedValue::TRANSFORM)
+  {
+    mTransform.mTransformInDevSpace = Move(aTransformInDevSpace);
+    mTransform.mFrameTransform = Move(aFrameTransform);
+    mTransform.mData = aData;
+  }
+
+  explicit AnimatedValue(const float& aValue)
+    : mType(AnimatedValue::OPACITY)
+    , mOpacity(aValue)
+  {
+  }
+
+  ~AnimatedValue() {}
+
+private:
+  AnimatedValue() = delete;
+};
+
+// CompositorAnimationStorage stores the layer animations and animated value
+// after sampling based on an unique id (CompositorAnimationsId)
+class CompositorAnimationStorage final
+{
+  typedef nsClassHashtable<nsUint64HashKey, AnimatedValue> AnimatedValueTable;
+  typedef nsClassHashtable<nsUint64HashKey, AnimationArray> AnimationsTable;
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorAnimationStorage)
+public:
+
+  /**
+   * Set the animation transform based on the unique id
+   */
+  void SetAnimatedValue(uint64_t aId,
+                        gfx::Matrix4x4&& aTransformInDevSpace,
+                        gfx::Matrix4x4&& aFrameTransform,
+                        const TransformData& aData);
+
+  /**
+   * Set the animation opacity based on the unique id
+   */
+  void SetAnimatedValue(uint64_t aId, const float& aOpacity);
+
+  /**
+   * Return the animated value if a given id can map to its animated value
+   */
+  AnimatedValue* GetAnimatedValue(const uint64_t& aId) const;
+
+  /**
+   * Set the animations based on the unique id
+   */
+  void SetAnimations(uint64_t aId, const AnimationArray& aAnimations);
+
+  /**
+   * Return the animations if a given id can map to its animations
+   */
+  AnimationArray* GetAnimations(const uint64_t& aId) const;
+
+  /**
+   * Clear AnimatedValues and Animations data
+   */
+  void Clear();
+
+private:
+  ~CompositorAnimationStorage() { Clear(); };
+
+private:
+  AnimatedValueTable mAnimatedValues;
+  AnimationsTable mAnimations;
+};
+
 class AnimationHelper
 {
 public:
-
   static bool
   SampleAnimationForEachNode(TimeStamp aPoint,
                              AnimationArray& aAnimations,
                              InfallibleTArray<AnimData>& aAnimationData,
                              StyleAnimationValue& aAnimationValue,
                              bool& aHasInEffectAnimations);
 
   static void
   SetAnimations(AnimationArray& aAnimations,
                 InfallibleTArray<AnimData>& aAnimData,
                 StyleAnimationValue& aBaseAnimationStyle);
+  static uint64_t GetNextCompositorAnimationsId();
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_AnimationHelper_h
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -187,32 +187,40 @@ LayerManager::RemoveUserData(void* aKey)
 // Layer
 
 Layer::Layer(LayerManager* aManager, void* aImplData) :
   mManager(aManager),
   mParent(nullptr),
   mNextSibling(nullptr),
   mPrevSibling(nullptr),
   mImplData(aImplData),
+  mCompositorAnimationsId(0),
   mUseTileSourceRect(false),
 #ifdef DEBUG
   mDebugColorIndex(0),
 #endif
   mAnimationGeneration(0)
 {
 }
 
 Layer::~Layer()
 {
 }
 
 Animation*
 Layer::AddAnimation()
 {
-  MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) AddAnimation", this));
+  // Here generates a new id when the first animation is added and
+  // this id is used to represent the animations in this layer.
+  if (!mCompositorAnimationsId) {
+    mCompositorAnimationsId = AnimationHelper::GetNextCompositorAnimationsId();
+  }
+
+  MOZ_LAYERS_LOG_IF_SHADOWABLE(
+    this, ("Layer::Mutated(%p) AddAnimation with id=%" PRIu64, this, mCompositorAnimationsId));
 
   MOZ_ASSERT(!mPendingAnimations, "should have called ClearAnimations first");
 
   Animation* anim = mAnimations.AppendElement();
 
   Mutated();
   return anim;
 }
@@ -223,16 +231,17 @@ Layer::ClearAnimations()
   mPendingAnimations = nullptr;
 
   if (mAnimations.IsEmpty() && mAnimationData.IsEmpty()) {
     return;
   }
 
   MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ClearAnimations", this));
   mAnimations.Clear();
+  mCompositorAnimationsId = 0;
   mAnimationData.Clear();
   Mutated();
 }
 
 Animation*
 Layer::AddAnimationForNextTransaction()
 {
   MOZ_ASSERT(mPendingAnimations,
@@ -250,21 +259,23 @@ Layer::ClearAnimationsForNextTransaction
   if (!mPendingAnimations) {
     mPendingAnimations = new AnimationArray;
   }
 
   mPendingAnimations->Clear();
 }
 
 void
-Layer::SetAnimations(const AnimationArray& aAnimations)
+Layer::SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations)
 {
-  MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) SetAnimations", this));
+  MOZ_LAYERS_LOG_IF_SHADOWABLE(
+    this, ("Layer::Mutated(%p) SetCompositorAnimations with id=%" PRIu64, this, mCompositorAnimationsId));
 
-  mAnimations = aAnimations;
+  mAnimations = aCompositorAnimations.animations();
+  mCompositorAnimationsId = aCompositorAnimations.id();
   mAnimationData.Clear();
   AnimationHelper::SetAnimations(mAnimations,
                                  mAnimationData,
                                  mBaseAnimationStyle);
 
   Mutated();
 }
 
@@ -1910,17 +1921,19 @@ Layer::PrintInfo(std::stringstream& aStr
   }
   for (uint32_t i = 0; i < mScrollMetadata.Length(); i++) {
     if (!mScrollMetadata[i].IsDefault()) {
       aStream << nsPrintfCString(" [metrics%d=", i).get();
       AppendToString(aStream, mScrollMetadata[i], "", "]");
     }
   }
   if (!mAnimations.IsEmpty()) {
-    aStream << nsPrintfCString(" [%d animations]", (int) mAnimations.Length()).get();
+    aStream << nsPrintfCString(" [%d animations with id=%" PRIu64 " ]",
+                               (int) mAnimations.Length(),
+                               mCompositorAnimationsId).get();
   }
 }
 
 // The static helper function sets the transform matrix into the packet
 static void
 DumpTransform(layerscope::LayersPacket::Layer::Matrix* aLayerMatrix, const Matrix4x4& aMatrix)
 {
   aLayerMatrix->set_is2d(aMatrix.Is2D());
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -83,16 +83,17 @@ class ClientLayerManager;
 class HostLayerManager;
 class Layer;
 class LayerMetricsWrapper;
 class PaintedLayer;
 class ContainerLayer;
 class ImageLayer;
 class DisplayItemLayer;
 class ColorLayer;
+class CompositorAnimations;
 class CompositorBridgeChild;
 class TextLayer;
 class CanvasLayer;
 class BorderLayer;
 class ReadbackLayer;
 class ReadbackProcessor;
 class RefLayer;
 class HostLayer;
@@ -1218,17 +1219,17 @@ public:
   // Call AddAnimation to add a new animation to this layer from layout code.
   // Caller must fill in all the properties of the returned animation.
   // A later animation overrides an earlier one.
   Animation* AddAnimation();
   // ClearAnimations clears animations on this layer.
   void ClearAnimations();
   // This is only called when the layer tree is updated. Do not call this from
   // layout code.  To add an animation to this layer, use AddAnimation.
-  void SetAnimations(const AnimationArray& aAnimations);
+  void SetCompositorAnimations(const CompositorAnimations& aCompositorAnimations);
   // Go through all animations in this layer and its children and, for
   // any animations with a null start time, update their start time such
   // that at |aReadyTime| the animation's current time corresponds to its
   // 'initial current time' value.
   void StartPendingAnimations(const TimeStamp& aReadyTime);
 
   // These are a parallel to AddAnimation and clearAnimations, except
   // they add pending animations that apply only when the next
@@ -1410,16 +1411,17 @@ public:
    *  traversal.
    */
   bool GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult,
                                            nsIntPoint* aLayerOffset);
 
   // Note that all lengths in animation data are either in CSS pixels or app
   // units and must be converted to device pixels by the compositor.
   AnimationArray& GetAnimations() { return mAnimations; }
+  uint64_t GetCompositorAnimationsId() { return mCompositorAnimationsId; }
   InfallibleTArray<AnimData>& GetAnimationData() { return mAnimationData; }
 
   uint64_t GetAnimationGeneration() { return mAnimationGeneration; }
   void SetAnimationGeneration(uint64_t aCount) { mAnimationGeneration = aCount; }
 
   bool HasTransformAnimation() const;
 
   StyleAnimationValue GetBaseAnimationStyle() const
@@ -1916,16 +1918,17 @@ protected:
   nsTArray<ScrollMetadata> mScrollMetadata;
   EventRegions mEventRegions;
   // A mutation of |mTransform| that we've queued to be applied at the
   // end of the next transaction (if nothing else overrides it in the
   // meantime).
   nsAutoPtr<gfx::Matrix4x4> mPendingTransform;
   gfx::Matrix4x4 mEffectiveTransform;
   AnimationArray mAnimations;
+  uint64_t mCompositorAnimationsId;
   // See mPendingTransform above.
   nsAutoPtr<AnimationArray> mPendingAnimations;
   InfallibleTArray<AnimData> mAnimationData;
   Maybe<ParentLayerIntRect> mClipRect;
   gfx::IntRect mTileSourceRect;
   gfx::TiledIntRegion mInvalidRegion;
   nsTArray<RefPtr<AsyncPanZoomController> > mApzcs;
   bool mUseTileSourceRect;
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -62,22 +62,24 @@ IsSameDimension(dom::ScreenOrientationIn
 }
 
 static bool
 ContentMightReflowOnOrientationChange(const IntRect& rect)
 {
   return rect.width != rect.height;
 }
 
-AsyncCompositionManager::AsyncCompositionManager(HostLayerManager* aManager)
+  AsyncCompositionManager::AsyncCompositionManager(CompositorBridgeParent* aParent,
+                                                   HostLayerManager* aManager)
   : mLayerManager(aManager)
   , mIsFirstPaint(true)
   , mLayersUpdated(false)
   , mPaintSyncId(0)
   , mReadyForCompose(true)
+  , mCompositorBridge(aParent)
 {
 }
 
 AsyncCompositionManager::~AsyncCompositionManager()
 {
 }
 
 void
@@ -571,101 +573,116 @@ AsyncCompositionManager::AlignFixedAndSt
     }
   }
 
   return;
 }
 
 static void
 ApplyAnimatedValue(Layer* aLayer,
+                   CompositorAnimationStorage* aStorage,
                    nsCSSPropertyID aProperty,
                    const AnimationData& aAnimationData,
                    const StyleAnimationValue& aValue)
 {
   if (aValue.IsNull()) {
     // Return gracefully if we have no valid StyleAnimationValue.
     return;
   }
 
   HostLayer* layerCompositor = aLayer->AsHostLayer();
   switch (aProperty) {
     case eCSSProperty_opacity: {
       MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Float,
                  "Interpolated value for opacity should be float");
       layerCompositor->SetShadowOpacity(aValue.GetFloatValue());
       layerCompositor->SetShadowOpacitySetByAnimation(true);
+      aStorage->SetAnimatedValue(aLayer->GetCompositorAnimationsId(),
+                                 aValue.GetFloatValue());
+
       break;
     }
     case eCSSProperty_transform: {
       MOZ_ASSERT(aValue.GetUnit() == StyleAnimationValue::eUnit_Transform,
                  "The unit of interpolated value for transform should be "
                  "transform");
       nsCSSValueSharedList* list = aValue.GetCSSValueSharedListValue();
 
       const TransformData& transformData = aAnimationData.get_TransformData();
       nsPoint origin = transformData.origin();
       // we expect all our transform data to arrive in device pixels
       Point3D transformOrigin = transformData.transformOrigin();
       nsDisplayTransform::FrameTransformProperties props(list,
                                                          transformOrigin);
 
+      Matrix4x4 transform =
+        nsDisplayTransform::GetResultingTransformMatrix(props, origin,
+                                                        transformData.appUnitsPerDevPixel(),
+                                                        0, &transformData.bounds());
+      Matrix4x4 frameTransform = transform;
+
       // If our parent layer is a perspective layer, then the offset into reference
       // frame coordinates is already on that layer. If not, then we need to ask
       // for it to be added here.
-      uint32_t flags = 0;
       if (!aLayer->GetParent() ||
           !aLayer->GetParent()->GetTransformIsPerspective()) {
-        flags = nsDisplayTransform::OFFSET_BY_ORIGIN;
+        nsLayoutUtils::PostTranslate(transform, origin,
+                                     transformData.appUnitsPerDevPixel(),
+                                     true);
       }
 
-      Matrix4x4 transform =
-        nsDisplayTransform::GetResultingTransformMatrix(props, origin,
-                                                        transformData.appUnitsPerDevPixel(),
-                                                        flags, &transformData.bounds());
-
       if (ContainerLayer* c = aLayer->AsContainerLayer()) {
         transform.PostScale(c->GetInheritedXScale(), c->GetInheritedYScale(), 1);
       }
+
       layerCompositor->SetShadowBaseTransform(transform);
       layerCompositor->SetShadowTransformSetByAnimation(true);
+      aStorage->SetAnimatedValue(aLayer->GetCompositorAnimationsId(),
+                                 Move(transform), Move(frameTransform),
+                                 transformData);
       break;
     }
     default:
       MOZ_ASSERT_UNREACHABLE("Unhandled animated property");
   }
 }
 
 static bool
-SampleAnimations(Layer* aLayer, TimeStamp aPoint, uint64_t* aLayerAreaAnimated)
+SampleAnimations(Layer* aLayer,
+                 CompositorAnimationStorage* aStorage,
+                 TimeStamp aPoint,
+                 uint64_t* aLayerAreaAnimated)
 {
   bool activeAnimations = false;
 
   ForEachNode<ForwardIterator>(
       aLayer,
-      [&activeAnimations, &aPoint, &aLayerAreaAnimated] (Layer* layer)
+      [aStorage, &activeAnimations, &aPoint, &aLayerAreaAnimated] (Layer* layer)
       {
         bool hasInEffectAnimations = false;
         StyleAnimationValue animationValue = layer->GetBaseAnimationStyle();
         activeAnimations |=
           AnimationHelper::SampleAnimationForEachNode(aPoint,
                                                       layer->GetAnimations(),
                                                       layer->GetAnimationData(),
                                                       animationValue,
                                                       hasInEffectAnimations);
         if (hasInEffectAnimations) {
           Animation& animation = layer->GetAnimations().LastElement();
           ApplyAnimatedValue(layer,
+                             aStorage,
                              animation.property(),
                              animation.data(),
                              animationValue);
           if (aLayerAreaAnimated) {
             *aLayerAreaAnimated += (layer->GetVisibleRegion().Area());
           }
         }
       });
+
   return activeAnimations;
 }
 
 static bool
 SampleAPZAnimations(const LayerMetricsWrapper& aLayer, TimeStamp aSampleTime)
 {
   bool activeAnimations = false;
 
@@ -1303,31 +1320,43 @@ AsyncCompositionManager::TransformShadow
   PROFILER_LABEL("AsyncCompositionManager", "TransformShadowTree",
     js::ProfileEntry::Category::GRAPHICS);
 
   Layer* root = mLayerManager->GetRoot();
   if (!root) {
     return false;
   }
 
+  // GetAnimationStorage in CompositorBridgeParent expects id as 0
+  CompositorAnimationStorage* storage =
+    mCompositorBridge->GetAnimationStorage(0);
   // First, compute and set the shadow transforms from OMT animations.
   // NB: we must sample animations *before* sampling pan/zoom
   // transforms.
   // Use a previous vsync time to make main thread animations and compositor
   // more in sync with each other.
   // On the initial frame we use aVsyncTimestamp here so the timestamp on the
   // second frame are the same as the initial frame, but it does not matter.
   uint64_t layerAreaAnimated = 0;
-  bool wantNextFrame = SampleAnimations(root,
-    !mPreviousFrameTimeStamp.IsNull() ?
-      mPreviousFrameTimeStamp : aCurrentFrame,
-    &layerAreaAnimated);
+  bool wantNextFrame =
+    SampleAnimations(root,
+                     storage,
+                     !mPreviousFrameTimeStamp.IsNull() ?
+                       mPreviousFrameTimeStamp : aCurrentFrame,
+                     &layerAreaAnimated);
+
   mAnimationMetricsTracker.UpdateAnimationInProgress(
     wantNextFrame, layerAreaAnimated);
 
+  if (!wantNextFrame) {
+    // Clean up the CompositorAnimationStorage because
+    // there are no active animations running
+    storage->Clear();
+  }
+
   // Reset the previous time stamp if we don't already have any running
   // animations to avoid using the time which is far behind for newly
   // started animations.
   mPreviousFrameTimeStamp = wantNextFrame ? aCurrentFrame : TimeStamp();
 
   if (!(aSkip & TransformsToSkip::APZ)) {
     // FIXME/bug 775437: unify this interface with the ~native-fennec
     // derived code
--- a/gfx/layers/composite/AsyncCompositionManager.h
+++ b/gfx/layers/composite/AsyncCompositionManager.h
@@ -66,17 +66,17 @@ struct AsyncTransform {
 class AsyncCompositionManager final
 {
   friend class AutoResolveRefLayers;
   ~AsyncCompositionManager();
 
 public:
   NS_INLINE_DECL_REFCOUNTING(AsyncCompositionManager)
 
-  explicit AsyncCompositionManager(HostLayerManager* aManager);
+  explicit AsyncCompositionManager(CompositorBridgeParent* aParent, HostLayerManager* aManager);
 
   /**
    * This forces the is-first-paint flag to true. This is intended to
    * be called by the widget code when it loses its viewport information
    * (or for whatever reason wants to refresh the viewport information).
    * The information refresh happens because the compositor will call
    * SetFirstPaintViewport on the next frame of composition.
    */
@@ -236,16 +236,18 @@ private:
   bool mReadyForCompose;
 
   gfx::Matrix mWorldTransform;
   LayerTransformRecorder mLayerTransformRecorder;
 
   TimeStamp mPreviousFrameTimeStamp;
   AnimationMetricsTracker mAnimationMetricsTracker;
 
+  CompositorBridgeParent* mCompositorBridge;
+
 #ifdef MOZ_WIDGET_ANDROID
   // The following two fields are only needed on Fennec with C++ APZ, because
   // then we need to reposition the gecko scrollbar to deal with the
   // dynamic toolbar shifting content around.
   FrameMetrics::ViewID mRootScrollableId;
   ScreenMargin mFixedLayerMargins;
 #endif
 };
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -30,16 +30,17 @@
 #include "mozilla/gfx/2D.h"          // for DrawTarget
 #include "mozilla/gfx/GPUChild.h"       // for GfxPrefValue
 #include "mozilla/gfx/Point.h"          // for IntSize
 #include "mozilla/gfx/Rect.h"          // for IntSize
 #include "mozilla/gfx/gfxVars.h"        // for gfxVars
 #include "VRManager.h"                  // for VRManager
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/BasicCompositor.h"  // for BasicCompositor
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/CompositorOGL.h"  // for CompositorOGL
 #include "mozilla/layers/CompositorThread.h"
@@ -314,16 +315,17 @@ CompositorBridgeParent::CompositorBridge
   , mPauseCompositionMonitor("PauseCompositionMonitor")
   , mResumeCompositionMonitor("ResumeCompositionMonitor")
   , mResetCompositorMonitor("ResetCompositorMonitor")
   , mRootLayerTreeID(0)
   , mOverrideComposeReadiness(false)
   , mForceCompositionTask(nullptr)
   , mCompositorThreadHolder(CompositorThreadHolder::GetSingleton())
   , mCompositorScheduler(nullptr)
+  , mAnimationStorage(nullptr)
   , mPaintTime(TimeDuration::Forever())
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   , mLastPluginUpdateLayerTreeId(0)
   , mDeferPluginWindows(false)
   , mPluginWindowsHidden(false)
 #endif
 {
   // Always run destructor on the main thread
@@ -633,16 +635,17 @@ CompositorBridgeParent::RecvNotifyApprox
 void
 CompositorBridgeParent::ActorDestroy(ActorDestroyReason why)
 {
   StopAndClearResources();
 
   RemoveCompositor(mCompositorID);
 
   mCompositionManager = nullptr;
+  mAnimationStorage = nullptr;
 
   if (mApzcTreeManager) {
     mApzcTreeManager->ClearTree();
     mApzcTreeManager = nullptr;
   }
 
   { // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
@@ -1293,16 +1296,27 @@ CompositorBridgeParent::ApplyAsyncProper
       CancelCurrentCompositeTask();
       // Pretend we composited in case someone is waiting for this event.
       TimeStamp now = TimeStamp::Now();
       DidComposite(now, now);
     }
   }
 }
 
+CompositorAnimationStorage*
+CompositorBridgeParent::GetAnimationStorage(const uint64_t& aId)
+{
+  MOZ_ASSERT(aId == 0);
+
+  if (!mAnimationStorage) {
+    mAnimationStorage = new CompositorAnimationStorage();
+  }
+  return mAnimationStorage;
+}
+
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvGetFrameUniformity(FrameUniformityData* aOutData)
 {
   mCompositionManager->GetFrameUniformity(aOutData);
   return IPC_OK();
 }
 
 void
@@ -1441,17 +1455,17 @@ CompositorBridgeParent::AllocPLayerTrans
   if (!mLayerManager) {
     NS_WARNING("Failed to initialise Compositor");
     *aSuccess = false;
     LayerTransactionParent* p = new LayerTransactionParent(nullptr, this, 0);
     p->AddIPDLReference();
     return p;
   }
 
-  mCompositionManager = new AsyncCompositionManager(mLayerManager);
+  mCompositionManager = new AsyncCompositionManager(this, mLayerManager);
   *aSuccess = true;
 
   *aTextureFactoryIdentifier = mLayerManager->GetTextureFactoryIdentifier();
   LayerTransactionParent* p = new LayerTransactionParent(mLayerManager, this, 0);
   p->AddIPDLReference();
   return p;
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -59,16 +59,17 @@ class Shmem;
 } // namespace ipc
 
 namespace layers {
 
 class APZCTreeManager;
 class APZCTreeManagerParent;
 class AsyncCompositionManager;
 class Compositor;
+class CompositorAnimationStorage;
 class CompositorBridgeParent;
 class CompositorVsyncScheduler;
 class HostLayerManager;
 class LayerTransactionParent;
 class PAPZParent;
 class CrossProcessCompositorBridgeParent;
 class CompositorThreadHolder;
 class InProcessCompositorSession;
@@ -100,16 +101,17 @@ public:
 
   virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) { }
 
   virtual void ForceComposite(LayerTransactionParent* aLayerTree) { }
   virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
                                  const TimeStamp& aTime) { return true; }
   virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) { }
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree) = 0;
+  virtual CompositorAnimationStorage* GetAnimationStorage(const uint64_t& aId) { return nullptr; }
   virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) = 0;
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) { }
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) = 0;
   virtual void UpdatePaintTime(LayerTransactionParent* aLayerTree, const TimeDuration& aPaintTime) {}
 
@@ -222,16 +224,17 @@ public:
                                    const TransactionInfo& aInfo,
                                    bool aHitTestUpdate) override;
   virtual void ForceComposite(LayerTransactionParent* aLayerTree) override;
   virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
                                  const TimeStamp& aTime) override;
   virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) override;
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree)
                override;
+  virtual CompositorAnimationStorage* GetAnimationStorage(const uint64_t& aId) override;
   virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) override;
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) override;
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) override;
   virtual AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aLayerTree) override { return mCompositionManager; }
 
@@ -592,16 +595,17 @@ protected:
   RefPtr<APZCTreeManager> mApzcTreeManager;
 
   RefPtr<CompositorThreadHolder> mCompositorThreadHolder;
   RefPtr<CompositorVsyncScheduler> mCompositorScheduler;
   // This makes sure the compositorParent is not destroyed before receiving
   // confirmation that the channel is closed.
   // mSelfRef is cleared in DeferredDestroy which is scheduled by ActorDestroy.
   RefPtr<CompositorBridgeParent> mSelfRef;
+  RefPtr<CompositorAnimationStorage> mAnimationStorage;
 
   TimeDuration mPaintTime;
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
   // cached plugin data used to reduce the number of updates we request.
   uint64_t mLastPluginUpdateLayerTreeId;
   nsIntPoint mPluginsLayerOffset;
   nsIntRegion mPluginsLayerVisibleRegion;
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/layers/CrossProcessCompositorBridgeParent.h"
 #include <stdint.h>                     // for uint64_t
 #include "LayerTransactionParent.h"     // for LayerTransactionParent
 #include "base/message_loop.h"          // for MessageLoop
 #include "base/task.h"                  // for CancelableTask, etc
 #include "base/thread.h"                // for Thread
 #include "mozilla/ipc/Transport.h"      // for Transport
+#include "mozilla/layers/AnimationHelper.h" // for CompositorAnimationStorage
 #include "mozilla/layers/APZCTreeManager.h"  // for APZCTreeManager
 #include "mozilla/layers/APZCTreeManagerParent.h"  // for APZCTreeManagerParent
 #include "mozilla/layers/APZThreadUtils.h"  // for APZCTreeManager
 #include "mozilla/layers/AsyncCompositionManager.h"
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
@@ -402,16 +403,32 @@ CrossProcessCompositorBridgeParent::Appl
   if (!state) {
     return;
   }
 
   MOZ_ASSERT(state->mParent);
   state->mParent->ApplyAsyncProperties(aLayerTree);
 }
 
+CompositorAnimationStorage*
+CrossProcessCompositorBridgeParent::GetAnimationStorage(
+    const uint64_t& aId)
+{
+  MOZ_ASSERT(aId != 0);
+  const CompositorBridgeParent::LayerTreeState* state =
+    CompositorBridgeParent::GetIndirectShadowTree(aId);
+  if (!state) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(state->mParent);
+  // GetAnimationStorage in CompositorBridgeParent expects id as 0
+  return state->mParent->GetAnimationStorage(0);
+}
+
 void
 CrossProcessCompositorBridgeParent::FlushApzRepaints(const LayerTransactionParent* aLayerTree)
 {
   uint64_t id = aLayerTree->GetId();
   MOZ_ASSERT(id != 0);
   const CompositorBridgeParent::LayerTreeState* state =
     CompositorBridgeParent::GetIndirectShadowTree(id);
   if (!state) {
--- a/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
+++ b/gfx/layers/ipc/CrossProcessCompositorBridgeParent.h
@@ -9,16 +9,17 @@
 
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 
 namespace mozilla {
 namespace layers {
 
 class CompositorOptions;
+class CompositorAnimationStorage;
 
 /**
  * This class handles layer updates pushed directly from child processes to
  * the compositor thread. It's associated with a CompositorBridgeParent on the
  * compositor thread. While it uses the PCompositorBridge protocol to manage
  * these updates, it doesn't actually drive compositing itself. For that it
  * hands off work to the CompositorBridgeParent it's associated with.
  */
@@ -100,16 +101,18 @@ public:
                                    bool aHitTestUpdate) override;
   virtual void ForceComposite(LayerTransactionParent* aLayerTree) override;
   virtual void NotifyClearCachedResources(LayerTransactionParent* aLayerTree) override;
   virtual bool SetTestSampleTime(LayerTransactionParent* aLayerTree,
                                  const TimeStamp& aTime) override;
   virtual void LeaveTestMode(LayerTransactionParent* aLayerTree) override;
   virtual void ApplyAsyncProperties(LayerTransactionParent* aLayerTree)
                override;
+  virtual CompositorAnimationStorage*
+    GetAnimationStorage(const uint64_t& aId) override;
   virtual void FlushApzRepaints(const LayerTransactionParent* aLayerTree) override;
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) override;
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) override;
 
   virtual AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aParent) override;
--- a/gfx/layers/ipc/LayerAnimationUtils.cpp
+++ b/gfx/layers/ipc/LayerAnimationUtils.cpp
@@ -28,18 +28,25 @@ AnimationUtils::TimingFunctionToComputed
       StepFunction sf = aTimingFunction.get_StepFunction();
       nsTimingFunction::Type type = sf.type() == 1 ?
         nsTimingFunction::Type::StepStart :
         nsTimingFunction::Type::StepEnd;
       ComputedTimingFunction result;
       result.Init(nsTimingFunction(type, sf.steps()));
       return Some(result);
     }
+    case TimingFunction::TFramesFunction: {
+      FramesFunction ff = aTimingFunction.get_FramesFunction();
+      ComputedTimingFunction result;
+      result.Init(nsTimingFunction(nsTimingFunction::Type::Frames,
+                                   ff.frames()));
+      return Some(result);
+    }
     default:
       MOZ_ASSERT_UNREACHABLE(
-        "Function must be null, bezier or step");
+        "Function must be null, bezier, step or frames");
       break;
   }
   return Nothing();
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -10,16 +10,17 @@
 #include "apz/src/AsyncPanZoomController.h"
 #include "CompositableHost.h"           // for CompositableParent, Get, etc
 #include "ImageLayers.h"                // for ImageLayer
 #include "Layers.h"                     // for Layer, ContainerLayer, etc
 #include "CompositableTransactionParent.h"  // for EditReplyVector
 #include "CompositorBridgeParent.h"
 #include "gfxPrefs.h"
 #include "mozilla/gfx/BasePoint3D.h"    // for BasePoint3D
+#include "mozilla/layers/AnimationHelper.h" // for GetAnimatedPropValue
 #include "mozilla/layers/CanvasLayerComposite.h"
 #include "mozilla/layers/ColorLayerComposite.h"
 #include "mozilla/layers/Compositor.h"  // for Compositor
 #include "mozilla/layers/ContainerLayerComposite.h"
 #include "mozilla/layers/ImageBridgeParent.h" // for ImageBridgeParent
 #include "mozilla/layers/ImageLayerComposite.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
@@ -523,17 +524,17 @@ LayerTransactionParent::SetLayerAttribut
   layer->SetVisibleRegion(common.visibleRegion());
   layer->SetEventRegions(common.eventRegions());
   layer->SetClipRect(common.useClipRect() ? Some(common.clipRect()) : Nothing());
   if (LayerHandle maskLayer = common.maskLayer()) {
     layer->SetMaskLayer(AsLayer(maskLayer));
   } else {
     layer->SetMaskLayer(nullptr);
   }
-  layer->SetAnimations(common.animations());
+  layer->SetCompositorAnimations(common.compositorAnimations());
   layer->SetScrollMetadata(common.scrollMetadata());
   layer->SetDisplayListLog(common.displayListLog().get());
 
   // The updated invalid region is added to the existing one, since we can
   // update multiple times before the next composite.
   layer->AddInvalidRegion(common.invalidRegion());
 
   nsTArray<RefPtr<Layer>> maskLayers;
@@ -690,104 +691,77 @@ LayerTransactionParent::RecvSetTestSampl
 mozilla::ipc::IPCResult
 LayerTransactionParent::RecvLeaveTestMode()
 {
   mCompositorBridge->LeaveTestMode(this);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-LayerTransactionParent::RecvGetAnimationOpacity(const LayerHandle& aParent,
+LayerTransactionParent::RecvGetAnimationOpacity(const uint64_t& aCompositorAnimationsId,
                                                 float* aOpacity,
                                                 bool* aHasAnimationOpacity)
 {
   *aHasAnimationOpacity = false;
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  RefPtr<Layer> layer = AsLayer(aParent);
-  if (!layer) {
+  mCompositorBridge->ApplyAsyncProperties(this);
+
+  CompositorAnimationStorage* storage =
+    mCompositorBridge->GetAnimationStorage(GetId());
+
+  if (!storage) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  mCompositorBridge->ApplyAsyncProperties(this);
+  auto value = storage->GetAnimatedValue(aCompositorAnimationsId);
 
-  if (!layer->AsHostLayer()->GetShadowOpacitySetByAnimation()) {
+  if (!value || value->mType != AnimatedValue::OPACITY) {
     return IPC_OK();
   }
 
-  *aOpacity = layer->GetLocalOpacity();
+  *aOpacity = value->mOpacity;
   *aHasAnimationOpacity = true;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-LayerTransactionParent::RecvGetAnimationTransform(const LayerHandle& aLayerHandle,
+LayerTransactionParent::RecvGetAnimationTransform(const uint64_t& aCompositorAnimationsId,
                                                   MaybeTransform* aTransform)
 {
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return IPC_FAIL_NO_REASON(this);
   }
 
-  Layer* layer = AsLayer(aLayerHandle);
-  if (!layer) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-
   // Make sure we apply the latest animation style or else we can end up with
   // a race between when we temporarily clear the animation transform (in
   // CompositorBridgeParent::SetShadowProperties) and when animation recalculates
   // the value.
   mCompositorBridge->ApplyAsyncProperties(this);
 
-  // This method is specific to transforms applied by animation.
-  // This is because this method uses the information stored with an animation
-  // such as the origin of the reference frame corresponding to the layer, to
-  // recover the untranslated transform from the shadow transform. For
-  // transforms that are not set by animation we don't have this information
-  // available.
-  if (!layer->AsHostLayer()->GetShadowTransformSetByAnimation()) {
+  CompositorAnimationStorage* storage =
+    mCompositorBridge->GetAnimationStorage(GetId());
+
+  if (!storage) {
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  auto value = storage->GetAnimatedValue(aCompositorAnimationsId);
+
+  if (!value || value->mType != AnimatedValue::TRANSFORM) {
     *aTransform = mozilla::void_t();
     return IPC_OK();
   }
 
-  // The following code recovers the untranslated transform
-  // from the shadow transform by undoing the translations in
-  // AsyncCompositionManager::SampleValue.
-
-  Matrix4x4 transform = layer->AsHostLayer()->GetShadowBaseTransform();
-  if (ContainerLayer* c = layer->AsContainerLayer()) {
-    // Undo the scale transform applied by AsyncCompositionManager::SampleValue
-    transform.PostScale(1.0f/c->GetInheritedXScale(),
-                        1.0f/c->GetInheritedYScale(),
-                        1.0f);
-  }
-  float scale = 1;
-  Point3D scaledOrigin;
-  Point3D transformOrigin;
-  for (uint32_t i=0; i < layer->GetAnimations().Length(); i++) {
-    if (layer->GetAnimations()[i].data().type() == AnimationData::TTransformData) {
-      const TransformData& data = layer->GetAnimations()[i].data().get_TransformData();
-      scale = data.appUnitsPerDevPixel();
-      scaledOrigin =
-        Point3D(NS_round(NSAppUnitsToFloatPixels(data.origin().x, scale)),
-                NS_round(NSAppUnitsToFloatPixels(data.origin().y, scale)),
-                0.0f);
-      transformOrigin = data.transformOrigin();
-      break;
-    }
-  }
-
-  // If our parent isn't a perspective layer, then the offset into reference
-  // frame coordinates will have been applied to us. Add an inverse translation
-  // to cancel it out.
-  if (!layer->GetParent() || !layer->GetParent()->GetTransformIsPerspective()) {
-    transform.PostTranslate(-scaledOrigin.x, -scaledOrigin.y, -scaledOrigin.z);
-  }
+  Matrix4x4 transform = value->mTransform.mFrameTransform;
+  const TransformData& data = value->mTransform.mData;
+  float scale = data.appUnitsPerDevPixel();
+  Point3D transformOrigin = data.transformOrigin();
 
   // Undo the rebasing applied by
   // nsDisplayTransform::GetResultingTransformMatrixInternal
   transform.ChangeBasis(-transformOrigin);
 
   // Convert to CSS pixels (this undoes the operations performed by
   // nsStyleTransformMatrix::ProcessTranslatePart which is called from
   // nsDisplayTransform::GetResultingTransformMatrix)
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -120,20 +120,20 @@ protected:
                                                       const TextureInfo& aInfo) override;
   virtual mozilla::ipc::IPCResult RecvReleaseLayer(const LayerHandle& aHandle) override;
   virtual mozilla::ipc::IPCResult RecvReleaseCompositable(const CompositableHandle& aHandle) override;
 
   virtual mozilla::ipc::IPCResult RecvClearCachedResources() override;
   virtual mozilla::ipc::IPCResult RecvForceComposite() override;
   virtual mozilla::ipc::IPCResult RecvSetTestSampleTime(const TimeStamp& aTime) override;
   virtual mozilla::ipc::IPCResult RecvLeaveTestMode() override;
-  virtual mozilla::ipc::IPCResult RecvGetAnimationOpacity(const LayerHandle& aLayerHandle,
+  virtual mozilla::ipc::IPCResult RecvGetAnimationOpacity(const uint64_t& aCompositorAnimationsId,
                                                           float* aOpacity,
                                                           bool* aHasAnimationOpacity) override;
-  virtual mozilla::ipc::IPCResult RecvGetAnimationTransform(const LayerHandle& aLayerHandle,
+  virtual mozilla::ipc::IPCResult RecvGetAnimationTransform(const uint64_t& aCompositorAnimationsId,
                                                             MaybeTransform* aTransform)
                                          override;
   virtual mozilla::ipc::IPCResult RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aId,
                                                            const float& aX, const float& aY) override;
   virtual mozilla::ipc::IPCResult RecvSetAsyncZoom(const FrameMetrics::ViewID& aId,
                                                    const float& aValue) override;
   virtual mozilla::ipc::IPCResult RecvFlushApzRepaints() override;
   virtual mozilla::ipc::IPCResult RecvGetAPZTestData(APZTestData* aOutData) override;
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -97,20 +97,25 @@ struct CubicBezierFunction {
 };
 
 struct StepFunction {
   int steps;
   // 1 = nsTimingFunction::StepStart, 2 = nsTimingFunction::StepEnd
   int type;
 };
 
+struct FramesFunction {
+  int frames;
+};
+
 union TimingFunction {
   null_t;
   CubicBezierFunction;
   StepFunction;
+  FramesFunction;
 };
 
 // Send the angle with units rather than sending all angles in radians
 // to avoid having floating point error introduced by unit switching.
 struct CSSAngle {
   float value;
   int unit; // an nsCSSUnit that is valid for angles
 };
@@ -228,26 +233,33 @@ struct Animation {
   bool isNotPlaying;
   // The base style that animations should composite with. This is only set for
   // animations with a composite mode of additive or accumulate, and only for
   // the first animation in the set (i.e. the animation that is lowest in the
   // stack). In all other cases the value is null_t.
   Animatable baseStyle;
 };
 
+struct CompositorAnimations {
+  Animation[] animations;
+  // This id is used to map the layer animations between content
+  // and compositor side
+  uint64_t id;
+};
+
 // Change a layer's attributes
 struct CommonLayerAttributes {
   LayerIntRegion visibleRegion;
   EventRegions eventRegions;
   bool useClipRect;
   ParentLayerIntRect clipRect;
   LayerHandle maskLayer;
   LayerHandle[] ancestorMaskLayers;
   // Animated colors will only honored for ColorLayers.
-  Animation[] animations;
+  CompositorAnimations compositorAnimations;
   nsIntRegion invalidRegion;
   ScrollMetadata[] scrollMetadata;
   nsCString displayListLog;
 };
 
 struct PaintedLayerAttributes {
   nsIntRegion validRegion;
 };
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -76,25 +76,25 @@ parent:
   // animations. sampleTime must not be null.
   sync SetTestSampleTime(TimeStamp sampleTime);
   // Leave test mode and resume normal compositing
   sync LeaveTestMode();
 
   // Returns the value of the opacity applied to the layer by animation.
   // |hasAnimationOpacity| is true if the layer has an opacity value
   // specified by animation. If it's false, |opacity| value is indefinite.
-  sync GetAnimationOpacity(LayerHandle layer) returns (float opacity,
+  sync GetAnimationOpacity(uint64_t aCompositorAnimationsId) returns (float opacity,
                                                   bool hasAnimationOpacity);
 
   // Returns the value of the transform applied to the layer by animation after
   // factoring out translation components introduced to account for the offset
   // of the corresponding frame and transform origin and after converting to CSS
   // pixels. If the layer is not transformed by animation, the return value will
   // be void_t.
-  sync GetAnimationTransform(LayerHandle layer) returns (MaybeTransform transform);
+  sync GetAnimationTransform(uint64_t aCompositorAnimationId) returns (MaybeTransform transform);
 
   // The next time the layer tree is composited, add this async scroll offset in
   // CSS pixels for the given ViewID.
   // Useful for testing rendering of async scrolling.
   sync SetAsyncScrollOffset(ViewID id, float x, float y);
 
   // The next time the layer tree is composited, include this async zoom in
   // for the given ViewID.
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -663,17 +663,18 @@ ShadowLayerForwarder::EndTransaction(con
     common.useClipRect() = !!mutant->GetClipRect();
     common.clipRect() = (common.useClipRect() ?
                          *mutant->GetClipRect() : ParentLayerIntRect());
     if (Layer* maskLayer = mutant->GetMaskLayer()) {
       common.maskLayer() = Shadow(maskLayer->AsShadowableLayer());
     } else {
       common.maskLayer() = LayerHandle();
     }
-    common.animations() = mutant->GetAnimations();
+    common.compositorAnimations().id() = mutant->GetCompositorAnimationsId();
+    common.compositorAnimations().animations() = mutant->GetAnimations();
     common.invalidRegion() = mutant->GetInvalidRegion().GetRegion();
     common.scrollMetadata() = mutant->GetAllScrollMetadata();
     for (size_t i = 0; i < mutant->GetAncestorMaskLayerCount(); i++) {
       auto layer = Shadow(mutant->GetAncestorMaskLayerAt(i)->AsShadowableLayer());
       common.ancestorMaskLayers().AppendElement(layer);
     }
     nsCString log;
     mutant->GetDisplayListLog(log);
--- a/js/src/old-configure.in
+++ b/js/src/old-configure.in
@@ -695,20 +695,16 @@ case "$target" in
           if test -z `echo $CFLAGS | grep -i [-/]arch:` ; then
             CFLAGS="$CFLAGS -arch:SSE2"
           fi
           if test -z `echo $CXXFLAGS | grep -i [-/]arch:` ; then
             CXXFLAGS="$CXXFLAGS -arch:SSE2"
           fi
           changequote([,])
         fi
-        dnl VS2013+ requires -FS when parallel building by make -jN.
-        dnl If nothing, compiler sometimes causes C1041 error.
-        CFLAGS="$CFLAGS -FS"
-        CXXFLAGS="$CXXFLAGS -FS"
         dnl VS2013+ supports -Gw for better linker optimizations.
         dnl http://blogs.msdn.com/b/vcblog/archive/2013/09/11/introducing-gw-compiler-switch.aspx
         dnl Disabled on ASan because it causes false-positive ODR violations.
         if test -z "$MOZ_ASAN"; then
             CFLAGS="$CFLAGS -Gw"
             CXXFLAGS="$CXXFLAGS -Gw"
         fi
         # khuey says we can safely ignore MSVC warning C4251
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -63,33 +63,33 @@ std::ostream& operator<<(std::ostream& a
   switch (aHint) {
     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
     AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
   }
   return aStream;
 }
 #undef AC_PROCESS_ENUM_TO_STREAM
 
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sSelectionBarEnabled = false;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sCaretShownWhenLongTappingOnEmptyContent = false;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sCaretsAlwaysTilt = false;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sCaretsAlwaysShowWhenScrolling = true;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sCaretsScriptUpdates = false;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sCaretsAllowDraggingAcrossOtherCaret = true;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sHapticFeedback = false;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sExtendSelectionForPhoneNumber = false;
-/*static*/ bool
+/* static */ bool
 AccessibleCaretManager::sHideCaretsForMouseInput = true;
 
 AccessibleCaretManager::AccessibleCaretManager(nsIPresShell* aPresShell)
   : mPresShell(aPresShell)
 {
   if (!mPresShell) {
     return;
   }
@@ -806,16 +806,24 @@ AccessibleCaretManager::GetFrameSelectio
 
     return fs.forget();
   } else {
     // For non-editable content
     return mPresShell->FrameSelection();
   }
 }
 
+nsAutoString
+AccessibleCaretManager::StringifiedSelection() const
+{
+  nsAutoString str;
+  GetSelection()->Stringify(str);
+  return str;
+}
+
 Element*
 AccessibleCaretManager::GetEditingHostForFrame(nsIFrame* aFrame) const
 {
   if (!aFrame) {
     return nullptr;
   }
 
   auto content = aFrame->GetContent();
@@ -914,37 +922,48 @@ AccessibleCaretManager::SetSelectionDrag
   #ifdef MOZ_WIDGET_ANDROID
     nsIDocument* doc = mPresShell->GetDocument();
     MOZ_ASSERT(doc);
     nsIWidget* widget = nsContentUtils::WidgetForDocument(doc);
     static_cast<nsWindow*>(widget)->SetSelectionDragState(aState);
   #endif
 }
 
+bool
+AccessibleCaretManager::IsPhoneNumber(nsAString& aCandidate) const
+{
+  RefPtr<nsIDocument> doc = mPresShell->GetDocument();
+  nsAutoString phoneNumberRegex(
+    NS_LITERAL_STRING("(^\\+)?[0-9 ,\\-.()*#pw]{1,30}$"));
+  return nsContentUtils::IsPatternMatching(aCandidate, phoneNumberRegex, doc);
+}
+
 void
 AccessibleCaretManager::SelectMoreIfPhoneNumber() const
 {
-  SetSelectionDirection(eDirNext);
-  ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
+  nsAutoString selectedText = StringifiedSelection();
+
+  if (IsPhoneNumber(selectedText)) {
+    SetSelectionDirection(eDirNext);
+    ExtendPhoneNumberSelection(NS_LITERAL_STRING("forward"));
 
-  SetSelectionDirection(eDirPrevious);
-  ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
+    SetSelectionDirection(eDirPrevious);
+    ExtendPhoneNumberSelection(NS_LITERAL_STRING("backward"));
 
-  SetSelectionDirection(eDirNext);
+    SetSelectionDirection(eDirNext);
+  }
 }
 
 void
 AccessibleCaretManager::ExtendPhoneNumberSelection(const nsAString& aDirection) const
 {
   if (!mPresShell) {
     return;
   }
 
-  RefPtr<nsIDocument> doc = mPresShell->GetDocument();
-
   // Extend the phone number selection until we find a boundary.
   RefPtr<Selection> selection = GetSelection();
 
   while (selection) {
     const nsRange* anchorFocusRange = selection->GetAnchorFocusRange();
     if (!anchorFocusRange) {
       return;
     }
@@ -952,18 +971,17 @@ AccessibleCaretManager::ExtendPhoneNumbe
     // Backup the anchor focus range since both anchor node and focus node might
     // be changed after calling Selection::Modify().
     RefPtr<nsRange> oldAnchorFocusRange = anchorFocusRange->CloneRange();
 
     // Save current focus node, focus offset and the selected text so that
     // we can compare them with the modified ones later.
     nsINode* oldFocusNode = selection->GetFocusNode();
     uint32_t oldFocusOffset = selection->FocusOffset();
-    nsAutoString oldSelectedText;
-    selection->Stringify(oldSelectedText);
+    nsAutoString oldSelectedText = StringifiedSelection();
 
     // Extend the selection by one char.
     selection->Modify(NS_LITERAL_STRING("extend"),
                       aDirection,
                       NS_LITERAL_STRING("character"));
     if (IsTerminated()) {
       return;
     }
@@ -974,22 +992,19 @@ AccessibleCaretManager::ExtendPhoneNumbe
       return;
     }
 
     // If the changed selection isn't a valid phone number, we're done.
     // Also, if the selection was extended to a new block node, the string
     // returned by stringify() won't have a new line at the beginning or the
     // end of the string. Therefore, if either focus node or offset is
     // changed, but selected text is not changed, we're done, too.
-    nsAutoString selectedText;
-    selection->Stringify(selectedText);
-    nsAutoString phoneRegex(NS_LITERAL_STRING("(^\\+)?[0-9 ,\\-.()*#pw]{1,30}$"));
+    nsAutoString selectedText = StringifiedSelection();
 
-    if (!nsContentUtils::IsPatternMatching(selectedText, phoneRegex, doc) ||
-        oldSelectedText == selectedText) {
+    if (!IsPhoneNumber(selectedText) || oldSelectedText == selectedText) {
       // Backout the undesired selection extend, restore the old anchor focus
       // range before exit.
       selection->SetAnchorFocusToRange(oldAnchorFocusRange);
       return;
     }
   }
 }
 
--- a/layout/base/AccessibleCaretManager.h
+++ b/layout/base/AccessibleCaretManager.h
@@ -156,18 +156,23 @@ protected:
 
   // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
   // then re-focus the window.
   void ChangeFocusToOrClearOldFocus(nsIFrame* aFrame) const;
 
   nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
   void SetSelectionDragState(bool aState) const;
 
-  // Called to extend a selection if possible that it's a phone number.
+  // Return true if the candidate string is a phone number.
+  bool IsPhoneNumber(nsAString& aCandidate) const;
+
+  // Extend the current selection forwards and backwards if it's already a
+  // phone number.
   void SelectMoreIfPhoneNumber() const;
+
   // Extend the current phone number selection in the requested direction.
   void ExtendPhoneNumberSelection(const nsAString& aDirection) const;
 
   void SetSelectionDirection(nsDirection aDir) const;
 
   // If aDirection is eDirNext, get the frame for the range start in the first
   // range from the current selection, and return the offset into that frame as
   // well as the range start node and the node offset. Otherwise, get the frame
@@ -182,16 +187,17 @@ protected:
 
   // Caller is responsible to use IsTerminated() to check whether PresShell is
   // still valid.
   void FlushLayout() const;
 
   dom::Element* GetEditingHostForFrame(nsIFrame* aFrame) const;
   dom::Selection* GetSelection() const;
   already_AddRefed<nsFrameSelection> GetFrameSelection() const;
+  nsAutoString StringifiedSelection() const;
 
   // Get the union of all the child frame scrollable overflow rects for aFrame,
   // which is used as a helper function to restrict the area where the caret can
   // be dragged. Returns the rect relative to aFrame.
   nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame) const;
 
   // Restrict the active caret's dragging position based on
   // sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -861,23 +861,16 @@ GeckoRestyleManager::ReparentStyleContex
   nsIFrame* providerChild = nullptr;
   if (isChild) {
     ReparentStyleContext(providerFrame);
     // Get the style context again after ReparentStyleContext() which might have
     // changed it.
     newParentContext = providerFrame->StyleContext();
     providerChild = providerFrame;
   }
-  NS_ASSERTION(newParentContext, "Reparenting something that has no usable"
-               " parent? Shouldn't happen!");
-  // XXX need to do something here to produce the correct style context for
-  // an IB split whose first inline part is inside a first-line frame.
-  // Currently the first IB anonymous block's style context takes the first
-  // part's style context as parent, which is wrong since first-line style
-  // should not apply to the anonymous block.
 
 #ifdef DEBUG
   {
     // Check that our assumption that continuations of the same
     // pseudo-type and with the same style context parent have the
     // same style context is valid before the reresolution.  (We need
     // to check the pseudo-type and style context parent because of
     // :first-letter and :first-line, where we create styled and
@@ -893,16 +886,38 @@ GeckoRestyleManager::ReparentStyleContex
                      nextContinuationContext->GetPseudo() ||
                    oldContext->GetParent() !=
                      nextContinuationContext->GetParent(),
                    "continuations should have the same style context");
     }
   }
 #endif
 
+  if (!newParentContext && !oldContext->GetParent()) {
+    // No need to do anything here.
+#ifdef DEBUG
+    // Make sure we have no children, so we really know there is nothing to do.
+    nsIFrame::ChildListIterator lists(aFrame);
+    for (; !lists.IsDone(); lists.Next()) {
+      MOZ_ASSERT(lists.CurrentList().IsEmpty(),
+                 "Failing to reparent style context for child of "
+                 "non-inheriting anon box");
+    }
+#endif // DEBUG
+    return NS_OK;
+  }
+
+  NS_ASSERTION(newParentContext, "Reparenting something that has no usable"
+               " parent? Shouldn't happen!");
+  // XXX need to do something here to produce the correct style context for
+  // an IB split whose first inline part is inside a first-line frame.
+  // Currently the first IB anonymous block's style context takes the first
+  // part's style context as parent, which is wrong since first-line style
+  // should not apply to the anonymous block.
+
   nsIFrame* prevContinuation =
     GetPrevContinuationWithPossiblySameStyle(aFrame);
   nsStyleContext* prevContinuationContext;
   bool copyFromContinuation =
     prevContinuation &&
     (prevContinuationContext = prevContinuation->StyleContext())
       ->GetPseudo() == oldContext->GetPseudo() &&
      prevContinuationContext->GetParent() == newParentContext;
@@ -2090,19 +2105,21 @@ ElementRestyler::ComputeRestyleResultFro
   nsIFrame* parent = mFrame->GetParent();
 
   if (parent) {
     // Also if the parent has a pseudo, as this frame's style context will
     // be inheriting from a grandparent frame's style context (or a further
     // ancestor).
     nsIAtom* parentPseudoTag = parent->StyleContext()->GetPseudo();
     if (parentPseudoTag &&
-        parentPseudoTag != nsCSSAnonBoxes::mozOtherNonElement) {
+        parentPseudoTag != nsCSSAnonBoxes::firstLetterContinuation) {
       MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::mozText,
                  "Style of text node should not be parent of anything");
+      MOZ_ASSERT(parentPseudoTag != nsCSSAnonBoxes::oofPlaceholder,
+                 "Style of placeholder should not be parent of anything");
       LOG_RESTYLE_CONTINUE("the old style context's parent is for a pseudo");
       aRestyleResult = RestyleResult::eContinue;
       // Parent style context pseudo-ness doesn't affect whether we can
       // return RestyleResult::eStopWithStyleChange.
       //
       // If we had later conditions to check in this function, we would
       // continue to check them, in case we set aCanStopWithStyleChange to
       // false.
@@ -2461,18 +2478,23 @@ ElementRestyler::RestyleSelf(nsIFrame* a
     // Just use the style context from the frame's previous
     // continuation.
     LOG_RESTYLE("using previous continuation's context");
     newContext = prevContinuationContext;
   } else if (pseudoTag == nsCSSAnonBoxes::mozText) {
     MOZ_ASSERT(aSelf->GetType() == nsGkAtoms::textFrame);
     newContext =
       styleSet->ResolveStyleForText(aSelf->GetContent(), parentContext);
-  } else if (nsCSSAnonBoxes::IsNonElement(pseudoTag)) {
-    newContext = styleSet->ResolveStyleForOtherNonElement(parentContext);
+  } else if (pseudoTag == nsCSSAnonBoxes::firstLetterContinuation) {
+    newContext = styleSet->ResolveStyleForFirstLetterContinuation(parentContext);
+  } else if (pseudoTag == nsCSSAnonBoxes::oofPlaceholder) {
+    // We still need to ResolveStyleForPlaceholder() here, because we may be
+    // doing a ruletree reconstruct and hence actually changing our style
+    // context.
+    newContext = styleSet->ResolveStyleForPlaceholder();
   }
   else {
     Element* element = ElementForStyleContext(mParentContent, aSelf, pseudoType);
     if (!MustRestyleSelf(aRestyleHint, element)) {
       if (CanReparentStyleContext(aRestyleHint)) {
         LOG_RESTYLE("reparenting style context");
         newContext =
           styleSet->ReparentStyleContext(oldContext, parentContext, element);
@@ -3467,17 +3489,17 @@ void
 GeckoRestyleManager::ComputeAndProcessStyleChange(
     nsIFrame*              aFrame,
     nsChangeHint           aMinChange,
     RestyleTracker&        aRestyleTracker,
     nsRestyleHint          aRestyleHint,
     const RestyleHintData& aRestyleHintData)
 {
   MOZ_ASSERT(mReframingStyleContexts, "should have rsc");
-  nsStyleChangeList changeList;
+  nsStyleChangeList changeList(StyleBackendType::Gecko);
   nsTArray<ElementRestyler::ContextToClear> contextsToClear;
 
   // swappedStructOwners needs to be kept alive until after
   // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
   // calls; see comment in ElementRestyler::Restyle.
   nsTArray<RefPtr<nsStyleContext>> swappedStructOwners;
   ElementRestyler::ComputeStyleChangeFor(aFrame, &changeList, aMinChange,
                                          aRestyleTracker, aRestyleHint,
@@ -3512,17 +3534,17 @@ GeckoRestyleManager::ComputeAndProcessSt
   nsTArray<nsCSSSelector*> selectorsForDescendants;
   nsTArray<nsIContent*> visibleKidsOfHiddenElement;
   nsTArray<ElementRestyler::ContextToClear> contextsToClear;
 
   // swappedStructOwners needs to be kept alive until after
   // ProcessRestyledFrames and ClearCachedInheritedStyleDataOnDescendants
   // calls; see comment in ElementRestyler::Restyle.
   nsTArray<RefPtr<nsStyleContext>> swappedStructOwners;
-  nsStyleChangeList changeList;
+  nsStyleChangeList changeList(StyleBackendType::Gecko);
   ElementRestyler r(frame->PresContext(), aElement, &changeList, aMinChange,
                     aRestyleTracker, selectorsForDescendants, treeMatchContext,
                     visibleKidsOfHiddenElement, contextsToClear,
                     swappedStructOwners);
   r.RestyleChildrenOfDisplayContentsElement(frame, aNewContext, aMinChange,
                                             aRestyleTracker,
                                             aRestyleHint, aRestyleHintData);
   ProcessRestyledFrames(changeList);
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2938,17 +2938,17 @@ PresShell::RecreateFramesFor(nsIContent*
   NS_ASSERTION(mViewManager, "Should have view manager");
 
   // Have to make sure that the content notifications are flushed before we
   // start messing with the frame model; otherwise we can get content doubling.
   mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
 
   nsAutoScriptBlocker scriptBlocker;
 
-  nsStyleChangeList changeList;
+  nsStyleChangeList changeList(mPresContext->StyleSet()->BackendType());
   changeList.AppendChange(nullptr, aContent, nsChangeHint_ReconstructFrame);
 
   // Mark ourselves as not safe to flush while we're doing frame construction.
   ++mChangeNestCount;
   RestyleManager* restyleManager = mPresContext->RestyleManager();
   restyleManager->ProcessRestyledFrames(changeList);
   restyleManager->FlushOverflowChangedTracker();
   --mChangeNestCount;
@@ -9580,17 +9580,17 @@ PresShell::Observe(nsISupports* aSubject
       mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
 
       if (weakRoot.IsAlive()) {
         WalkFramesThroughPlaceholders(mPresContext, rootFrame,
                                       &ReResolveMenusAndTrees, nullptr);
 
         // Because "chrome:" URL equality is messy, reframe image box
         // frames (hack!).
-        nsStyleChangeList changeList;
+        nsStyleChangeList changeList(mPresContext->StyleSet()->BackendType());
         WalkFramesThroughPlaceholders(mPresContext, rootFrame,
                                       ReframeImageBoxes, &changeList);
         // Mark ourselves as not safe to flush while we're doing frame
         // construction.
         {
           nsAutoScriptBlocker scriptBlocker;
           ++mChangeNestCount;
           RestyleManager* restyleManager = mPresContext->RestyleManager();
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -93,17 +93,17 @@ RestyleTracker::ProcessOneRestyle(Elemen
     }
 #endif
     mRestyleManager->RestyleElement(aElement, primaryFrame, aChangeHint,
                                     *this, aRestyleHint, aRestyleHintData);
   } else if (aChangeHint &&
              (primaryFrame ||
               (aChangeHint & nsChangeHint_ReconstructFrame))) {
     // Don't need to recompute style; just apply the hint
-    nsStyleChangeList changeList;
+    nsStyleChangeList changeList(StyleBackendType::Gecko);
     changeList.AppendChange(primaryFrame, aElement, aChangeHint);
     mRestyleManager->ProcessRestyledFrames(changeList);
   }
 }
 
 void
 RestyleTracker::DoProcessRestyles()
 {
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -382,17 +382,17 @@ ServoRestyleManager::ProcessPendingResty
   // in a loop because certain rare paths in the frame constructor (like
   // uninstalling XBL bindings) can trigger additional style validations.
   mInStyleRefresh = true;
   while (styleSet->StyleDocument()) {
     PresContext()->EffectCompositor()->ClearElementsToRestyle();
 
     // Recreate style contexts, and queue up change hints (which also handle
     // lazy frame construction).
-    nsStyleChangeList currentChanges;
+    nsStyleChangeList currentChanges(StyleBackendType::Servo);
     DocumentStyleRootIterator iter(doc);
     while (Element* root = iter.GetNextStyleRoot()) {
       ProcessPostTraversal(root, nullptr, styleSet, currentChanges);
     }
 
     // Process the change hints.
     //
     // Unfortunately, the frame constructor can generate new change hints while
new file mode 100644
--- /dev/null
+++ b/layout/base/crashtests/1343937.html
@@ -0,0 +1,13 @@
+<html>
+    <head>
+        <style>
+            .c3::before {
+                position: fixed;
+                overflow-x: hidden;
+            }
+        </style>
+    </head>
+    <body>
+        <optgroup class='c3'></optgroup>
+    </body>
+</html>
\ No newline at end of file
--- a/layout/base/crashtests/crashtests.list
+++ b/layout/base/crashtests/crashtests.list
@@ -105,17 +105,17 @@ load 344057-1.xhtml
 load 344064-1.html
 load 344300-1.html
 load 344300-2.html
 load 344340-1.xul
 load 347898-1.html
 load 348126-1.html
 load 348688-1.html
 load 348708-1.xhtml
-asserts(2) asserts-if(stylo,0) load 348729-1.html # bug 548836
+asserts(1) asserts-if(stylo,0) load 348729-1.html # bug 548836
 load 349095-1.xhtml
 load 350128-1.xhtml
 load 350267-1.html
 load 354133-1.html
 load 354766-1.xhtml
 load 354771-1.xul
 load 355989-1.xhtml
 load 355993-1.xhtml
@@ -483,9 +483,10 @@ load 1288946-1.html
 load 1288946-2a.html
 load 1288946-2b.html
 load 1297835.html
 load 1299736-1.html
 load 1308793.svg
 load 1308848-1.html
 load 1308848-2.html
 load 1338772-1.html
+load 1343937.html
 asserts(0-1) load 1343606.html # bug 1343948
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1243,18 +1243,17 @@ nsFrameConstructorState::ConstructBackdr
   nsFrameState placeholderType;
   nsAbsoluteItems* frameItems = GetOutOfFlowFrameItems(backdropFrame,
                                                        true, true, false,
                                                        &placeholderType);
   MOZ_ASSERT(placeholderType == PLACEHOLDER_FOR_TOPLAYER);
 
   nsIFrame* placeholder = nsCSSFrameConstructor::
     CreatePlaceholderFrameFor(mPresShell, aContent, backdropFrame,
-                              frame->StyleContext(), frame, nullptr,
-                              PLACEHOLDER_FOR_TOPLAYER);
+                              frame, nullptr, PLACEHOLDER_FOR_TOPLAYER);
   nsFrameList temp(placeholder, placeholder);
   frame->SetInitialChildList(nsIFrame::kBackdropList, temp);
 
   frameItems->AddChild(backdropFrame);
 }
 
 void
 nsFrameConstructorState::AddChild(nsIFrame* aNewFrame,
@@ -1286,22 +1285,20 @@ nsFrameConstructorState::AddChild(nsIFra
   } else {
     frameItems = &aFrameItems;
     placeholderType = nsFrameState(0);
   }
 
   if (placeholderType) {
     NS_ASSERTION(frameItems != &aFrameItems,
                  "Putting frame in-flow _and_ want a placeholder?");
-    nsStyleContext* parentContext = aStyleContext->GetParent();
     nsIFrame* placeholderFrame =
       nsCSSFrameConstructor::CreatePlaceholderFrameFor(mPresShell,
                                                        aContent,
                                                        aNewFrame,
-                                                       parentContext,
                                                        aParentFrame,
                                                        nullptr,
                                                        placeholderType);
 
     placeholderFrame->AddStateBits(mAdditionalStateBits);
     // Add the placeholder frame to the flow
     aFrameItems.AddChild(placeholderFrame);
 
@@ -3029,23 +3026,22 @@ nsCSSFrameConstructor::ConstructPageFram
   return pageFrame;
 }
 
 /* static */
 nsIFrame*
 nsCSSFrameConstructor::CreatePlaceholderFrameFor(nsIPresShell*     aPresShell,
                                                  nsIContent*       aContent,
                                                  nsIFrame*         aFrame,
-                                                 nsStyleContext*   aParentStyle,
                                                  nsContainerFrame* aParentFrame,
                                                  nsIFrame*         aPrevInFlow,
                                                  nsFrameState      aTypeBit)
 {
   RefPtr<nsStyleContext> placeholderStyle = aPresShell->StyleSet()->
-    ResolveStyleForOtherNonElement(aParentStyle);
+    ResolveStyleForPlaceholder();
 
   // The placeholder frame gets a pseudo style context
   nsPlaceholderFrame* placeholderFrame =
     (nsPlaceholderFrame*)NS_NewPlaceholderFrame(aPresShell, placeholderStyle,
                                                 aTypeBit);
 
   placeholderFrame->Init(aContent, aParentFrame, aPrevInFlow);
 
@@ -9124,17 +9120,16 @@ nsCSSFrameConstructor::CreateContinuingF
     newFrame->Init(content, aParentFrame, aFrame);
   } else if (nsGkAtoms::placeholderFrame == frameType) {
     // create a continuing out of flow frame
     nsIFrame* oofFrame = nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
     nsIFrame* oofContFrame =
       CreateContinuingFrame(aPresContext, oofFrame, aParentFrame);
     newFrame =
       CreatePlaceholderFrameFor(shell, content, oofContFrame,
-                                styleContext->GetParent(),
                                 aParentFrame, aFrame,
                                 aFrame->GetStateBits() & PLACEHOLDER_TYPE_MASK);
   } else if (nsGkAtoms::fieldSetFrame == frameType) {
     nsContainerFrame* fieldset = NS_NewFieldSetFrame(shell, styleContext);
 
     fieldset->Init(content, aParentFrame, aFrame);
 
     // Create a continuing area frame
@@ -10853,28 +10848,37 @@ nsCSSFrameConstructor::AddFCItemsForAnon
     // and we need to make sure that the originating element is the <input>,
     // not the <video>, because that's where the |orient| attribute lives.
     //
     // The upshot of all of this is that, to find the style parent (and
     // originating element, if applicable), we walk up our parent chain to the
     // first element that is not itself NAC (distinct from whether it happens
     // to be in a NAC subtree).
     //
+    // The one exception to all of this is scrollbar content, which we parent
+    // directly to the scrollframe. This is because the special-snowflake
+    // construction of scroll frames doesn't result in the placeholder frame
+    // being constructed until later, which means that GetInFlowParent() doesn't
+    // work right in the case of out-of-flow scrollframes.
+    //
     // To implement all this, we need to pass the correct parent style context
     // here because SetPrimaryFrame() may not have been called on the content
     // yet and thus ResolveStyleContext can't find it otherwise.
     //
     // We don't need to worry about display:contents here, because such
     // elements don't get a frame and thus can't generate NAC. But we do need
     // to worry about anonymous boxes, which CorrectStyleParentFrame handles
     // for us.
     nsIFrame* inheritFrame = aFrame;
-    while (inheritFrame->GetContent()->IsNativeAnonymous()) {
-      inheritFrame = inheritFrame->GetParent();
-    }
+    if (!content->IsNativeScrollbarContent()) {
+      while (inheritFrame->GetContent()->IsNativeAnonymous()) {
+        inheritFrame = inheritFrame->GetInFlowParent();
+      }
+    }
+
     if (inheritFrame->GetType() == nsGkAtoms::canvasFrame) {
       // CorrectStyleParentFrame returns nullptr if the prospective parent is
       // the canvas frame, so avoid calling it in that situation.
     } else {
       inheritFrame = nsFrame::CorrectStyleParentFrame(inheritFrame, pseudo);
     }
     Element* originating = pseudo ? inheritFrame->GetContent()->AsElement() : nullptr;
 
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -1308,17 +1308,16 @@ private:
                          nsStyleContext*              aStyleContext);
 
   // END TABLE SECTION
 
 protected:
   static nsIFrame* CreatePlaceholderFrameFor(nsIPresShell*     aPresShell,
                                              nsIContent*       aContent,
                                              nsIFrame*         aFrame,
-                                             nsStyleContext*   aParentStyle,
                                              nsContainerFrame* aParentFrame,
                                              nsIFrame*         aPrevInFlow,
                                              nsFrameState      aTypeBit);
 
   static nsIFrame* CreateBackdropFrameFor(nsIPresShell* aPresShell,
                                           nsIContent* aContent,
                                           nsIFrame* aFrame,
                                           nsContainerFrame* aParentFrame);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -593,20 +593,20 @@ GetMinAndMaxScaleForAnimationProperty(co
         // FIXME: Bug 1311257: We need to get the baseStyle for
         //        RawServoAnimationValue.
         UpdateMinMaxScale(aFrame, { baseStyle, nullptr }, aMinScale, aMaxScale);
       }
 
       for (const AnimationPropertySegment& segment : prop.mSegments) {
         // In case of add or accumulate composite, StyleAnimationValue does
         // not have a valid value.
-        if (segment.mFromComposite == dom::CompositeOperation::Replace) {
+        if (segment.HasReplacableFromValue()) {
           UpdateMinMaxScale(aFrame, segment.mFromValue, aMinScale, aMaxScale);
         }
-        if (segment.mToComposite == dom::CompositeOperation::Replace) {
+        if (segment.HasReplacableToValue()) {
           UpdateMinMaxScale(aFrame, segment.mToValue, aMinScale, aMaxScale);
         }
       }
     }
   }
 }
 
 gfxSize
--- a/layout/base/nsStyleChangeList.cpp
+++ b/layout/base/nsStyleChangeList.cpp
@@ -36,19 +36,27 @@ nsStyleChangeList::AppendChange(nsIFrame
               aHint & nsChangeHint_ReconstructFrame),
              "Shouldn't be trying to restyle non-elements directly, "
              "except if it's a display:contents child or a text node "
              "doing lazy frame construction");
   MOZ_ASSERT(!(aHint & nsChangeHint_AllReflowHints) ||
              (aHint & nsChangeHint_NeedReflow),
              "Reflow hint bits set without actually asking for a reflow");
 
-  // Filter out all other changes for same content
-  if (!IsEmpty() && (aHint & nsChangeHint_ReconstructFrame)) {
-    if (aContent) {
+  // If Servo fires reconstruct at a node, it is the only change hint fired at
+  // that node.
+  if (IsServo()) {
+    for (size_t i = 0; i < Length(); ++i) {
+      MOZ_ASSERT_IF(aContent && ((aHint | (*this)[i].mHint) & nsChangeHint_ReconstructFrame),
+                    (*this)[i].mContent != aContent);
+    }
+  } else {
+    // Filter out all other changes for same content for Gecko (Servo asserts against this
+    // case above).
+    if (aContent && (aHint & nsChangeHint_ReconstructFrame)) {
       // NOTE: This is captured by reference to please static analysis.
       // Capturing it by value as a pointer should be fine in this case.
       RemoveElementsBy([&](const nsStyleChangeData& aData) {
         return aData.mContent == aContent;
       });
     }
   }
 
--- a/layout/base/nsStyleChangeList.h
+++ b/layout/base/nsStyleChangeList.h
@@ -7,16 +7,17 @@
  * a list of the recomputation that needs to be done in response to a
  * style change
  */
 
 #ifndef nsStyleChangeList_h___
 #define nsStyleChangeList_h___
 
 #include "mozilla/Attributes.h"
+#include "mozilla/StyleBackendType.h"
 
 #include "nsChangeHint.h"
 #include "nsCOMPtr.h"
 
 class nsIFrame;
 class nsIContent;
 
 struct nsStyleChangeData
@@ -34,14 +35,34 @@ class nsStyleChangeList : private AutoTA
 public:
   using base_type::begin;
   using base_type::end;
   using base_type::IsEmpty;
   using base_type::Clear;
   using base_type::Length;
   using base_type::operator[];
 
-  nsStyleChangeList() { MOZ_COUNT_CTOR(nsStyleChangeList); }
+  explicit nsStyleChangeList(mozilla::StyleBackendType aType) :
+    mType(aType) { MOZ_COUNT_CTOR(nsStyleChangeList); }
   ~nsStyleChangeList() { MOZ_COUNT_DTOR(nsStyleChangeList); }
   void AppendChange(nsIFrame* aFrame, nsIContent* aContent, nsChangeHint aHint);
+
+  // Starting from the end of the list, removes all changes until the list is
+  // empty or an element with |mContent != aContent| is found.
+  void PopChangesForContent(nsIContent* aContent)
+  {
+    while (Length() > 0) {
+      if (LastElement().mContent == aContent) {
+        RemoveElementAt(Length() - 1);
+      } else {
+        break;
+      }
+    }
+  }
+
+  bool IsGecko() const { return mType == mozilla::StyleBackendType::Gecko; }
+  bool IsServo() const { return mType == mozilla::StyleBackendType::Servo; }
+
+private:
+  mozilla::StyleBackendType mType;
 };
 
 #endif /* nsStyleChangeList_h___ */
--- a/layout/generic/nsFirstLetterFrame.cpp
+++ b/layout/generic/nsFirstLetterFrame.cpp
@@ -64,17 +64,17 @@ nsFirstLetterFrame::Init(nsIContent*    
   RefPtr<nsStyleContext> newSC;
   if (aPrevInFlow) {
     // Get proper style context for ourselves.  We're creating the frame
     // that represents everything *except* the first letter, so just create
     // a style context like we would for a text node.
     nsStyleContext* parentStyleContext = mStyleContext->GetParent();
     if (parentStyleContext) {
       newSC = PresContext()->StyleSet()->
-        ResolveStyleForOtherNonElement(parentStyleContext);
+        ResolveStyleForFirstLetterContinuation(parentStyleContext);
       SetStyleContextWithoutNotification(newSC);
     }
   }
 
   nsContainerFrame::Init(aContent, aParent, aPrevInFlow);
 }
 
 void
@@ -329,17 +329,17 @@ nsFirstLetterFrame::CreateContinuationFo
     CreateContinuingFrame(aPresContext, aChild, parent, aIsFluid);
 
   // The continuation will have gotten the first letter style from its
   // prev continuation, so we need to repair the style context so it
   // doesn't have the first letter styling.
   nsStyleContext* parentSC = this->StyleContext()->GetParent();
   if (parentSC) {
     RefPtr<nsStyleContext> newSC;
-    newSC = presShell->StyleSet()->ResolveStyleForOtherNonElement(parentSC);
+    newSC = presShell->StyleSet()->ResolveStyleForFirstLetterContinuation(parentSC);
     continuation->SetStyleContext(newSC);
     nsLayoutUtils::MarkDescendantsDirty(continuation);
   }
 
   //XXX Bidi may not be involved but we have to use the list name
   // kNoReflowPrincipalList because this is just like creating a continuation
   // except we have to insert it in a different place and we don't want a
   // reflow command to try to be issued.
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -9152,42 +9152,49 @@ GetCorrectedParent(const nsIFrame* aFram
   //
   // We need to take special care not to disrupt the style inheritance of frames
   // whose content is NAC but who implement a pseudo (like an anonymous
   // box, or a non-NAC-backed pseudo like ::first-line) that does not match the
   // one that the NAC implements, if any.
   nsIContent* content = aFrame->GetContent();
   Element* element =
     content && content->IsElement() ? content->AsElement() : nullptr;
-  if (element && element->IsNativeAnonymous() &&
+  if (element && element->IsNativeAnonymous() && !element->IsNativeScrollbarContent() &&
       element->GetPseudoElementType() == aFrame->StyleContext()->GetPseudoType()) {
     while (parent->GetContent() && parent->GetContent()->IsNativeAnonymous()) {
-      parent = parent->GetParent();
+      parent = parent->GetInFlowParent();
     }
   }
 
   return nsFrame::CorrectStyleParentFrame(parent, pseudo);
 }
 
 /* static */
 nsIFrame*
 nsFrame::CorrectStyleParentFrame(nsIFrame* aProspectiveParent,
                                  nsIAtom* aChildPseudo)
 {
   NS_PRECONDITION(aProspectiveParent, "Must have a prospective parent");
 
-  // Anon boxes are parented to their actual parent already, except
-  // for non-elements.  Those should not be treated as an anon box.
-  if (aChildPseudo && !nsCSSAnonBoxes::IsNonElement(aChildPseudo) &&
-      nsCSSAnonBoxes::IsAnonBox(aChildPseudo)) {
-    NS_ASSERTION(aChildPseudo != nsCSSAnonBoxes::mozAnonymousBlock &&
-                 aChildPseudo != nsCSSAnonBoxes::mozAnonymousPositionedBlock,
-                 "Should have dealt with kids that have "
-                 "NS_FRAME_PART_OF_IBSPLIT elsewhere");
-    return aProspectiveParent;
+  if (aChildPseudo) {
+    // Non-inheriting anon boxes have no style parent frame at all.
+    if (nsCSSAnonBoxes::IsNonInheritingAnonBox(aChildPseudo)) {
+      return nullptr;
+    }
+
+    // Other anon boxes are parented to their actual parent already, except
+    // for non-elements.  Those should not be treated as an anon box.
+    if (!nsCSSAnonBoxes::IsNonElement(aChildPseudo) &&
+        nsCSSAnonBoxes::IsAnonBox(aChildPseudo)) {
+      NS_ASSERTION(aChildPseudo != nsCSSAnonBoxes::mozAnonymousBlock &&
+                   aChildPseudo != nsCSSAnonBoxes::mozAnonymousPositionedBlock,
+                   "Should have dealt with kids that have "
+                   "NS_FRAME_PART_OF_IBSPLIT elsewhere");
+      return aProspectiveParent;
+    }
   }
 
   // Otherwise, walk up out of all anon boxes.  For placeholder frames, walk out
   // of all pseudo-elements as well.  Otherwise ReparentStyleContext could cause
   // style data to be out of sync with the frame tree.
   nsIFrame* parent = aProspectiveParent;
   do {
     if (parent->GetStateBits() & NS_FRAME_PART_OF_IBSPLIT) {
@@ -9281,23 +9288,23 @@ nsFrame::DoGetParentStyleContext(nsIFram
     // frame would have if we didn't mangle the frame structure.
     *aProviderFrame = GetCorrectedParent(this);
     return *aProviderFrame ? (*aProviderFrame)->StyleContext() : nullptr;
   }
 
   // We're an out-of-flow frame.  For out-of-flow frames, we must
   // resolve underneath the placeholder's parent.  The placeholder is
   // reached from the first-in-flow.
-  nsIFrame* placeholder = fm->GetPlaceholderFrameFor(FirstInFlow());
+  nsPlaceholderFrame* placeholder = fm->GetPlaceholderFrameFor(FirstInFlow());
   if (!placeholder) {
     NS_NOTREACHED("no placeholder frame for out-of-flow frame");
     *aProviderFrame = GetCorrectedParent(this);
     return *aProviderFrame ? (*aProviderFrame)->StyleContext() : nullptr;
   }
-  return placeholder->GetParentStyleContext(aProviderFrame);
+  return placeholder->GetParentStyleContextForOutOfFlow(aProviderFrame);
 }
 
 void
 nsFrame::GetLastLeaf(nsPresContext* aPresContext, nsIFrame **aFrame)
 {
   if (!aFrame || !*aFrame) {
     return;
   }
@@ -10082,16 +10089,21 @@ nsFrame::UpdateStyleOfChildAnonBox(nsIFr
   //    anonymous boxed directly.
   uint32_t equalStructs, samePointerStructs; // Not used, actually.
   nsChangeHint childHint = aChildFrame->StyleContext()->CalcStyleDifference(
     newContext,
     NS_HintsNotHandledForDescendantsIn(aHintForThisFrame),
     &equalStructs,
     &samePointerStructs);
   if (childHint) {
+    if (childHint & nsChangeHint_ReconstructFrame) {
+      // If we generate a reconstruct here, remove any non-reconstruct hints we
+      // may have already generated for this content.
+      aChangeList.PopChangesForContent(aChildFrame->GetContent());
+    }
     aChangeList.AppendChange(aChildFrame, aChildFrame->GetContent(), childHint);
   }
 
   for (nsIFrame* kid = aChildFrame; kid; kid = kid->GetNextContinuation()) {
     kid->SetStyleContext(newContext);
   }
 
   // Now that we've updated the style on aChildFrame, check whether it itself
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -793,16 +793,23 @@ public:
 
   virtual void SetAdditionalStyleContext(int32_t aIndex,
                                          nsStyleContext* aStyleContext) = 0;
 
   /**
    * Accessor functions for geometric parent.
    */
   nsContainerFrame* GetParent() const { return mParent; }
+
+  /**
+   * Gets the parent of a frame, using the parent of the placeholder for
+   * out-of-flow frames.
+   */
+  inline nsContainerFrame* GetInFlowParent();
+
   /**
    * Set this frame's parent to aParent.
    * If the frame may have moved into or out of a scrollframe's
    * frame subtree, StickyScrollContainer::NotifyReparentedFrameAcrossScrollFrameBoundary
    * must also be called.
    */
   void SetParent(nsContainerFrame* aParent);
 
--- a/layout/generic/nsIFrameInlines.h
+++ b/layout/generic/nsIFrameInlines.h
@@ -3,18 +3,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIFrameInlines_h___
 #define nsIFrameInlines_h___
 
 #include "nsContainerFrame.h"
+#include "nsPlaceholderFrame.h"
 #include "nsStyleStructInlines.h"
 #include "nsCSSAnonBoxes.h"
+#include "nsFrameManager.h"
 
 bool
 nsIFrame::IsFlexItem() const
 {
   return GetParent() &&
     GetParent()->GetType() == nsGkAtoms::flexContainerFrame &&
     !(GetStateBits() & NS_FRAME_OUT_OF_FLOW);
 }
@@ -164,9 +166,20 @@ void
 nsIFrame::PropagateRootElementWritingMode(mozilla::WritingMode aRootElemWM)
 {
   MOZ_ASSERT(GetType() == nsGkAtoms::canvasFrame);
   for (auto f = this; f; f = f->GetParent()) {
     f->mWritingMode = aRootElemWM;
   }
 }
 
+nsContainerFrame*
+nsIFrame::GetInFlowParent()
+{
+  if (GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+    nsFrameManager* fm = PresContext()->FrameManager();
+    return fm->GetPlaceholderFrameFor(FirstContinuation())->GetParent();
+  }
+
+  return GetParent();
+}
+
 #endif
--- a/layout/generic/nsPlaceholderFrame.cpp
+++ b/layout/generic/nsPlaceholderFrame.cpp
@@ -187,17 +187,17 @@ nsPlaceholderFrame::CanContinueTextRun()
     return false;
   }
   // first-letter frames can continue text runs, and placeholders for floated
   // first-letter frames can too
   return mOutOfFlowFrame->CanContinueTextRun();
 }
 
 nsStyleContext*
-nsPlaceholderFrame::GetParentStyleContext(nsIFrame** aProviderFrame) const
+nsPlaceholderFrame::GetParentStyleContextForOutOfFlow(nsIFrame** aProviderFrame) const
 {
   NS_PRECONDITION(GetParent(), "How can we not have a parent here?");
 
   nsIContent* parentContent = mContent ? mContent->GetFlattenedTreeParent() : nullptr;
   if (parentContent) {
     nsStyleContext* sc =
       PresContext()->FrameManager()->GetDisplayContentsStyleFor(parentContent);
     if (sc) {
--- a/layout/generic/nsPlaceholderFrame.h
+++ b/layout/generic/nsPlaceholderFrame.h
@@ -132,17 +132,17 @@ public:
   virtual mozilla::a11y::AccType AccessibleType() override
   {
     nsIFrame* realFrame = GetRealFrameForPlaceholder(this);
     return realFrame ? realFrame->AccessibleType() :
                        nsFrame::AccessibleType();
   }
 #endif
 
-  virtual nsStyleContext* GetParentStyleContext(nsIFrame** aProviderFrame) const override;
+  nsStyleContext* GetParentStyleContextForOutOfFlow(nsIFrame** aProviderFrame) const;
 
   bool RenumberFrameAndDescendants(int32_t* aOrdinal,
                                    int32_t aDepth,
                                    int32_t aIncrement,
                                    bool aForCounting) override
   {
     return mOutOfFlowFrame->
       RenumberFrameAndDescendants(aOrdinal, aDepth, aIncrement, aForCounting);
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -428,16 +428,20 @@ ToTimingFunction(const Maybe<ComputedTim
   }
 
   if (aCTF->HasSpline()) {
     const nsSMILKeySpline* spline = aCTF->GetFunction();
     return TimingFunction(CubicBezierFunction(spline->X1(), spline->Y1(),
                                               spline->X2(), spline->Y2()));
   }
 
+  if (aCTF->GetType() == nsTimingFunction::Type::Frames) {
+    return TimingFunction(FramesFunction(aCTF->GetFrames()));
+  }
+
   uint32_t type = aCTF->GetType() == nsTimingFunction::Type::StepStart ? 1 : 2;
   return TimingFunction(StepFunction(aCTF->GetSteps(), type));
 }
 
 static void
 SetAnimatable(nsCSSPropertyID aProperty,
               const AnimationValue& aAnimationValue,
               nsIFrame* aFrame,
--- a/layout/printing/nsPrintData.cpp
+++ b/layout/printing/nsPrintData.cpp
@@ -20,23 +20,23 @@
 static mozilla::LazyLogModule gPrintingLog("printing");
 
 #define PR_PL(_p1)  MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
 
 //---------------------------------------------------
 //-- nsPrintData Class Impl
 //---------------------------------------------------
 nsPrintData::nsPrintData(ePrintDataType aType) :
-  mType(aType), mDebugFilePtr(nullptr), mPrintObject(nullptr), mSelectedPO(nullptr),
+  mType(aType), mDebugFilePtr(nullptr), mSelectedPO(nullptr),
   mPrintDocList(0), mIsIFrameSelected(false),
   mIsParentAFrameSet(false), mOnStartSent(false),
   mIsAborted(false), mPreparingForPrint(false), mDocWasToBeDestroyed(false),
-  mShrinkToFit(false), mPrintFrameType(nsIPrintSettings::kFramesAsIs), 
+  mShrinkToFit(false), mPrintFrameType(nsIPrintSettings::kFramesAsIs),
   mNumPrintablePages(0), mNumPagesPrinted(0),
-  mShrinkRatio(1.0), mOrigDCScale(1.0), mPPEventListeners(nullptr), 
+  mShrinkRatio(1.0), mOrigDCScale(1.0), mPPEventListeners(nullptr),
   mBrandName(nullptr)
 {
   MOZ_COUNT_CTOR(nsPrintData);
   nsCOMPtr<nsIStringBundle> brandBundle;
   nsCOMPtr<nsIStringBundleService> svc =
     mozilla::services::GetStringBundleService();
   if (svc) {
     svc->CreateBundle( "chrome://branding/locale/brand.properties", getter_AddRefs( brandBundle ) );
@@ -71,26 +71,24 @@ nsPrintData::~nsPrintData()
     bool isCancelled = false;
     mPrintSettings->GetIsCancelled(&isCancelled);
 
     nsresult rv = NS_OK;
     if (mType == eIsPrinting) {
       if (!isCancelled && !mIsAborted) {
         rv = mPrintDC->EndDocument();
       } else {
-        rv = mPrintDC->AbortDocument();  
+        rv = mPrintDC->AbortDocument();
       }
       if (NS_FAILED(rv)) {
         // XXX nsPrintData::ShowPrintErrorDialog(rv);
       }
     }
   }
 
-  delete mPrintObject;
-
   if (mBrandName) {
     free(mBrandName);
   }
 }
 
 void nsPrintData::OnStartPrinting()
 {
   if (!mOnStartSent) {
--- a/layout/printing/nsPrintData.h
+++ b/layout/printing/nsPrintData.h
@@ -2,42 +2,43 @@
 /* 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 nsPrintData_h___
 #define nsPrintData_h___
 
 #include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
 
 // Interfaces
 #include "nsDeviceContext.h"
 #include "nsIPrintProgressParams.h"
 #include "nsIPrintSettings.h"
 #include "nsTArray.h"
 #include "nsCOMArray.h"
 
 // Classes
 class nsPrintObject;
 class nsPrintPreviewListener;
 class nsIWebProgressListener;
 
 //------------------------------------------------------------------------
 // nsPrintData Class
 //
-// mPreparingForPrint - indicates that we have started Printing but 
-//   have not gone to the timer to start printing the pages. It gets turned 
+// mPreparingForPrint - indicates that we have started Printing but
+//   have not gone to the timer to start printing the pages. It gets turned
 //   off right before we go to the timer.
 //
 // mDocWasToBeDestroyed - Gets set when "someone" tries to unload the document
-//   while we were prparing to Print. This typically happens if a user starts 
-//   to print while a page is still loading. If they start printing and pause 
-//   at the print dialog and then the page comes in, we then abort printing 
+//   while we were prparing to Print. This typically happens if a user starts
+//   to print while a page is still loading. If they start printing and pause
+//   at the print dialog and then the page comes in, we then abort printing
 //   because the document is no longer stable.
-// 
+//
 //------------------------------------------------------------------------
 class nsPrintData {
 public:
 
   typedef enum {eIsPrinting, eIsPrintPreview } ePrintDataType;
 
   explicit nsPrintData(ePrintDataType aType);
   ~nsPrintData(); // non-virtual
@@ -52,25 +53,29 @@ public:
 
   void DoOnStatusChange(nsresult aStatus);
 
 
   ePrintDataType               mType;            // the type of data this is (Printing or Print Preview)
   RefPtr<nsDeviceContext>   mPrintDC;
   FILE                        *mDebugFilePtr;    // a file where information can go to when printing
 
-  nsPrintObject *                mPrintObject;
-  nsPrintObject *                mSelectedPO;
+  mozilla::UniquePtr<nsPrintObject> mPrintObject;
+  nsPrintObject* mSelectedPO; // This is a non-owning pointer.
 
   nsCOMArray<nsIWebProgressListener> mPrintProgressListeners;
   nsCOMPtr<nsIPrintProgressParams> mPrintProgressParams;
 
   nsCOMPtr<nsPIDOMWindowOuter> mCurrentFocusWin; // cache a pointer to the currently focused window
 
+  // Array of non-owning pointers to all the nsPrintObjects owned by this
+  // nsPrintData. This includes this->mPrintObject, as well as all of its
+  // mKids (and their mKids, etc.)
   nsTArray<nsPrintObject*>    mPrintDocList;
+
   bool                        mIsIFrameSelected;
   bool                        mIsParentAFrameSet;
   bool                        mOnStartSent;
   bool                        mIsAborted;           // tells us the document is being aborted
   bool                        mPreparingForPrint;   // see comments above
   bool                        mDocWasToBeDestroyed; // see comments above
   bool                        mShrinkToFit;
   int16_t                     mPrintFrameType;
--- a/layout/printing/nsPrintEngine.cpp
+++ b/layout/printing/nsPrintEngine.cpp
@@ -132,17 +132,17 @@ using namespace mozilla::dom;
 
 //-----------------------------------------------------
 // PR LOGGING
 #include "mozilla/Logging.h"
 
 #ifdef DEBUG
 // PR_LOGGING is force to always be on (even in release builds)
 // but we only want some of it on,
-//#define EXTENDED_DEBUG_PRINTING 
+//#define EXTENDED_DEBUG_PRINTING
 #endif
 
 #define DUMP_LAYOUT_LEVEL 9 // this turns on the dumping of each doucment's layout info
 
 #ifndef PR_PL
 static mozilla::LazyLogModule gPrintingLog("printing")
 
 #define PR_PL(_p1)  MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
@@ -161,18 +161,18 @@ static const char * gPrintRangeStr[]    
 
 #ifdef EXTENDED_DEBUG_PRINTING
 // Forward Declarations
 static void DumpPrintObjectsListStart(const char * aStr, nsTArray<nsPrintObject*> * aDocList);
 static void DumpPrintObjectsTree(nsPrintObject * aPO, int aLevel= 0, FILE* aFD = nullptr);
 static void DumpPrintObjectsTreeLayout(nsPrintObject * aPO,nsDeviceContext * aDC, int aLevel= 0, FILE * aFD = nullptr);
 
 #define DUMP_DOC_LIST(_title) DumpPrintObjectsListStart((_title), mPrt->mPrintDocList);
-#define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject);
-#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject, mPrt->mPrintDC);
+#define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject.get());
+#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject.get(), mPrt->mPrintDC);
 #else
 #define DUMP_DOC_LIST(_title)
 #define DUMP_DOC_TREE
 #define DUMP_DOC_TREELAYOUT
 #endif
 
 class nsScriptSuppressor
 {
@@ -184,17 +184,17 @@ public:
 
   void Suppress()
   {
     if (mPrintEngine) {
       mSuppressed = true;
       mPrintEngine->TurnScriptingOn(false);
     }
   }
-  
+
   void Unsuppress()
   {
     if (mPrintEngine && mSuppressed) {
       mPrintEngine->TurnScriptingOn(true);
     }
     mSuppressed = false;
   }
 
@@ -255,17 +255,17 @@ void nsPrintEngine::DestroyPrintingData(
   mPrt = nullptr;
 }
 
 //---------------------------------------------------------------------------------
 //-- Section: Methods needed by the DocViewer
 //---------------------------------------------------------------------------------
 
 //--------------------------------------------------------
-nsresult nsPrintEngine::Initialize(nsIDocumentViewerPrint* aDocViewerPrint, 
+nsresult nsPrintEngine::Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
                                    nsIDocShell*            aContainer,
                                    nsIDocument*            aDocument,
                                    float                   aScreenDPI,
                                    FILE*                   aDebugFile)
 {
   NS_ENSURE_ARG_POINTER(aDocViewerPrint);
   NS_ENSURE_ARG_POINTER(aContainer);
   NS_ENSURE_ARG_POINTER(aDocument);
@@ -297,18 +297,18 @@ nsPrintEngine::Cancelled()
 {
   if (mPrt && mPrt->mPrintSettings) {
     return mPrt->mPrintSettings->SetIsCancelled(true);
   }
   return NS_ERROR_FAILURE;
 }
 
 //-------------------------------------------------------
-// Install our event listeners on the document to prevent 
-// some events from being processed while in PrintPreview 
+// Install our event listeners on the document to prevent
+// some events from being processed while in PrintPreview
 //
 // No return code - if this fails, there isn't much we can do
 void
 nsPrintEngine::InstallPrintPreviewListener()
 {
   if (!mPrt->mPPEventListeners) {
     nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mContainer);
     if (!docShell) {
@@ -319,18 +319,18 @@ nsPrintEngine::InstallPrintPreviewListen
       nsCOMPtr<EventTarget> target = win->GetFrameElementInternal();
       mPrt->mPPEventListeners = new nsPrintPreviewListener(target);
       mPrt->mPPEventListeners->AddListeners();
     }
   }
 }
 
 //----------------------------------------------------------------------
-nsresult 
-nsPrintEngine::GetSeqFrameAndCountPagesInternal(nsPrintObject*  aPO,
+nsresult
+nsPrintEngine::GetSeqFrameAndCountPagesInternal(const UniquePtr<nsPrintObject>& aPO,
                                                 nsIFrame*&    aSeqFrame,
                                                 int32_t&      aCount)
 {
   NS_ENSURE_ARG_POINTER(aPO);
 
   // This is sometimes incorrectly called before the pres shell has been created
   // (bug 1141756). MOZ_DIAGNOSTIC_ASSERT so we'll still see the crash in
   // Nightly/Aurora in case the other patch fixes this.
@@ -508,22 +508,21 @@ nsPrintEngine::DoCommonPrint(bool       
       nsCOMPtr<nsIContentViewer> viewer;
       webContainer->GetContentViewer(getter_AddRefs(viewer));
       if (viewer && viewer->GetDocument() && viewer->GetDocument()->IsShowing()) {
         viewer->GetDocument()->OnPageHide(false, nullptr);
       }
     }
 
     nsAutoScriptBlocker scriptBlocker;
-    mPrt->mPrintObject = new nsPrintObject();
-    NS_ENSURE_TRUE(mPrt->mPrintObject, NS_ERROR_OUT_OF_MEMORY);
+    mPrt->mPrintObject = MakeUnique<nsPrintObject>();
     rv = mPrt->mPrintObject->Init(webContainer, aDoc, aIsPrintPreview);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    NS_ENSURE_TRUE(mPrt->mPrintDocList.AppendElement(mPrt->mPrintObject),
+    NS_ENSURE_TRUE(mPrt->mPrintDocList.AppendElement(mPrt->mPrintObject.get()),
                    NS_ERROR_OUT_OF_MEMORY);
 
     mPrt->mIsParentAFrameSet = IsParentAFrameSet(webContainer);
     mPrt->mPrintObject->mFrameType = mPrt->mIsParentAFrameSet ? eFrameSet : eDoc;
 
     // Build the "tree" of PrintObjects
     BuildDocTree(mPrt->mPrintObject->mDocShell, &mPrt->mPrintDocList,
                  mPrt->mPrintObject);
@@ -614,17 +613,17 @@ nsPrintEngine::DoCommonPrint(bool       
         // ShowPrintDialog triggers an event loop which means we can't assume
         // that the state of this->{anything} matches the state we've checked
         // above. Including that a given {thing} is non null.
         if (!mPrt) {
           return NS_ERROR_FAILURE;
         }
 
         if (NS_SUCCEEDED(rv)) {
-          // since we got the dialog and it worked then make sure we 
+          // since we got the dialog and it worked then make sure we
           // are telling GFX we want to print silent
           printSilently = true;
 
           if (mPrt->mPrintSettings && !aIsPrintPreview) {
             // The user might have changed shrink-to-fit in the print dialog, so update our copy of its state
             mPrt->mPrintSettings->GetShrinkToFit(&mPrt->mShrinkToFit);
 
             // If we haven't already added the RemotePrintJob as a listener,
@@ -648,17 +647,17 @@ nsPrintEngine::DoCommonPrint(bool       
         // No dialog service available
         rv = NS_ERROR_NOT_IMPLEMENTED;
       }
     } else {
       // Call any code that requires a run of the event loop.
       rv = mPrt->mPrintSettings->SetupSilentPrinting();
     }
     // Check explicitly for abort because it's expected
-    if (rv == NS_ERROR_ABORT) 
+    if (rv == NS_ERROR_ABORT)
       return rv;
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   rv = devspec->Init(nullptr, mPrt->mPrintSettings, aIsPrintPreview);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mPrt->mPrintDC = new nsDeviceContext();
@@ -779,18 +778,18 @@ nsPrintEngine::Print(nsIPrintSettings*  
   nsCOMPtr<nsIDOMDocument> doc =
     do_QueryInterface(mPrtPreview && mPrtPreview->mPrintObject ?
                         mPrtPreview->mPrintObject->mDocument : mDocument);
 
   return CommonPrint(false, aPrintSettings, aWebProgressListener, doc);
 }
 
 NS_IMETHODIMP
-nsPrintEngine::PrintPreview(nsIPrintSettings* aPrintSettings, 
-                            mozIDOMWindowProxy* aChildDOMWin, 
+nsPrintEngine::PrintPreview(nsIPrintSettings* aPrintSettings,
+                            mozIDOMWindowProxy* aChildDOMWin,
                             nsIWebProgressListener* aWebProgressListener)
 {
   // Get the DocShell and see if it is busy
   // (We can't Print Preview this document if it is still busy)
   nsCOMPtr<nsIDocShell> docShell(do_QueryReferent(mContainer));
   NS_ENSURE_STATE(docShell);
 
   uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE;
@@ -817,67 +816,67 @@ NS_IMETHODIMP
 nsPrintEngine::GetIsFramesetDocument(bool *aIsFramesetDocument)
 {
   nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer));
   *aIsFramesetDocument = IsParentAFrameSet(webContainer);
   return NS_OK;
 }
 
 //----------------------------------------------------------------------------------
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsPrintEngine::GetIsIFrameSelected(bool *aIsIFrameSelected)
 {
   *aIsIFrameSelected = false;
 
   // Get the docshell for this documentviewer
   nsCOMPtr<nsIDocShell> webContainer(do_QueryReferent(mContainer));
   // Get the currently focused window
   nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
   if (currentFocusWin && webContainer) {
-    // Get whether the doc contains a frameset 
+    // Get whether the doc contains a frameset
     // Also, check to see if the currently focus docshell
     // is a child of this docshell
     bool isParentFrameSet;
     *aIsIFrameSelected = IsThereAnIFrameSelected(webContainer, currentFocusWin, isParentFrameSet);
   }
   return NS_OK;
 }
 
 //----------------------------------------------------------------------------------
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsPrintEngine::GetIsRangeSelection(bool *aIsRangeSelection)
 {
-  // Get the currently focused window 
+  // Get the currently focused window
   nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
   *aIsRangeSelection = IsThereARangeSelection(currentFocusWin);
   return NS_OK;
 }
 
 //----------------------------------------------------------------------------------
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsPrintEngine::GetIsFramesetFrameSelected(bool *aIsFramesetFrameSelected)
 {
-  // Get the currently focused window 
+  // Get the currently focused window
   nsCOMPtr<nsPIDOMWindowOuter> currentFocusWin = FindFocusedDOMWindow();
   *aIsFramesetFrameSelected = currentFocusWin != nullptr;
   return NS_OK;
 }
 
 //----------------------------------------------------------------------------------
 NS_IMETHODIMP
 nsPrintEngine::GetPrintPreviewNumPages(int32_t *aPrintPreviewNumPages)
 {
   NS_ENSURE_ARG_POINTER(aPrintPreviewNumPages);
 
   nsPrintData* prt = nullptr;
   nsIFrame* seqFrame  = nullptr;
   *aPrintPreviewNumPages = 0;
 
   // When calling this function, the FinishPrintPreview() function might not
-  // been called as there are still some 
+  // been called as there are still some
   if (mPrtPreview) {
     prt = mPrtPreview.get();
   } else {
     prt = mPrt.get();
   }
   if ((!prt) ||
       NS_FAILED(GetSeqFrameAndCountPagesInternal(prt->mPrintObject, seqFrame, *aPrintPreviewNumPages))) {
     return NS_ERROR_FAILURE;
@@ -1019,42 +1018,42 @@ nsPrintEngine::CheckForPrinters(nsIPrint
 }
 
 //----------------------------------------------------------------------
 // Set up to use the "pluggable" Print Progress Dialog
 void
 nsPrintEngine::ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify)
 {
   // default to not notifying, that if something here goes wrong
-  // or we aren't going to show the progress dialog we can straight into 
+  // or we aren't going to show the progress dialog we can straight into
   // reflowing the doc for printing.
   aDoNotify = false;
 
   // Assume we can't do progress and then see if we can
   bool showProgresssDialog = false;
 
   // if it is already being shown then don't bother to find out if it should be
   // so skip this and leave mShowProgressDialog set to FALSE
   if (!mProgressDialogIsShown) {
     showProgresssDialog = Preferences::GetBool("print.show_print_progress");
   }
 
   // Turning off the showing of Print Progress in Prefs overrides
-  // whether the calling PS desire to have it on or off, so only check PS if 
+  // whether the calling PS desire to have it on or off, so only check PS if
   // prefs says it's ok to be on.
   if (showProgresssDialog) {
     mPrt->mPrintSettings->GetShowPrintProgress(&showProgresssDialog);
   }
 
   // Now open the service to get the progress dialog
   // If we don't get a service, that's ok, then just don't show progress
   if (showProgresssDialog) {
     nsCOMPtr<nsIPrintingPromptService> printPromptService(do_GetService(kPrintingPromptService));
     if (printPromptService) {
-      nsPIDOMWindowOuter* domWin = mDocument->GetWindow(); 
+      nsPIDOMWindowOuter* domWin = mDocument->GetWindow();
       if (!domWin) return;
 
       nsCOMPtr<nsIDocShell> docShell = domWin->GetDocShell();
       if (!docShell) return;
       nsCOMPtr<nsIDocShellTreeOwner> owner;
       docShell->GetTreeOwner(getter_AddRefs(owner));
       nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(owner);
       if (!browserChrome) return;
@@ -1065,18 +1064,18 @@ nsPrintEngine::ShowPrintProgress(bool aI
         // isn't supported. See bug 301560.
         return;
       }
 
       nsCOMPtr<nsIWebProgressListener> printProgressListener;
 
       nsCOMPtr<nsIWebBrowserPrint> wbp(do_QueryInterface(mDocViewerPrint));
       nsresult rv = printPromptService->ShowProgress(domWin, wbp, mPrt->mPrintSettings, this, aIsForPrinting,
-                                                     getter_AddRefs(printProgressListener), 
-                                                     getter_AddRefs(mPrt->mPrintProgressParams), 
+                                                     getter_AddRefs(printProgressListener),
+                                                     getter_AddRefs(mPrt->mPrintProgressParams),
                                                      &aDoNotify);
       if (NS_SUCCEEDED(rv)) {
         if (printProgressListener) {
           mPrt->mPrintProgressListeners.AppendObject(printProgressListener);
         }
 
         if (mPrt->mPrintProgressParams) {
           SetDocAndURLIntoProgress(mPrt->mPrintObject, mPrt->mPrintProgressParams);
@@ -1155,17 +1154,17 @@ nsPrintEngine::IsParentAFrameSet(nsIDocS
 
 
 //---------------------------------------------------------------------
 // Recursively build a list of sub documents to be printed
 // that mirrors the document tree
 void
 nsPrintEngine::BuildDocTree(nsIDocShell *      aParentNode,
                             nsTArray<nsPrintObject*> * aDocList,
-                            nsPrintObject *            aPO)
+                            const UniquePtr<nsPrintObject>& aPO)
 {
   NS_ASSERTION(aParentNode, "Pointer is null!");
   NS_ASSERTION(aDocList, "Pointer is null!");
   NS_ASSERTION(aPO, "Pointer is null!");
 
   int32_t childWebshellCount;
   aParentNode->GetChildCount(&childWebshellCount);
   if (childWebshellCount > 0) {
@@ -1175,24 +1174,24 @@ nsPrintEngine::BuildDocTree(nsIDocShell 
       nsCOMPtr<nsIDocShell> childAsShell(do_QueryInterface(child));
 
       nsCOMPtr<nsIContentViewer>  viewer;
       childAsShell->GetContentViewer(getter_AddRefs(viewer));
       if (viewer) {
         nsCOMPtr<nsIContentViewerFile> viewerFile(do_QueryInterface(viewer));
         if (viewerFile) {
           nsCOMPtr<nsIDOMDocument> doc = do_GetInterface(childAsShell);
-          nsPrintObject * po = new nsPrintObject();
-          po->mParent = aPO;
+          auto po = MakeUnique<nsPrintObject>();
+          po->mParent = aPO.get();
           nsresult rv = po->Init(childAsShell, doc, aPO->mPrintPreview);
           if (NS_FAILED(rv))
             NS_NOTREACHED("Init failed?");
-          aPO->mKids.AppendElement(po);
-          aDocList->AppendElement(po);
-          BuildDocTree(childAsShell, aDocList, po);
+          aPO->mKids.AppendElement(Move(po));
+          aDocList->AppendElement(aPO->mKids.LastElement().get());
+          BuildDocTree(childAsShell, aDocList, aPO->mKids.LastElement());
         }
       }
     }
   }
 }
 
 //---------------------------------------------------------------------
 void
@@ -1233,18 +1232,18 @@ nsPrintEngine::GetDocumentTitleAndURL(ns
 
 //---------------------------------------------------------------------
 // The walks the PO tree and for each document it walks the content
 // tree looking for any content that are sub-shells
 //
 // It then sets the mContent pointer in the "found" PO object back to the
 // the document that contained it.
 void
-nsPrintEngine::MapContentToWebShells(nsPrintObject* aRootPO,
-                                     nsPrintObject* aPO)
+nsPrintEngine::MapContentToWebShells(const UniquePtr<nsPrintObject>& aRootPO,
+                                     const UniquePtr<nsPrintObject>& aPO)
 {
   NS_ASSERTION(aRootPO, "Pointer is null!");
   NS_ASSERTION(aPO, "Pointer is null!");
 
   // Recursively walk the content from the root item
   // XXX Would be faster to enumerate the subdocuments, although right now
   //     nsIDocument doesn't expose quite what would be needed.
   nsCOMPtr<nsIContentViewer> viewer;
@@ -1259,18 +1258,18 @@ nsPrintEngine::MapContentToWebShells(nsP
   Element* rootElement = doc->GetRootElement();
   if (rootElement) {
     MapContentForPO(aPO, rootElement);
   } else {
     NS_WARNING("Null root content on (sub)document.");
   }
 
   // Continue recursively walking the chilren of this PO
-  for (uint32_t i=0;i<aPO->mKids.Length();i++) {
-    MapContentToWebShells(aRootPO, aPO->mKids[i]);
+  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+    MapContentToWebShells(aRootPO, kid);
   }
 
 }
 
 //-------------------------------------------------------
 // A Frame's sub-doc may contain content or a FrameSet
 // When it contains a FrameSet the mFrameType for the PrintObject
 // is always set to an eFrame. Which is fine when printing "AsIs"
@@ -1280,24 +1279,23 @@ nsPrintEngine::MapContentToWebShells(nsP
 //
 // This method walks the PO tree and checks to see if the PrintObject is
 // an eFrame and has children that are eFrames (meaning it's a Frame containing a FrameSet)
 // If so, then the mFrameType need to be changed to eFrameSet
 //
 // Also note: We only want to call this we are printing "Each Frame Separately"
 //            when printing "As Is" leave it as an eFrame
 void
-nsPrintEngine::CheckForChildFrameSets(nsPrintObject* aPO)
+nsPrintEngine::CheckForChildFrameSets(const UniquePtr<nsPrintObject>& aPO)
 {
   NS_ASSERTION(aPO, "Pointer is null!");
 
   // Continue recursively walking the chilren of this PO
   bool hasChildFrames = false;
-  for (uint32_t i=0;i<aPO->mKids.Length();i++) {
-    nsPrintObject* po = aPO->mKids[i];
+  for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
     if (po->mFrameType == eFrame) {
       hasChildFrames = true;
       CheckForChildFrameSets(po);
     }
   }
 
   if (hasChildFrames && aPO->mFrameType == eFrame) {
     aPO->mFrameType = eFrameSet;
@@ -1315,37 +1313,35 @@ nsPrintEngine::CheckForChildFrameSets(ns
 // This is used later to (after reflow) to find the absolute location
 // of the sub-doc on its parent's page frame so it can be
 // printed in the correct location.
 //
 // This method recursvely "walks" the content for a document finding
 // all the Frames and IFrames, then sets the "mFrameType" data member
 // which tells us what type of PO we have
 void
-nsPrintEngine::MapContentForPO(nsPrintObject*   aPO,
+nsPrintEngine::MapContentForPO(const UniquePtr<nsPrintObject>& aPO,
                                nsIContent*      aContent)
 {
   NS_PRECONDITION(aPO && aContent, "Null argument");
 
   nsIDocument* doc = aContent->GetComposedDoc();
 
   NS_ASSERTION(doc, "Content without a document from a document tree?");
 
   nsIDocument* subDoc = doc->GetSubDocumentFor(aContent);
 
   if (subDoc) {
     nsCOMPtr<nsIDocShell> docShell(subDoc->GetDocShell());
 
     if (docShell) {
       nsPrintObject * po = nullptr;
-      int32_t cnt = aPO->mKids.Length();
-      for (int32_t i=0;i<cnt;i++) {
-        nsPrintObject* kid = aPO->mKids.ElementAt(i);
+      for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
         if (kid->mDocument == subDoc) {
-          po = kid;
+          po = kid.get();
           break;
         }
       }
 
       // XXX If a subdocument has no onscreen presentation, there will be no PO
       //     This is even if there should be a print presentation
       if (po) {
 
@@ -1377,26 +1373,26 @@ nsPrintEngine::MapContentForPO(nsPrintOb
 bool
 nsPrintEngine::IsThereAnIFrameSelected(nsIDocShell* aDocShell,
                                        nsPIDOMWindowOuter* aDOMWin,
                                        bool& aIsParentFrameSet)
 {
   aIsParentFrameSet = IsParentAFrameSet(aDocShell);
   bool iFrameIsSelected = false;
   if (mPrt && mPrt->mPrintObject) {
-    nsPrintObject* po = FindPrintObjectByDOMWin(mPrt->mPrintObject, aDOMWin);
+    nsPrintObject* po = FindPrintObjectByDOMWin(mPrt->mPrintObject.get(), aDOMWin);
     iFrameIsSelected = po && po->mFrameType == eIFrame;
   } else {
     // First, check to see if we are a frameset
     if (!aIsParentFrameSet) {
       // Check to see if there is a currenlt focused frame
       // if so, it means the selected frame is either the main docshell
       // or an IFRAME
       if (aDOMWin) {
-        // Get the main docshell's DOMWin to see if it matches 
+        // Get the main docshell's DOMWin to see if it matches
         // the frame that is selected
         nsPIDOMWindowOuter* domWin = aDocShell ? aDocShell->GetWindow() : nullptr;
         if (domWin != aDOMWin) {
           iFrameIsSelected = true; // we have a selected IFRAME
         }
       }
     }
   }
@@ -1410,28 +1406,28 @@ nsPrintEngine::IsThereAnIFrameSelected(n
 void
 nsPrintEngine::SetPrintPO(nsPrintObject* aPO, bool aPrint)
 {
   NS_ASSERTION(aPO, "Pointer is null!");
 
   // Set whether to print flag
   aPO->mDontPrint = !aPrint;
 
-  for (uint32_t i=0;i<aPO->mKids.Length();i++) {
-    SetPrintPO(aPO->mKids[i], aPrint);
-  } 
+  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+    SetPrintPO(kid.get(), aPrint);
+  }
 }
 
 //---------------------------------------------------------------------
 // This will first use a Title and/or URL from the PrintSettings
 // if one isn't set then it uses the one from the document
 // then if not title is there we will make sure we send something back
 // depending on the situation.
 void
-nsPrintEngine::GetDisplayTitleAndURL(nsPrintObject*   aPO,
+nsPrintEngine::GetDisplayTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
                                      nsAString&       aTitle,
                                      nsAString&       aURLStr,
                                      eDocTitleDefault aDefType)
 {
   NS_ASSERTION(aPO, "Pointer is null!");
 
   if (!mPrt)
     return;
@@ -1509,28 +1505,28 @@ nsresult nsPrintEngine::CleanupOnFailure
   PR_PL(("****  Failed %s - rv 0x%" PRIX32, aIsPrinting?"Printing":"Print Preview",
          static_cast<uint32_t>(aResult)));
 
   /* cleanup... */
   if (mPagePrintTimer) {
     mPagePrintTimer->Stop();
     DisconnectPagePrintTimer();
   }
-  
+
   if (aIsPrinting) {
     SetIsPrinting(false);
   } else {
     SetIsPrintPreview(false);
     SetIsCreatingPrintPreview(false);
   }
 
   /* cleanup done, let's fire-up an error dialog to notify the user
-   * what went wrong... 
-   * 
-   * When rv == NS_ERROR_ABORT, it means we want out of the 
+   * what went wrong...
+   *
+   * When rv == NS_ERROR_ABORT, it means we want out of the
    * print job without displaying any error messages
    */
   if (aResult != NS_ERROR_ABORT) {
     FirePrintingErrorEvent(aResult);
   }
 
   FirePrintCompletionEvent();
 
@@ -1609,23 +1605,23 @@ nsPrintEngine::ReconstructAndReflow(bool
 
     po->mPresShell->ReconstructFrames();
 
     // For all views except the first one, setup the root view.
     // ??? Can there be multiple po for the top-level-document?
     bool documentIsTopLevel = true;
     if (i != 0) {
       nsSize adjSize;
-      bool doReturn; 
+      bool doReturn;
       nsresult rv = SetRootView(po, doReturn, documentIsTopLevel, adjSize);
 
       MOZ_ASSERT(!documentIsTopLevel, "How could this happen?");
-      
+
       if (NS_FAILED(rv) || doReturn) {
-        return rv; 
+        return rv;
       }
     }
 
     po->mPresShell->FlushPendingNotifications(FlushType::Layout);
 
     nsresult rv = UpdateSelectionAndShrinkPrintObject(po, documentIsTopLevel);
     NS_ENSURE_SUCCESS(rv, rv);
   }
@@ -1634,17 +1630,17 @@ nsPrintEngine::ReconstructAndReflow(bool
 
 //-------------------------------------------------------
 nsresult
 nsPrintEngine::SetupToPrintContent()
 {
   nsresult rv;
 
   bool didReconstruction = false;
-  
+
   // If some new content got loaded since the initial reflow rebuild
   // everything.
   if (mDidLoadDataForPrinting) {
     rv = ReconstructAndReflow(DoSetPixelScale());
     didReconstruction = true;
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
@@ -1685,36 +1681,36 @@ nsPrintEngine::SetupToPrintContent()
         // Single document so use the Shrink as calculated for the PO
         calcRatio = mPrt->mPrintObject->mShrinkRatio;
       }
       PR_PL(("**************************************************************************\n"));
       PR_PL(("STF Ratio is: %8.5f Effective Ratio: %8.5f Diff: %8.5f\n", mPrt->mShrinkRatio, calcRatio,  mPrt->mShrinkRatio-calcRatio));
       PR_PL(("**************************************************************************\n"));
     }
   }
-  
+
   // If the frames got reconstructed and reflowed the number of pages might
   // has changed.
   if (didReconstruction) {
     FirePrintPreviewUpdateEvent();
   }
-  
+
   DUMP_DOC_LIST(("\nAfter Reflow------------------------------------------"));
   PR_PL(("\n"));
   PR_PL(("-------------------------------------------------------\n"));
   PR_PL(("\n"));
 
   CalcNumPrintablePages(mPrt->mNumPrintablePages);
 
   PR_PL(("--- Printing %d pages\n", mPrt->mNumPrintablePages));
   DUMP_DOC_TREELAYOUT;
 
   // Print listener setup...
   if (mPrt != nullptr) {
-    mPrt->OnStartPrinting();    
+    mPrt->OnStartPrinting();
   }
 
   nsAutoString fileNameStr;
   // check to see if we are printing to a file
   bool isPrintToFile = false;
   mPrt->mPrintSettings->GetPrintToFile(&isPrintToFile);
   if (isPrintToFile) {
     // On some platforms The BeginDocument needs to know the name of the file.
@@ -1737,30 +1733,30 @@ nsPrintEngine::SetupToPrintContent()
     mPrt->mPrintSettings->GetEndPageRange(&endPage);
     if (endPage > mPrt->mNumPrintablePages) {
       endPage = mPrt->mNumPrintablePages;
     }
   }
 
   rv = NS_OK;
   // BeginDocument may pass back a FAILURE code
-  // i.e. On Windows, if you are printing to a file and hit "Cancel" 
+  // i.e. On Windows, if you are printing to a file and hit "Cancel"
   //      to the "File Name" dialog, this comes back as an error
-  // Don't start printing when regression test are executed  
+  // Don't start printing when regression test are executed
   if (!mPrt->mDebugFilePtr && mIsDoingPrinting) {
     rv = mPrt->mPrintDC->BeginDocument(docTitleStr, fileNameStr, startPage,
                                        endPage);
-  } 
+  }
 
   if (mIsCreatingPrintPreview) {
     // Copy docTitleStr and docURLStr to the pageSequenceFrame, to be displayed
     // in the header
     nsIPageSequenceFrame *seqFrame = mPrt->mPrintObject->mPresShell->GetPageSequenceFrame();
     if (seqFrame) {
-      seqFrame->StartPrint(mPrt->mPrintObject->mPresContext, 
+      seqFrame->StartPrint(mPrt->mPrintObject->mPresContext,
                            mPrt->mPrintSettings, docTitleStr, docURLStr);
     }
   }
 
   PR_PL(("****************** Begin Document ************************\n"));
 
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -1775,40 +1771,40 @@ nsPrintEngine::SetupToPrintContent()
 
   return rv;
 }
 
 //-------------------------------------------------------
 // Recursively reflow each sub-doc and then calc
 // all the frame locations of the sub-docs
 nsresult
-nsPrintEngine::ReflowDocList(nsPrintObject* aPO, bool aSetPixelScale)
+nsPrintEngine::ReflowDocList(const UniquePtr<nsPrintObject>& aPO,
+                             bool aSetPixelScale)
 {
   NS_ENSURE_ARG_POINTER(aPO);
 
   // Check to see if the subdocument's element has been hidden by the parent document
   if (aPO->mParent && aPO->mParent->mPresShell) {
     nsIFrame* frame = aPO->mContent ? aPO->mContent->GetPrimaryFrame() : nullptr;
     if (!frame || !frame->StyleVisibility()->IsVisible()) {
-      SetPrintPO(aPO, false);
+      SetPrintPO(aPO.get(), false);
       aPO->mInvisible = true;
       return NS_OK;
     }
   }
 
-  UpdateZoomRatio(aPO, aSetPixelScale);
+  UpdateZoomRatio(aPO.get(), aSetPixelScale);
 
   nsresult rv;
   // Reflow the PO
   rv = ReflowPrintObject(aPO);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  int32_t cnt = aPO->mKids.Length();
-  for (int32_t i=0;i<cnt;i++) {
-    rv = ReflowDocList(aPO->mKids[i], aSetPixelScale);
+  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+    rv = ReflowDocList(kid, aSetPixelScale);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
 void
 nsPrintEngine::FirePrintPreviewUpdateEvent()
 {
@@ -1875,17 +1871,17 @@ nsPrintEngine::OnStateChange(nsIWebProgr
   if (name.EqualsLiteral("about:document-onload-blocker")) {
     return NS_OK;
   }
   if (aStateFlags & STATE_START) {
     ++mLoadCounter;
   } else if (aStateFlags & STATE_STOP) {
     mDidLoadDataForPrinting = true;
     --mLoadCounter;
-   
+
     // If all resources are loaded, then do a small timeout and if there
     // are still no new requests, then another reflow.
     if (mLoadCounter == 0) {
       AfterNetworkPrint(true);
     }
   }
   return NS_OK;
 }
@@ -1947,17 +1943,17 @@ nsPrintEngine::UpdateZoomRatio(nsPrintOb
     } else {
       ratio = aPO->mShrinkRatio - 0.005f; // round down
     }
     aPO->mZoomRatio = ratio;
   } else if (!mPrt->mShrinkToFit) {
     double scaling;
     mPrt->mPrintSettings->GetScaling(&scaling);
     aPO->mZoomRatio = float(scaling);
-  } 
+  }
 }
 
 nsresult
 nsPrintEngine::UpdateSelectionAndShrinkPrintObject(nsPrintObject* aPO,
                                                    bool aDocumentIsTopLevel)
 {
   nsCOMPtr<nsIPresShell> displayShell = aPO->mDocShell->GetPresShell();
   // Transfer Selection Ranges to the new Print PresShell
@@ -1990,17 +1986,17 @@ nsPrintEngine::UpdateSelectionAndShrinkP
     nsIPageSequenceFrame* pageSequence = aPO->mPresShell->GetPageSequenceFrame();
     NS_ENSURE_STATE(pageSequence);
     pageSequence->GetSTFPercent(aPO->mShrinkRatio);
     // Limit the shrink-to-fit scaling for some text-ish type of documents.
     nsAutoString contentType;
     aPO->mPresShell->GetDocument()->GetContentType(contentType);
     if (contentType.EqualsLiteral("application/xhtml+xml") ||
         StringBeginsWith(contentType, NS_LITERAL_STRING("text/"))) {
-      int32_t limitPercent = 
+      int32_t limitPercent =
         Preferences::GetInt("print.shrink-to-fit.scale-limit-percent", 20);
       limitPercent = std::max(0, limitPercent);
       limitPercent = std::min(100, limitPercent);
       float minShrinkRatio = float(limitPercent) / 100;
       aPO->mShrinkRatio = std::max(aPO->mShrinkRatio, minShrinkRatio);
     }
   }
   return NS_OK;
@@ -2033,19 +2029,19 @@ nsPrintEngine::GetParentViewForRoot()
       return cv->FindContainerView();
     }
   }
   return nullptr;
 }
 
 nsresult
 nsPrintEngine::SetRootView(
-    nsPrintObject* aPO, 
-    bool& doReturn, 
-    bool& documentIsTopLevel, 
+    nsPrintObject* aPO,
+    bool& doReturn,
+    bool& documentIsTopLevel,
     nsSize& adjSize
 )
 {
   bool canCreateScrollbars = true;
 
   nsView* rootView;
   nsView* parentView = nullptr;
 
@@ -2092,37 +2088,37 @@ nsPrintEngine::SetRootView(
     aPO->mViewManager->RemoveChild(rootView);
     rootView->SetParent(parentView);
   } else {
     // Create a child window of the parent that is our "root view/window"
     nsRect tbounds = nsRect(nsPoint(0, 0), adjSize);
     rootView = aPO->mViewManager->CreateView(tbounds, parentView);
     NS_ENSURE_TRUE(rootView, NS_ERROR_OUT_OF_MEMORY);
   }
-    
+
   if (mIsCreatingPrintPreview && documentIsTopLevel) {
     aPO->mPresContext->SetPaginatedScrolling(canCreateScrollbars);
   }
 
   // Setup hierarchical relationship in view manager
   aPO->mViewManager->SetRootView(rootView);
 
   return NS_OK;
 }
 
 // Reflow a nsPrintObject
 nsresult
-nsPrintEngine::ReflowPrintObject(nsPrintObject * aPO)
+nsPrintEngine::ReflowPrintObject(const UniquePtr<nsPrintObject>& aPO)
 {
   NS_ENSURE_STATE(aPO);
 
   if (!aPO->IsPrintable()) {
     return NS_OK;
   }
-  
+
   NS_ASSERTION(!aPO->mPresContext, "Recreating prescontext");
 
   // create the PresContext
   nsPresContext::nsPresContextType type =
       mIsCreatingPrintPreview ? nsPresContext::eContext_PrintPreview:
                                 nsPresContext::eContext_Print;
   nsView* parentView =
     aPO->mParent && aPO->mParent->IsPrintable() ? nullptr : GetParentViewForRoot();
@@ -2153,31 +2149,32 @@ nsPrintEngine::ReflowPrintObject(nsPrint
   aPO->mPresShell = aPO->mDocument->CreateShell(aPO->mPresContext,
                                                 aPO->mViewManager, styleSet);
   if (!aPO->mPresShell) {
     styleSet->Delete();
     return NS_ERROR_FAILURE;
   }
 
   styleSet->EndUpdate();
-  
+
   // The pres shell now owns the style set object.
 
 
   bool doReturn = false;;
   bool documentIsTopLevel = false;
-  nsSize adjSize; 
-
-  rv = SetRootView(aPO, doReturn, documentIsTopLevel, adjSize);
+  nsSize adjSize;
+
+  rv = SetRootView(aPO.get(), doReturn, documentIsTopLevel, adjSize);
 
   if (NS_FAILED(rv) || doReturn) {
-    return rv; 
+    return rv;
   }
 
-  PR_PL(("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting w,h to %d,%d\n", aPO, aPO->mPresShell.get(),
+  PR_PL(("In DV::ReflowPrintObject PO: %p pS: %p (%9s) Setting w,h to %d,%d\n",
+         aPO.get(), aPO->mPresShell.get(),
          gFrameTypesStr[aPO->mFrameType], adjSize.width, adjSize.height));
 
 
   // This docshell stuff is weird; will go away when we stop having multiple
   // presentations per document
   aPO->mPresContext->SetContainer(aPO->mDocShell);
 
   aPO->mPresShell->BeginObservingDocument();
@@ -2199,17 +2196,17 @@ nsPrintEngine::ReflowPrintObject(nsPrint
   rv = aPO->mPresShell->Initialize(adjSize.width, adjSize.height);
 
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ASSERTION(aPO->mPresShell, "Presshell should still be here");
 
   // Process the reflow event Initialize posted
   aPO->mPresShell->FlushPendingNotifications(FlushType::Layout);
 
-  rv = UpdateSelectionAndShrinkPrintObject(aPO, documentIsTopLevel);
+  rv = UpdateSelectionAndShrinkPrintObject(aPO.get(), documentIsTopLevel);
   NS_ENSURE_SUCCESS(rv, rv);
 
 #ifdef EXTENDED_DEBUG_PRINTING
     if (kPrintingLogMod && kPrintingLogMod->level == DUMP_LAYOUT_LEVEL) {
       nsAutoCString docStr;
       nsAutoCString urlStr;
       GetDocTitleAndURL(aPO, docStr, urlStr);
       char filename[256];
@@ -2274,31 +2271,31 @@ nsPrintEngine::CalcNumPrintablePages(int
 
 //-----------------------------------------------------------------
 //-- Section: Printing Methods
 //-----------------------------------------------------------------
 
 //-------------------------------------------------------
 // Called for each DocShell that needs to be printed
 bool
-nsPrintEngine::PrintDocContent(nsPrintObject* aPO, nsresult& aStatus)
+nsPrintEngine::PrintDocContent(const UniquePtr<nsPrintObject>& aPO,
+                               nsresult& aStatus)
 {
   NS_ASSERTION(aPO, "Pointer is null!");
   aStatus = NS_OK;
 
   if (!aPO->mHasBeenPrinted && aPO->IsPrintable()) {
     aStatus = DoPrint(aPO);
     return true;
   }
 
   // If |aPO->mPrintAsIs| and |aPO->mHasBeenPrinted| are true,
   // the kids frames are already processed in |PrintPage|.
   if (!aPO->mInvisible && !(aPO->mPrintAsIs && aPO->mHasBeenPrinted)) {
-    for (uint32_t i=0;i<aPO->mKids.Length();i++) {
-      nsPrintObject* po = aPO->mKids[i];
+    for (const UniquePtr<nsPrintObject>& po : aPO->mKids) {
       bool printed = PrintDocContent(po, aStatus);
       if (printed || NS_FAILED(aStatus)) {
         return true;
       }
     }
   }
   return false;
 }
@@ -2386,21 +2383,21 @@ static nsresult CloneSelection(nsIDocume
   for (int32_t i = 0; i < rangeCount; ++i) {
       CloneRangeToSelection(origSelection->GetRangeAt(i), aDoc, selection);
   }
   return NS_OK;
 }
 
 //-------------------------------------------------------
 nsresult
-nsPrintEngine::DoPrint(nsPrintObject * aPO)
+nsPrintEngine::DoPrint(const UniquePtr<nsPrintObject>& aPO)
 {
   PR_PL(("\n"));
   PR_PL(("**************************** %s ****************************\n", gFrameTypesStr[aPO->mFrameType]));
-  PR_PL(("****** In DV::DoPrint   PO: %p \n", aPO));
+  PR_PL(("****** In DV::DoPrint   PO: %p \n", aPO.get()));
 
   nsIPresShell*   poPresShell   = aPO->mPresShell;
   nsPresContext*  poPresContext = aPO->mPresContext;
 
   NS_ASSERTION(poPresContext, "PrintObject has not been reflowed");
   NS_ASSERTION(poPresContext->Type() != nsPresContext::eContext_PrintPreview,
                "How did this context end up here?");
 
@@ -2526,27 +2523,27 @@ nsPrintEngine::DoPrint(nsPrintObject * a
         SetIsPrinting(false);
         return NS_ERROR_FAILURE;
       }
 
       mPageSeqFrame = seqFrame;
       pageSequence->StartPrint(poPresContext, mPrt->mPrintSettings, docTitleStr, docURLStr);
 
       // Schedule Page to Print
-      PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO, gFrameTypesStr[aPO->mFrameType]));
+      PR_PL(("Scheduling Print of PO: %p (%s) \n", aPO.get(), gFrameTypesStr[aPO->mFrameType]));
       StartPagePrintTimer(aPO);
     }
   }
 
   return NS_OK;
 }
 
 //---------------------------------------------------------------------
 void
-nsPrintEngine::SetDocAndURLIntoProgress(nsPrintObject* aPO,
+nsPrintEngine::SetDocAndURLIntoProgress(const UniquePtr<nsPrintObject>& aPO,
                                         nsIPrintProgressParams* aParams)
 {
   NS_ASSERTION(aPO, "Must have valid nsPrintObject");
   NS_ASSERTION(aParams, "Must have valid nsIPrintProgressParams");
 
   if (!aPO || !aPO->mDocShell || !aParams) {
     return;
   }
@@ -2728,17 +2725,17 @@ nsPrintEngine::PrintPage(nsPrintObject* 
     endPage = numPages;
     aInRange = true;
   }
 
   // XXX This is wrong, but the actual behavior in the presence of a print
   // range sucks.
   if (mPrt->mPrintFrameType == nsIPrintSettings::kEachFrameSep)
     endPage = mPrt->mNumPrintablePages;
-  
+
   mPrt->DoOnProgressChange(++mPrt->mNumPagesPrinted, endPage, false, 0);
 
   // Print the Page
   // if a print job was cancelled externally, an EndPage or BeginPage may
   // fail and the failure is passed back here.
   // Returning true means we are done printing.
   //
   // When rv == NS_ERROR_ABORT, it means we want out of the
@@ -2929,32 +2926,32 @@ nsPrintEngine::GetPageRangeForSelection(
 
 
 //-----------------------------------------------------------------
 //-- Section: Misc Support Methods
 //-----------------------------------------------------------------
 
 //---------------------------------------------------------------------
 void nsPrintEngine::SetIsPrinting(bool aIsPrinting)
-{ 
+{
   mIsDoingPrinting = aIsPrinting;
   // Calling SetIsPrinting while in print preview confuses the document viewer
   // This is safe because we prevent exiting print preview while printing
   if (!mIsDoingPrintPreview && mDocViewerPrint) {
     mDocViewerPrint->SetIsPrinting(aIsPrinting);
   }
   if (mPrt && aIsPrinting) {
     mPrt->mPreparingForPrint = true;
   }
 }
 
 //---------------------------------------------------------------------
-void nsPrintEngine::SetIsPrintPreview(bool aIsPrintPreview) 
-{ 
-  mIsDoingPrintPreview = aIsPrintPreview; 
+void nsPrintEngine::SetIsPrintPreview(bool aIsPrintPreview)
+{
+  mIsDoingPrintPreview = aIsPrintPreview;
 
   if (mDocViewerPrint) {
     mDocViewerPrint->SetIsPrintPreview(aIsPrintPreview);
   }
 }
 
 //---------------------------------------------------------------------
 void
@@ -2982,17 +2979,17 @@ bool nsPrintEngine::HasFramesetChild(nsI
        child = child->GetNextSibling()) {
     if (child->IsHTMLElement(nsGkAtoms::frameset)) {
       return true;
     }
   }
 
   return false;
 }
- 
+
 
 
 /** ---------------------------------------------------
  *  Get the Focused Frame for a documentviewer
  */
 already_AddRefed<nsPIDOMWindowOuter>
 nsPrintEngine::FindFocusedDOMWindow()
 {
@@ -3092,18 +3089,18 @@ nsPrintEngine::DonePrintingPages(nsPrint
 // Recursively sets the PO items to be printed "As Is"
 // from the given item down into the tree
 void
 nsPrintEngine::SetPrintAsIs(nsPrintObject* aPO, bool aAsIs)
 {
   NS_ASSERTION(aPO, "Pointer is null!");
 
   aPO->mPrintAsIs = aAsIs;
-  for (uint32_t i=0;i<aPO->mKids.Length();i++) {
-    SetPrintAsIs(aPO->mKids[i], aAsIs);
+  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+    SetPrintAsIs(kid.get(), aAsIs);
   }
 }
 
 //-------------------------------------------------------
 // Given a DOMWindow it recursively finds the PO object that matches
 nsPrintObject*
 nsPrintEngine::FindPrintObjectByDOMWin(nsPrintObject* aPO,
                                        nsPIDOMWindowOuter* aDOMWin)
@@ -3116,19 +3113,18 @@ nsPrintEngine::FindPrintObjectByDOMWin(n
     return nullptr;
   }
 
   nsCOMPtr<nsIDocument> doc = aDOMWin->GetDoc();
   if (aPO->mDocument && aPO->mDocument->GetOriginalDocument() == doc) {
     return aPO;
   }
 
-  int32_t cnt = aPO->mKids.Length();
-  for (int32_t i = 0; i < cnt; ++i) {
-    nsPrintObject* po = FindPrintObjectByDOMWin(aPO->mKids[i], aDOMWin);
+  for (const UniquePtr<nsPrintObject>& kid : aPO->mKids) {
+    nsPrintObject* po = FindPrintObjectByDOMWin(kid.get(), aDOMWin);
     if (po) {
       return po;
     }
   }
 
   return nullptr;
 }
 
@@ -3173,25 +3169,24 @@ nsPrintEngine::EnablePOsForPrinting()
   //
   // This means there are not FrameSets,
   // but the document could contain an IFrame
   if (printHowEnable == nsIPrintSettings::kFrameEnableNone) {
 
     // Print all the pages or a sub range of pages
     if (printRangeType == nsIPrintSettings::kRangeAllPages ||
         printRangeType == nsIPrintSettings::kRangeSpecifiedPageRange) {
-      SetPrintPO(mPrt->mPrintObject, true);
+      SetPrintPO(mPrt->mPrintObject.get(), true);
 
       // Set the children so they are PrinAsIs
       // In this case, the children are probably IFrames
       if (mPrt->mPrintObject->mKids.Length() > 0) {
-        for (uint32_t i=0;i<mPrt->mPrintObject->mKids.Length();i++) {
-          nsPrintObject* po = mPrt->mPrintObject->mKids[i];
+        for (const UniquePtr<nsPrintObject>& po : mPrt->mPrintObject->mKids) {
           NS_ASSERTION(po, "nsPrintObject can't be null!");
-          SetPrintAsIs(po);
+          SetPrintAsIs(po.get());
         }
 
         // ***** Another override *****
         mPrt->mPrintFrameType = nsIPrintSettings::kFramesAsIs;
       }
       PR_PL(("PrintFrameType:     %s \n", gPrintFrameTypeStr[mPrt->mPrintFrameType]));
       PR_PL(("HowToEnableFrameUI: %s \n", gFrameHowToEnableStr[printHowEnable]));
       PR_PL(("PrintRange:         %s \n", gPrintRangeStr[printRangeType]));
@@ -3200,17 +3195,17 @@ nsPrintEngine::EnablePOsForPrinting()
 
     // This means we are either printed a selected IFrame or
     // we are printing the current selection
     if (printRangeType == nsIPrintSettings::kRangeSelection) {
 
       // If the currentFocusDOMWin can'r be null if something is selected
       if (mPrt->mCurrentFocusWin) {
         // Find the selected IFrame
-        nsPrintObject * po = FindPrintObjectByDOMWin(mPrt->mPrintObject, mPrt->mCurrentFocusWin);
+        nsPrintObject * po = FindPrintObjectByDOMWin(mPrt->mPrintObject.get(), mPrt->mCurrentFocusWin);
         if (po != nullptr) {
           mPrt->mSelectedPO = po;
           // Makes sure all of its children are be printed "AsIs"
           SetPrintAsIs(po);
 
           // Now, only enable this POs (the selected PO) and all of its children
           SetPrintPO(po, true);
 
@@ -3248,17 +3243,17 @@ nsPrintEngine::EnablePOsForPrinting()
     }
   }
 
   // check to see if there is a selection when a FrameSet is present
   if (printRangeType == nsIPrintSettings::kRangeSelection) {
     // If the currentFocusDOMWin can'r be null if something is selected
     if (mPrt->mCurrentFocusWin) {
       // Find the selected IFrame
-      nsPrintObject * po = FindPrintObjectByDOMWin(mPrt->mPrintObject, mPrt->mCurrentFocusWin);
+      nsPrintObject * po = FindPrintObjectByDOMWin(mPrt->mPrintObject.get(), mPrt->mCurrentFocusWin);
       if (po != nullptr) {
         mPrt->mSelectedPO = po;
         // Makes sure all of its children are be printed "AsIs"
         SetPrintAsIs(po);
 
         // Now, only enable this POs (the selected PO) and all of its children
         SetPrintPO(po, true);
 
@@ -3279,28 +3274,28 @@ nsPrintEngine::EnablePOsForPrinting()
         PR_PL(("PrintRange:         %s \n", gPrintRangeStr[printRangeType]));
         return NS_OK;
       }
     }
   }
 
   // If we are printing "AsIs" then sets all the POs to be printed as is
   if (mPrt->mPrintFrameType == nsIPrintSettings::kFramesAsIs) {
-    SetPrintAsIs(mPrt->mPrintObject);
-    SetPrintPO(mPrt->mPrintObject, true);
+    SetPrintAsIs(mPrt->mPrintObject.get());
+    SetPrintPO(mPrt->mPrintObject.get(), true);
     return NS_OK;
   }
 
   // If we are printing the selected Frame then
   // find that PO for that selected DOMWin and set it all of its
   // children to be printed
   if (mPrt->mPrintFrameType == nsIPrintSettings::kSelectedFrame) {
 
     if ((mPrt->mIsParentAFrameSet && mPrt->mCurrentFocusWin) || mPrt->mIsIFrameSelected) {
-      nsPrintObject * po = FindPrintObjectByDOMWin(mPrt->mPrintObject, mPrt->mCurrentFocusWin);
+      nsPrintObject * po = FindPrintObjectByDOMWin(mPrt->mPrintObject.get(), mPrt->mCurrentFocusWin);
       if (po != nullptr) {
         mPrt->mSelectedPO = po;
         // NOTE: Calling this sets the "po" and
         // we don't want to do this for documents that have no children,
         // because then the "DoEndPage" gets called and it shouldn't
         if (po->mKids.Length() > 0) {
           // Makes sure that itself, and all of its children are printed "AsIs"
           SetPrintAsIs(po);
@@ -3311,17 +3306,17 @@ nsPrintEngine::EnablePOsForPrinting()
       }
     }
     return NS_OK;
   }
 
   // If we are print each subdoc separately,
   // then don't print any of the FraneSet Docs
   if (mPrt->mPrintFrameType == nsIPrintSettings::kEachFrameSep) {
-    SetPrintPO(mPrt->mPrintObject, true);
+    SetPrintPO(mPrt->mPrintObject.get(), true);
     int32_t cnt = mPrt->mPrintDocList.Length();
     for (int32_t i=0;i<cnt;i++) {
       nsPrintObject* po = mPrt->mPrintDocList.ElementAt(i);
       NS_ASSERTION(po, "nsPrintObject can't be null!");
       if (po->mFrameType == eFrameSet) {
         po->mDontPrint = true;
       }
     }
@@ -3490,17 +3485,17 @@ nsPrintEngine::FinishPrintPreview()
 
 //-----------------------------------------------------------------
 //-- Done: Finishing up or Cleaning up
 //-----------------------------------------------------------------
 
 
 /*=============== Timer Related Code ======================*/
 nsresult
-nsPrintEngine::StartPagePrintTimer(nsPrintObject* aPO)
+nsPrintEngine::StartPagePrintTimer(const UniquePtr<nsPrintObject>& aPO)
 {
   if (!mPagePrintTimer) {
     // Get the delay time in between the printing of each page
     // this gives the user more time to press cancel
     int32_t printPageDelay = 50;
     mPrt->mPrintSettings->GetPrintPageDelay(&printPageDelay);
 
     RefPtr<nsPagePrintTimer> timer =
@@ -3514,21 +3509,21 @@ nsPrintEngine::StartPagePrintTimer(nsPri
       printSession->GetRemotePrintJob(getter_AddRefs(remotePrintJob));
       if (NS_SUCCEEDED(rv) && remotePrintJob) {
         remotePrintJob->SetPagePrintTimer(mPagePrintTimer);
         remotePrintJob->SetPrintEngine(this);
       }
     }
   }
 
-  return mPagePrintTimer->Start(aPO);
+  return mPagePrintTimer->Start(aPO.get());
 }
 
 /*=============== nsIObserver Interface ======================*/
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsPrintEngine::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   rv = InitPrintDocConstruction(true);
   if (!mIsDoingPrinting && mPrtPreview) {
       mPrtPreview->OnEndPrinting();
   }
@@ -3838,17 +3833,19 @@ static void DumpPrintObjectsTree(nsPrint
     NS_ASSERTION(po, "nsPrintObject can't be null!");
     for (int32_t k=0;k<aLevel;k++) fprintf(fd, "  ");
     fprintf(fd, "%s %p %p %p %p %d %d,%d,%d,%d\n", types[po->mFrameType], po, po->mDocShell.get(), po->mSeqFrame,
            po->mPageFrame, po->mPageNum, po->mRect.x, po->mRect.y, po->mRect.width, po->mRect.height);
   }
 }
 
 //-------------------------------------------------------------
-static void GetDocTitleAndURL(nsPrintObject* aPO, nsACString& aDocStr, nsACString& aURLStr)
+static void GetDocTitleAndURL(const UniquePtr<nsPrintObject>& aPO,
+                              nsACString& aDocStr,
+                              nsACString& aURLStr)
 {
   nsAutoString docTitleStr;
   nsAutoString docURLStr;
   nsPrintEngine::GetDisplayTitleAndURL(aPO,
                                        docTitleStr, docURLStr,
                                        nsPrintEngine::eDocTitleDefURLDoc);
   aDocStr = NS_ConvertUTF16toUTF8(docTitleStr);
   aURLStr = NS_ConvertUTF16toUTF8(docURLStr);
@@ -3910,18 +3907,18 @@ static void DumpPrintObjectsListStart(co
   NS_ASSERTION(aStr, "Pointer is null!");
   NS_ASSERTION(aDocList, "Pointer is null!");
 
   PR_PL(("%s\n", aStr));
   DumpPrintObjectsList(aDocList);
 }
 
 #define DUMP_DOC_LIST(_title) DumpPrintObjectsListStart((_title), mPrt->mPrintDocList);
-#define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject);
-#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject, mPrt->mPrintDC);
+#define DUMP_DOC_TREE DumpPrintObjectsTree(mPrt->mPrintObject.get());
+#define DUMP_DOC_TREELAYOUT DumpPrintObjectsTreeLayout(mPrt->mPrintObject.get(), mPrt->mPrintDC);
 
 #else
 #define DUMP_DOC_LIST(_title)
 #define DUMP_DOC_TREE
 #define DUMP_DOC_TREELAYOUT
 #endif
 
 //---------------------------------------------------------------
--- a/layout/printing/nsPrintEngine.h
+++ b/layout/printing/nsPrintEngine.h
@@ -73,17 +73,17 @@ public:
     eDocTitleDefURLDoc
   };
 
   nsPrintEngine();
 
   void Destroy();
   void DestroyPrintingData();
 
-  nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint, 
+  nsresult Initialize(nsIDocumentViewerPrint* aDocViewerPrint,
                       nsIDocShell*            aContainer,
                       nsIDocument*            aDocument,
                       float                   aScreenDPI,
                       FILE*                   aDebugFile);
 
   nsresult GetSeqFrameAndCountPages(nsIFrame*& aSeqFrame, int32_t& aCount);
 
   //
@@ -92,62 +92,64 @@ public:
   nsresult DocumentReadyForPrinting();
   nsresult GetSelectionDocument(nsIDeviceContextSpec * aDevSpec,
                                 nsIDocument ** aNewDoc);
 
   nsresult SetupToPrintContent();
   nsresult EnablePOsForPrinting();
   nsPrintObject* FindSmallestSTF();
 
-  bool     PrintDocContent(nsPrintObject* aPO, nsresult& aStatus);
-  nsresult DoPrint(nsPrintObject * aPO);
+  bool PrintDocContent(const mozilla::UniquePtr<nsPrintObject>& aPO,
+                       nsresult& aStatus);
+  nsresult DoPrint(const mozilla::UniquePtr<nsPrintObject>& aPO);
 
   void SetPrintPO(nsPrintObject* aPO, bool aPrint);
 
   void TurnScriptingOn(bool aDoTurnOn);
   bool CheckDocumentForPPCaching();
   void InstallPrintPreviewListener();
 
   // nsIDocumentViewerPrint Printing Methods
   bool     HasPrintCallbackCanvas();
   bool     PrePrintPage();
   bool     PrintPage(nsPrintObject* aPOect, bool& aInRange);
   bool     DonePrintingPages(nsPrintObject* aPO, nsresult aResult);
 
   //---------------------------------------------------------------------
   void BuildDocTree(nsIDocShell *      aParentNode,
                     nsTArray<nsPrintObject*> * aDocList,
-                    nsPrintObject *            aPO);
-  nsresult ReflowDocList(nsPrintObject * aPO, bool aSetPixelScale);
+                    const mozilla::UniquePtr<nsPrintObject>& aPO);
+  nsresult ReflowDocList(const mozilla::UniquePtr<nsPrintObject>& aPO,
+                         bool aSetPixelScale);
 
-  nsresult ReflowPrintObject(nsPrintObject * aPO);
+  nsresult ReflowPrintObject(const mozilla::UniquePtr<nsPrintObject>& aPO);
 
-  void CheckForChildFrameSets(nsPrintObject* aPO);
+  void CheckForChildFrameSets(const mozilla::UniquePtr<nsPrintObject>& aPO);
 
   void CalcNumPrintablePages(int32_t& aNumPages);
   void ShowPrintProgress(bool aIsForPrinting, bool& aDoNotify);
   nsresult CleanupOnFailure(nsresult aResult, bool aIsPrinting);
   // If FinishPrintPreview() fails, caller may need to reset the state of the
   // object, for example by calling CleanupOnFailure().
   nsresult FinishPrintPreview();
   static void CloseProgressDialog(nsIWebProgressListener* aWebProgressListener);
-  void SetDocAndURLIntoProgress(nsPrintObject* aPO,
+  void SetDocAndURLIntoProgress(const mozilla::UniquePtr<nsPrintObject>& aPO,
                                 nsIPrintProgressParams* aParams);
   void EllipseLongString(nsAString& aStr, const uint32_t aLen, bool aDoFront);
   nsresult CheckForPrinters(nsIPrintSettings* aPrintSettings);
   void CleanupDocTitleArray(char16_t**& aArray, int32_t& aCount);
 
   bool IsThereARangeSelection(nsPIDOMWindowOuter* aDOMWin);
 
   void FirePrintingErrorEvent(nsresult aPrintError);
   //---------------------------------------------------------------------
 
 
   // Timer Methods
-  nsresult StartPagePrintTimer(nsPrintObject* aPO);
+  nsresult StartPagePrintTimer(const mozilla::UniquePtr<nsPrintObject>& aPO);
 
   bool IsWindowsInOurSubTree(nsPIDOMWindowOuter* aDOMWindow);
   static bool IsParentAFrameSet(nsIDocShell * aParent);
   bool IsThereAnIFrameSelected(nsIDocShell* aDocShell,
                                nsPIDOMWindowOuter* aDOMWin,
                                bool& aIsParentFrameSet);
 
   static nsPrintObject* FindPrintObjectByDOMWin(nsPrintObject* aParentObject,
@@ -157,31 +159,31 @@ public:
   already_AddRefed<nsPIDOMWindowOuter> FindFocusedDOMWindow();
 
   //---------------------------------------------------------------------
   // Static Methods
   //---------------------------------------------------------------------
   static void GetDocumentTitleAndURL(nsIDocument* aDoc,
                                      nsAString&   aTitle,
                                      nsAString&   aURLStr);
-  void GetDisplayTitleAndURL(nsPrintObject*   aPO,
+  void GetDisplayTitleAndURL(const mozilla::UniquePtr<nsPrintObject>& aPO,
                              nsAString&       aTitle,
                              nsAString&       aURLStr,
                              eDocTitleDefault aDefType);
 
   static bool HasFramesetChild(nsIContent* aContent);
 
   bool     CheckBeforeDestroy();
   nsresult Cancelled();
 
   nsIPresShell* GetPrintPreviewPresShell() {return mPrtPreview->mPrintObject->mPresShell;}
 
   float GetPrintPreviewScale() { return mPrtPreview->mPrintObject->
                                         mPresContext->GetPrintPreviewScale(); }
-  
+
   static nsIPresShell* GetPresShellFor(nsIDocShell* aDocShell);
 
   // These calls also update the DocViewer
   void SetIsPrinting(bool aIsPrinting);
   bool GetIsPrinting()
   {
     return mIsDoingPrinting;
   }
@@ -211,17 +213,17 @@ protected:
                        nsIWebProgressListener* aWebProgressListener,
                        nsIDOMDocument* aDoc);
 
   nsresult DoCommonPrint(bool aIsPrintPreview, nsIPrintSettings* aPrintSettings,
                          nsIWebProgressListener* aWebProgressListener,
                          nsIDOMDocument* aDoc);
 
   void FirePrintCompletionEvent();
-  static nsresult GetSeqFrameAndCountPagesInternal(nsPrintObject*  aPO,
+  static nsresult GetSeqFrameAndCountPagesInternal(const mozilla::UniquePtr<nsPrintObject>& aPO,
                                                    nsIFrame*&      aSeqFrame,
                                                    int32_t&        aCount);
 
   static nsresult FindSelectionBoundsWithList(nsFrameList::Enumerator& aChildFrames,
                                               nsIFrame *      aParentFrame,
                                               nsRect&         aRect,
                                               nsIFrame *&     aStartFrame,
                                               nsRect&         aStartRect,
@@ -238,34 +240,36 @@ protected:
   static nsresult GetPageRangeForSelection(nsIPageSequenceFrame* aPageSeqFrame,
                                            nsIFrame**            aStartFrame,
                                            int32_t&              aStartPageNum,
                                            nsRect&               aStartRect,
                                            nsIFrame**            aEndFrame,
                                            int32_t&              aEndPageNum,
                                            nsRect&               aEndRect);
 
-  static void MapContentForPO(nsPrintObject* aPO, nsIContent* aContent);
+  static void MapContentForPO(const mozilla::UniquePtr<nsPrintObject>& aPO,
+                              nsIContent* aContent);
 
-  static void MapContentToWebShells(nsPrintObject* aRootPO, nsPrintObject* aPO);
+  static void MapContentToWebShells(const mozilla::UniquePtr<nsPrintObject>& aRootPO,
+                                    const mozilla::UniquePtr<nsPrintObject>& aPO);
 
   static void SetPrintAsIs(nsPrintObject* aPO, bool aAsIs = true);
 
   void DisconnectPagePrintTimer();
 
   // Static member variables
   bool mIsCreatingPrintPreview;
   bool mIsDoingPrinting;
   bool mIsDoingPrintPreview; // per DocumentViewer
   bool mProgressDialogIsShown;
 
   nsCOMPtr<nsIDocumentViewerPrint> mDocViewerPrint;
   nsWeakPtr               mContainer;
   float                   mScreenDPI;
-  
+
   mozilla::UniquePtr<nsPrintData> mPrt;
   nsPagePrintTimer*       mPagePrintTimer;
   WeakFrame               mPageSeqFrame;
 
   // Print Preview
   mozilla::UniquePtr<nsPrintData> mPrtPreview;
   mozilla::UniquePtr<nsPrintData> mOldPrtPreview;
 
--- a/layout/printing/nsPrintObject.cpp
+++ b/layout/printing/nsPrintObject.cpp
@@ -26,34 +26,30 @@ nsPrintObject::nsPrintObject() :
 {
   MOZ_COUNT_CTOR(nsPrintObject);
 }
 
 
 nsPrintObject::~nsPrintObject()
 {
   MOZ_COUNT_DTOR(nsPrintObject);
-  for (uint32_t i=0;i<mKids.Length();i++) {
-    nsPrintObject* po = mKids[i];
-    delete po;
-  }
 
   DestroyPresentation();
   if (mDidCreateDocShell && mDocShell) {
     nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(mDocShell));
     if (baseWin) {
       baseWin->Destroy();
     }
-  }                            
+  }
   mDocShell = nullptr;
-  mTreeOwner = nullptr; // mTreeOwner must be released after mDocShell; 
+  mTreeOwner = nullptr; // mTreeOwner must be released after mDocShell;
 }
 
 //------------------------------------------------------------------
-nsresult 
+nsresult
 nsPrintObject::Init(nsIDocShell* aDocShell, nsIDOMDocument* aDoc,
                     bool aPrintPreview)
 {
   mPrintPreview = aPrintPreview;
 
   if (mPrintPreview || mParent) {
     mDocShell = aDocShell;
   } else {
@@ -92,17 +88,17 @@ nsPrintObject::Init(nsIDocShell* aDocShe
   NS_ENSURE_STATE(clonedDOMDoc);
 
   viewer->SetDOMDocument(clonedDOMDoc);
   return NS_OK;
 }
 
 //------------------------------------------------------------------
 // Resets PO by destroying the presentation
-void 
+void
 nsPrintObject::DestroyPresentation()
 {
   if (mPresShell) {
     mPresShell->EndObservingDocument();
     nsAutoScriptBlocker scriptBlocker;
     nsCOMPtr<nsIPresShell> shell = mPresShell;
     mPresShell = nullptr;
     shell->Destroy();
--- a/layout/printing/nsPrintObject.h
+++ b/layout/printing/nsPrintObject.h
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 nsPrintObject_h___
 #define nsPrintObject_h___
 
 #include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
 
 // Interfaces
 #include "nsCOMPtr.h"
 #include "nsIPresShell.h"
 #include "nsViewManager.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeOwner.h"
 
@@ -44,19 +45,19 @@ public:
   nsCOMPtr<nsIDocument>    mDocument;
 
   RefPtr<nsPresContext>  mPresContext;
   nsCOMPtr<nsIPresShell>   mPresShell;
   RefPtr<nsViewManager> mViewManager;
 
   nsCOMPtr<nsIContent>     mContent;
   PrintObjectType  mFrameType;
-  
-  nsTArray<nsPrintObject*> mKids;
-  nsPrintObject*   mParent;
+
+  nsTArray<mozilla::UniquePtr<nsPrintObject>> mKids;
+  nsPrintObject*   mParent; // This is a non-owning pointer.
   bool             mHasBeenPrinted;
   bool             mDontPrint;
   bool             mPrintAsIs;
   bool             mInvisible;        // Indicates PO is set to not visible by CSS
   bool             mPrintPreview;
   bool             mDidCreateDocShell;
   float            mShrinkRatio;
   float            mZoomRatio;
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -24,16 +24,20 @@ using namespace mozilla;
 using namespace mozilla::dom;
 
 ServoStyleSet::ServoStyleSet()
   : mPresContext(nullptr)
   , mBatching(0)
 {
 }
 
+ServoStyleSet::~ServoStyleSet()
+{
+}
+
 void
 ServoStyleSet::Init(nsPresContext* aPresContext)
 {
   mPresContext = aPresContext;
   mRawSet.reset(Servo_StyleSet_Init(aPresContext));
 
   // Now that we have an mRawSet, go ahead and notify about whatever stylesheets
   // we have so far.
@@ -72,16 +76,19 @@ ServoStyleSet::BeginShutdown()
   while (Element* root = iter.GetNextStyleRoot()) {
     ServoRestyleManager::ClearServoDataFromSubtree(root);
   }
 }
 
 void
 ServoStyleSet::Shutdown()
 {
+  // Make sure we drop our cached style contexts before the presshell arena
+  // starts going away.
+  ClearNonInheritingStyleContexts();
   mRawSet = nullptr;
 }
 
 bool
 ServoStyleSet::GetAuthorStyleDisabled() const
 {
   return false;
 }
@@ -242,33 +249,54 @@ ServoStyleSet::ResolveStyleForText(nsICo
     Servo_ComputedValues_Inherit(mRawSet.get(), parentComputedValues).Consume();
 
   return GetContext(computedValues.forget(), aParentContext,
                     nsCSSAnonBoxes::mozText, CSSPseudoElementType::AnonBox,
                     nullptr);
 }
 
 already_AddRefed<nsStyleContext>
-ServoStyleSet::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
+ServoStyleSet::ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext)
 {
-  // The parent context can be null if the non-element share a style context
-  // with the root of an anonymous subtree.
-  const ServoComputedValues* parent =
-    aParentContext ? aParentContext->StyleSource().AsServoComputedValues() : nullptr;
+  const ServoComputedValues* parent = aParentContext->StyleSource().AsServoComputedValues();
   RefPtr<ServoComputedValues> computedValues =
     Servo_ComputedValues_Inherit(mRawSet.get(), parent).Consume();
   MOZ_ASSERT(computedValues);
 
   return GetContext(computedValues.forget(), aParentContext,
-                    nsCSSAnonBoxes::mozOtherNonElement,
+                    nsCSSAnonBoxes::firstLetterContinuation,
                     CSSPseudoElementType::AnonBox,
                     nullptr);
 }
 
 already_AddRefed<nsStyleContext>
+ServoStyleSet::ResolveStyleForPlaceholder()
+{
+  RefPtr<nsStyleContext>& cache =
+    mNonInheritingStyleContexts[
+      static_cast<nsCSSAnonBoxes::NonInheritingBase>(nsCSSAnonBoxes::NonInheriting::oofPlaceholder)];
+  if (cache) {
+    RefPtr<nsStyleContext> retval = cache;
+    return retval.forget();
+  }
+
+  RefPtr<ServoComputedValues> computedValues =
+    Servo_ComputedValues_Inherit(mRawSet.get(), nullptr).Consume();
+  MOZ_ASSERT(computedValues);
+
+  RefPtr<nsStyleContext> retval =
+    GetContext(computedValues.forget(), nullptr,
+               nsCSSAnonBoxes::oofPlaceholder,
+               CSSPseudoElementType::AnonBox,
+               nullptr);
+  cache = retval;
+  return retval.forget();
+}
+
+already_AddRefed<nsStyleContext>
 ServoStyleSet::ResolvePseudoElementStyle(Element* aOriginatingElement,
                                          CSSPseudoElementType aType,
                                          nsStyleContext* aParentContext,
                                          Element* aPseudoElement)
 {
   if (aPseudoElement) {
     NS_WARNING("stylo: We don't support CSS_PSEUDO_ELEMENT_SUPPORTS_USER_ACTION_STATE yet");
   }
@@ -638,18 +666,27 @@ ServoStyleSet::FillKeyframesForName(cons
                                              &aTimingFunction,
                                              aComputedValues,
                                              &aKeyframes);
 }
 
 void
 ServoStyleSet::RebuildData()
 {
+  ClearNonInheritingStyleContexts();
   Servo_StyleSet_RebuildData(mRawSet.get());
 }
 
 already_AddRefed<ServoComputedValues>
 ServoStyleSet::ResolveServoStyle(Element* aElement)
 {
   return Servo_ResolveStyle(aElement, mRawSet.get()).Consume();
 }
 
+void
+ServoStyleSet::ClearNonInheritingStyleContexts()
+{
+  for (RefPtr<nsStyleContext>& ptr : mNonInheritingStyleContexts) {
+    ptr = nullptr;
+  }  
+}
+
 bool ServoStyleSet::sInServoTraversal = false;
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -10,16 +10,17 @@
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/ServoBindingTypes.h"
 #include "mozilla/ServoElementSnapshot.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/SheetType.h"
 #include "mozilla/UniquePtr.h"
 #include "nsCSSPseudoElements.h"
+#include "nsCSSAnonBoxes.h"
 #include "nsChangeHint.h"
 #include "nsIAtom.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
@@ -55,16 +56,17 @@ public:
     // callers, and are likely to take the main-thread codepath if this function
     // returns false. So we assert against other non-main-thread callers here.
     MOZ_ASSERT_IF(aAssertServoTraversalOrMainThread,
                   sInServoTraversal || NS_IsMainThread());
     return sInServoTraversal;
   }
 
   ServoStyleSet();
+  ~ServoStyleSet();
 
   void Init(nsPresContext* aPresContext);
   void BeginShutdown();
   void Shutdown();
 
   bool GetAuthorStyleDisabled() const;
   nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
 
@@ -77,23 +79,56 @@ public:
                   LazyComputeBehavior aMayCompute);
 
   already_AddRefed<nsStyleContext>
   ResolveStyleFor(dom::Element* aElement,
                   nsStyleContext* aParentContext,
                   LazyComputeBehavior aMayCompute,
                   TreeMatchContext& aTreeMatchContext);
 
+  // Get a style context for a text node (which no rules will match).
+  //
+  // The returned style context will have nsCSSAnonBoxes::mozText as its pseudo.
+  //
+  // (Perhaps mozText should go away and we shouldn't even create style
+  // contexts for such content nodes, when text-combine-upright is not
+  // present.  However, not doing any rule matching for them is a first step.)
   already_AddRefed<nsStyleContext>
   ResolveStyleForText(nsIContent* aTextNode,
                       nsStyleContext* aParentContext);
 
+  // Get a style context for a first-letter continuation (which no rules will
+  // match).
+  //
+  // The returned style context will have
+  // nsCSSAnonBoxes::firstLetterContinuation as its pseudo.
+  //
+  // (Perhaps nsCSSAnonBoxes::firstLetterContinuation should go away and we
+  // shouldn't even create style contexts for such frames.  However, not doing
+  // any rule matching for them is a first step.  And right now we do use this
+  // style context for some things)
   already_AddRefed<nsStyleContext>
-  ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
+  ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext);
 
+  // Get a style context for a placeholder frame (which no rules will match).
+  //
+  // The returned style context will have nsCSSAnonBoxes::oofPlaceholder as
+  // its pseudo.
+  //
+  // (Perhaps nsCSSAnonBoxes::oofPaceholder should go away and we shouldn't even
+  // create style contexts for placeholders.  However, not doing any rule
+  // matching for them is a first step.)
+  already_AddRefed<nsStyleContext>
+  ResolveStyleForPlaceholder();
+
+  // Get a style context for a pseudo-element.  aParentElement must be
+  // non-null.  aPseudoID is the CSSPseudoElementType for the
+  // pseudo-element.  aPseudoElement must be non-null if the pseudo-element
+  // type is one that allows user action pseudo-classes after it or allows
+  // style attributes; otherwise, it is ignored.
   already_AddRefed<nsStyleContext>
   ResolvePseudoElementStyle(dom::Element* aOriginatingElement,
                             mozilla::CSSPseudoElementType aType,
                             nsStyleContext* aParentContext,
                             dom::Element* aPseudoElement);
 
   // Resolves style for a (possibly-pseudo) Element without assuming that the
   // style has been resolved, and without worrying about setting the style
@@ -218,20 +253,31 @@ private:
 
   /**
    * Perform all lazy operations required before traversing
    * a subtree.  Returns whether a post-traversal is required.
    */
   bool PrepareAndTraverseSubtree(RawGeckoElementBorrowed aRoot,
                                  mozilla::TraversalRootBehavior aRootBehavior);
 
+  /**
+   * Clear our cached mNonInheritingStyleContexts.  We do this when we want to
+   * make sure those style contexts won't live too long (e.g. when rebuilding
+   * all style data or when shutting down the style set).
+   */
+  void ClearNonInheritingStyleContexts();
+
   nsPresContext* mPresContext;
   UniquePtr<RawServoStyleSet> mRawSet;
   EnumeratedArray<SheetType, SheetType::Count,
                   nsTArray<RefPtr<ServoStyleSheet>>> mSheets;
   int32_t mBatching;
 
+  // Stores pointers to our cached style contexts for non-inheriting anonymous
+  // boxes.
+  RefPtr<nsStyleContext> mNonInheritingStyleContexts[static_cast<nsCSSAnonBoxes::NonInheritingBase>(nsCSSAnonBoxes::NonInheriting::_Count)];
+
   static bool sInServoTraversal;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_ServoStyleSet_h
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -121,17 +121,19 @@ public:
     ResolveStyleFor(dom::Element* aElement,
                     nsStyleContext* aParentContext,
                     LazyComputeBehavior aMayCompute,
                     TreeMatchContext& aTreeMatchContext);
     inline already_AddRefed<nsStyleContext>
     ResolveStyleForText(nsIContent* aTextNode,
                         nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
-    ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
+    ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext);
+    inline already_AddRefed<nsStyleContext>
+    ResolveStyleForPlaceholder();
     inline already_AddRefed<nsStyleContext>
     ResolvePseudoElementStyle(dom::Element* aParentElement,
                               mozilla::CSSPseudoElementType aType,
                               nsStyleContext* aParentContext,
                               dom::Element* aPseudoElement);
     inline already_AddRefed<nsStyleContext>
     ResolveAnonymousBoxStyle(nsIAtom* aPseudoTag, nsStyleContext* aParentContext,
                              uint32_t aFlags = 0);
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -97,19 +97,25 @@ StyleSetHandle::Ptr::ResolveStyleFor(dom
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolveStyleForText(nsIContent* aTextNode,
                                          nsStyleContext* aParentContext)
 {
   FORWARD(ResolveStyleForText, (aTextNode, aParentContext));
 }
 
 already_AddRefed<nsStyleContext>
-StyleSetHandle::Ptr::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
+StyleSetHandle::Ptr::ResolveStyleForPlaceholder()
 {
-  FORWARD(ResolveStyleForOtherNonElement, (aParentContext));
+  FORWARD(ResolveStyleForPlaceholder, ());
+}
+
+already_AddRefed<nsStyleContext>
+StyleSetHandle::Ptr::ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext)
+{
+  FORWARD(ResolveStyleForFirstLetterContinuation, (aParentContext));
 }
 
 already_AddRefed<nsStyleContext>
 StyleSetHandle::Ptr::ResolvePseudoElementStyle(dom::Element* aParentElement,
                                                CSSPseudoElementType aType,
                                                nsStyleContext* aParentContext,
                                                dom::Element* aPseudoElement)
 {
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -465,33 +465,26 @@ private:
     nsPresContext* aPresContext,
     nsCSSKeyframeRule* aKeyframeRule,
     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction);
   nsTArray<PropertyValuePair> GetKeyframePropertyValues(
     nsPresContext* aPresContext,
     nsCSSKeyframeRule* aKeyframeRule,
     nsCSSPropertyIDSet& aAnimatedProperties);
   void FillInMissingKeyframeValues(
-    nsPresContext* aPresContext,
     nsCSSPropertyIDSet aAnimatedProperties,
     nsCSSPropertyIDSet aPropertiesSetAtStart,
     nsCSSPropertyIDSet aPropertiesSetAtEnd,
     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
     nsTArray<Keyframe>& aKeyframes);
-  void AppendProperty(nsPresContext* aPresContext,
-                      nsCSSPropertyID aProperty,
-                      nsTArray<PropertyValuePair>& aPropertyValues);
-  nsCSSValue GetComputedValue(nsPresContext* aPresContext,
-                              nsCSSPropertyID aProperty);
 
   RefPtr<nsStyleContext> mStyleContext;
   NonOwningAnimationTarget mTarget;
 
   ResolvedStyleCache mResolvedStyles;
-  RefPtr<nsStyleContext> mStyleWithoutAnimation;
 };
 
 static Maybe<ComputedTimingFunction>
 ConvertTimingFunction(const nsTimingFunction& aTimingFunction);
 
 template<class BuilderType>
 static void
 UpdateOldAnimationPropertiesWithNew(
@@ -800,19 +793,19 @@ GeckoCSSAnimationBuilder::BuildAnimation
       keyframes.RemoveElementAt(keyframeIdx - 1);
       // existingKeyframe might dangle now
     }
   }
 
   // Finally, we need to look for any animated properties that have an
   // implicit 'to' or 'from' value and fill in the appropriate keyframe
   // with the current computed style.
-  FillInMissingKeyframeValues(aPresContext, animatedProperties,
-                              propertiesSetAtStart, propertiesSetAtEnd,
-                              inheritedTimingFunction, keyframes);
+  FillInMissingKeyframeValues(animatedProperties, propertiesSetAtStart,
+                              propertiesSetAtEnd, inheritedTimingFunction,
+                              keyframes);
 
   return keyframes;
 }
 
 Maybe<ComputedTimingFunction>
 GeckoCSSAnimationBuilder::GetKeyframeTimingFunction(
     nsPresContext* aPresContext,
     nsCSSKeyframeRule* aKeyframeRule,
@@ -922,17 +915,16 @@ FindMatchingKeyframe(
     }
     ++aIndex;
   }
   return false;
 }
 
 void
 GeckoCSSAnimationBuilder::FillInMissingKeyframeValues(
-    nsPresContext* aPresContext,
     nsCSSPropertyIDSet aAnimatedProperties,
     nsCSSPropertyIDSet aPropertiesSetAtStart,
     nsCSSPropertyIDSet aPropertiesSetAtEnd,
     const Maybe<ComputedTimingFunction>& aInheritedTimingFunction,
     nsTArray<Keyframe>& aKeyframes)
 {
   static const size_t kNotSet = static_cast<size_t>(-1);
 
@@ -979,71 +971,28 @@ GeckoCSSAnimationBuilder::FillInMissingK
   for (nsCSSPropertyID prop = nsCSSPropertyID(0);
        prop < eCSSProperty_COUNT_no_shorthands;
        prop = nsCSSPropertyID(prop + 1)) {
     if (!aAnimatedProperties.HasProperty(prop)) {
       continue;
     }
 
     if (startKeyframe && !aPropertiesSetAtStart.HasProperty(prop)) {
-      AppendProperty(aPresContext, prop, startKeyframe->mPropertyValues);
+      PropertyValuePair propertyValue;
+      propertyValue.mProperty = prop;
+      startKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
     }
     if (endKeyframe && !aPropertiesSetAtEnd.HasProperty(prop)) {
-      AppendProperty(aPresContext, prop, endKeyframe->mPropertyValues);
+      PropertyValuePair propertyValue;
+      propertyValue.mProperty = prop;
+      endKeyframe->mPropertyValues.AppendElement(Move(propertyValue));
     }
   }
 }
 
-void
-GeckoCSSAnimationBuilder::AppendProperty(
-    nsPresContext* aPresContext,
-    nsCSSPropertyID aProperty,
-    nsTArray<PropertyValuePair>& aPropertyValues)
-{
-  PropertyValuePair propertyValue;
-  propertyValue.mProperty = aProperty;
-  propertyValue.mValue = GetComputedValue(aPresContext, aProperty);
-
-  aPropertyValues.AppendElement(Move(propertyValue));
-}
-
-nsCSSValue
-GeckoCSSAnimationBuilder::GetComputedValue(nsPresContext* aPresContext,
-                                           nsCSSPropertyID aProperty)
-{
-  nsCSSValue result;
-  StyleAnimationValue computedValue;
-
-  if (!mStyleWithoutAnimation) {
-    MOZ_ASSERT(aPresContext->StyleSet()->IsGecko(),
-               "ServoStyleSet should not use nsAnimationManager for "
-               "animations");
-    mStyleWithoutAnimation = aPresContext->StyleSet()->AsGecko()->
-      ResolveStyleByRemovingAnimation(mTarget.mElement, mStyleContext,
-                                      eRestyle_AllHintsWithAnimations);
-  }
-
-  if (StyleAnimationValue::ExtractComputedValue(aProperty,
-                                                mStyleWithoutAnimation,
-                                                computedValue)) {
-    DebugOnly<bool> uncomputeResult =
-      StyleAnimationValue::UncomputeValue(aProperty, Move(computedValue),
-                                          result);
-    MOZ_ASSERT(uncomputeResult,
-               "Unable to get specified value from computed value");
-  }
-
-  // If we hit this assertion, it probably means we are fetching a value from
-  // the computed style that we don't know how to represent as
-  // a StyleAnimationValue.
-  MOZ_ASSERT(result.GetUnit() != eCSSUnit_Null, "Got null computed value");
-
-  return result;
-}
-
 template<class BuilderType>
 static nsAnimationManager::OwningCSSAnimationPtrArray
 BuildAnimations(nsPresContext* aPresContext,
                 const NonOwningAnimationTarget& aTarget,
                 const nsStyleAutoArray<StyleAnimation>& aStyleAnimations,
                 uint32_t aStyleAnimationNameCount,
                 BuilderType& aBuilder,
                 nsAnimationManager::CSSAnimationCollection* aCollection)
--- a/layout/style/nsCSSAnonBoxList.h
+++ b/layout/style/nsCSSAnonBoxList.h
@@ -3,34 +3,49 @@
  * 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/. */
 
 /* atom list for CSS anonymous boxes */
 
 /*
  * This file contains the list of nsIAtoms and their values for CSS
  * pseudo-element-ish things used internally for anonymous boxes.  It is
- * designed to be used as inline input to nsCSSAnonBoxes.cpp *only*
- * through the magic of C preprocessing.  All entries must be enclosed
- * in the macro CSS_ANON_BOX which will have cruel and unusual things
- * done to it.  The entries should be kept in some sort of logical
- * order.  The first argument to CSS_ANON_BOX is the C++ identifier of
- * the atom.  The second argument is the string value of the atom.
+ * designed to be used as inline input to nsCSSAnonBoxes.cpp *only* through the
+ * magic of C preprocessing.  All entries must be enclosed in the macros
+ * CSS_ANON_BOX and CSS_NON_INHERITING_ANON_BOX which will have cruel and
+ * unusual things done to it.  The entries should be kept in some sort of
+ * logical order.  The first argument to
+ * CSS_ANON_BOX/CSS_NON_INHERITING_ANON_BOX is the C++ identifier of the atom.
+ * The second argument is the string value of the atom.
+ *
+ * CSS_NON_INHERITING_ANON_BOX is used for anon boxes that never inherit style
+ * from anything.  This means all their property values are the initial values
+ * of those properties.
  */
 
 // OUTPUT_CLASS=nsCSSAnonBoxes
-// MACRO_NAME=CSS_ANON_BOX
+// MACRO_NAME=CSS_ANON_BOX/CSS_NON_INHERITING_ANON_BOX
 
-// ::-moz-text and ::-moz-other-non-element are non-elements which no
-// rule will match.
+#ifndef CSS_NON_INHERITING_ANON_BOX
+#  ifdef DEFINED_CSS_NON_INHERITING_ANON_BOX
+#    error "Recursive includes of nsCSSAnonBoxList.h?"
+#  endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
+#  define CSS_NON_INHERITING_ANON_BOX CSS_ANON_BOX
+#  define DEFINED_CSS_NON_INHERITING_ANON_BOX
+#endif /* CSS_NON_INHERITING_ANON_BOX */
+
+// ::-moz-text, ::-moz-oof-placeholder, and ::-moz-first-letter-continuation are
+// non-elements which no rule will match.
 CSS_ANON_BOX(mozText, ":-moz-text")
-// This anonymous box has two uses:
-// 1. placeholder frames,
-// 2. nsFirstLetterFrames for content outside the ::first-letter.
-CSS_ANON_BOX(mozOtherNonElement, ":-moz-other-non-element")
+// placeholder frames for out of flows.  Note that :-moz-placeholder is used for
+// the pseudo-element that represents the placeholder text in <input
+// placeholder="foo">, so we need a different string here.
+CSS_NON_INHERITING_ANON_BOX(oofPlaceholder, ":-moz-oof-placeholder")
+// nsFirstLetterFrames for content outside the ::first-letter.
+CSS_ANON_BOX(firstLetterContinuation, ":-moz-first-letter-continuation")
 
 CSS_ANON_BOX(mozAnonymousBlock, ":-moz-anonymous-block")
 CSS_ANON_BOX(mozAnonymousPositionedBlock, ":-moz-anonymous-positioned-block")
 CSS_ANON_BOX(mozMathMLAnonymousBlock, ":-moz-mathml-anonymous-block")
 CSS_ANON_BOX(mozXULAnonymousBlock, ":-moz-xul-anonymous-block")
 
 // Framesets
 CSS_ANON_BOX(horizontalFramesetBorder, ":-moz-hframeset-border")
@@ -95,8 +110,13 @@ CSS_ANON_BOX(moztreecheckbox, ":-moz-tre
 CSS_ANON_BOX(moztreeprogressmeter, ":-moz-tree-progressmeter")
 CSS_ANON_BOX(moztreedropfeedback, ":-moz-tree-drop-feedback")
 #endif
 
 CSS_ANON_BOX(mozSVGMarkerAnonChild, ":-moz-svg-marker-anon-child")
 CSS_ANON_BOX(mozSVGOuterSVGAnonChild, ":-moz-svg-outer-svg-anon-child")
 CSS_ANON_BOX(mozSVGForeignContent, ":-moz-svg-foreign-content")
 CSS_ANON_BOX(mozSVGText, ":-moz-svg-text")
+
+#ifdef DEFINED_CSS_NON_INHERITING_ANON_BOX
+#  undef DEFINED_CSS_NON_INHERITING_ANON_BOX
+#  undef CSS_NON_INHERITING_ANON_BOX
+#endif /* DEFINED_CSS_NON_INHERITING_ANON_BOX */
--- a/layout/style/nsCSSAnonBoxes.cpp
+++ b/layout/style/nsCSSAnonBoxes.cpp
@@ -20,19 +20,29 @@ using namespace mozilla;
 #undef CSS_ANON_BOX
 
 #define CSS_ANON_BOX(name_, value_) \
   NS_STATIC_ATOM_BUFFER(name_##_buffer, value_)
 #include "nsCSSAnonBoxList.h"
 #undef CSS_ANON_BOX
 
 static const nsStaticAtom CSSAnonBoxes_info[] = {
-#define CSS_ANON_BOX(name_, value_) \
+  // Put the non-inheriting anon boxes first, so we can index into them easily.
+#define CSS_ANON_BOX(name_, value_) /* nothing */
+#define CSS_NON_INHERITING_ANON_BOX(name_, value_) \
   NS_STATIC_ATOM(name_##_buffer, (nsIAtom**)&nsCSSAnonBoxes::name_),
 #include "nsCSSAnonBoxList.h"
+#undef CSS_NON_INHERITING_ANON_BOX
+#undef CSS_ANON_BOX
+
+#define CSS_ANON_BOX(name_, value_)                                   \
+  NS_STATIC_ATOM(name_##_buffer, (nsIAtom**)&nsCSSAnonBoxes::name_),
+#define CSS_NON_INHERITING_ANON_BOX(name_, value_) /* nothing */
+#include "nsCSSAnonBoxList.h"
+#undef CSS_NON_INHERITING_ANON_BOX
 #undef CSS_ANON_BOX
 };
 
 void nsCSSAnonBoxes::AddRefAtoms()
 {
   NS_RegisterStaticAtoms(CSSAnonBoxes_info);
 }
 
@@ -45,8 +55,15 @@ bool nsCSSAnonBoxes::IsAnonBox(nsIAtom *
 #ifdef MOZ_XUL
 /* static */ bool
 nsCSSAnonBoxes::IsTreePseudoElement(nsIAtom* aPseudo)
 {
   return StringBeginsWith(nsDependentAtomString(aPseudo),
                           NS_LITERAL_STRING(":-moz-tree-"));
 }
 #endif
+
+/* static */ nsIAtom*
+nsCSSAnonBoxes::GetNonInheritingPseudoAtom(NonInheriting aBoxType)
+{
+  MOZ_ASSERT(aBoxType < NonInheriting::_Count);
+  return *CSSAnonBoxes_info[static_cast<NonInheritingBase>(aBoxType)].mAtom;
+}
--- a/layout/style/nsCSSAnonBoxes.h
+++ b/layout/style/nsCSSAnonBoxes.h
@@ -19,16 +19,49 @@ public:
 
   static void AddRefAtoms();
 
   static bool IsAnonBox(nsIAtom *aAtom);
 #ifdef MOZ_XUL
   static bool IsTreePseudoElement(nsIAtom* aPseudo);
 #endif
   static bool IsNonElement(nsIAtom* aPseudo)
-    { return aPseudo == mozText || aPseudo == mozOtherNonElement; }
+  {
+    return aPseudo == mozText || aPseudo == oofPlaceholder ||
+           aPseudo == firstLetterContinuation;
+  }
 
 #define CSS_ANON_BOX(_name, _value) static nsICSSAnonBoxPseudo* _name;
 #include "nsCSSAnonBoxList.h"
 #undef CSS_ANON_BOX
+
+  typedef uint8_t NonInheritingBase;
+  enum class NonInheriting : NonInheritingBase {
+#define CSS_ANON_BOX(_name, _value) /* nothing */
+#define CSS_NON_INHERITING_ANON_BOX(_name, _value) _name,
+#include "nsCSSAnonBoxList.h"
+#undef CSS_NON_INHERITING_ANON_BOX
+#undef CSS_ANON_BOX
+    _Count
+  };
+
+  // Be careful using this: if we have a lot of non-inheriting anon box types it
+  // might not be very fast.  We may want to think of ways to handle that
+  // (e.g. by moving to an enum instead of an atom, like we did for
+  // pseudo-elements, or by adding a new value of the pseudo-element enum for
+  // non-inheriting anon boxes or something).
+  static bool IsNonInheritingAnonBox(nsIAtom* aPseudo)
+  {
+    return
+#define CSS_ANON_BOX(_name, _value) /* nothing */
+#define CSS_NON_INHERITING_ANON_BOX(_name, _value) _name == aPseudo ||
+#include "nsCSSAnonBoxList.h"
+#undef CSS_NON_INHERITING_ANON_BOX
+#undef CSS_ANON_BOX
+      0;
+  }
+
+  // Get the atom for a given non-inheriting anon box type.  aBoxType must be <
+  // NonInheriting::_Count.
+  static nsIAtom* GetNonInheritingPseudoAtom(NonInheriting aBoxType);
 };
 
 #endif /* nsCSSAnonBoxes_h___ */
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -280,16 +280,17 @@ CSS_KEY(fixed, fixed)
 CSS_KEY(flat, flat)
 CSS_KEY(flex, flex)
 CSS_KEY(flex-end, flex_end)
 CSS_KEY(flex-start, flex_start)
 CSS_KEY(flip, flip)
 CSS_KEY(flow-root, flow_root)
 CSS_KEY(forwards, forwards)
 CSS_KEY(fraktur, fraktur)
+CSS_KEY(frames, frames)
 CSS_KEY(from-image, from_image)
 CSS_KEY(full-width, full_width)
 CSS_KEY(fullscreen, fullscreen)
 CSS_KEY(grab, grab)
 CSS_KEY(grabbing, grabbing)
 CSS_KEY(grad, grad)
 CSS_KEY(grayscale, grayscale)
 CSS_KEY(graytext, graytext)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -1064,16 +1064,17 @@ protected:
   bool ParseShadowList(nsCSSPropertyID aProperty);
   bool ParseShapeOutside(nsCSSValue& aValue);
   bool ParseTransitionProperty();
   bool ParseTransitionTimingFunctionValues(nsCSSValue& aValue);
   bool ParseTransitionTimingFunctionValueComponent(float& aComponent,
                                                      char aStop,
                                                      bool aIsXPoint);
   bool ParseTransitionStepTimingFunctionValues(nsCSSValue& aValue);
+  bool ParseTransitionFramesTimingFunctionValues(nsCSSValue& aValue);
   enum ParseAnimationOrTransitionShorthandResult {
     eParseAnimationOrTransitionShorthand_Values,
     eParseAnimationOrTransitionShorthand_Inherit,
     eParseAnimationOrTransitionShorthand_Error
   };
   ParseAnimationOrTransitionShorthandResult
     ParseAnimationOrTransitionShorthand(const nsCSSPropertyID* aProperties,
                                         const nsCSSValue* aInitialValues,
@@ -7938,16 +7939,23 @@ CSSParserImpl::ParseVariant(nsCSSValue& 
     }
     if (tk->mIdent.LowerCaseEqualsLiteral("steps")) {
       if (!ParseTransitionStepTimingFunctionValues(aValue)) {
         SkipUntil(')');
         return CSSParseResult::Error;
       }
       return CSSParseResult::Ok;
     }
+    if (tk->mIdent.LowerCaseEqualsLiteral("frames")) {
+      if (!ParseTransitionFramesTimingFunctionValues(aValue)) {
+        SkipUntil(')');
+        return CSSParseResult::Error;
+      }
+      return CSSParseResult::Ok;
+    }
   }
   if ((aVariantMask & VARIANT_CALC) &&
       IsCSSTokenCalcFunction(*tk)) {
     // calc() currently allows only lengths, percents, numbers, and integers.
     //
     // Note that VARIANT_NUMBER can be mixed with VARIANT_LENGTH and
     // VARIANT_PERCENTAGE in the list of allowed types (numbers can be used as
     // coefficients).
@@ -16796,16 +16804,49 @@ CSSParserImpl::ParseTransitionStepTiming
   if (!ExpectSymbol(')', true)) {
     return false;
   }
 
   aValue.SetArrayValue(val, eCSSUnit_Steps);
   return true;
 }
 
+bool
+CSSParserImpl::ParseTransitionFramesTimingFunctionValues(nsCSSValue& aValue)
+{
+  NS_ASSERTION(!mHavePushBack &&
+               mToken.mType == eCSSToken_Function &&
+               mToken.mIdent.LowerCaseEqualsLiteral("frames"),
+               "unexpected initial state");
+
+  nsCSSKeyword functionName = nsCSSKeywords::LookupKeyword(mToken.mIdent);
+  MOZ_ASSERT(functionName == eCSSKeyword_frames);
+
+  nsCSSValue frameNumber;
+  if (!ParseSingleTokenOneOrLargerVariant(frameNumber, VARIANT_INTEGER,
+                                          nullptr)) {
+    return false;
+  }
+  MOZ_ASSERT(frameNumber.GetIntValue() >= 1,
+             "Parsing function should've enforced OneOrLarger, per its name");
+
+  // The number of frames must be a positive integer greater than one.
+  if (frameNumber.GetIntValue() == 1) {
+    return false;
+  }
+
+  if (!ExpectSymbol(')', true)) {
+    return false;
+  }
+
+  RefPtr<nsCSSValue::Array> val = aValue.InitFunction(functionName, 1);
+  val->Item(1) = frameNumber;
+  return true;
+}
+
 static nsCSSValueList*
 AppendValueToList(nsCSSValue& aContainer,
                   nsCSSValueList* aTail,
                   const nsCSSValue& aValue)
 {
   nsCSSValueList* entry;
   if (aContainer.GetUnit() == eCSSUnit_Null) {
     MOZ_ASSERT(!aTail, "should not have an entry");
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -6563,19 +6563,23 @@ nsComputedDOMStyle::AppendTimingFunction
                                                    aTimingFunction.mFunc.mY1,
                                                    aTimingFunction.mFunc.mX2,
                                                    aTimingFunction.mFunc.mY2,
                                                    tmp);
       break;
     case nsTimingFunction::Type::StepStart:
     case nsTimingFunction::Type::StepEnd:
       nsStyleUtil::AppendStepsTimingFunction(aTimingFunction.mType,
-                                             aTimingFunction.mSteps,
+                                             aTimingFunction.mStepsOrFrames,
                                              tmp);
       break;
+    case nsTimingFunction::Type::Frames:
+      nsStyleUtil::AppendFramesTimingFunction(aTimingFunction.mStepsOrFrames,
+                                              tmp);
+      break;
     default:
       nsStyleUtil::AppendCubicBezierKeywordTimingFunction(aTimingFunction.mType,
                                                           tmp);
       break;
   }
   timingFunction->SetString(tmp);
   aValueList->AppendCSSValue(timingFunction.forget());
 }
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -5512,16 +5512,28 @@ nsRuleNode::ComputeTimingFunction(const 
         nsTimingFunction::Type type =
           (array->Item(1).GetIntValue() ==
             NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START) ?
               nsTimingFunction::Type::StepStart :
               nsTimingFunction::Type::StepEnd;
         aResult = nsTimingFunction(type, array->Item(0).GetIntValue());
       }
       break;
+    case eCSSUnit_Function:
+      {
+        nsCSSValue::Array* array = aValue.GetArrayValue();
+        NS_ASSERTION(array && array->Count() == 2, "Need 2 items");
+        NS_ASSERTION(array->Item(0).GetKeywordValue() == eCSSKeyword_frames,
+                     "should be frames function");
+        NS_ASSERTION(array->Item(1).GetUnit() == eCSSUnit_Integer,
+                     "unexpected frames function value");
+        aResult = nsTimingFunction(nsTimingFunction::Type::Frames,
+                                   array->Item(1).GetIntValue());
+      }
+      break;
     default:
       NS_NOTREACHED("Invalid transition property unit");
   }
 }
 
 static uint8_t
 GetWillChangeBitFieldFromPropFlags(const nsCSSPropertyID& aProp)
 {
--- a/layout/style/nsStyleContext.cpp
+++ b/layout/style/nsStyleContext.cpp
@@ -860,17 +860,28 @@ nsStyleContext::ApplyStyleFixups(bool aS
 
   // CSS2.1 section 9.2.4 specifies fixups for the 'display' property of
   // the root element.  We can't implement them in nsRuleNode because we
   // don't want to store all display structs that aren't 'block',
   // 'inline', or 'table' in the style context tree on the off chance
   // that the root element has its style reresolved later.  So do them
   // here if needed, by changing the style data, so that other code
   // doesn't get confused by looking at the style data.
-  if (!mParent) {
+  if (!mParent &&
+      // We don't want to blockify various anon boxes that just happen to not
+      // inherit from anything.  So restrict blockification only to actual
+      // elements, the viewport (which should be block anyway, but in SVG
+      // document's isn't because we lazy-load ua.css there), and the ::backdrop
+      // pseudo-element.  This last is explicitly allowed to have any specified
+      // display type in the spec, but computes to a blockified display type per
+      // various provisions of
+      // https://fullscreen.spec.whatwg.org/#new-stacking-layer
+      (!mPseudoTag ||
+       mPseudoTag == nsCSSAnonBoxes::viewport ||
+       mPseudoTag == nsCSSPseudoElements::backdrop)) {
     auto displayVal = disp->mDisplay;
     if (displayVal != mozilla::StyleDisplay::Contents) {
       nsRuleNode::EnsureBlockDisplay(displayVal, true);
     } else {
       // http://dev.w3.org/csswg/css-display/#transformations
       // "... a display-outside of 'contents' computes to block-level
       //  on the root element."
       displayVal = mozilla::StyleDisplay::Block;
@@ -1210,22 +1221,22 @@ nsStyleContext::CalcStyleDifferenceInter
     // Both style contexts have a style-if-visited.
     bool change = false;
 
     // NB: Calling Peek on |this|, not |thisVis|, since callers may look
     // at a struct on |this| without looking at the same struct on
     // |thisVis| (including this function if we skip one of these checks
     // due to change being true already or due to the old style context
     // not having a style-if-visited), but not the other way around.
-#define STYLE_FIELD(name_) thisVisStruct->name_ != otherVisStruct->name_ ||
+#define STYLE_FIELD(name_) thisVisStruct->name_ != otherVisStruct->name_
 #define STYLE_STRUCT(name_, fields_)                                    \
     if (!change && PeekStyle##name_()) {                                \
       const nsStyle##name_* thisVisStruct = thisVis->Style##name_();    \
       const nsStyle##name_* otherVisStruct = otherVis->Style##name_();  \
-      if (MOZ_FOR_EACH(STYLE_FIELD, (), fields_) false) {               \
+      if (MOZ_FOR_EACH_SEPARATED(STYLE_FIELD, (||), (), fields_)) {     \
         change = true;                                                  \
       }                                                                 \
     }
 #include "nsCSSVisitedDependentPropList.h"
 #undef STYLE_STRUCT
 #undef STYLE_FIELD
 
     if (change) {
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -305,16 +305,19 @@ nsStyleSet::BeginReconstruct()
   NS_ASSERTION(!mInReconstruct, "Unmatched begin/end?");
   NS_ASSERTION(mRuleTree, "Reconstructing before first construction?");
   mInReconstruct = true;
 
   // Clear any ArenaRefPtr-managed style contexts, as we don't want them
   // held on to after the rule tree has been reconstructed.
   PresContext()->PresShell()->ClearArenaRefPtrs(eArenaObjectID_nsStyleContext);
 
+  // Clear our cached style contexts for non-inheriting anonymous boxes.
+  ClearNonInheritingStyleContexts();
+
 #ifdef DEBUG
   MOZ_ASSERT(!mOldRootNode);
   mOldRootNode = mRuleTree;
 #endif
 
   // Create a new rule tree root, dropping the reference to our old rule tree.
   // After reconstruction, we will re-enable GC, and allow everything to be
   // collected.
@@ -1825,23 +1828,42 @@ nsStyleSet::ResolveStyleForText(nsIConte
 {
   MOZ_ASSERT(aTextNode && aTextNode->IsNodeOfType(nsINode::eTEXT));
   return GetContext(aParentContext, mRuleTree, nullptr,
                     nsCSSAnonBoxes::mozText,
                     CSSPseudoElementType::AnonBox, nullptr, eNoFlags);
 }
 
 already_AddRefed<nsStyleContext>
-nsStyleSet::ResolveStyleForOtherNonElement(nsStyleContext* aParentContext)
+nsStyleSet::ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext)
 {
   return GetContext(aParentContext, mRuleTree, nullptr,
-                    nsCSSAnonBoxes::mozOtherNonElement,
+                    nsCSSAnonBoxes::firstLetterContinuation,
                     CSSPseudoElementType::AnonBox, nullptr, eNoFlags);
 }
 
+already_AddRefed<nsStyleContext>
+nsStyleSet::ResolveStyleForPlaceholder()
+{
+  RefPtr<nsStyleContext>& cache =
+    mNonInheritingStyleContexts[
+      static_cast<nsCSSAnonBoxes::NonInheritingBase>(nsCSSAnonBoxes::NonInheriting::oofPlaceholder)];
+  if (cache) {
+    RefPtr<nsStyleContext> retval = cache;
+    return retval.forget();
+  }
+
+  RefPtr<nsStyleContext> retval =
+    GetContext(nullptr, mRuleTree, nullptr,
+               nsCSSAnonBoxes::oofPlaceholder,
+               CSSPseudoElementType::AnonBox, nullptr, eNoFlags);
+  cache = retval;
+  return retval.forget();
+}
+
 void
 nsStyleSet::WalkRestrictionRule(CSSPseudoElementType aPseudoType,
                                 nsRuleWalker* aRuleWalker)
 {
   // This needs to match GetPseudoRestriction in nsRuleNode.cpp.
   aRuleWalker->SetLevel(SheetType::Agent, false, false);
   if (aPseudoType == CSSPseudoElementType::firstLetter)
     aRuleWalker->Forward(mFirstLetterRule);
@@ -2259,16 +2281,19 @@ void
 nsStyleSet::BeginShutdown()
 {
   mInShutdown = 1;
 }
 
 void
 nsStyleSet::Shutdown()
 {
+  // Make sure we drop our cached style contexts before the presshell arena
+  // starts going away.
+  ClearNonInheritingStyleContexts();
   mRuleTree = nullptr;
   GCRuleTrees();
   MOZ_ASSERT(mUnusedRuleNodeList.isEmpty());
   MOZ_ASSERT(mUnusedRuleNodeCount == 0);
 }
 
 
 void
@@ -2603,8 +2628,16 @@ nsStyleSet::ClearSelectors()
   if (!mRuleTree) {
     return;
   }
   MOZ_ASSERT(PresContext()->RestyleManager()->IsGecko(),
              "stylo: the style set and restyle manager must have the same "
              "StyleBackendType");
   PresContext()->RestyleManager()->AsGecko()->ClearSelectors();
 }
+
+void
+nsStyleSet::ClearNonInheritingStyleContexts()
+{
+  for (RefPtr<nsStyleContext>& ptr : mNonInheritingStyleContexts) {
+    ptr = nullptr;
+  }
+}
--- a/layout/style/nsStyleSet.h
+++ b/layout/style/nsStyleSet.h
@@ -21,16 +21,17 @@
 #include "mozilla/SheetType.h"
 
 #include "nsIStyleRuleProcessor.h"
 #include "nsBindingManager.h"
 #include "nsRuleNode.h"
 #include "nsTArray.h"
 #include "nsCOMArray.h"
 #include "nsIStyleRule.h"
+#include "nsCSSAnonBoxes.h"
 
 class gfxFontFeatureValueSet;
 class nsCSSKeyframesRule;
 class nsCSSFontFeatureValuesRule;
 class nsCSSPageRule;
 class nsCSSCounterStyleRule;
 class nsICSSPseudoComparator;
 class nsRuleWalker;
@@ -204,28 +205,39 @@ class nsStyleSet final
   // The returned style context will have nsCSSAnonBoxes::mozText as its pseudo.
   //
   // (Perhaps mozText should go away and we shouldn't even create style
   // contexts for such content nodes, when text-combine-upright is not
   // present.  However, not doing any rule matching for them is a first step.)
   already_AddRefed<nsStyleContext>
   ResolveStyleForText(nsIContent* aTextNode, nsStyleContext* aParentContext);
 
-  // Get a style context for a non-element (which no rules will match)
-  // other than a text node, such as placeholder frames, and the
-  // nsFirstLetterFrame for everything after the first letter.
+  // Get a style context for a first-letter continuation (which no rules will
+  // match).
+  //
+  // The returned style context will have
+  // nsCSSAnonBoxes::firstLetterContinuation as its pseudo.
   //
-  // The returned style context will have nsCSSAnonBoxes::mozOtherNonElement as
+  // (Perhaps nsCSSAnonBoxes::firstLetterContinuation should go away and we
+  // shouldn't even create style contexts for such frames.  However, not doing
+  // any rule matching for them is a first step.  And right now we do use this
+  // style context for some things)
+  already_AddRefed<nsStyleContext>
+  ResolveStyleForFirstLetterContinuation(nsStyleContext* aParentContext);
+
+  // Get a style context for a placeholder frame (which no rules will match).
+  //
+  // The returned style context will have nsCSSAnonBoxes::oofPlaceholder as
   // its pseudo.
   //
-  // (Perhaps mozOtherNonElement should go away and we shouldn't even
-  // create style contexts for such content nodes.  However, not doing
-  // any rule matching for them is a first step.)
+  // (Perhaps nsCSSAnonBoxes::oofPlaceholder should go away and we shouldn't
+  // even create style contexts for placeholders.  However, not doing any rule
+  // matching for them is a first step.)
   already_AddRefed<nsStyleContext>
-  ResolveStyleForOtherNonElement(nsStyleContext* aParentContext);
+  ResolveStyleForPlaceholder();
 
   // Get a style context for a pseudo-element.  aParentElement must be
   // non-null.  aPseudoID is the CSSPseudoElementType for the
   // pseudo-element.  aPseudoElement must be non-null if the pseudo-element
   // type is one that allows user action pseudo-classes after it or allows
   // style attributes; otherwise, it is ignored.
   already_AddRefed<nsStyleContext>
   ResolvePseudoElementStyle(mozilla::dom::Element* aParentElement,
@@ -554,16 +566,21 @@ private:
   ResolvePseudoElementStyleInternal(mozilla::dom::Element* aParentElement,
                                     mozilla::CSSPseudoElementType aType,
                                     nsStyleContext* aParentContext,
                                     mozilla::dom::Element* aPseudoElement,
                                     AnimationFlag aAnimationFlag);
 
   nsPresContext* PresContext() { return mRuleTree->PresContext(); }
 
+  // Clear our cached mNonInheritingStyleContexts.  We do this when we want to
+  // make sure those style contexts won't live too long (e.g. at ruletree
+  // reconstruct or shutdown time).
+  void ClearNonInheritingStyleContexts();
+
   // The sheets in each array in mSheets are stored with the most significant
   // sheet last.
   // The arrays for ePresHintSheet, eStyleAttrSheet, eTransitionSheet,
   // eAnimationSheet and eSVGAttrAnimationSheet are always empty.
   // (FIXME:  We should reduce the storage needed for them.)
   mozilla::EnumeratedArray<mozilla::SheetType, mozilla::SheetType::Count,
                            nsTArray<RefPtr<mozilla::CSSStyleSheet>>> mSheets;
 
@@ -617,16 +634,20 @@ private:
   RefPtr<nsInitialStyleRule> mInitialStyleRule;
 
   // Style rule that sets the internal -x-text-zoom property on
   // <svg:text> elements to disable the effect of text zooming.
   RefPtr<nsDisableTextZoomStyleRule> mDisableTextZoomStyleRule;
 
   // whether font feature values lookup object needs initialization
   RefPtr<gfxFontFeatureValueSet> mFontFeatureValuesLookup;
+
+  // Stores pointers to our cached style contexts for non-inheriting anonymous
+  // boxes.
+  RefPtr<nsStyleContext> mNonInheritingStyleContexts[static_cast<nsCSSAnonBoxes::NonInheritingBase>(nsCSSAnonBoxes::NonInheriting::_Count)];
 };
 
 #ifdef MOZILLA_INTERNAL_API
 inline
 void nsRuleNode::AddRef()
 {
   if (mRefCnt++ == 0) {
     MOZ_ASSERT(mPresContext->StyleSet()->IsGecko(),
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3077,23 +3077,23 @@ nsStyleBackground::IsTransparent(nsStyle
 }
 
 void
 nsTimingFunction::AssignFromKeyword(int32_t aTimingFunctionType)
 {
   switch (aTimingFunctionType) {
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_START:
       mType = Type::StepStart;
-      mSteps = 1;
+      mStepsOrFrames = 1;
       return;
     default:
       MOZ_FALLTHROUGH_ASSERT("aTimingFunctionType must be a keyword value");
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_STEP_END:
       mType = Type::StepEnd;
-      mSteps = 1;
+      mStepsOrFrames = 1;
       return;
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE:
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_LINEAR:
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN:
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_OUT:
     case NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE_IN_OUT:
       mType = static_cast<Type>(aTimingFunctionType);
       break;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -2342,23 +2342,26 @@ struct nsTimingFunction
     Ease,         // ease
     Linear,       // linear
     EaseIn,       // ease-in
     EaseOut,      // ease-out
     EaseInOut,    // ease-in-out
     StepStart,    // step-start and steps(..., start)
     StepEnd,      // step-end, steps(..., end) and steps(...)
     CubicBezier,  // cubic-bezier()
+    Frames,       // frames()
   };
 
   // Whether the timing function type is represented by a spline,
   // and thus will have mFunc filled in.
   static bool IsSplineType(Type aType)
   {
-    return aType != Type::StepStart && aType != Type::StepEnd;
+    return aType != Type::StepStart &&
+           aType != Type::StepEnd &&
+           aType != Type::Frames;
   }
 
   explicit nsTimingFunction(int32_t aTimingFunctionType
                               = NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE)
   {
     AssignFromKeyword(aTimingFunctionType);
   }
 
@@ -2368,39 +2371,41 @@ struct nsTimingFunction
     mFunc.mX1 = x1;
     mFunc.mY1 = y1;
     mFunc.mX2 = x2;
     mFunc.mY2 = y2;
   }
 
   enum class Keyword { Implicit, Explicit };
 
-  nsTimingFunction(Type aType, uint32_t aSteps)
+  nsTimingFunction(Type aType, uint32_t aStepsOrFrames)
     : mType(aType)
   {
-    MOZ_ASSERT(mType == Type::StepStart || mType == Type::StepEnd,
+    MOZ_ASSERT(mType == Type::StepStart ||
+               mType == Type::StepEnd ||
+               mType == Type::Frames,
                "wrong type");
-    mSteps = aSteps;
+    mStepsOrFrames = aStepsOrFrames;
   }
 
   nsTimingFunction(const nsTimingFunction& aOther)
   {
     *this = aOther;
   }
 
   Type mType;
   union {
     struct {
       float mX1;
       float mY1;
       float mX2;
       float mY2;
     } mFunc;
     struct {
-      uint32_t mSteps;
+      uint32_t mStepsOrFrames;
     };
   };
 
   nsTimingFunction&
   operator=(const nsTimingFunction& aOther)
   {
     if (&aOther == this) {
       return *this;
@@ -2409,32 +2414,32 @@ struct nsTimingFunction
     mType = aOther.mType;
 
     if (HasSpline()) {
       mFunc.mX1 = aOther.mFunc.mX1;
       mFunc.mY1 = aOther.mFunc.mY1;
       mFunc.mX2 = aOther.mFunc.mX2;
       mFunc.mY2 = aOther.mFunc.mY2;
     } else {
-      mSteps = aOther.mSteps;
+      mStepsOrFrames = aOther.mStepsOrFrames;
     }
 
     return *this;
   }
 
   bool operator==(const nsTimingFunction& aOther) const
   {
     if (mType != aOther.mType) {
       return false;
     }
     if (HasSpline()) {
       return mFunc.mX1 == aOther.mFunc.mX1 && mFunc.mY1 == aOther.mFunc.mY1 &&
              mFunc.mX2 == aOther.mFunc.mX2 && mFunc.mY2 == aOther.mFunc.mY2;
     }
-    return mSteps == aOther.mSteps;
+    return mStepsOrFrames == aOther.mStepsOrFrames;
   }
 
   bool operator!=(const nsTimingFunction& aOther) const
   {
     return !(*this == aOther);
   }
 
   bool HasSpline() const { return IsSplineType(mType); }
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -652,16 +652,25 @@ nsStyleUtil::AppendStepsTimingFunction(n
   if (aType == nsTimingFunction::Type::StepStart) {
     aResult.AppendLiteral(", start)");
   } else {
     aResult.AppendLiteral(")");
   }
 }
 
 /* static */ void
+nsStyleUtil::AppendFramesTimingFunction(uint32_t aFrames,
+                                        nsAString& aResult)
+{
+  aResult.AppendLiteral("frames(");
+  aResult.AppendInt(aFrames);
+  aResult.AppendLiteral(")");
+}
+
+/* static */ void
 nsStyleUtil::AppendCubicBezierTimingFunction(float aX1, float aY1,
                                              float aX2, float aY2,
                                              nsAString& aResult)
 {
   // set the value from the cubic-bezier control points
   // (We could try to regenerate the keywords if we want.)
   aResult.AppendLiteral("cubic-bezier(");
   aResult.AppendFloat(aX1);
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -82,16 +82,18 @@ public:
   static void AppendCSSNumber(float aNumber, nsAString& aResult)
   {
     aResult.AppendFloat(aNumber);
   }
 
   static void AppendStepsTimingFunction(nsTimingFunction::Type aType,
                                         uint32_t aSteps,
                                         nsAString& aResult);
+  static void AppendFramesTimingFunction(uint32_t aFrames,
+                                         nsAString& aResult);
   static void AppendCubicBezierTimingFunction(float aX1, float aY1,
                                               float aX2, float aY2,
                                               nsAString& aResult);
   static void AppendCubicBezierKeywordTimingFunction(
       nsTimingFunction::Type aType,
       nsAString& aResult);
 
   static void AppendSerializedFontSrc(const nsCSSValue& aValue,
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -968,18 +968,18 @@ var gCSSProperties = {
     other_values: [ "paused", "running, running", "paused, running", "paused, paused", "running, paused", "paused, running, running, running, paused, running" ],
     invalid_values: [ "0" ]
   },
   "animation-timing-function": {
     domProp: "animationTimingFunction",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "ease" ],
-    other_values: [ "cubic-bezier(0.25, 0.1, 0.25, 1.0)", "linear", "ease-in", "ease-out", "ease-in-out", "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)", "cubic-bezier(0.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.25, 1.5, 0.75, -0.5)", "step-start", "step-end", "steps(1)", "steps(2, start)", "steps(386)", "steps(3, end)" ],
-    invalid_values: [ "none", "auto", "cubic-bezier(0.25, 0.1, 0.25)", "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)", "cubic-bezier(-0.5, 0.5, 0.5, 0.5)", "cubic-bezier(1.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.5, 0.5, -0.5, 0.5)", "cubic-bezier(0.5, 0.5, 1.5, 0.5)", "steps(2, step-end)", "steps(0)", "steps(-2)", "steps(0, step-end, 1)" ]
+    other_values: [ "cubic-bezier(0.25, 0.1, 0.25, 1.0)", "linear", "ease-in", "ease-out", "ease-in-out", "linear, ease-in, cubic-bezier(0.1, 0.2, 0.8, 0.9)", "cubic-bezier(0.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.25, 1.5, 0.75, -0.5)", "step-start", "step-end", "steps(1)", "steps(2, start)", "steps(386)", "steps(3, end)", "frames(2)", "frames(1000)", "frames( 2 )" ],
+    invalid_values: [ "none", "auto", "cubic-bezier(0.25, 0.1, 0.25)", "cubic-bezier(0.25, 0.1, 0.25, 0.25, 1.0)", "cubic-bezier(-0.5, 0.5, 0.5, 0.5)", "cubic-bezier(1.5, 0.5, 0.5, 0.5)", "cubic-bezier(0.5, 0.5, -0.5, 0.5)", "cubic-bezier(0.5, 0.5, 1.5, 0.5)", "steps(2, step-end)", "steps(0)", "steps(-2)", "steps(0, step-end, 1)", "frames(1)", "frames(-2)", "frames", "frames()", "frames(,)", "frames(a)", "frames(2.0)", "frames(2.5)", "frames(2 3)" ]
   },
   "-moz-appearance": {
     domProp: "MozAppearance",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "none" ],
     other_values: [ "radio", "menulist" ],
     invalid_values: []
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -51,19 +51,21 @@ Any line which doesn't follow the format
     * test_animations_omta_start.html [1]
     * test_animations_pausing.html [1]
     * test_animations_playbackrate.html [1]
   * Events:
     * test_animations_event_handler_attribute.html [10]
     * test_animations_event_order.html [12]
   * SMIL Animation
     * test_restyles_in_smil_animation.html [2]
+  * CSS Timing Functions: Frames timing functions
+    * test_value_storage.html `frames` [30]
   * Property parsing and computation:
     * test_property_syntax_errors.html `animation` [404]
-    * test_value_storage.html `animation` [265]
+    * test_value_storage.html `animation` [280]
 * test_any_dynamic.html: -moz-any pseudo class [2]
 * CSSOM support:
   * @namespace ##easy##
     * test_at_rule_parse_serialize.html [1]
     * test_bug765590.html [1]
     * test_font_face_parser.html `@namespace` [1]
   * @import
     * test_bug221428.html [1]
--- a/layout/svg/SVGGeometryFrame.cpp
+++ b/layout/svg/SVGGeometryFrame.cpp
@@ -25,16 +25,17 @@
 #include "SVGGeometryElement.h"
 #include "nsSVGUtils.h"
 #include "mozilla/ArrayUtils.h"
 #include "SVGAnimatedTransformList.h"
 #include "SVGContentUtils.h"
 #include "SVGGraphicsElement.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsIFrame*
 NS_NewSVGGeometryFrame(nsIPresShell* aPresShell,
@@ -702,17 +703,17 @@ SVGGeometryFrame::GetBBoxContribution(co
 // SVGGeometryFrame methods:
 
 gfxMatrix
 SVGGeometryFrame::GetCanvasTM()
 {
   NS_ASSERTION(GetParent(), "null parent");
 
   nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(GetParent());
-  dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent);
+  SVGGraphicsElement *content = static_cast<SVGGraphicsElement*>(mContent);
 
   return content->PrependLocalTransformsTo(parent->GetCanvasTM());
 }
 
 SVGGeometryFrame::MarkerProperties
 SVGGeometryFrame::GetMarkerProperties(SVGGeometryFrame *aFrame)
 {
   NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation");
--- a/layout/svg/SVGImageContext.cpp
+++ b/layout/svg/SVGImageContext.cpp
@@ -5,16 +5,17 @@
 
 
 // Main header first:
 #include "SVGImageContext.h"
 
 // Keep others in (case-insensitive) order:
 #include "gfxUtils.h"
 #include "mozilla/Preferences.h"
+#include "nsIFrame.h"
 #include "nsPresContext.h"
 
 namespace mozilla {
 
 bool
 SVGImageContext::MaybeStoreContextPaint(nsIFrame* aFromFrame)
 {
   static bool sEnabledForContent = false;
--- a/layout/svg/nsSVGMarkerFrame.cpp
+++ b/layout/svg/nsSVGMarkerFrame.cpp
@@ -10,16 +10,17 @@
 #include "gfxContext.h"
 #include "nsSVGEffects.h"
 #include "mozilla/dom/SVGMarkerElement.h"
 #include "SVGGeometryElement.h"
 #include "SVGGeometryFrame.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
+using namespace mozilla::image;
 
 nsContainerFrame*
 NS_NewSVGMarkerFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsSVGMarkerFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsSVGMarkerFrame)
--- a/layout/svg/nsSVGMarkerFrame.h
+++ b/layout/svg/nsSVGMarkerFrame.h
@@ -79,45 +79,45 @@ public:
                  nsGkAtoms::svgMarkerAnonChildFrame,
                "Where is our anonymous child?");
     return PrincipalChildList().FirstChild()->GetContentInsertionFrame();
   }
 
   // nsSVGMarkerFrame methods:
   nsresult PaintMark(gfxContext& aContext,
                      const gfxMatrix& aToMarkedFrameUserSpace,
-                     SVGGeometryFrame *aMarkedFrame,
+                     mozilla::SVGGeometryFrame *aMarkedFrame,
                      nsSVGMark *aMark,
                      float aStrokeWidth);
 
   SVGBBox GetMarkBBoxContribution(const Matrix &aToBBoxUserspace,
                                   uint32_t aFlags,
-                                  SVGGeometryFrame *aMarkedFrame,
+                                  mozilla::SVGGeometryFrame *aMarkedFrame,
                                   const nsSVGMark *aMark,
                                   float aStrokeWidth);
 
 private:
   // stuff needed for callback
-  SVGGeometryFrame *mMarkedFrame;
+  mozilla::SVGGeometryFrame *mMarkedFrame;
   float mStrokeWidth, mX, mY, mAutoAngle;
   bool mIsStart;  // whether the callback is for a marker-start marker
 
   // nsSVGContainerFrame methods:
   virtual gfxMatrix GetCanvasTM() override;
 
   // A helper class to allow us to paint markers safely. The helper
   // automatically sets and clears the mInUse flag on the marker frame (to
   // prevent nasty reference loops) as well as the reference to the marked
   // frame and its coordinate context. It's easy to mess this up
   // and break things, so this helper makes the code far more robust.
   class MOZ_RAII AutoMarkerReferencer
   {
   public:
     AutoMarkerReferencer(nsSVGMarkerFrame *aFrame,
-                         SVGGeometryFrame *aMarkedFrame
+                         mozilla::SVGGeometryFrame *aMarkedFrame
                          MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
     ~AutoMarkerReferencer();
   private:
     nsSVGMarkerFrame *mFrame;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   };
 
   // nsSVGMarkerFrame methods:
--- a/layout/svg/nsSVGMaskFrame.cpp
+++ b/layout/svg/nsSVGMaskFrame.cpp
@@ -16,16 +16,17 @@
 #ifdef BUILD_ARM_NEON
 #include "mozilla/arm.h"
 #include "nsSVGMaskFrameNEON.h"
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
+using namespace mozilla::image;
 
 // c = n / 255
 // c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4)) * 255 + 0.5
 static const uint8_t gsRGBToLinearRGBMap[256] = {
   0,   0,   0,   0,   0,   0,   0,   1,
   1,   1,   1,   1,   1,   1,   1,   1,
   1,   1,   2,   2,   2,   2,   2,   2,
   2,   2,   3,   3,   3,   3,   3,   3,
--- a/layout/svg/nsSVGOuterSVGFrame.cpp
+++ b/layout/svg/nsSVGOuterSVGFrame.cpp
@@ -16,16 +16,17 @@
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGForeignObjectFrame.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "mozilla/dom/SVGViewElement.h"
 #include "nsSubDocumentFrame.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::image;
 
 //----------------------------------------------------------------------
 // Implementation helpers
 
 void
 nsSVGOuterSVGFrame::RegisterForeignObject(nsSVGForeignObjectFrame* aFrame)
 {
   NS_ASSERTION(aFrame, "Who on earth is calling us?!");
--- a/layout/svg/nsSVGPatternFrame.cpp
+++ b/layout/svg/nsSVGPatternFrame.cpp
@@ -22,16 +22,17 @@
 #include "mozilla/dom/SVGPatternElement.h"
 #include "nsSVGUtils.h"
 #include "nsSVGAnimatedTransformList.h"
 #include "SVGContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
+using namespace mozilla::image;
 
 //----------------------------------------------------------------------
 // Helper classes
 
 class MOZ_RAII nsSVGPatternFrame::AutoPatternReferencer
 {
 public:
   explicit AutoPatternReferencer(nsSVGPatternFrame *aFrame
--- a/layout/svg/nsSVGPatternFrame.h
+++ b/layout/svg/nsSVGPatternFrame.h
@@ -137,17 +137,17 @@ protected:
                           const gfxRect &callerBBox,
                           const Matrix &callerCTM,
                           nsIFrame *aTarget);
 
 private:
   // this is a *temporary* reference to the frame of the element currently
   // referencing our pattern.  This must be temporary because different
   // referencing frames will all reference this one frame
-  SVGGeometryFrame                 *mSource;
+  mozilla::SVGGeometryFrame        *mSource;
   nsAutoPtr<gfxMatrix>              mCTM;
 
 protected:
   // This flag is used to detect loops in xlink:href processing
   bool                              mLoopFlag;
   bool                              mNoHRefURI;
 };
 
--- a/layout/svg/nsSVGSwitchFrame.cpp
+++ b/layout/svg/nsSVGSwitchFrame.cpp
@@ -6,16 +6,17 @@
 // Keep in (case-insensitive) order:
 #include "gfxRect.h"
 #include "nsSVGEffects.h"
 #include "nsSVGGFrame.h"
 #include "mozilla/dom/SVGSwitchElement.h"
 #include "nsSVGUtils.h"
 
 using namespace mozilla::gfx;
+using namespace mozilla::image;
 
 class nsSVGSwitchFrame : public nsSVGGFrame
 {
   friend nsIFrame*
   NS_NewSVGSwitchFrame(nsIPresShell* aPresShell, nsStyleContext* aContext);
 protected:
   explicit nsSVGSwitchFrame(nsStyleContext* aContext)
     : nsSVGGFrame(aContext) {}
--- a/layout/xul/tree/nsTreeStyleCache.cpp
+++ b/layout/xul/tree/nsTreeStyleCache.cpp
@@ -78,17 +78,17 @@ nsTreeStyleCache::GetStyleContext(nsICSS
   }
   if (!result) {
     // We missed the cache. Resolve this pseudo-style.
     // XXXheycam ServoStyleSets do not support XUL tree styles.
     RefPtr<nsStyleContext> newResult;
     if (aPresContext->StyleSet()->IsServo()) {
       NS_ERROR("stylo: ServoStyleSets should not support XUL tree styles yet");
       newResult = aPresContext->StyleSet()->
-        ResolveStyleForOtherNonElement(aContext);
+        ResolveStyleForPlaceholder();
     } else {
       newResult = aPresContext->StyleSet()->AsGecko()->
         ResolveXULTreePseudoStyle(aContent->AsElement(), aPseudoElement,
                                   aContext, aComparator);
     }
 
     // Put the style context in our table, transferring the owning reference to the table.
     if (!mCache) {
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -343,19 +343,19 @@ MP4Metadata::ReadTrackIndex(FallibleTArr
   if (mRustTestMode && ret && mRust) {
     mp4parse_byte_data data = {};
     bool rustRet = mRust->ReadTrackIndice(&data, aTrackID);
     MOZ_DIAGNOSTIC_ASSERT(rustRet);
     MOZ_DIAGNOSTIC_ASSERT(data.length == aDest.Length());
     for (uint32_t i = 0; i < data.length; i++) {
       MOZ_DIAGNOSTIC_ASSERT(data.indices[i].start_offset == aDest[i].start_offset);
       MOZ_DIAGNOSTIC_ASSERT(data.indices[i].end_offset == aDest[i].end_offset);
-      MOZ_DIAGNOSTIC_ASSERT(llabs(int64_t(data.indices[i].start_composition - aDest[i].start_composition)) <= 1);
-      MOZ_DIAGNOSTIC_ASSERT(llabs(int64_t(data.indices[i].end_composition - aDest[i].end_composition)) <= 1);
-      MOZ_DIAGNOSTIC_ASSERT(llabs(int64_t(data.indices[i].start_decode - aDest[i].start_decode)) <= 1);
+      MOZ_DIAGNOSTIC_ASSERT(llabs(data.indices[i].start_composition - int64_t(aDest[i].start_composition)) <= 1);
+      MOZ_DIAGNOSTIC_ASSERT(llabs(data.indices[i].end_composition - int64_t(aDest[i].end_composition)) <= 1);
+      MOZ_DIAGNOSTIC_ASSERT(llabs(data.indices[i].start_decode - int64_t(aDest[i].start_decode)) <= 1);
       MOZ_DIAGNOSTIC_ASSERT(data.indices[i].sync == aDest[i].sync);
     }
   }
 #endif
 
   return ret;
 }
 
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -46,19 +46,19 @@ typedef struct mp4parse_track_info {
 	uint32_t track_id;
 	uint64_t duration;
 	int64_t media_time;
 } mp4parse_track_info;
 
 typedef struct mp4parse_indice {
 	uint64_t start_offset;
 	uint64_t end_offset;
-	uint64_t start_composition;
-	uint64_t end_composition;
-	uint64_t start_decode;
+	int64_t start_composition;
+	int64_t end_composition;
+	int64_t start_decode;
 	bool sync;
 } mp4parse_indice;
 
 typedef struct mp4parse_byte_data {
 	uint32_t length;
 	uint8_t const* data;
 	mp4parse_indice const* indices;
 } mp4parse_byte_data;
--- a/media/libstagefright/binding/mp4parse-cargo.patch
+++ b/media/libstagefright/binding/mp4parse-cargo.patch
@@ -5,19 +5,19 @@ index ff9422c..814c4c6 100644
 @@ -18,18 +18,12 @@ exclude = [
  ]
  
  [dependencies]
 -byteorder = "1.0.0"
 -afl = { version = "0.1.1", optional = true }
 -afl-plugin = { version = "0.1.1", optional = true }
 -abort_on_panic = { version = "1.0.0", optional = true }
--bitreader = { version = "0.2.0" }
+-bitreader = { version = "0.3.0" }
 +byteorder = "1.0.0"
-+bitreader = { version = "0.2.0" }
++bitreader = { version = "0.3.0" }
 
  [dev-dependencies]
  test-assembler = "0.1.2"
  
 -[features]
 -fuzz = ["afl", "afl-plugin", "abort_on_panic"]
 -
  # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
@@ -34,17 +34,17 @@ index aeeebc65..5c0836a 100644
 -build = "build.rs"
 -
 -[badges]
 -travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 +build = false
  
  [dependencies]
  byteorder = "1.0.0"
- mp4parse = {version = "0.7.0", path = "../mp4parse"}
+ mp4parse = {version = "0.7.1", path = "../mp4parse"}
  
 -[build-dependencies]
 -rusty-cheddar = "0.3.2"
 -
 -[features]
 -fuzz = ["mp4parse/fuzz"]
 -
  # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse"
-version = "0.7.0"
+version = "0.7.1"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
@@ -19,16 +19,16 @@ exclude = [
   "*.mp4",
 ]
 
 [badges]
 travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 
 [dependencies]
 byteorder = "1.0.0"
-bitreader = { version = "0.2.0" }
+bitreader = { version = "0.3.0" }
 
 [dev-dependencies]
 test-assembler = "0.1.2"
 
 # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
 [profile.release]
 debug-assertions = true
--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -411,19 +411,19 @@ pub struct TrackTimeScale(pub u64, pub u
 /// Members are time in scale units and the track id.
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub struct TrackScaledTime(pub u64, pub usize);
 
 /// A fragmented file contains no sample data in stts, stsc, and stco.
 #[derive(Debug, Default)]
 pub struct EmptySampleTableBoxes {
     // TODO: Track has stts, stsc and stco, this structure can be discarded.
-    pub empty_stts : bool,
-    pub empty_stsc : bool,
-    pub empty_stco : bool,
+    pub empty_stts: bool,
+    pub empty_stsc: bool,
+    pub empty_stco: bool,
 }
 
 /// Check boxes contain data.
 impl EmptySampleTableBoxes {
     pub fn all_empty(&self) -> bool {
         self.empty_stts & self.empty_stsc & self.empty_stco
     }
 }
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
@@ -1,11 +1,11 @@
 [package]
 name = "mp4parse_capi"
-version = "0.7.0"
+version = "0.7.1"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
 documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
@@ -17,13 +17,13 @@ repository = "https://github.com/mozilla
 exclude = [
   "*.mp4",
 ]
 
 build = false
 
 [dependencies]
 byteorder = "1.0.0"
-mp4parse = {version = "0.7.0", path = "../mp4parse"}
+mp4parse = {version = "0.7.1", path = "../mp4parse"}
 
 # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
 [profile.release]
 debug-assertions = true
--- a/media/libstagefright/binding/mp4parse_capi/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -60,38 +60,41 @@ use mp4parse::Track;
 // rusty-cheddar's C enum generation doesn't namespace enum members by
 // prefixing them, so we're forced to do it in our member names until
 // https://github.com/Sean1708/rusty-cheddar/pull/35 is fixed.  Importing
 // the members into the module namespace avoids doubling up on the
 // namespacing on the Rust side.
 use mp4parse_error::*;
 use mp4parse_track_type::*;
 
+#[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_error {
     MP4PARSE_OK = 0,
     MP4PARSE_ERROR_BADARG = 1,
     MP4PARSE_ERROR_INVALID = 2,
     MP4PARSE_ERROR_UNSUPPORTED = 3,
     MP4PARSE_ERROR_EOF = 4,
     MP4PARSE_ERROR_IO = 5,
 }
 
+#[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_track_type {
     MP4PARSE_TRACK_TYPE_VIDEO = 0,
     MP4PARSE_TRACK_TYPE_AUDIO = 1,
 }
 
 impl Default for mp4parse_track_type {
     fn default() -> Self { mp4parse_track_type::MP4PARSE_TRACK_TYPE_VIDEO }
 }
 
+#[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_codec {
     MP4PARSE_CODEC_UNKNOWN,
     MP4PARSE_CODEC_AAC,
     MP4PARSE_CODEC_FLAC,
     MP4PARSE_CODEC_OPUS,
     MP4PARSE_CODEC_AVC,
@@ -114,19 +117,19 @@ pub struct mp4parse_track_info {
     // TODO(kinetik): include crypto guff
 }
 
 #[repr(C)]
 #[derive(Default, Debug, PartialEq)]
 pub struct mp4parse_indice {
     pub start_offset: u64,
     pub end_offset: u64,
-    pub start_composition: u64,
-    pub end_composition: u64,
-    pub start_decode: u64,
+    pub start_composition: i64,
+    pub end_composition: i64,
+    pub start_decode: i64,
     pub sync: bool,
 }
 
 #[repr(C)]
 pub struct mp4parse_byte_data {
     pub length: u32,
     // cheddar can't handle generic type, so it needs to be multiple data types here.
     pub data: *const u8,
@@ -663,32 +666,39 @@ pub unsafe extern fn mp4parse_get_indice
     match index_table.get(&track_id) {
         Some(v) => {
             (*indices).set_indices(v);
             return MP4PARSE_OK;
         },
         _ => {},
     }
 
+    let media_time = match (&track.media_time, &track.timescale) {
+        (&Some(t), &Some(s)) => {
+            track_time_to_us(t, s).map(|v| v as i64)
+        },
+        _ => None,
+    };
+
+    let empty_duration = match (&track.empty_duration, &context.timescale) {
+        (&Some(e), &Some(s)) => {
+            media_time_to_us(e, s).map(|v| v as i64)
+        },
+        _ => None
+    };
+
     // Find the track start offset time from 'elst'.
     // 'media_time' maps start time onward, 'empty_duration' adds time offset
     // before first frame is displayed.
-    let offset_time =
-        match (&track.empty_duration, &track.media_time, &context.timescale) {
-            (&Some(empty_duration), &Some(media_time), &Some(scale)) => {
-                (empty_duration.0 as i64 - media_time.0 as i64) * scale.0 as i64
-            },
-            (&Some(empty_duration), _, &Some(scale)) => {
-                empty_duration.0 as i64 * scale.0 as i64
-            },
-            (_, &Some(media_time), &Some(scale)) => {
-                (0 - media_time.0 as i64) * scale.0 as i64
-            },
-            _ => 0,
-        };
+    let offset_time = match (empty_duration, media_time) {
+        (Some(e), Some(m)) => e - m,
+        (Some(e), None) => e,
+        (None, Some(m)) => m,
+        _ => 0,
+    };
 
     match create_sample_table(track, offset_time) {
         Some(v) => {
             (*indices).set_indices(&v);
             index_table.insert(track_id, v);
             return MP4PARSE_OK;
         },
         _ => {},
@@ -957,20 +967,20 @@ fn create_sample_table(track: &Track, tr
     let mut sum_delta = PresentationTime::new(0, timescale);
     for sample in sample_table.as_mut_slice() {
         let decode_time = sum_delta.to_us();
         sum_delta.time += stts_iter.next_delta() as i64;
 
         // ctts_offset is the current sample offset time.
         let ctts_offset = PresentationTime::new(ctts_offset_iter.next_offset_time(), timescale);
 
-        let start_composition = (decode_time + ctts_offset.to_us() + track_offset_time) as u64;
-        let end_composition = (sum_delta.to_us() + ctts_offset.to_us() + track_offset_time) as u64;
+        let start_composition = decode_time + ctts_offset.to_us() + track_offset_time;
+        let end_composition = sum_delta.to_us() + ctts_offset.to_us() + track_offset_time;
 
-        sample.start_decode = decode_time as u64;
+        sample.start_decode = decode_time;
         sample.start_composition = start_composition;
         sample.end_composition = end_composition;
     }
 
     // Correct composition end time due to 'ctts' causes composition time re-ordering.
     //
     // Composition end time is not in specification. However, gecko needs it, so we need to
     // calculate to correct the composition end time.
--- a/media/libstagefright/binding/update-rust.sh
+++ b/media/libstagefright/binding/update-rust.sh
@@ -1,13 +1,13 @@
 #!/bin/sh -e
 # Script to update mp4parse-rust sources to latest upstream
 
 # Default version.
-VER=1fbd8ce2f76c93b88bc7bc41bb67ca76a9f67712
+VER=a422808043e6a21ee98729dd8bfe1e8234897d18
 
 # Accept version or commit from the command line.
 if test -n "$1"; then
   VER=$1
 fi
 
 echo "Fetching sources..."
 rm -rf _upstream
--- a/media/libstagefright/moz.build
+++ b/media/libstagefright/moz.build
@@ -152,17 +152,18 @@ if CONFIG['_MSC_VER']:
         '-wd4309', # '=' : truncation of constant value
         '-wd4355', # 'this' : used in base member initializer list
         '-wd4804', # '>' : unsafe use of type 'bool' in operation
         '-wd4099', # mismatched class/struct tags
     ]
 elif CONFIG['GNU_CXX']:
     CFLAGS += [
         '-Wno-comment',
-        '-Wno-sign-compare'
+        '-Wno-sign-compare',
+        '-Wno-unused-function',
     ]
     CXXFLAGS += [
         '-Wno-format',
         '-Wno-format-security',
         '-Wno-multichar',
         '-Wno-sign-compare',
         '-Wno-unused',
     ]
--- a/mfbt/MacroForEach.h
+++ b/mfbt/MacroForEach.h
@@ -29,130 +29,195 @@
  *   #define MACRO_B(k, x) (k + x) +
  *   int b = MOZ_FOR_EACH(MACRO_B, (5,), (1, 2)) 0;
  *   // Expands to: MACRO_B(5, 1) MACRO_B(5, 2) 0;
  *
  *   #define MACRO_C(k1, k2, x) (k1 + k2 + x) +
  *   int c = MOZ_FOR_EACH(MACRO_C, (5, 8,), (1, 2)) 0;
  *   // Expands to: MACRO_B(5, 8, 1) MACRO_B(5, 8, 2) 0;
  *
+ * MOZ_FOR_EACH_SEPARATED(aMacro, aSeparator, aFixedArgs, aArgs) is identical
+ * to MOZ_FOR_EACH except that it inserts |aSeparator| between each call to
+ * the macro. |aSeparator| must be wrapped by parens. For example:
+ *
+ *   #define MACRO_A(x) x
+ *   int a = MOZ_FOR_EACH_SEPARATED(MACRO_A, (+), (), (1, 2, 3));
+ *   // Expands to: MACRO_A(1) + MACRO_A(2) + MACRO_A(3);
+ *   // And further to: 1 + 2 + 3
+ *
+ *   #define MACRO_B(t, n) t n
+ *   void test(MOZ_FOR_EACH_SEPARATED(MACRO_B, (,), (int,), (a, b)));
+ *   // Expands to: void test(MACRO_B(int, a) , MACRO_B(int, b));
+ *   // And further to: void test(int a , int b);
+ *
  * If the |aFixedArgs| list is not empty, a trailing comma must be included.
  *
  * The |aArgs| list must be not be empty and may be up to 50 items long. Use
  * MOZ_STATIC_ASSERT_VALID_ARG_COUNT to ensure that violating this constraint
  * results in a compile-time error.
  */
 #define MOZ_FOR_EACH_EXPAND_HELPER(...) __VA_ARGS__
 #define MOZ_FOR_EACH_GLUE(a, b) a b
-#define MOZ_FOR_EACH(aMacro, aFixedArgs, aArgs) \
+#define MOZ_FOR_EACH_SEPARATED(aMacro, aSeparator, aFixedArgs, aArgs) \
   MOZ_FOR_EACH_GLUE( \
     MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_FOR_EACH_, \
                                    MOZ_FOR_EACH_EXPAND_HELPER aArgs), \
-    (aMacro, aFixedArgs, aArgs))
+    (aMacro, aSeparator, aFixedArgs, aArgs))
+#define MOZ_FOR_EACH(aMacro, aFixedArgs, aArgs) \
+  MOZ_FOR_EACH_SEPARATED(aMacro, (), aFixedArgs, aArgs)
 
 #define MOZ_FOR_EACH_HELPER_GLUE(a, b) a b
 #define MOZ_FOR_EACH_HELPER(aMacro, aFixedArgs, aArgs) \
   MOZ_FOR_EACH_HELPER_GLUE( \
     aMacro, \
     (MOZ_FOR_EACH_EXPAND_HELPER aFixedArgs MOZ_ARG_1 aArgs))
 
-#define MOZ_FOR_EACH_1(m, fa, a) \
+#define MOZ_FOR_EACH_1(m, s, fa, a) \
   MOZ_FOR_EACH_HELPER(m, fa, a)
-#define MOZ_FOR_EACH_2(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_1(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_3(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_2(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_4(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_3(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_5(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_4(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_6(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_5(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_7(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_6(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_8(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_7(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_9(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_8(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_10(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_9(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_11(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_10(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_12(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_11(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_13(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_12(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_14(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_13(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_15(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_14(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_16(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_15(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_17(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_16(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_18(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_17(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_19(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_18(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_20(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_19(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_21(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_20(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_22(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_21(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_23(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_22(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_24(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_23(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_25(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_24(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_26(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_25(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_27(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_26(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_28(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_27(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_29(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_28(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_30(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_29(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_31(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_30(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_32(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_31(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_33(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_32(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_34(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_33(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_35(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_34(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_36(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_35(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_37(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_36(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_38(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_37(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_39(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_38(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_40(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_39(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_41(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_40(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_42(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_41(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_43(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_42(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_44(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_43(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_45(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_44(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_46(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_45(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_47(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_46(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_48(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_47(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_49(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_48(m, fa, (MOZ_ARGS_AFTER_1 a))
-#define MOZ_FOR_EACH_50(m, fa, a) \
-  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_49(m, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_2(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_1(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_3(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_2(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_4(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_3(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_5(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_4(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_6(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_5(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_7(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_6(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_8(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_7(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_9(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_8(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_10(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_9(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_11(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_10(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_12(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_11(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_13(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_12(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_14(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_13(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_15(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_14(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_16(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_15(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_17(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_16(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_18(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_17(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_19(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_18(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_20(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_19(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_21(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_20(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_22(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_21(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_23(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_22(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_24(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_23(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_25(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_24(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_26(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_25(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_27(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_26(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_28(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_27(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_29(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_28(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_30(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_29(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_31(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_30(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_32(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_31(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_33(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_32(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_34(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_33(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_35(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_34(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_36(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_35(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_37(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_36(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_38(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_37(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_39(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_38(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_40(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_39(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_41(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_40(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_42(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_41(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_43(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_42(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_44(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_43(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_45(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_44(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_46(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_45(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_47(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_46(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_48(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_47(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_49(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_48(m, s, fa, (MOZ_ARGS_AFTER_1 a))
+#define MOZ_FOR_EACH_50(m, s, fa, a) \
+  MOZ_FOR_EACH_HELPER(m, fa, a) MOZ_FOR_EACH_EXPAND_HELPER s \
+  MOZ_FOR_EACH_49(m, s, fa, (MOZ_ARGS_AFTER_1 a))
 
 #endif /* mozilla_MacroForEach_h */
--- a/mfbt/tests/TestMacroForEach.cpp
+++ b/mfbt/tests/TestMacroForEach.cpp
@@ -2,33 +2,45 @@
 /* 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 "mozilla/Assertions.h"
 #include "mozilla/MacroForEach.h"
 
+#define HELPER_IDENTITY(x) x
 #define HELPER_IDENTITY_PLUS(x) x +
 static_assert(MOZ_FOR_EACH(HELPER_IDENTITY_PLUS, (), (10)) 0 == 10, "");
 static_assert(MOZ_FOR_EACH(HELPER_IDENTITY_PLUS, (), (1, 1, 1)) 0 == 3, "");
+static_assert(MOZ_FOR_EACH_SEPARATED(HELPER_IDENTITY, (+),
+                                     (), (10)) == 10, "");
+static_assert(MOZ_FOR_EACH_SEPARATED(HELPER_IDENTITY, (+),
+                                     (), (1, 1, 1)) == 3, "");
 
 #define HELPER_DEFINE_VAR(x) const int test1_##x = x;
 MOZ_FOR_EACH(HELPER_DEFINE_VAR, (), (10, 20))
 static_assert(test1_10 == 10 && test1_20 == 20, "");
 
 #define HELPER_DEFINE_VAR2(k, x) const int test2_##x = k + x;
 MOZ_FOR_EACH(HELPER_DEFINE_VAR2, (5,), (10, 20))
 static_assert(test2_10 == 15 && test2_20 == 25, "");
 
-#define HELPER_IDENTITY_COMMA(k1, k2, x) k1, k2, x,
+#define HELPER_DEFINE_PARAM(t, n) t n
+constexpr int
+test(MOZ_FOR_EACH_SEPARATED(HELPER_DEFINE_PARAM, (,), (int,), (a, b, c)))
+{
+  return a + b + c;
+}
+static_assert(test(1, 2, 3) == 6, "");
 
 int
 main()
 {
+#define HELPER_IDENTITY_COMMA(k1, k2, x) k1, k2, x,
   const int a[] = {
     MOZ_FOR_EACH(HELPER_IDENTITY_COMMA, (1, 2,), (10, 20, 30))
   };
   MOZ_RELEASE_ASSERT(a[0] == 1 && a[1] == 2 && a[2] == 10 &&
                      a[3] == 1 && a[4] == 2 && a[5] == 20 &&
                      a[6] == 1 && a[7] == 2 && a[8] == 30,
                      "MOZ_FOR_EACH args enumerated in incorrect order");
   return 0;
--- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
@@ -188,18 +188,17 @@ import java.util.concurrent.ConcurrentLi
                 return;
             }
 
             Sample output = obtainOutputSample(index, info);
             try {
                 mSentIndices.add(index);
                 mSentOutputs.add(output);
                 mCallbacks.onOutput(output);
-            } catch (RemoteException e) {
-                // Dead recipient.
+            } catch (Exception e) {
                 e.printStackTrace();
                 mCodec.releaseOutputBuffer(index, false);
             }
 
             boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
             if (DEBUG && eos) {
                 Log.d(LOGTAG, "output EOS");
             }
--- a/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/CodecProxy.java
@@ -172,17 +172,17 @@ public final class CodecProxy {
         }
         try {
             Sample sample = processInput(bytes, info, cryptoInfo);
             if (sample == null) {
                 return false;
             }
             mRemote.queueInput(sample);
             sample.dispose();
-        } catch (RemoteException | IOException e) {
+        } catch (Exception e) {
             Log.e(LOGTAG, "fail to input sample: size=" + info.size +
                     ", pts=" + info.presentationTimeUs +
                     ", flags=" + Integer.toHexString(info.flags), e);
             return false;
         }
 
         return true;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java
@@ -156,17 +156,17 @@ public class MediaControlService extends
     }
 
     private boolean isMediaPlaying() {
         return mMediaState.equals(State.PLAYING);
     }
 
     private void initialize() {
         if (mInitialize ||
-            !isAndroidVersionLollopopOrHigher()) {
+            !isAndroidVersionLollipopOrHigher()) {
             return;
         }
 
         Log.d(LOGTAG, "initialize");
         getGeckoPreference();
         initMediaSession();
 
         coverSize = (int) getResources().getDimension(R.dimen.notification_media_cover);
@@ -185,17 +185,17 @@ public class MediaControlService extends
         setState(State.STOPPED);
         PrefsHelper.removeObserver(mPrefsObserver);
 
         Tabs.unregisterOnTabsChangedListener(this);
         mInitialize = false;
         stopSelf();
     }
 
-    private boolean isAndroidVersionLollopopOrHigher() {
+    private boolean isAndroidVersionLollipopOrHigher() {
         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
     }
 
     private void handleIntent(Intent intent) {
         if (intent == null || intent.getAction() == null || !mInitialize) {
             return;
         }
 
@@ -507,9 +507,9 @@ public class MediaControlService extends
                 Intent pauseIntent = new Intent(getApplicationContext(), MediaControlService.class);
                 pauseIntent.setAction(ACTION_PAUSE);
                 handleIntent(pauseIntent);
             }
         }
 
     }
 
-}
\ No newline at end of file
+}
--- a/old-configure.in
+++ b/old-configure.in
@@ -981,20 +981,16 @@ case "$target" in
             dnl and doesn't have a separate arch for SSSE3
             SSSE3_FLAGS="-arch:SSE2"
         fi
         dnl clang-cl requires appropriate flags to enable SSSE3 support
         dnl on all architectures.
         if test -n "$CLANG_CL"; then
             SSSE3_FLAGS="-mssse3"
         fi
-        dnl VS2013+ requires -FS when parallel building by make -jN.
-        dnl If nothing, compiler sometimes causes C1041 error.
-        CFLAGS="$CFLAGS -FS"
-        CXXFLAGS="$CXXFLAGS -FS"
         dnl VS2013+ supports -Gw for better linker optimizations.
         dnl http://blogs.msdn.com/b/vcblog/archive/2013/09/11/introducing-gw-compiler-switch.aspx
         dnl Disabled on ASan because it causes false-positive ODR violations.
         if test -z "$MOZ_ASAN"; then
             CFLAGS="$CFLAGS -Gw"
             CXXFLAGS="$CXXFLAGS -Gw"
         fi
         # khuey says we can safely ignore MSVC warning C4251
--- a/services/sync/modules/service.js
+++ b/services/sync/modules/service.js
@@ -271,18 +271,16 @@ Sync11Service.prototype = {
     }
     return false;
   },
 
   /**
    * Prepare to initialize the rest of Weave after waiting a little bit
    */
   onStartup: function onStartup() {
-    this._migratePrefs();
-
     this.status = Status;
     this.identity = Status._authManager;
     this.collectionKeys = new CollectionKeyManager();
 
     this.errorHandler = new ErrorHandler(this);
 
     this._log = Log.repository.getLogger("Sync.Service");
     this._log.level =
@@ -343,49 +341,16 @@ Sync11Service.prototype = {
 
   _checkSetup: function _checkSetup() {
     if (!this.enabled) {
       return this.status.service = STATUS_DISABLED;
     }
     return this.status.checkSetup();
   },
 
-  _migratePrefs: function _migratePrefs() {
-    // Migrate old debugLog prefs.
-    let logLevel = Svc.Prefs.get("log.appender.debugLog");
-    if (logLevel) {
-      Svc.Prefs.set("log.appender.file.level", logLevel);
-      Svc.Prefs.reset("log.appender.debugLog");
-    }
-    if (Svc.Prefs.get("log.appender.debugLog.enabled")) {
-      Svc.Prefs.set("log.appender.file.logOnSuccess", true);
-      Svc.Prefs.reset("log.appender.debugLog.enabled");
-    }
-
-    // Migrate old extensions.weave.* prefs if we haven't already tried.
-    if (Svc.Prefs.get("migrated", false))
-      return;
-
-    // Grab the list of old pref names
-    let oldPrefBranch = "extensions.weave.";
-    let oldPrefNames = Cc["@mozilla.org/preferences-service;1"].
-                       getService(Ci.nsIPrefService).
-                       getBranch(oldPrefBranch).
-                       getChildList("", {});
-
-    // Map each old pref to the current pref branch
-    let oldPref = new Preferences(oldPrefBranch);
-    for (let pref of oldPrefNames)
-      Svc.Prefs.set(pref, oldPref.get(pref));
-
-    // Remove all the old prefs and remember that we've migrated
-    oldPref.resetBranch("");
-    Svc.Prefs.set("migrated", true);
-  },
-
   /**
    * Register the built-in engines for certain applications
    */
   _registerEngines: function _registerEngines() {
     this.engineManager = new EngineManager(this);
 
     let engines = [];
     // Applications can provide this preference (comma-separated list)
--- a/services/sync/tests/unit/head_appinfo.js
+++ b/services/sync/tests/unit/head_appinfo.js
@@ -14,22 +14,16 @@ gSyncProfile = do_get_profile();
 // Init FormHistoryStartup and pretend we opened a profile.
 var fhs = Cc["@mozilla.org/satchel/form-history-startup;1"]
             .getService(Ci.nsIObserver);
 fhs.observe(null, "profile-after-change", null);
 
 // An app is going to have some prefs set which xpcshell tests don't.
 Services.prefs.setCharPref("identity.sync.tokenserver.uri", "http://token-server");
 
-// Set the validation prefs to attempt bookmark validation every time to avoid non-determinism.
-Services.prefs.setIntPref("services.sync.engine.bookmarks.validation.interval", 0);
-Services.prefs.setIntPref("services.sync.engine.bookmarks.validation.percentageChance", 100);
-Services.prefs.setIntPref("services.sync.engine.bookmarks.validation.maxRecords", -1);
-Services.prefs.setBoolPref("services.sync.engine.bookmarks.validation.enabled", true);
-
 // Make sure to provide the right OS so crypto loads the right binaries
 function getOS() {
   switch (mozinfo.os) {
     case "win":
       return "WINNT";
     case "mac":
       return "Darwin";
     default:
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -488,8 +488,16 @@ function registerRotaryEngine() {
   Service.engineManager.clear();
 
   Service.engineManager.register(RotaryEngine);
   let engine = Service.engineManager.get("rotary");
   engine.enabled = true;
 
   return { engine, tracker: engine._tracker };
 }
+
+// Set the validation prefs to attempt validation every time to avoid non-determinism.
+function enableValidationPrefs() {
+  Svc.Prefs.set("engine.bookmarks.validation.interval", 0);
+  Svc.Prefs.set("engine.bookmarks.validation.percentageChance", 100);
+  Svc.Prefs.set("engine.bookmarks.validation.maxRecords", -1);
+  Svc.Prefs.set("engine.bookmarks.validation.enabled", true);
+}
--- a/services/sync/tests/unit/test_bookmark_engine.js
+++ b/services/sync/tests/unit/test_bookmark_engine.js
@@ -112,16 +112,18 @@ add_task(async function test_change_duri
 
   let bz_id = PlacesUtils.bookmarks.insertBookmark(
     PlacesUtils.bookmarksMenuFolderId, Utils.makeURI("https://bugzilla.mozilla.org/"),
     PlacesUtils.bookmarks.DEFAULT_INDEX, "Bugzilla");
   let bz_guid = await PlacesUtils.promiseItemGuid(bz_id);
     _(`Bugzilla GUID: ${bz_guid}`);
 
   await PlacesTestUtils.markBookmarksAsSynced();
+  enableValidationPrefs();
+
   Svc.Obs.notify("weave:engine:start-tracking");
 
   try {
     let folder1_id = PlacesUtils.bookmarks.createFolder(
       PlacesUtils.bookmarks.toolbarFolder, "Folder 1", 0);
     let folder1_guid = store.GUIDForId(folder1_id);
     _(`Folder GUID: ${folder1_guid}`);
 
--- a/services/sync/tests/unit/test_bookmark_repair.js
+++ b/services/sync/tests/unit/test_bookmark_repair.js
@@ -53,16 +53,18 @@ async function cleanup(server) {
   bookmarksEngine._store.wipe();
   clientsEngine._store.wipe();
   Svc.Prefs.resetBranch("");
   Service.recordManager.clearCache();
   await promiseStopServer(server);
 }
 
 add_task(async function test_something() {
+  enableValidationPrefs();
+
   _("Ensure that a validation error triggers a repair request.");
 
   let contents = {
     meta: {
       global: {
         engines: {
           clients: {
             version: clientsEngine.version,
--- a/services/sync/tests/unit/test_collections_recovery.js
+++ b/services/sync/tests/unit/test_collections_recovery.js
@@ -2,16 +2,18 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Verify that we wipe the server if we have to regenerate keys.
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 add_task(async function test_missing_crypto_collection() {
+  enableValidationPrefs();
+
   let johnHelper = track_collections_helper();
   let johnU      = johnHelper.with_updated_collection;
   let johnColls  = johnHelper.collections;
 
   let empty = false;
   function maybe_empty(handler) {
     return function(request, response) {
       if (empty) {
--- a/services/sync/tests/unit/test_corrupt_keys.js
+++ b/services/sync/tests/unit/test_corrupt_keys.js
@@ -9,16 +9,18 @@ Cu.import("resource://services-sync/engi
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/status.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
 add_task(async function test_locally_changed_keys() {
+  enableValidationPrefs();
+
   let hmacErrorCount = 0;
   function counting(f) {
     return function() {
       hmacErrorCount++;
       return f.call(this);
     };
   }
 
--- a/services/sync/tests/unit/test_errorhandler_1.js
+++ b/services/sync/tests/unit/test_errorhandler_1.js
@@ -14,16 +14,17 @@ Cu.import("resource://gre/modules/FileUt
 Cu.import("resource://gre/modules/PromiseUtils.jsm");
 
 var fakeServer = new SyncServer();
 fakeServer.start();
 
 do_register_cleanup(function() {
   return new Promise(resolve => {
     fakeServer.stop(resolve);
+    Svc.Prefs.resetBranch("");
   });
 });
 
 var fakeServerUrl = "http://localhost:" + fakeServer.port;
 
 const logsdir = FileUtils.getDir("ProfD", ["weave", "logs"], true);
 
 const PROLONGED_ERROR_DURATION =
@@ -59,16 +60,18 @@ function run_test() {
 function clean() {
   Service.startOver();
   Status.resetSync();
   Status.resetBackoff();
   errorHandler.didReportProlongedError = false;
 }
 
 add_task(async function test_401_logout() {
+  enableValidationPrefs();
+
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   await sync_and_validate_telem();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
 
@@ -102,16 +105,18 @@ add_task(async function test_401_logout(
   _("Starting first sync.");
   let ping = await sync_and_validate_telem(true);
   deepEqual(ping.failureReason, { name: "httperror", code: 401 });
   _("First sync done.");
   await deferred.promise;
 });
 
 add_task(async function test_credentials_changed_logout() {
+  enableValidationPrefs();
+
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   await sync_and_validate_telem();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
 
@@ -355,16 +360,18 @@ add_task(function test_shouldReportLogin
   do_check_true(errorHandler.shouldReportError());
   // But any other status with a missing clusterURL is treated as a mid-sync
   // 401 (ie, should be treated as a node reassignment)
   Status.login = LOGIN_SUCCEEDED;
   do_check_false(errorHandler.shouldReportError());
 });
 
 add_task(async function test_login_syncAndReportErrors_non_network_error() {
+  enableValidationPrefs();
+
   // Test non-network errors are reported
   // when calling syncAndReportErrors
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
   Service.identity.resetSyncKeyBundle();
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
@@ -373,16 +380,18 @@ add_task(async function test_login_syncA
   await promiseObserved;
   do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_syncAndReportErrors_non_network_error() {
+  enableValidationPrefs();
+
   // Test non-network errors are reported
   // when calling syncAndReportErrors
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   Service.sync();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
@@ -404,16 +413,18 @@ add_task(async function test_sync_syncAn
   do_check_eq(Status.sync, CREDENTIALS_CHANGED);
   // If we clean this tick, telemetry won't get the right error
   await promiseNextTick();
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_login_syncAndReportErrors_prolonged_non_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, non-network errors are
   // reported when calling syncAndReportErrors.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
   Service.identity.resetSyncKeyBundle();
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
@@ -422,16 +433,18 @@ add_task(async function test_login_syncA
   await promiseObserved;
   do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_syncAndReportErrors_prolonged_non_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, non-network errors are
   // reported when calling syncAndReportErrors.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   Service.sync();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
@@ -453,16 +466,18 @@ add_task(async function test_sync_syncAn
   do_check_eq(Status.sync, CREDENTIALS_CHANGED);
   // If we clean this tick, telemetry won't get the right error
   await promiseNextTick();
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_login_syncAndReportErrors_network_error() {
+  enableValidationPrefs();
+
   // Test network errors are reported when calling syncAndReportErrors.
   await configureIdentity({username: "broken.wipe"});
   Service.clusterURL = fakeServerUrl;
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   errorHandler.syncAndReportErrors();
@@ -470,16 +485,18 @@ add_task(async function test_login_syncA
 
   do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
 
   clean();
 });
 
 
 add_test(function test_sync_syncAndReportErrors_network_error() {
+  enableValidationPrefs();
+
   // Test network errors are reported when calling syncAndReportErrors.
   Services.io.offline = true;
 
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
     do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
 
     Services.io.offline = false;
@@ -487,16 +504,18 @@ add_test(function test_sync_syncAndRepor
     run_next_test();
   });
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   errorHandler.syncAndReportErrors();
 });
 
 add_task(async function test_login_syncAndReportErrors_prolonged_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, network errors are reported
   // when calling syncAndReportErrors.
   await configureIdentity({username: "johndoe"});
 
   Service.clusterURL = fakeServerUrl;
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
@@ -504,16 +523,18 @@ add_task(async function test_login_syncA
   errorHandler.syncAndReportErrors();
   await promiseObserved;
   do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
 
   clean();
 });
 
 add_test(function test_sync_syncAndReportErrors_prolonged_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, network errors are reported
   // when calling syncAndReportErrors.
   Services.io.offline = true;
 
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
     do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
 
@@ -522,16 +543,18 @@ add_test(function test_sync_syncAndRepor
     run_next_test();
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
   errorHandler.syncAndReportErrors();
 });
 
 add_task(async function test_login_prolonged_non_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, non-network errors are reported
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
   Service.identity.resetSyncKeyBundle();
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
   setLastSync(PROLONGED_ERROR_DURATION);
@@ -540,16 +563,18 @@ add_task(async function test_login_prolo
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_prolonged_non_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, non-network errors are reported
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   Service.sync();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
@@ -569,32 +594,36 @@ add_task(async function test_sync_prolon
   await promiseObserved;
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_login_prolonged_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, network errors are reported
   await configureIdentity({username: "johndoe"});
   Service.clusterURL = fakeServerUrl;
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
   setLastSync(PROLONGED_ERROR_DURATION);
   Service.sync();
   await promiseObserved;
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
 
   clean();
 });
 
 add_test(function test_sync_prolonged_network_error() {
+  enableValidationPrefs();
+
   // Test prolonged, network errors are reported
   Services.io.offline = true;
 
   Svc.Obs.add("weave:ui:sync:error", function onSyncError() {
     Svc.Obs.remove("weave:ui:sync:error", onSyncError);
     do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
     do_check_true(errorHandler.didReportProlongedError);
 
@@ -603,16 +632,18 @@ add_test(function test_sync_prolonged_ne
     run_next_test();
   });
 
   setLastSync(PROLONGED_ERROR_DURATION);
   Service.sync();
 });
 
 add_task(async function test_login_non_network_error() {
+  enableValidationPrefs();
+
   // Test non-network errors are reported
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
   Service.identity.resetSyncKeyBundle();
 
   let promiseObserved = promiseOneObserver("weave:ui:login:error");
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
@@ -621,16 +652,18 @@ add_task(async function test_login_non_n
   do_check_eq(Status.login, LOGIN_FAILED_NO_PASSPHRASE);
   do_check_false(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_non_network_error() {
+  enableValidationPrefs();
+
   // Test non-network errors are reported
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   // By calling sync, we ensure we're logged in.
   Service.sync();
   do_check_eq(Status.sync, SYNC_SUCCEEDED);
   do_check_true(Service.isLoggedIn);
@@ -645,16 +678,18 @@ add_task(async function test_sync_non_ne
   do_check_eq(Status.sync, CREDENTIALS_CHANGED);
   do_check_false(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_login_network_error() {
+  enableValidationPrefs();
+
   await configureIdentity({username: "johndoe"});
   Service.clusterURL = fakeServerUrl;
 
   let promiseObserved = promiseOneObserver("weave:ui:clear-error");
   // Test network errors are not reported.
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   Service.sync();
@@ -662,16 +697,18 @@ add_task(async function test_login_netwo
   do_check_eq(Status.login, LOGIN_FAILED_NETWORK_ERROR);
   do_check_false(errorHandler.didReportProlongedError);
 
   Services.io.offline = false;
   clean();
 });
 
 add_test(function test_sync_network_error() {
+  enableValidationPrefs();
+
   // Test network errors are not reported.
   Services.io.offline = true;
 
   Svc.Obs.add("weave:ui:sync:finish", function onUIUpdate() {
     Svc.Obs.remove("weave:ui:sync:finish", onUIUpdate);
     do_check_eq(Status.sync, LOGIN_FAILED_NETWORK_ERROR);
     do_check_false(errorHandler.didReportProlongedError);
 
@@ -680,16 +717,18 @@ add_test(function test_sync_network_erro
     run_next_test();
   });
 
   setLastSync(NON_PROLONGED_ERROR_DURATION);
   Service.sync();
 });
 
 add_task(async function test_sync_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test server maintenance errors are not reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   const BACKOFF = 42;
   let engine = engineManager.get("catapult");
   engine.enabled = true;
   engine.exception = {status: 503,
@@ -714,16 +753,18 @@ add_task(async function test_sync_server
   do_check_eq(Status.sync, SERVER_MAINTENANCE);
   do_check_false(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_info_collections_login_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test info/collections server maintenance errors are not reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   await configureIdentity({username: "broken.info"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
@@ -752,16 +793,18 @@ add_task(async function test_info_collec
   do_check_false(errorHandler.didReportProlongedError);
 
   Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_meta_global_login_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test meta/global server maintenance errors are not reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   await configureIdentity({username: "broken.meta"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
--- a/services/sync/tests/unit/test_errorhandler_2.js
+++ b/services/sync/tests/unit/test_errorhandler_2.js
@@ -59,16 +59,18 @@ function run_test() {
 function clean() {
   Service.startOver();
   Status.resetSync();
   Status.resetBackoff();
   errorHandler.didReportProlongedError = false;
 }
 
 add_task(async function test_crypto_keys_login_server_maintenance_error() {
+  enableValidationPrefs();
+
   Status.resetSync();
   // Test crypto/keys server maintenance errors are not reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   await configureIdentity({username: "broken.keys"}, server);
 
   // Force re-download of keys
@@ -101,16 +103,18 @@ add_task(async function test_crypto_keys
   do_check_false(errorHandler.didReportProlongedError);
 
   Svc.Obs.remove("weave:ui:login:error", onUIUpdate);
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_sync_prolonged_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test prolonged server maintenance errors are reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   const BACKOFF = 42;
   let engine = engineManager.get("catapult");
   engine.enabled = true;
   engine.exception = {status: 503,
@@ -131,16 +135,18 @@ add_task(async function test_sync_prolon
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
 
   await promiseStopServer(server);
   clean();
 });
 
 add_task(async function test_info_collections_login_prolonged_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test info/collections prolonged server maintenance errors are reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   await configureIdentity({username: "broken.info"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
@@ -163,16 +169,18 @@ add_task(async function test_info_collec
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_meta_global_login_prolonged_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test meta/global prolonged server maintenance errors are reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   await configureIdentity({username: "broken.meta"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
@@ -195,16 +203,18 @@ add_task(async function test_meta_global
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_download_crypto_keys_login_prolonged_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test crypto/keys prolonged server maintenance errors are reported.
   let server = EHTestsCommon.sync_httpd_setup();
   await EHTestsCommon.setUp(server);
 
   await configureIdentity({username: "broken.keys"}, server);
   // Force re-download of keys
   Service.collectionKeys.clear();
 
@@ -228,16 +238,18 @@ add_task(async function test_download_cr
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
   do_check_true(errorHandler.didReportProlongedError);
 
   clean();
   await promiseStopServer(server);
 });
 
 add_task(async function test_upload_crypto_keys_login_prolonged_server_maintenance_error() {
+  enableValidationPrefs();
+
   // Test crypto/keys prolonged server maintenance errors are reported.
   let server = EHTestsCommon.sync_httpd_setup();
 
   // Start off with an empty account, do not upload a key.
   await configureIdentity({username: "broken.keys"}, server);
 
   let backoffInterval;
   Svc.Obs.add("weave:service:backoff:interval", function observe(subject, data) {
@@ -260,16 +272,18 @@ add_task(async function test_upload_cryp
   do_check_eq(Status.sync, PROLONGED_SYNC_FAILURE);
</