Backed out 4 changesets (bug 1062106) for build bustage
authorIris Hsiao <ihsiao@mozilla.com>
Tue, 21 Mar 2017 16:41:18 +0800
changeset 348596 63cbef012e2a58f56fc46c3b4802ae25f414a99c
parent 348595 c8f8b6cb5b7bce0f3c038a8d454488f3b5661796
child 348597 17ca3c39d1d988548f5c1c89ecdad4856e67c19e
push id31532
push userkwierso@gmail.com
push dateTue, 21 Mar 2017 22:32:51 +0000
treeherdermozilla-central@18bb0299dd9b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1062106
milestone55.0a1
backs out7ac1fffb6a876253d580abddfd62411673b93f3b
7682b2da0437cafe57b2358adf7400ce2d4b9ee7
e77bfa57be613acfa77da4c3d28ac61c9ef83ba6
a565aca3013c5b7bb3d3c09ba09a26d0ca6bb781
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
Backed out 4 changesets (bug 1062106) for build bustage Backed out changeset 7ac1fffb6a87 (bug 1062106) Backed out changeset 7682b2da0437 (bug 1062106) Backed out changeset e77bfa57be61 (bug 1062106) Backed out changeset a565aca3013c (bug 1062106)
devtools/client/performance/docs/markers.md
dom/base/nsDocument.cpp
dom/base/nsIDocument.h
dom/base/nsINode.h
dom/smil/moz.build
dom/smil/nsSMILAnimationController.cpp
dom/smil/nsSMILCompositor.cpp
dom/smil/nsSMILMappedAttribute.cpp
dom/smil/nsSMILMappedAttribute.h
dom/smil/nsSMILTargetIdentifier.h
dom/smil/nsSMILTimeContainer.cpp
dom/svg/SVGAnimateMotionElement.cpp
dom/svg/SVGAnimateMotionElement.h
dom/svg/SVGAnimationElement.cpp
dom/svg/SVGAnimationElement.h
dom/svg/nsSVGElement.cpp
dom/svg/nsSVGElement.h
layout/base/RestyleManager.cpp
layout/base/nsChangeHint.h
layout/reftests/svg/smil/anim-remove-6.svg
layout/reftests/svg/smil/mapped-attr-vs-css-prop-1.svg
layout/reftests/svg/smil/reftest.list
layout/style/SVGAttrAnimationRuleProcessor.cpp
layout/style/SVGAttrAnimationRuleProcessor.h
layout/style/SheetType.h
layout/style/moz.build
layout/style/nsStyleSet.cpp
layout/style/test/test_restyles_in_smil_animation.html
--- a/devtools/client/performance/docs/markers.md
+++ b/devtools/client/performance/docs/markers.md
@@ -49,16 +49,17 @@ needs to figure out the computational st
   any amount of the following, separated via " | ". All future restyleHints
   are from `RestyleManager::RestyleHintToString`.
 
   * "eRestyle_Self"
   * "eRestyle_Subtree"
   * "eRestyle_LaterSiblings"
   * "eRestyle_CSSTransitions"
   * "eRestyle_CSSAnimations"
+  * "eRestyle_SVGAttrAnimations"
   * "eRestyle_StyleAttribute"
   * "eRestyle_StyleAttribute_Animations"
   * "eRestyle_Force"
   * "eRestyle_ForceDescendants"
 
 
 ## Javascript
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -183,16 +183,17 @@
 
 // FOR CSP (autogenerated by xpidl)
 #include "nsIContentSecurityPolicy.h"
 #include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/dom/nsCSPService.h"
 #include "mozilla/dom/nsCSPUtils.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsHTMLCSSStyleSheet.h"
+#include "SVGAttrAnimationRuleProcessor.h"
 #include "mozilla/dom/DOMImplementation.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/Comment.h"
 #include "nsTextNode.h"
 #include "mozilla/dom/Link.h"
 #include "mozilla/dom/HTMLElementBinding.h"
 #include "nsXULAppAPI.h"
 #include "mozilla/dom/Touch.h"
@@ -2295,16 +2296,21 @@ nsDocument::ResetStylesheetsToURI(nsIURI
   } else {
     mAttrStyleSheet = new nsHTMLStyleSheet(this);
   }
 
   if (!mStyleAttrStyleSheet) {
     mStyleAttrStyleSheet = new nsHTMLCSSStyleSheet();
   }
 
+  if (!mSVGAttrAnimationRuleProcessor) {
+    mSVGAttrAnimationRuleProcessor =
+      new mozilla::SVGAttrAnimationRuleProcessor();
+  }
+
   // Now set up our style sets
   nsCOMPtr<nsIPresShell> shell = GetShell();
   if (shell) {
     FillStyleSet(shell->StyleSet());
   }
 }
 
 static void
@@ -12357,16 +12363,22 @@ nsDocument::DocAddSizeOfExcludingThis(ns
     CSSLoader()->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf);
 
   aWindowSizes->mDOMOtherSize +=
     mAttrStyleSheet ?
     mAttrStyleSheet->DOMSizeOfIncludingThis(aWindowSizes->mMallocSizeOf) :
     0;
 
   aWindowSizes->mDOMOtherSize +=
+    mSVGAttrAnimationRuleProcessor ?
+    mSVGAttrAnimationRuleProcessor->DOMSizeOfIncludingThis(
+                                      aWindowSizes->mMallocSizeOf) :
+    0;
+
+  aWindowSizes->mDOMOtherSize +=
     mStyledLinks.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
 
   aWindowSizes->mDOMOtherSize +=
     mIdentifierMap.SizeOfExcludingThis(aWindowSizes->mMallocSizeOf);
 
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - many!
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -104,16 +104,17 @@ struct nsCSSSelectorList;
 
 namespace mozilla {
 class AbstractThread;
 class CSSStyleSheet;
 class ErrorResult;
 class EventStates;
 class PendingAnimationTracker;
 class StyleSetHandle;
+class SVGAttrAnimationRuleProcessor;
 template<typename> class OwningNonNull;
 
 namespace css {
 class Loader;
 class ImageLoader;
 class Rule;
 } // namespace css
 
@@ -1276,16 +1277,26 @@ public:
   /**
    * Get this document's inline style sheet.  May return null if there
    * isn't one
    */
   nsHTMLCSSStyleSheet* GetInlineStyleSheet() const {
     return mStyleAttrStyleSheet;
   }
 
+  /**
+   * Get this document's SVG Animation rule processor.  May return null
+   * if there isn't one.
+   */
+  mozilla::SVGAttrAnimationRuleProcessor*
+  GetSVGAttrAnimationRuleProcessor() const
+  {
+    return mSVGAttrAnimationRuleProcessor;
+  }
+
   virtual void SetScriptGlobalObject(nsIScriptGlobalObject* aGlobalObject) = 0;
 
   /**
    * Get/set the object from which the context for the event/script handling can
    * be got. Normally GetScriptHandlingObject() returns the same object as
    * GetScriptGlobalObject(), but if the document is loaded as data,
    * non-null may be returned, even if GetScriptGlobalObject() returns null.
    * aHasHadScriptHandlingObject is set true if document has had the object
@@ -3006,16 +3017,17 @@ protected:
 
   // This is a weak reference, but we hold a strong reference to mNodeInfo,
   // which in turn holds a strong reference to this mNodeInfoManager.
   nsNodeInfoManager* mNodeInfoManager;
   RefPtr<mozilla::css::Loader> mCSSLoader;
   RefPtr<mozilla::css::ImageLoader> mStyleImageLoader;
   RefPtr<nsHTMLStyleSheet> mAttrStyleSheet;
   RefPtr<nsHTMLCSSStyleSheet> mStyleAttrStyleSheet;
+  RefPtr<mozilla::SVGAttrAnimationRuleProcessor> mSVGAttrAnimationRuleProcessor;
 
   // Tracking for images in the document.
   RefPtr<mozilla::dom::ImageTracker> mImageTracker;
 
   // The set of all object, embed, applet, video/audio elements or
   // nsIObjectLoadingContent or nsIDocumentActivity for which this is the
   // owner document. (They might not be in the document.)
   // These are non-owning pointers, the elements are responsible for removing
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -277,16 +277,17 @@ private:
 // defined, it is inherited from nsINode.
 // This macro isn't actually specific to nodes, and bug 956400 will move it into MFBT.
 #define NS_DECL_SIZEOF_EXCLUDING_THIS \
   virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override;
 
 // Categories of node properties
 // 0 is global.
 #define DOM_USER_DATA         1
+#define SMIL_MAPPED_ATTR_ANIMVAL 2
 
 // IID for the nsINode interface
 #define NS_INODE_IID \
 { 0x70ba4547, 0x7699, 0x44fc, \
   { 0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a } }
 
 /**
  * An internal interface that abstracts some DOMNode-related parts that both
--- a/dom/smil/moz.build
+++ b/dom/smil/moz.build
@@ -14,16 +14,17 @@ EXPORTS += [
     'nsISMILType.h',
     'nsSMILAnimationController.h',
     'nsSMILAnimationFunction.h',
     'nsSMILCompositorTable.h',
     'nsSMILCSSProperty.h',
     'nsSMILInstanceTime.h',
     'nsSMILInterval.h',
     'nsSMILKeySpline.h',
+    'nsSMILMappedAttribute.h',
     'nsSMILMilestone.h',
     'nsSMILNullType.h',
     'nsSMILRepeatCount.h',
     'nsSMILSetAnimationFunction.h',
     'nsSMILTargetIdentifier.h',
     'nsSMILTimeContainer.h',
     'nsSMILTimedElement.h',
     'nsSMILTimeValue.h',
@@ -42,16 +43,17 @@ UNIFIED_SOURCES += [
     'nsSMILAnimationFunction.cpp',
     'nsSMILCompositor.cpp',
     'nsSMILCSSProperty.cpp',
     'nsSMILCSSValueType.cpp',
     'nsSMILFloatType.cpp',
     'nsSMILInstanceTime.cpp',
     'nsSMILInterval.cpp',
     'nsSMILKeySpline.cpp',
+    'nsSMILMappedAttribute.cpp',
     'nsSMILNullType.cpp',
     'nsSMILParserUtils.cpp',
     'nsSMILRepeatCount.cpp',
     'nsSMILSetAnimationFunction.cpp',
     'nsSMILTimeContainer.cpp',
     'nsSMILTimedElement.cpp',
     'nsSMILTimeValue.cpp',
     'nsSMILTimeValueSpec.cpp',
--- a/dom/smil/nsSMILAnimationController.cpp
+++ b/dom/smil/nsSMILAnimationController.cpp
@@ -682,20 +682,46 @@ nsSMILAnimationController::GetTargetIden
     return false;
 
   // animateTransform can only animate transforms, conversely transforms
   // can only be animated by animateTransform
   if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
       (aAnimElem->IsSVGElement(nsGkAtoms::animateTransform)))
     return false;
 
+  // Look up target (animated) attribute-type
+  nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType();
+
+  // Check if an 'auto' attributeType refers to a CSS property or XML attribute.
+  // Note that SMIL requires we search for CSS properties first. So if they
+  // overlap, 'auto' = 'CSS'. (SMILANIM 3.1)
+  bool isCSS = false;
+  if (attributeType == eSMILTargetAttrType_auto) {
+    if (attributeNamespaceID == kNameSpaceID_None) {
+      // width/height are special as they may be attributes or for
+      // outer-<svg> elements, mapped into style.
+      if (attributeName == nsGkAtoms::width ||
+          attributeName == nsGkAtoms::height) {
+        isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
+      } else {
+        nsCSSPropertyID prop =
+          nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
+                                     CSSEnabledState::eForAllContent);
+        isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
+      }
+    }
+  } else {
+    isCSS = (attributeType == eSMILTargetAttrType_CSS);
+  }
+
   // Construct the key
-  aResult.mElement              = targetElem;
-  aResult.mAttributeName        = attributeName;
+  aResult.mElement = targetElem;
+  aResult.mAttributeName = attributeName;
   aResult.mAttributeNamespaceID = attributeNamespaceID;
+  aResult.mIsCSS = isCSS;
 
   return true;
 }
 
 void
 nsSMILAnimationController::AddStyleUpdatesTo(RestyleTracker& aTracker)
 {
   MOZ_ASSERT(mMightHavePendingStyleUpdates,
@@ -705,19 +731,23 @@ nsSMILAnimationController::AddStyleUpdat
     SVGAnimationElement* animElement = iter.Get()->GetKey();
 
     nsSMILTargetIdentifier key;
     if (!GetTargetIdentifierForAnimation(animElement, key)) {
       // Something's wrong/missing about animation's target; skip this animation
       continue;
     }
 
-    aTracker.AddPendingRestyle(key.mElement,
-                               eRestyle_StyleAttribute_Animations,
-                               nsChangeHint(0));
+    // mIsCSS true means that the rules are the ones returned from
+    // Element::GetSMILOverrideStyleDeclaration (via nsSMILCSSProperty objects),
+    // and mIsCSS false means the rules are nsSMILMappedAttribute objects
+    // returned from nsSVGElement::GetAnimatedContentDeclarationBlock.
+    nsRestyleHint rshint = key.mIsCSS ? eRestyle_StyleAttribute_Animations
+                                      : eRestyle_SVGAttrAnimations;
+    aTracker.AddPendingRestyle(key.mElement, rshint, nsChangeHint(0));
   }
 
   mMightHavePendingStyleUpdates = false;
 }
 
 //----------------------------------------------------------------------
 // Add/remove child time containers
 
--- a/dom/smil/nsSMILCompositor.cpp
+++ b/dom/smil/nsSMILCompositor.cpp
@@ -20,17 +20,18 @@ nsSMILCompositor::KeyEquals(KeyTypePoint
 /*static*/ PLDHashNumber
 nsSMILCompositor::HashKey(KeyTypePointer aKey)
 {
   // Combine the 3 values into one numeric value, which will be hashed.
   // NOTE: We right-shift one of the pointers by 2 to get some randomness in
   // its 2 lowest-order bits. (Those shifted-off bits will always be 0 since
   // our pointers will be word-aligned.)
   return (NS_PTR_TO_UINT32(aKey->mElement.get()) >> 2) +
-    NS_PTR_TO_UINT32(aKey->mAttributeName.get());
+    NS_PTR_TO_UINT32(aKey->mAttributeName.get()) +
+    (aKey->mIsCSS ? 1 : 0);
 }
 
 // Cycle-collection support
 void
 nsSMILCompositor::Traverse(nsCycleCollectionTraversalCallback* aCallback)
 {
   if (!mKey.mElement)
     return;
@@ -121,36 +122,28 @@ nsSMILCompositor::ClearAnimationEffects(
   smilAttr->ClearAnimValue();
 }
 
 // Protected Helper Functions
 // --------------------------
 nsISMILAttr*
 nsSMILCompositor::CreateSMILAttr()
 {
-  nsCSSPropertyID propID =
-    nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
-                               CSSEnabledState::eForAllContent);
-  if (nsSMILCSSProperty::IsPropertyAnimatable(propID)) {
-    // If we are animating the 'width' or 'height' of an outer SVG
-    // element we should animate it as a CSS property, but for other elements
-    // (e.g. <rect>) we should animate it as a length attribute.
-    // The easiest way to test for an outer SVG element, is to see if it is an
-    // SVG-namespace element mapping its width/height attribute to style.
-    bool animateAsAttr = (mKey.mAttributeName == nsGkAtoms::width ||
-                          mKey.mAttributeName == nsGkAtoms::height) &&
-                         mKey.mElement->GetNameSpaceID() == kNameSpaceID_SVG &&
-                         !mKey.mElement->IsAttributeMapped(mKey.mAttributeName);
-    if (!animateAsAttr) {
-      return new nsSMILCSSProperty(propID, mKey.mElement.get());
+  if (mKey.mIsCSS) {
+    nsCSSPropertyID propId =
+      nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
+                                 CSSEnabledState::eForAllContent);
+    if (nsSMILCSSProperty::IsPropertyAnimatable(propId)) {
+      return new nsSMILCSSProperty(propId, mKey.mElement.get());
     }
+  } else {
+    return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID,
+                                          mKey.mAttributeName);
   }
-
-  return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID,
-                                        mKey.mAttributeName);
+  return nullptr;
 }
 
 uint32_t
 nsSMILCompositor::GetFirstFuncToAffectSandwich()
 {
   // For performance reasons, we throttle most animations on elements in
   // display:none subtrees. (We can't throttle animations that target the
   // "display" property itself, though -- if we did, display:none elements
new file mode 100644
--- /dev/null
+++ b/dom/smil/nsSMILMappedAttribute.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of a SMIL-animatable mapped attribute on an element */
+#include "nsSMILMappedAttribute.h"
+#include "nsContentUtils.h"
+#include "nsError.h" // For NS_PROPTABLE_PROP_OVERWRITTEN
+#include "nsSMILValue.h"
+#include "nsSMILCSSValueType.h"
+#include "nsIDocument.h"
+#include "nsIPresShell.h"
+#include "nsCSSProps.h"
+#include "mozilla/dom/Element.h"
+
+// Callback function, for freeing string buffers stored in property table
+static void
+ReleaseStringBufferPropertyValue(void*    aObject,       /* unused */
+                                 nsIAtom* aPropertyName, /* unused */
+                                 void*    aPropertyValue,
+                                 void*    aData          /* unused */)
+{
+  nsStringBuffer* buf = static_cast<nsStringBuffer*>(aPropertyValue);
+  buf->Release();
+}
+
+
+nsresult
+nsSMILMappedAttribute::ValueFromString(const nsAString& aStr,
+                                       const mozilla::dom::SVGAnimationElement* aSrcElement,
+                                       nsSMILValue& aValue,
+                                       bool& aPreventCachingOfSandwich) const
+{
+  NS_ENSURE_TRUE(IsPropertyAnimatable(mPropID), NS_ERROR_FAILURE);
+
+  nsSMILCSSValueType::ValueFromString(mPropID, mElement, aStr, aValue,
+                                      &aPreventCachingOfSandwich);
+  return aValue.IsNull() ? NS_ERROR_FAILURE : NS_OK;
+}
+
+nsSMILValue
+nsSMILMappedAttribute::GetBaseValue() const
+{
+  nsAutoString baseStringValue;
+  RefPtr<nsIAtom> attrName = GetAttrNameAtom();
+  bool success = mElement->GetAttr(kNameSpaceID_None, attrName,
+                                     baseStringValue);
+  nsSMILValue baseValue;
+  if (success) {
+    // For base values, we don't need to worry whether the value returned is
+    // context-sensitive or not since the compositor will take care of comparing
+    // the returned (computed) base value and its cached value and determining
+    // if an update is required or not.
+    nsSMILCSSValueType::ValueFromString(mPropID, mElement,
+                                        baseStringValue, baseValue, nullptr);
+  } else {
+    // Attribute is unset -- use computed value.
+    // FIRST: Temporarily clear animated value, to make sure it doesn't pollute
+    // the computed value. (We want base value, _without_ animations applied.)
+    void* buf = mElement->UnsetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                                        attrName, nullptr);
+    FlushChangesToTargetAttr();
+
+    // SECOND: we use nsSMILCSSProperty::GetBaseValue to look up the property's
+    // computed value.  NOTE: This call will temporarily clear the SMIL
+    // override-style for the corresponding CSS property on our target element.
+    // This prevents any animations that target the CSS property from affecting
+    // animations that target the mapped attribute.
+    baseValue = nsSMILCSSProperty::GetBaseValue();
+
+    // FINALLY: If we originally had an animated value set, then set it again.
+    if (buf) {
+      mElement->SetProperty(SMIL_MAPPED_ATTR_ANIMVAL, attrName, buf,
+                            ReleaseStringBufferPropertyValue);
+      FlushChangesToTargetAttr();
+    }
+  }
+  return baseValue;
+}
+
+nsresult
+nsSMILMappedAttribute::SetAnimValue(const nsSMILValue& aValue)
+{
+  NS_ENSURE_TRUE(IsPropertyAnimatable(mPropID), NS_ERROR_FAILURE);
+
+  // Convert nsSMILValue to string
+  nsAutoString valStr;
+  if (!nsSMILCSSValueType::ValueToString(aValue, valStr)) {
+    NS_WARNING("Failed to convert nsSMILValue for mapped attr into a string");
+    return NS_ERROR_FAILURE;
+  }
+
+  RefPtr<nsIAtom> attrName = GetAttrNameAtom();
+  nsStringBuffer* oldValStrBuf = static_cast<nsStringBuffer*>
+    (mElement->GetProperty(SMIL_MAPPED_ATTR_ANIMVAL, attrName));
+  if (oldValStrBuf) {
+    nsString oldValStr;
+    nsContentUtils::PopulateStringFromStringBuffer(oldValStrBuf, oldValStr);
+    if (valStr.Equals(oldValStr)) {
+      // New animated value is the same as the old; nothing to do.
+      return NS_OK;
+    }
+  }
+
+  // Set the string as this mapped attribute's animated value.
+  nsStringBuffer* valStrBuf =
+    nsCSSValue::BufferFromString(nsString(valStr)).take();
+  nsresult rv = mElement->SetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                                      attrName, valStrBuf,
+                                      ReleaseStringBufferPropertyValue);
+  if (rv == NS_PROPTABLE_PROP_OVERWRITTEN) {
+    rv = NS_OK;
+  }
+  FlushChangesToTargetAttr();
+
+  return rv;
+}
+
+void
+nsSMILMappedAttribute::ClearAnimValue()
+{
+  RefPtr<nsIAtom> attrName = GetAttrNameAtom();
+  mElement->DeleteProperty(SMIL_MAPPED_ATTR_ANIMVAL, attrName);
+  FlushChangesToTargetAttr();
+}
+
+void
+nsSMILMappedAttribute::FlushChangesToTargetAttr() const
+{
+  // Clear animated content-style-rule
+  mElement->DeleteProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                           SMIL_MAPPED_ATTR_STYLEDECL_ATOM);
+  nsIDocument* doc = mElement->GetUncomposedDoc();
+
+  // Request animation restyle
+  if (doc) {
+    nsIPresShell* shell = doc->GetShell();
+    if (shell) {
+      shell->RestyleForAnimation(mElement, eRestyle_Self);
+    }
+  }
+}
+
+already_AddRefed<nsIAtom>
+nsSMILMappedAttribute::GetAttrNameAtom() const
+{
+  return NS_Atomize(nsCSSProps::GetStringValue(mPropID));
+}
new file mode 100644
--- /dev/null
+++ b/dom/smil/nsSMILMappedAttribute.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* representation of a SMIL-animatable mapped attribute on an element */
+
+#ifndef NS_SMILMAPPEDATTRIBUTE_H_
+#define NS_SMILMAPPEDATTRIBUTE_H_
+
+#include "mozilla/Attributes.h"
+#include "nsSMILCSSProperty.h"
+
+/* We'll use the empty-string atom |nsGkAtoms::_empty| as the key for storing an
+ * element's animated content style declaration in its Property Table, under the
+ * property-category SMIL_MAPPED_ATTR_ANIMVAL.  Everything else stored in that
+ * category is keyed off of the XML attribute name, so the empty string is a
+ * good "reserved" key to use for storing the style rule (since XML attributes
+ * all have nonempty names).
+ */
+#define SMIL_MAPPED_ATTR_STYLEDECL_ATOM nsGkAtoms::_empty
+
+/**
+ * nsSMILMappedAttribute: Implements the nsISMILAttr interface for SMIL
+ * animations whose targets are attributes that map to CSS properties.  An
+ * instance of this class represents a particular animation-targeted mapped
+ * attribute on a particular element.
+ */
+class nsSMILMappedAttribute : public nsSMILCSSProperty {
+public:
+  /**
+   * Constructs a new nsSMILMappedAttribute.
+   *
+   * @param  aPropID   The CSS property for the mapped attribute we're
+   *                   interested in animating.
+   * @param  aElement  The element whose attribute is being animated.
+   */
+  nsSMILMappedAttribute(nsCSSPropertyID aPropID, mozilla::dom::Element* aElement) :
+    nsSMILCSSProperty(aPropID, aElement) {}
+
+  // nsISMILAttr methods
+  virtual nsresult ValueFromString(const nsAString& aStr,
+                                   const mozilla::dom::SVGAnimationElement* aSrcElement,
+                                   nsSMILValue& aValue,
+                                   bool& aPreventCachingOfSandwich) const override;
+  virtual nsSMILValue GetBaseValue() const override;
+  virtual nsresult    SetAnimValue(const nsSMILValue& aValue) override;
+  virtual void        ClearAnimValue() override;
+
+protected:
+  // Helper Methods
+  void FlushChangesToTargetAttr() const;
+  already_AddRefed<nsIAtom> GetAttrNameAtom() const;
+};
+#endif // NS_SMILMAPPEDATTRIBUTE_H_
--- a/dom/smil/nsSMILTargetIdentifier.h
+++ b/dom/smil/nsSMILTargetIdentifier.h
@@ -7,42 +7,44 @@
 #ifndef NS_SMILTARGETIDENTIFIER_H_
 #define NS_SMILTARGETIDENTIFIER_H_
 
 #include "mozilla/dom/Element.h"
 
 /**
  * Struct: nsSMILTargetIdentifier
  *
- * Tuple of: { Animated Element, Attribute Name }
+ * Tuple of: { Animated Element, Attribute Name, Attribute Type (CSS vs. XML) }
  *
  * Used in nsSMILAnimationController as hash key for mapping an animation
  * target to the nsSMILCompositor for that target.
  *
  * NOTE: Need a nsRefPtr for the element & attribute name, because
  * nsSMILAnimationController retain its hash table for one sample into the
  * future, and we need to make sure their target isn't deleted in that time.
  */
 
 struct nsSMILTargetIdentifier
 {
   nsSMILTargetIdentifier()
     : mElement(nullptr), mAttributeName(nullptr),
-      mAttributeNamespaceID(kNameSpaceID_Unknown) {}
+      mAttributeNamespaceID(kNameSpaceID_Unknown), mIsCSS(false) {}
 
   inline bool Equals(const nsSMILTargetIdentifier& aOther) const
   {
     return (aOther.mElement              == mElement &&
             aOther.mAttributeName        == mAttributeName &&
-            aOther.mAttributeNamespaceID == mAttributeNamespaceID);
+            aOther.mAttributeNamespaceID == mAttributeNamespaceID &&
+            aOther.mIsCSS                == mIsCSS);
   }
 
   RefPtr<mozilla::dom::Element> mElement;
-  RefPtr<nsIAtom>               mAttributeName;
-  int32_t                       mAttributeNamespaceID;
+  RefPtr<nsIAtom>    mAttributeName;
+  int32_t              mAttributeNamespaceID;
+  bool                 mIsCSS;
 };
 
 /**
  * Class: nsSMILWeakTargetIdentifier
  *
  * Version of the above struct that uses non-owning pointers.  These are kept
  * private, to ensure that they aren't ever dereferenced (or used at all,
  * outside of Equals()).
@@ -50,32 +52,35 @@ struct nsSMILTargetIdentifier
  * This is solely for comparisons to determine if a target has changed
  * from one sample to the next.
  */
 class nsSMILWeakTargetIdentifier
 {
 public:
   // Trivial constructor
   nsSMILWeakTargetIdentifier()
-    : mElement(nullptr), mAttributeName(nullptr) {}
+    : mElement(nullptr), mAttributeName(nullptr), mIsCSS(false) {}
 
   // Allow us to update a weak identifier to match a given non-weak identifier
   nsSMILWeakTargetIdentifier&
     operator=(const nsSMILTargetIdentifier& aOther)
   {
     mElement = aOther.mElement;
     mAttributeName = aOther.mAttributeName;
+    mIsCSS = aOther.mIsCSS;
     return *this;
   }
 
   // Allow for comparison vs. non-weak identifier
   inline bool Equals(const nsSMILTargetIdentifier& aOther) const
   {
     return (aOther.mElement       == mElement &&
-            aOther.mAttributeName == mAttributeName);
+            aOther.mAttributeName == mAttributeName &&
+            aOther.mIsCSS         == mIsCSS);
   }
 
 private:
   const nsIContent* mElement;
   const nsIAtom*    mAttributeName;
+  bool              mIsCSS;
 };
 
 #endif // NS_SMILTARGETIDENTIFIER_H_
--- a/dom/smil/nsSMILTimeContainer.cpp
+++ b/dom/smil/nsSMILTimeContainer.cpp
@@ -6,18 +6,16 @@
 
 #include "nsSMILTimeContainer.h"
 #include "nsSMILTimeValue.h"
 #include "nsSMILTimedElement.h"
 #include <algorithm>
 
 #include "mozilla/AutoRestore.h"
 
-using namespace mozilla;
-
 nsSMILTimeContainer::nsSMILTimeContainer()
 :
   mParent(nullptr),
   mCurrentTime(0L),
   mParentOffset(0L),
   mPauseStart(0L),
   mNeedsPauseSample(false),
   mNeedsRewind(false),
--- a/dom/svg/SVGAnimateMotionElement.cpp
+++ b/dom/svg/SVGAnimateMotionElement.cpp
@@ -46,11 +46,20 @@ SVGAnimateMotionElement::GetTargetAttrib
   // <animateMotion> doesn't take an attributeName, since it doesn't target an
   // 'attribute' per se.  We'll use a unique dummy attribute-name so that our
   // nsSMILTargetIdentifier logic (which requires a attribute name) still works.
   *aNamespaceID = kNameSpaceID_None;
   *aLocalName = nsGkAtoms::mozAnimateMotionDummyAttr;
   return true;
 }
 
+nsSMILTargetAttrType
+SVGAnimateMotionElement::GetTargetAttributeType() const
+{
+  // <animateMotion> doesn't take an attributeType, since it doesn't target an
+  // 'attribute' per se.  We'll just return 'XML' for simplicity.  (This just
+  // needs to match what we expect in nsSVGElement::GetAnimAttr.)
+  return eSMILTargetAttrType_XML;
+}
+
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/svg/SVGAnimateMotionElement.h
+++ b/dom/svg/SVGAnimateMotionElement.h
@@ -32,16 +32,17 @@ protected:
 public:
   // nsIDOMNode specializations
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   // SVGAnimationElement
   virtual nsSMILAnimationFunction& AnimationFunction() override;
   virtual bool GetTargetAttributeName(int32_t *aNamespaceID,
                                       nsIAtom **aLocalName) const override;
+  virtual nsSMILTargetAttrType GetTargetAttributeType() const override;
 
   // nsSVGElement
   virtual nsIAtom* GetPathDataAttrName() const override {
     return nsGkAtoms::path;
   }
 
   // Utility method to let our <mpath> children tell us when they've changed,
   // so we can make sure our mAnimationFunction is marked as having changed.
--- a/dom/svg/SVGAnimationElement.cpp
+++ b/dom/svg/SVGAnimationElement.cpp
@@ -107,16 +107,31 @@ SVGAnimationElement::GetTargetAttributeN
   NS_ASSERTION(nameAttr->Type() == nsAttrValue::eAtom,
     "attributeName should have been parsed as an atom");
 
   return NS_SUCCEEDED(nsContentUtils::SplitQName(
                         this, nsDependentAtomString(nameAttr->GetAtomValue()),
                         aNamespaceID, aLocalName));
 }
 
+nsSMILTargetAttrType
+SVGAnimationElement::GetTargetAttributeType() const
+{
+  nsIContent::AttrValuesArray typeValues[] = { &nsGkAtoms::css,
+                                               &nsGkAtoms::XML,
+                                               nullptr};
+  nsSMILTargetAttrType smilTypes[] = { eSMILTargetAttrType_CSS,
+                                       eSMILTargetAttrType_XML };
+  int32_t index = FindAttrValueIn(kNameSpaceID_None,
+                                  nsGkAtoms::attributeType,
+                                  typeValues,
+                                  eCaseMatters);
+  return (index >= 0) ? smilTypes[index] : eSMILTargetAttrType_auto;
+}
+
 nsSMILTimedElement&
 SVGAnimationElement::TimedElement()
 {
   return mTimedElement;
 }
 
 nsSVGElement*
 SVGAnimationElement::GetTargetElement()
--- a/dom/svg/SVGAnimationElement.h
+++ b/dom/svg/SVGAnimationElement.h
@@ -61,16 +61,17 @@ public:
                                 const nsAttrValue* aValue, bool aNotify) override;
 
   const nsAttrValue* GetAnimAttr(nsIAtom* aName) const;
   bool GetAnimAttr(nsIAtom* aAttName, nsAString& aResult) const;
   bool HasAnimAttr(nsIAtom* aAttName) const;
   Element* GetTargetElementContent();
   virtual bool GetTargetAttributeName(int32_t* aNamespaceID,
                                       nsIAtom** aLocalName) const;
+  virtual nsSMILTargetAttrType GetTargetAttributeType() const;
   nsSMILTimedElement& TimedElement();
   nsSMILTimeContainer* GetTimeContainer();
   virtual nsSMILAnimationFunction& AnimationFunction() = 0;
 
   virtual bool IsEventAttributeName(nsIAtom* aName) override;
 
   // Utility methods for within SVG
   void ActivateByHyperlink();
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -42,16 +42,17 @@
 #include "SVGAnimatedLengthList.h"
 #include "SVGAnimatedPointList.h"
 #include "SVGAnimatedPathSegList.h"
 #include "SVGContentUtils.h"
 #include "SVGGeometryElement.h"
 #include "nsIFrame.h"
 #include "nsQueryObject.h"
 #include <stdarg.h>
+#include "nsSMILMappedAttribute.h"
 #include "SVGMotionSMILAttr.h"
 #include "nsAttrValueOrString.h"
 #include "nsSMILAnimationController.h"
 #include "mozilla/dom/SVGElementBinding.h"
 #include "mozilla/DeclarationBlock.h"
 #include "mozilla/DeclarationBlockInlines.h"
 #include "mozilla/Unused.h"
 #include "mozilla/RestyleManager.h"
@@ -928,16 +929,44 @@ nsSVGElement::WalkContentStyleRules(nsRu
     css::Declaration* declaration = mContentDeclarationBlock->AsGecko();
     declaration->SetImmutable();
     aRuleWalker->Forward(declaration);
   }
 
   return NS_OK;
 }
 
+void
+nsSVGElement::WalkAnimatedContentStyleRules(nsRuleWalker* aRuleWalker)
+{
+  // Update & walk the animated content style rule, to include style from
+  // animated mapped attributes.  But first, get nsPresContext to check
+  // whether this is a "no-animation restyle". (This should match the check
+  // in nsHTMLCSSStyleSheet::RulesMatching(), where we determine whether to
+  // apply the SMILOverrideStyle.)
+  RestyleManager* restyleManager =
+    aRuleWalker->PresContext()->RestyleManager();
+  MOZ_ASSERT(restyleManager->IsGecko(),
+             "stylo: Servo-backed style system should not be calling "
+             "WalkAnimatedContentStyleRules");
+  if (!restyleManager->AsGecko()->SkipAnimationRules()) {
+    // update/walk the animated content style rule.
+    DeclarationBlock* animContentDeclBlock = GetAnimatedContentDeclarationBlock();
+    if (!animContentDeclBlock) {
+      UpdateAnimatedContentDeclarationBlock();
+      animContentDeclBlock = GetAnimatedContentDeclarationBlock();
+    }
+    if (animContentDeclBlock) {
+      css::Declaration* declaration = animContentDeclBlock->AsGecko();
+      declaration->SetImmutable();
+      aRuleWalker->Forward(declaration);
+    }
+  }
+}
+
 NS_IMETHODIMP_(bool)
 nsSVGElement::IsAttributeMapped(const nsIAtom* name) const
 {
   if (name == nsGkAtoms::lang) {
     return true;
   }
   return nsSVGElementBase::IsAttributeMapped(name);
 }
@@ -1347,16 +1376,95 @@ nsSVGElement::UpdateContentDeclarationBl
 }
 
 const DeclarationBlock*
 nsSVGElement::GetContentDeclarationBlock() const
 {
   return mContentDeclarationBlock;
 }
 
+static void
+ParseMappedAttrAnimValueCallback(void*    aObject,
+                                 nsIAtom* aPropertyName,
+                                 void*    aPropertyValue,
+                                 void*    aData)
+{
+  MOZ_ASSERT(aPropertyName != SMIL_MAPPED_ATTR_STYLEDECL_ATOM,
+             "animated content style rule should have been removed "
+             "from properties table already (we're rebuilding it now)");
+
+  MappedAttrParser* mappedAttrParser = static_cast<MappedAttrParser*>(aData);
+  MOZ_ASSERT(mappedAttrParser, "parser should be non-null");
+
+  nsStringBuffer* animValBuf = static_cast<nsStringBuffer*>(aPropertyValue);
+  MOZ_ASSERT(animValBuf, "animated value should be non-null");
+
+  nsString animValStr;
+  nsContentUtils::PopulateStringFromStringBuffer(animValBuf, animValStr);
+
+  mappedAttrParser->ParseMappedAttrValue(aPropertyName, animValStr);
+}
+
+// Callback for freeing animated content decl block, in property table.
+static void
+ReleaseDeclBlock(void*    aObject,       /* unused */
+                 nsIAtom* aPropertyName,
+                 void*    aPropertyValue,
+                 void*    aData          /* unused */)
+{
+  MOZ_ASSERT(aPropertyName == SMIL_MAPPED_ATTR_STYLEDECL_ATOM,
+             "unexpected property name, for animated content style rule");
+  auto decl = static_cast<DeclarationBlock*>(aPropertyValue);
+  MOZ_ASSERT(decl, "unexpected null decl");
+  decl->Release();
+}
+
+void
+nsSVGElement::UpdateAnimatedContentDeclarationBlock()
+{
+  MOZ_ASSERT(!GetAnimatedContentDeclarationBlock(),
+             "Animated content declaration block already set");
+
+  nsIDocument* doc = OwnerDoc();
+  if (!doc) {
+    NS_ERROR("SVG element without owner document");
+    return;
+  }
+
+  // FIXME (bug 1342557): Support SMIL in Servo
+  MappedAttrParser mappedAttrParser(doc->CSSLoader(), doc->GetDocumentURI(),
+                                    GetBaseURI(), this, StyleBackendType::Gecko);
+  doc->PropertyTable(SMIL_MAPPED_ATTR_ANIMVAL)->
+    Enumerate(this, ParseMappedAttrAnimValueCallback, &mappedAttrParser);
+ 
+  RefPtr<DeclarationBlock> animContentDeclBlock =
+    mappedAttrParser.GetDeclarationBlock();
+
+  if (animContentDeclBlock) {
+#ifdef DEBUG
+    nsresult rv =
+#endif
+      SetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                  SMIL_MAPPED_ATTR_STYLEDECL_ATOM,
+                  animContentDeclBlock.forget().take(),
+                  ReleaseDeclBlock);
+    MOZ_ASSERT(rv == NS_OK,
+               "SetProperty failed (or overwrote something)");
+  }
+}
+
+DeclarationBlock*
+nsSVGElement::GetAnimatedContentDeclarationBlock()
+{
+  return
+    static_cast<DeclarationBlock*>(GetProperty(SMIL_MAPPED_ATTR_ANIMVAL,
+                                               SMIL_MAPPED_ATTR_STYLEDECL_ATOM,
+                                               nullptr));
+}
+
 /**
  * Helper methods for the type-specific WillChangeXXX methods.
  *
  * This method sends out appropriate pre-change notifications so that selector
  * restyles (e.g. due to changes that cause |elem[attr="val"]| to start/stop
  * matching) work, and it returns an nsAttrValue that _may_ contain the
  * attribute's pre-change value.
  *
@@ -2510,16 +2618,34 @@ nsSVGElement::RecompileScriptEventListen
     SetEventHandler(GetEventNameForAttr(attr), value, true);
   }
 }
 
 nsISMILAttr*
 nsSVGElement::GetAnimatedAttr(int32_t aNamespaceID, nsIAtom* aName)
 {
   if (aNamespaceID == kNameSpaceID_None) {
+    // We check mapped-into-style attributes first so that animations
+    // targeting width/height on outer-<svg> don't appear to be ignored
+    // because we returned a nsISMILAttr for the corresponding
+    // SVGAnimatedLength.
+
+    // Mapped attributes:
+    if (IsAttributeMapped(aName)) {
+      nsCSSPropertyID prop =
+        nsCSSProps::LookupProperty(nsDependentAtomString(aName),
+                                   CSSEnabledState::eForAllContent);
+      // Check IsPropertyAnimatable to avoid attributes that...
+      //  - map to explicitly unanimatable properties (e.g. 'direction')
+      //  - map to unsupported attributes (e.g. 'glyph-orientation-horizontal')
+      if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) {
+        return new nsSMILMappedAttribute(prop, this);
+      }
+    }
+
     // Transforms:
     if (GetTransformListAttrName() == aName) {
       // The transform attribute is being animated, so we must ensure that the
       // SVGAnimatedTransformList is/has been allocated:
       return GetAnimatedTransformList(DO_ALLOCATE)->ToSMILAttr(this);
     }
 
     // Motion (fake 'attribute' for animateMotion)
--- a/dom/svg/nsSVGElement.h
+++ b/dom/svg/nsSVGElement.h
@@ -352,16 +352,19 @@ protected:
   virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
                                 const nsAttrValue* aValue, bool aNotify) override;
   virtual bool ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute,
                                 const nsAString& aValue, nsAttrValue& aResult) override;
   static nsresult ReportAttributeParseFailure(nsIDocument* aDocument,
                                               nsIAtom* aAttribute,
                                               const nsAString& aValue);
 
+  void UpdateAnimatedContentDeclarationBlock();
+  mozilla::DeclarationBlock* GetAnimatedContentDeclarationBlock();
+
   nsAttrValue WillChangeValue(nsIAtom* aName);
   // aNewValue is set to the old value. This value may be invalid if
   // !StoresOwnData.
   void DidChangeValue(nsIAtom* aName, const nsAttrValue& aEmptyOrOldValue,
                       nsAttrValue& aNewValue);
   void MaybeSerializeAttrBeforeRemoval(nsIAtom* aName, bool aNotify);
 
   static nsIAtom* GetEventNameForAttr(nsIAtom* aAttr);
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -403,18 +403,18 @@ RestyleManager::ContentStateChangedInter
 
 /* static */ nsCString
 RestyleManager::RestyleHintToString(nsRestyleHint aHint)
 {
   nsCString result;
   bool any = false;
   const char* names[] = {
     "Self", "SomeDescendants", "Subtree", "LaterSiblings", "CSSTransitions",
-    "CSSAnimations", "StyleAttribute", "StyleAttribute_Animations", "Force",
-    "ForceDescendants"
+    "CSSAnimations", "SVGAttrAnimations", "StyleAttribute",
+    "StyleAttribute_Animations", "Force", "ForceDescendants"
   };
   uint32_t hint = aHint & ((1 << ArrayLength(names)) - 1);
   uint32_t rest = aHint & ~((1 << ArrayLength(names)) - 1);
   for (uint32_t i = 0; i < ArrayLength(names); i++) {
     if (hint & (1 << i)) {
       if (any) {
         result.AppendLiteral(" | ");
       }
--- a/layout/base/nsChangeHint.h
+++ b/layout/base/nsChangeHint.h
@@ -436,45 +436,53 @@ enum nsRestyleHint {
 
   // Replace the style data coming from CSS animations without updating
   // any other style data.  If a new style context results, update style
   // contexts on the descendants.  (Irrelevant if eRestyle_Self or
   // eRestyle_Subtree is also set, since those imply a superset of the
   // work.)
   eRestyle_CSSAnimations = 1 << 5,
 
+  // Replace the style data coming from SVG animations (SMIL Animations)
+  // without updating any other style data.  If a new style context
+  // results, update style contexts on the descendants.  (Irrelevant if
+  // eRestyle_Self or eRestyle_Subtree is also set, since those imply a
+  // superset of the work.)
+  eRestyle_SVGAttrAnimations = 1 << 6,
+
   // Replace the style data coming from inline style without updating
   // any other style data.  If a new style context results, update style
   // contexts on the descendants.  (Irrelevant if eRestyle_Self or
   // eRestyle_Subtree is also set, since those imply a superset of the
   // work.)  Supported only for element style contexts and not for
   // pseudo-elements or anonymous boxes, on which it converts to
   // eRestyle_Self.
   // If the change is for the advance of a declarative animation, use
   // the value below instead.
-  eRestyle_StyleAttribute = 1 << 6,
+  eRestyle_StyleAttribute = 1 << 7,
 
   // Same as eRestyle_StyleAttribute, but for when the change results
   // from the advance of a declarative animation.
-  eRestyle_StyleAttribute_Animations = 1 << 7,
+  eRestyle_StyleAttribute_Animations = 1 << 8,
 
   // Continue the restyling process to the current frame's children even
   // if this frame's restyling resulted in no style changes.
-  eRestyle_Force = 1 << 8,
+  eRestyle_Force = 1 << 9,
 
   // Continue the restyling process to all of the current frame's
   // descendants, even if any frame's restyling resulted in no style
   // changes.  (Implies eRestyle_Force.)  Note that this is weaker than
   // eRestyle_Subtree, which makes us rerun selector matching on all
   // descendants rather than just continuing the restyling process.
-  eRestyle_ForceDescendants = 1 << 9,
+  eRestyle_ForceDescendants = 1 << 10,
 
   // Useful unions:
   eRestyle_AllHintsWithAnimations = eRestyle_CSSTransitions |
                                     eRestyle_CSSAnimations |
+                                    eRestyle_SVGAttrAnimations |
                                     eRestyle_StyleAttribute_Animations,
 };
 
 // The functions below need an integral type to cast to to avoid
 // infinite recursion.
 typedef decltype(nsRestyleHint(0) + nsRestyleHint(0)) nsRestyleHint_size_t;
 
 inline constexpr nsRestyleHint operator|(nsRestyleHint aLeft,
--- a/layout/reftests/svg/smil/anim-remove-6.svg
+++ b/layout/reftests/svg/smil/anim-remove-6.svg
@@ -1,24 +1,24 @@
 <svg xmlns="http://www.w3.org/2000/svg"
      xmlns:xlink="http://www.w3.org/1999/xlink"
      onload="go()">
   <!-- In this test, we change an animation element's "attributeType" attribute
-       to refer to another namespace, which invalidates a completed, frozen animation
-       (since there is no animatable 'x' attribute in the xlink namespace on a rect)
-       We verify that animation effects are removed from the previously-targeted
-       attribute. -->
+       to "CSS", which invalidates a completed, frozen animation (since in SVG,
+       there's no CSS property that matches the attributeName that we're
+       animating, "x").  We verify that animation effects are removed from the
+       previously-targeted attribute. -->
   <script>
     function go() {
       // Seek animation before we start tweaking things, to make sure we've
       // already started sampling it.
       document.documentElement.setCurrentTime(2.0);
 
       var anim = document.getElementById("anim");
-      anim.setAttributeNS(null, "attributeName", "xlink:x");
+      anim.setAttributeNS(null, "attributeType", "CSS");
       setTimeAndSnapshot(2.5, false);
     }
   </script>
   <script xlink:href="smil-util.js" type="text/javascript"/>
   <rect x="15" y="15" width="200" height="200" fill="blue">
     <animate id="anim" attributeName="x"
              begin="0s" dur="2s" by="100" fill="freeze"/>
   </rect>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/mapped-attr-vs-css-prop-1.svg
@@ -0,0 +1,128 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="document.documentElement.setCurrentTime(1);
+             setTimeAndSnapshot(1, false)">
+  <!-- XXXdholbert the above "document.documentElement.setCurrentTime" call
+       shouldn't be necessary - it's a temporary workaround for Bug 552873 -->
+  <script xlink:href="smil-util.js" type="text/javascript"/>
+  <!-- This test consists of a 4x3 grid, containing various cases with
+       animations that have attributeType="CSS" vs "XML", for the
+       presentational ("mapped") attribute 'fill-opacity'.
+
+       As one would expect, the "CSS" and "XML" versions are treated as
+       separate animation targets.  However, there are some interactions:
+         - When they conflict, both the inline style & any animations with
+           attributeType="CSS" will have higher priority than the XML attribute
+           and animations with attributeType="XML".
+         - However, as described in the "FIRST ROW" comment below, animations
+           with attributeType="XML" can sometimes feed in to the base value
+           used for animations with attributeType="CSS". -->
+
+  <!-- GIANT GREEN BACKGROUND -->
+  <!-- (We'll put red rects on top, and then animate them to be transparent -->
+  <rect height="100%" width="100%" fill="lime" />
+
+  <!-- FIRST ROW: Additive CSS and XML animations, with CSS or XML base values.
+
+       When the base value is set using the inline style attribute, the
+       attributeType="XML" animation should be ignored, because the XML
+       attribute gets masked by the inline style.
+
+       However, when the base value is set using the XML attribute (or more
+       generally, when attributeType="XML" animations aren't masked by a value
+       in the inline style or in a stylesheet), then the animations will
+       effectively add together, because the (animated) XML attribute feeds
+       into the computed style, which gets used as the base value for the CSS
+       animation. -->
+  <g>
+    <!-- CSS base value + CSS animation + XML animation -->
+    <rect x="0" width="50" height="50" fill="red" style="fill-opacity: 0.5">
+      <animate attributeName="fill-opacity" attributeType="XML"
+               by="1" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               by="-0.5" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- CSS base value + XML animation + CSS animation -->
+    <rect x="50" width="50" height="50" fill="red" style="fill-opacity: 0.5">
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               by="-0.5" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="XML"
+               by="1" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- XML base value + CSS animation + XML animation -->
+    <rect x="100" width="50" height="50" fill="red" fill-opacity="0.5">
+      <animate attributeName="fill-opacity" attributeType="XML"
+               by="-0.2" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               by="-0.3" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- XML base value + XML animation + CSS animation -->
+    <rect x="150" width="50" height="50" fill="red" fill-opacity="0.5">
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               by="-0.2" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="XML"
+               by="-0.3" dur="1s" fill="freeze"/>
+    </rect>
+  </g>
+
+  <!-- SECOND ROW: Single animation, with CSS or XML attributeType & base value.
+       In every case except for CSS-base-value + XML animation, the animation
+       should take effect. -->
+  <g transform="translate(0, 50)">
+    <!-- CSS base value + CSS animation -->
+    <rect x="0" width="50" height="50" fill="red" style="fill-opacity: 0.5">
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               to="0" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- CSS base value + XML animation -->
+    <!-- (starting at fill-opacity 0, since anim shouldn't have any effect -->
+    <rect x="50" width="50" height="50" fill="red" style="fill-opacity: 0">
+      <animate attributeName="fill-opacity" attributeType="XML"
+               to="0.5" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- XML base value + CSS animation -->
+    <rect x="100" width="50" height="50" fill="red" fill-opacity="0.5">
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               to="0" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- XML base value + XML animation -->
+    <rect x="150" width="50" height="50" fill="red" fill-opacity="0.5">
+      <animate attributeName="fill-opacity" attributeType="XML"
+               to="0" dur="1s" fill="freeze"/>
+    </rect>
+  </g>
+
+  <!-- THIRD ROW: Competing animations, with CSS or XML attributeType & base
+       value. In each case, the attributeType="CSS" animation should win. -->
+  <g transform="translate(0, 100)">
+    <!-- CSS base value + CSS animation animation vs XML animation -->
+    <rect x="0" width="50" height="50" fill="red" style="fill-opacity: 0.5">
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               to="0" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="XML"
+               to="1" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- CSS base value + XML animation vs CSS animation -->
+    <rect x="50" width="50" height="50" fill="red" style="fill-opacity: 0.5">
+      <animate attributeName="fill-opacity" attributeType="XML"
+               to="1" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               to="0" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- XML base value + CSS animation vs XML animation -->
+    <rect x="100" width="50" height="50" fill="red" fill-opacity="0.5">
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               to="0" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="XML"
+               to="1" dur="1s" fill="freeze"/>
+    </rect>
+    <!-- XML base value + XML animation vs CSS animation -->
+    <rect x="150" width="50" height="50" fill="red" fill-opacity="0.5">
+      <animate attributeName="fill-opacity" attributeType="XML"
+               to="1" dur="1s" fill="freeze"/>
+      <animate attributeName="fill-opacity" attributeType="CSS"
+               to="0" dur="1s" fill="freeze"/>
+    </rect>
+  </g>
+</svg>
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -248,16 +248,19 @@ fuzzy-if(cocoaWidget&&layersGPUAccelerat
 == frozen-to-anim-1.svg lime.svg
 
 == inactivate-with-active-unchanged-1.svg anim-standard-ref.svg
 == inactivate-with-active-unchanged-2.svg anim-standard-ref.svg
 
 == mapped-attr-long-url-1.svg lime.svg
 == mapped-attr-long-url-2.svg lime.svg
 
+# interaction between xml mapped attributes and their css equivalents
+== mapped-attr-vs-css-prop-1.svg lime.svg
+
 == min-1.svg lime.svg
 
 == smil-transitions-interaction-1a.svg lime.svg
 == smil-transitions-interaction-1b.svg lime.svg
 == smil-transitions-interaction-2a.svg lime.svg
 == smil-transitions-interaction-2b.svg lime.svg
 == smil-transitions-interaction-3a.svg lime.svg
 == smil-transitions-interaction-3b.svg lime.svg
new file mode 100644
--- /dev/null
+++ b/layout/style/SVGAttrAnimationRuleProcessor.cpp
@@ -0,0 +1,122 @@
+/* -*- 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/.
+ */
+
+/*
+ * style rule processor for rules from SMIL Animation of SVG mapped
+ * attributes (attributes whose values are mapped into style)
+ */
+
+#include "SVGAttrAnimationRuleProcessor.h"
+#include "nsRuleProcessorData.h"
+#include "nsSVGElement.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+SVGAttrAnimationRuleProcessor::SVGAttrAnimationRuleProcessor()
+{
+}
+
+SVGAttrAnimationRuleProcessor::~SVGAttrAnimationRuleProcessor()
+{
+}
+
+NS_IMPL_ISUPPORTS(SVGAttrAnimationRuleProcessor, nsIStyleRuleProcessor)
+
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(ElementRuleProcessorData* aData)
+{
+  ElementRulesMatching(aData->mElement, aData->mRuleWalker);
+}
+
+void
+SVGAttrAnimationRuleProcessor::ElementRulesMatching(Element* aElement,
+                                                    nsRuleWalker* aRuleWalker)
+{
+  if (aElement->IsSVGElement()) {
+    static_cast<nsSVGElement*>(aElement)->
+      WalkAnimatedContentStyleRules(aRuleWalker);
+  }
+}
+
+/* virtual */ nsRestyleHint
+SVGAttrAnimationRuleProcessor::HasStateDependentStyle(StateRuleProcessorData* aData)
+{
+  return nsRestyleHint(0);
+}
+
+/* virtual */ nsRestyleHint
+SVGAttrAnimationRuleProcessor::HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData)
+{
+  return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+SVGAttrAnimationRuleProcessor::HasDocumentStateDependentStyle(StateRuleProcessorData* aData)
+{
+  return false;
+}
+
+/* virtual */ nsRestyleHint
+SVGAttrAnimationRuleProcessor::HasAttributeDependentStyle(
+    AttributeRuleProcessorData* aData,
+    RestyleHintData& aRestyleHintDataResult)
+{
+  return nsRestyleHint(0);
+}
+
+/* virtual */ bool
+SVGAttrAnimationRuleProcessor::MediumFeaturesChanged(nsPresContext* aPresContext)
+{
+  return false;
+}
+
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(PseudoElementRuleProcessorData* aData)
+{
+  // If SMIL Animation of SVG attributes can ever target
+  // pseudo-elements, we need to adjust either
+  // nsStyleSet::RuleNodeWithReplacement or the test in
+  // ElementRestyler::RestyleSelf (added in bug 977991 patch 4) to
+  // handle such styles.
+}
+
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(AnonBoxRuleProcessorData* aData)
+{
+  // If SMIL Animation of SVG attributes can ever target anonymous boxes,
+  // see comment in RulesMatching(PseudoElementRuleProcessorData*).
+}
+
+#ifdef MOZ_XUL
+/* virtual */ void
+SVGAttrAnimationRuleProcessor::RulesMatching(XULTreeRuleProcessorData* aData)
+{
+  // If SMIL Animation of SVG attributes can ever target XUL tree pseudos,
+  // see comment in RulesMatching(PseudoElementRuleProcessorData*).
+}
+#endif
+
+/* virtual */ size_t
+SVGAttrAnimationRuleProcessor::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return 0; // SVGAttrAnimationRuleProcessors are charged to the DOM, not layout
+}
+
+/* virtual */ size_t
+SVGAttrAnimationRuleProcessor::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return 0; // SVGAttrAnimationRuleProcessors are charged to the DOM, not layout
+}
+
+size_t
+SVGAttrAnimationRuleProcessor::DOMSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t n = aMallocSizeOf(this);
+
+  return n;
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/SVGAttrAnimationRuleProcessor.h
@@ -0,0 +1,68 @@
+/* -*- 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/. */
+
+/*
+ * style rule processor for rules from SMIL Animation of SVG mapped
+ * attributes (attributes whose values are mapped into style)
+ */
+
+#ifndef mozilla_SVGAttrAnimationRuleProcessor_h_
+#define mozilla_SVGAttrAnimationRuleProcessor_h_
+
+#include "nsIStyleRuleProcessor.h"
+
+class nsRuleWalker;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+} // namespace dom
+
+class SVGAttrAnimationRuleProcessor final : public nsIStyleRuleProcessor
+{
+public:
+  SVGAttrAnimationRuleProcessor();
+
+private:
+  ~SVGAttrAnimationRuleProcessor();
+
+public:
+  NS_DECL_ISUPPORTS
+
+  // nsIStyleRuleProcessor API
+  virtual void RulesMatching(ElementRuleProcessorData* aData) override;
+  virtual void RulesMatching(PseudoElementRuleProcessorData* aData) override;
+  virtual void RulesMatching(AnonBoxRuleProcessorData* aData) override;
+#ifdef MOZ_XUL
+  virtual void RulesMatching(XULTreeRuleProcessorData* aData) override;
+#endif
+  virtual nsRestyleHint HasStateDependentStyle(StateRuleProcessorData* aData) override;
+  virtual nsRestyleHint HasStateDependentStyle(PseudoElementStateRuleProcessorData* aData) override;
+  virtual bool HasDocumentStateDependentStyle(StateRuleProcessorData* aData) override;
+  virtual nsRestyleHint
+    HasAttributeDependentStyle(AttributeRuleProcessorData* aData,
+                               RestyleHintData& aRestyleHintDataResult) override;
+  virtual bool MediumFeaturesChanged(nsPresContext* aPresContext) override;
+  virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+    const MOZ_MUST_OVERRIDE override;
+  virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf)
+    const MOZ_MUST_OVERRIDE override;
+
+  size_t DOMSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+  // A shortcut for nsStyleSet to call RulesMatching with less setup.
+  void ElementRulesMatching(mozilla::dom::Element* aElement,
+                            nsRuleWalker* aRuleWalker);
+
+private:
+  SVGAttrAnimationRuleProcessor(const SVGAttrAnimationRuleProcessor& aCopy) = delete;
+  SVGAttrAnimationRuleProcessor& operator=(const SVGAttrAnimationRuleProcessor& aCopy) = delete;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(mozilla_SVGAttrAnimationRuleProcessor_h_) */
--- a/layout/style/SheetType.h
+++ b/layout/style/SheetType.h
@@ -16,16 +16,17 @@ namespace mozilla {
 // highest (for non-!important rules).
 //
 // Be sure to update NS_RULE_NODE_LEVEL_MASK when changing the number
 // of sheet types; static assertions enforce this.
 enum class SheetType : uint8_t {
   Agent, // CSS
   User, // CSS
   PresHint,
+  SVGAttrAnimation,
   Doc, // CSS
   ScopedDoc,
   StyleAttr,
   Override, // CSS
   Animation,
   Transition,
 
   Count,
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -223,16 +223,17 @@ UNIFIED_SOURCES += [
     'ServoNamespaceRule.cpp',
     'ServoSpecifiedValues.cpp',
     'ServoStyleRule.cpp',
     'ServoStyleSet.cpp',
     'ServoStyleSheet.cpp',
     'StyleAnimationValue.cpp',
     'StyleRule.cpp',
     'StyleSheet.cpp',
+    'SVGAttrAnimationRuleProcessor.cpp',
 ]
 
 # - nsCSSRuleProcessor.cpp needs to be built separately because it uses
 # plarena.h.
 # - nsLayoutStylesheetCache.cpp needs to be built separately because it uses
 # nsExceptionHandler.h, which includes windows.h.
 SOURCES += [
     'nsCSSRuleProcessor.cpp',
--- a/layout/style/nsStyleSet.cpp
+++ b/layout/style/nsStyleSet.cpp
@@ -31,16 +31,17 @@
 #include "nsRuleData.h"
 #include "nsRuleProcessorData.h"
 #include "nsAnimationManager.h"
 #include "nsStyleSheetService.h"
 #include "mozilla/dom/Element.h"
 #include "GeckoProfiler.h"
 #include "nsHTMLCSSStyleSheet.h"
 #include "nsHTMLStyleSheet.h"
+#include "SVGAttrAnimationRuleProcessor.h"
 #include "nsCSSRules.h"
 #include "nsPrintfCString.h"
 #include "nsIFrame.h"
 #include "mozilla/RestyleManager.h"
 #include "mozilla/RestyleManagerInlines.h"
 #include "nsQueryObject.h"
 
 #include <inttypes.h>
@@ -287,16 +288,17 @@ nsStyleSet::Init(nsPresContext *aPresCon
   mDisableTextZoomStyleRule = new nsDisableTextZoomStyleRule;
 
   mRuleTree = nsRuleNode::CreateRootNode(aPresContext);
 
   // Make an explicit GatherRuleProcessors call for the levels that
   // don't have style sheets.  The other levels will have their calls
   // triggered by DirtyRuleProcessors.
   GatherRuleProcessors(SheetType::PresHint);
+  GatherRuleProcessors(SheetType::SVGAttrAnimation);
   GatherRuleProcessors(SheetType::StyleAttr);
   GatherRuleProcessors(SheetType::Animation);
   GatherRuleProcessors(SheetType::Transition);
 }
 
 nsresult
 nsStyleSet::BeginReconstruct()
 {
@@ -471,16 +473,21 @@ nsStyleSet::GatherRuleProcessors(SheetTy
       MOZ_ASSERT(mSheets[aType].IsEmpty());
       mRuleProcessors[aType] = PresContext()->Document()->GetInlineStyleSheet();
       return NS_OK;
     case SheetType::PresHint:
       MOZ_ASSERT(mSheets[aType].IsEmpty());
       mRuleProcessors[aType] =
         PresContext()->Document()->GetAttributeStyleSheet();
       return NS_OK;
+    case SheetType::SVGAttrAnimation:
+      MOZ_ASSERT(mSheets[aType].IsEmpty());
+      mRuleProcessors[aType] =
+        PresContext()->Document()->GetSVGAttrAnimationRuleProcessor();
+      return NS_OK;
     default:
       // keep going
       break;
   }
   MOZ_ASSERT(IsCSSSheetType(aType));
   if (aType == SheetType::ScopedDoc) {
     // Create a rule processor for each scope.
     uint32_t count = mSheets[SheetType::ScopedDoc].Length();
@@ -1103,16 +1110,17 @@ nsStyleSet::FileRules(nsIStyleRuleProces
 
   NS_ASSERTION(mBatching == 0, "rule processors out of date");
 
   // Cascading order:
   // [least important]
   //  - UA normal rules                    = Agent        normal
   //  - User normal rules                  = User         normal
   //  - Presentation hints                 = PresHint     normal
+  //  - SVG Animation (highest pres hint)  = SVGAttrAnimation normal
   //  - Author normal rules                = Document     normal
   //  - Override normal rules              = Override     normal
   //  - animation rules                    = Animation    normal
   //  - Author !important rules            = Document     !important
   //  - Override !important rules          = Override     !important
   //  - User !important rules              = User         !important
   //  - UA !important rules                = Agent        !important
   //  - transition rules                   = Transition   normal
@@ -1134,17 +1142,21 @@ nsStyleSet::FileRules(nsIStyleRuleProces
   if (!skipUserStyles && mRuleProcessors[SheetType::User]) // NOTE: different
     (*aCollectorFunc)(mRuleProcessors[SheetType::User], aData);
   nsRuleNode* lastUserRN = aRuleWalker->CurrentNode();
   bool haveImportantUserRules = !aRuleWalker->GetCheckForImportantRules();
 
   aRuleWalker->SetLevel(SheetType::PresHint, false, false);
   if (mRuleProcessors[SheetType::PresHint])
     (*aCollectorFunc)(mRuleProcessors[SheetType::PresHint], aData);
-  nsRuleNode* lastPresHintRN = aRuleWalker->CurrentNode();
+
+  aRuleWalker->SetLevel(SheetType::SVGAttrAnimation, false, false);
+  if (mRuleProcessors[SheetType::SVGAttrAnimation])
+    (*aCollectorFunc)(mRuleProcessors[SheetType::SVGAttrAnimation], aData);
+  nsRuleNode* lastSVGAttrAnimationRN = aRuleWalker->CurrentNode();
 
   aRuleWalker->SetLevel(SheetType::Doc, false, true);
   bool cutOffInheritance = false;
   if (mBindingManager && aElement) {
     // We can supply additional document-level sheets that should be walked.
     mBindingManager->WalkRules(aCollectorFunc,
                                static_cast<ElementDependentRuleProcessorData*>(aData),
                                &cutOffInheritance);
@@ -1208,21 +1220,21 @@ nsStyleSet::FileRules(nsIStyleRuleProces
 #ifdef DEBUG
   else {
     AssertNoImportantRules(lastScopedRN, lastDocRN);
   }
 #endif
 
   if (haveImportantDocRules) {
     aRuleWalker->SetLevel(SheetType::Doc, true, false);
-    AddImportantRules(lastDocRN, lastPresHintRN, aRuleWalker);  // doc
+    AddImportantRules(lastDocRN, lastSVGAttrAnimationRN, aRuleWalker);  // doc
   }
 #ifdef DEBUG
   else {
-    AssertNoImportantRules(lastDocRN, lastPresHintRN);
+    AssertNoImportantRules(lastDocRN, lastSVGAttrAnimationRN);
   }
 #endif
 
   if (haveImportantStyleAttrRules) {
     aRuleWalker->SetLevel(SheetType::StyleAttr, true, false);
     AddImportantRules(lastStyleAttrRN, lastScopedRN, aRuleWalker);  // style attr
   }
 #ifdef DEBUG
@@ -1237,17 +1249,17 @@ nsStyleSet::FileRules(nsIStyleRuleProces
   }
 #ifdef DEBUG
   else {
     AssertNoImportantRules(lastOvrRN, lastStyleAttrRN);
   }
 #endif
 
 #ifdef DEBUG
-  AssertNoCSSRules(lastPresHintRN, lastUserRN);
+  AssertNoCSSRules(lastSVGAttrAnimationRN, lastUserRN);
 #endif
 
   if (haveImportantUserRules) {
     aRuleWalker->SetLevel(SheetType::User, true, false);
     AddImportantRules(lastUserRN, lastAgentRN, aRuleWalker); //user
   }
 #ifdef DEBUG
   else {
@@ -1294,16 +1306,19 @@ nsStyleSet::WalkRuleProcessors(nsIStyleR
 
   bool skipUserStyles = aData->mElement->IsInNativeAnonymousSubtree();
   if (!skipUserStyles && mRuleProcessors[SheetType::User]) // NOTE: different
     (*aFunc)(mRuleProcessors[SheetType::User], aData);
 
   if (mRuleProcessors[SheetType::PresHint])
     (*aFunc)(mRuleProcessors[SheetType::PresHint], aData);
 
+  if (mRuleProcessors[SheetType::SVGAttrAnimation])
+    (*aFunc)(mRuleProcessors[SheetType::SVGAttrAnimation], aData);
+
   bool cutOffInheritance = false;
   if (mBindingManager) {
     // We can supply additional document-level sheets that should be walked.
     if (aWalkAllXBLStylesheets) {
       mBindingManager->WalkAllRules(aFunc, aData);
     } else {
       mBindingManager->WalkRules(aFunc, aData, &cutOffInheritance);
     }
@@ -1477,16 +1492,17 @@ struct CascadeLevel {
   bool mCheckForImportantRules;
   nsRestyleHint mLevelReplacementHint;
 };
 
 static const CascadeLevel gCascadeLevels[] = {
   { SheetType::Agent,            false, false, nsRestyleHint(0) },
   { SheetType::User,             false, false, nsRestyleHint(0) },
   { SheetType::PresHint,         false, false, nsRestyleHint(0) },
+  { SheetType::SVGAttrAnimation, false, false, eRestyle_SVGAttrAnimations },
   { SheetType::Doc,              false, false, nsRestyleHint(0) },
   { SheetType::ScopedDoc,        false, false, nsRestyleHint(0) },
   { SheetType::StyleAttr,        false, true,  eRestyle_StyleAttribute |
                                                eRestyle_StyleAttribute_Animations },
   { SheetType::Override,         false, false, nsRestyleHint(0) },
   { SheetType::Animation,        false, false, eRestyle_CSSAnimations },
   { SheetType::ScopedDoc,        true,  false, nsRestyleHint(0) },
   { SheetType::Doc,              true,  false, nsRestyleHint(0) },
@@ -1514,16 +1530,17 @@ nsStyleSet::RuleNodeWithReplacement(Elem
              "should have aPseudoElement only for certain pseudo elements");
 
   // Remove the Force bits, which we don't need and which could confuse
   // the remainingReplacements code below.
   aReplacements &= ~(eRestyle_Force | eRestyle_ForceDescendants);
 
   MOZ_ASSERT(!(aReplacements & ~(eRestyle_CSSTransitions |
                                  eRestyle_CSSAnimations |
+                                 eRestyle_SVGAttrAnimations |
                                  eRestyle_StyleAttribute |
                                  eRestyle_StyleAttribute_Animations)),
              "unexpected replacement bits");
 
   // This array can be hot and often grows to ~20 elements, so inline storage
   // is best.
   AutoTArray<RuleNodeInfo, 30> rules;
 
@@ -1633,16 +1650,26 @@ nsStyleSet::RuleNodeWithReplacement(Elem
                                nullptr);
             if (rule) {
               ruleWalker.ForwardOnPossiblyCSSRule(rule);
               ruleWalker.CurrentNode()->SetIsAnimationRule();
             }
           }
           break;
         }
+        case SheetType::SVGAttrAnimation: {
+          SVGAttrAnimationRuleProcessor* ruleProcessor =
+            static_cast<SVGAttrAnimationRuleProcessor*>(
+              mRuleProcessors[SheetType::SVGAttrAnimation].get());
+          if (ruleProcessor &&
+              aPseudoType == CSSPseudoElementType::NotPseudo) {
+            ruleProcessor->ElementRulesMatching(aElement, &ruleWalker);
+          }
+          break;
+        }
         case SheetType::StyleAttr: {
           if (!level->mIsImportant) {
             // First time through, we handle the non-!important rule.
             nsHTMLCSSStyleSheet* ruleProcessor =
               static_cast<nsHTMLCSSStyleSheet*>(
                 mRuleProcessors[SheetType::StyleAttr].get());
             if (ruleProcessor) {
               lastScopedRN = ruleWalker.CurrentNode();
--- a/layout/style/test/test_restyles_in_smil_animation.html
+++ b/layout/style/test/test_restyles_in_smil_animation.html
@@ -41,17 +41,17 @@ function observeStyling(frameCount) {
   docShell.recordProfileTimelineMarkers = true;
   docShell.popProfileTimelineMarkers();
 
   return new Promise(function(resolve) {
     return waitForAnimationFrames(frameCount).then(function() {
       var markers = docShell.popProfileTimelineMarkers();
       docShell.recordProfileTimelineMarkers = false;
       var stylingMarkers = markers.filter(function(marker, index) {
-        return marker.restyleHint == "eRestyle_StyleAttribute_Animations";
+        return marker.restyleHint == "eRestyle_SVGAttrAnimations";
       });
       resolve(stylingMarkers);
     });
   });
 }
 
 function ensureElementRemoval(aElement) {
   return new Promise(function(resolve) {