Bug 619503 - Support SMIL animation of the class attribute r=dholbert,jwatt
authorRobert Longson <longsonr@gmail.com>
Sun, 23 Jan 2011 17:08:17 +0000
changeset 64109 3baa29a2109cbce384e39413780b8c11c5b8144d
parent 64108 999905af7783cc1f3fe3c5374ff0264b43099da3
child 64110 53c333d9961f88e320c021146281a679a9cde856
push id19314
push usereakhgari@mozilla.com
push dateTue, 29 Mar 2011 14:40:44 +0000
treeherdermozilla-central@021f97348434 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, jwatt
bugs619503
milestone2.2a1pre
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
Bug 619503 - Support SMIL animation of the class attribute r=dholbert,jwatt
content/svg/content/src/Makefile.in
content/svg/content/src/nsSVGClass.cpp
content/svg/content/src/nsSVGClass.h
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElement.h
content/svg/content/src/nsSVGStylableElement.cpp
content/svg/content/src/nsSVGStylableElement.h
layout/reftests/svg/dynamic-class-01.svg
layout/reftests/svg/reftest.list
layout/reftests/svg/smil/anim-class-01.svg
layout/reftests/svg/smil/anim-class-02.svg
layout/reftests/svg/smil/anim-class-03.svg
layout/reftests/svg/smil/anim-class-04-ref.svg
layout/reftests/svg/smil/anim-class-04.svg
layout/reftests/svg/smil/reftest.list
--- a/content/svg/content/src/Makefile.in
+++ b/content/svg/content/src/Makefile.in
@@ -62,16 +62,17 @@ CPPSRCS		= \
 		nsDOMSVGZoomEvent.cpp \
 		nsDOMSVGEvent.cpp \
 		nsSVGAElement.cpp \
 		nsSVGAltGlyphElement.cpp \
 		nsSVGAngle.cpp \
 		nsSVGAnimatedTransformList.cpp \
 		nsSVGBoolean.cpp \
 		nsSVGCircleElement.cpp \
+		nsSVGClass.cpp \
 		nsSVGClipPathElement.cpp \
 		nsSVGDataParser.cpp \
 		nsSVGDefsElement.cpp \
 		nsSVGDescElement.cpp \
 		nsSVGElement.cpp \
 		nsSVGElementFactory.cpp \
 		nsSVGEllipseElement.cpp \
 		nsSVGEnum.cpp \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGClass.cpp
@@ -0,0 +1,158 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SVG project.
+ *
+ * The Initial Developer of the Original Code is Robert Longson.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsSVGClass.h"
+#ifdef MOZ_SMIL
+#include "nsSMILValue.h"
+#include "SMILStringType.h"
+#endif // MOZ_SMIL
+
+using namespace mozilla;
+
+NS_SVG_VAL_IMPL_CYCLE_COLLECTION(nsSVGClass::DOMAnimatedString, mSVGElement)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGClass::DOMAnimatedString)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGClass::DOMAnimatedString)
+
+DOMCI_DATA(SVGAnimatedClass, nsSVGClass::DOMAnimatedString)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGClass::DOMAnimatedString)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMSVGAnimatedString)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGAnimatedString)
+NS_INTERFACE_MAP_END
+
+/* Implementation */
+
+void
+nsSVGClass::SetBaseValue(const nsAString& aValue,
+                         nsSVGElement *aSVGElement,
+                         PRBool aDoSetAttr)
+{
+  NS_ASSERTION(aSVGElement, "Null element passed to SetBaseValue");
+
+  aSVGElement->SetFlags(NODE_MAY_HAVE_CLASS);
+  if (aDoSetAttr) {
+    aSVGElement->SetAttr(kNameSpaceID_None, nsGkAtoms::_class, aValue, PR_TRUE);
+  }
+#ifdef MOZ_SMIL
+  if (mAnimVal) {
+    aSVGElement->AnimationNeedsResample();
+  }
+#endif
+}
+
+void
+nsSVGClass::GetAnimValue(nsAString& aResult, const nsSVGElement *aSVGElement) const
+{
+  if (mAnimVal) {
+    aResult = *mAnimVal;
+    return;
+  }
+
+  aSVGElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aResult);
+}
+
+void
+nsSVGClass::SetAnimValue(const nsAString& aValue, nsSVGElement *aSVGElement)
+{
+  if (!mAnimVal) {
+    mAnimVal = new nsString();
+  }
+  *mAnimVal = aValue;
+  aSVGElement->SetFlags(NODE_MAY_HAVE_CLASS);
+  aSVGElement->DidAnimateClass();
+}
+
+nsresult
+nsSVGClass::ToDOMAnimatedString(nsIDOMSVGAnimatedString **aResult,
+                                nsSVGElement *aSVGElement)
+{
+  *aResult = new DOMAnimatedString(this, aSVGElement);
+  NS_ADDREF(*aResult);
+  return NS_OK;
+}
+
+#ifdef MOZ_SMIL
+nsISMILAttr*
+nsSVGClass::ToSMILAttr(nsSVGElement *aSVGElement)
+{
+  return new SMILString(this, aSVGElement);
+}
+
+nsresult
+nsSVGClass::SMILString::ValueFromString(const nsAString& aStr,
+                                        const nsISMILAnimationElement* /*aSrcElement*/,
+                                        nsSMILValue& aValue,
+                                        PRBool& aPreventCachingOfSandwich) const
+{
+  nsSMILValue val(&SMILStringType::sSingleton);
+
+  *static_cast<nsAString*>(val.mU.mPtr) = aStr;
+  aValue.Swap(val);
+  aPreventCachingOfSandwich = PR_FALSE;
+  return NS_OK;
+}
+
+nsSMILValue
+nsSVGClass::SMILString::GetBaseValue() const
+{
+  nsSMILValue val(&SMILStringType::sSingleton);
+  mSVGElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                       *static_cast<nsAString*>(val.mU.mPtr));
+  return val;
+}
+
+void
+nsSVGClass::SMILString::ClearAnimValue()
+{
+  if (mVal->mAnimVal) {
+    mVal->mAnimVal = nsnull;
+    mSVGElement->DidAnimateClass();
+  }
+}
+
+nsresult
+nsSVGClass::SMILString::SetAnimValue(const nsSMILValue& aValue)
+{
+  NS_ASSERTION(aValue.mType == &SMILStringType::sSingleton,
+               "Unexpected type to assign animated value");
+  if (aValue.mType == &SMILStringType::sSingleton) {
+    mVal->SetAnimValue(*static_cast<nsAString*>(aValue.mU.mPtr), mSVGElement);
+  }
+  return NS_OK;
+}
+#endif // MOZ_SMIL
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGClass.h
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SVG project.
+ *
+ * The Initial Developer of the Original Code is Robert Longson.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef __NS_SVGCLASS_H__
+#define __NS_SVGCLASS_H__
+
+#include "nsIDOMSVGAnimatedString.h"
+#include "nsSVGElement.h"
+#include "nsDOMError.h"
+
+class nsSVGClass
+{
+
+public:
+  void Init() {
+    mAnimVal = nsnull;
+  }
+
+  void SetBaseValue(const nsAString& aValue,
+                    nsSVGElement *aSVGElement,
+                    PRBool aDoSetAttr);
+  void GetBaseValue(nsAString& aValue, nsSVGElement *aSVGElement) const
+    { aSVGElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aValue); }
+
+  void SetAnimValue(const nsAString& aValue, nsSVGElement *aSVGElement);
+  void GetAnimValue(nsAString& aValue, const nsSVGElement *aSVGElement) const;
+  PRBool IsAnimated() const
+    { return !!mAnimVal; }
+
+  nsresult ToDOMAnimatedString(nsIDOMSVGAnimatedString **aResult,
+                               nsSVGElement *aSVGElement);
+#ifdef MOZ_SMIL
+  // Returns a new nsISMILAttr object that the caller must delete
+  nsISMILAttr* ToSMILAttr(nsSVGElement *aSVGElement);
+#endif // MOZ_SMIL
+
+private:
+
+  nsAutoPtr<nsString> mAnimVal;
+
+public:
+  struct DOMAnimatedString : public nsIDOMSVGAnimatedString
+  {
+    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+    NS_DECL_CYCLE_COLLECTION_CLASS(DOMAnimatedString)
+
+    DOMAnimatedString(nsSVGClass *aVal, nsSVGElement *aSVGElement)
+      : mVal(aVal), mSVGElement(aSVGElement) {}
+
+    nsSVGClass* mVal; // kept alive because it belongs to content
+    nsRefPtr<nsSVGElement> mSVGElement;
+
+    NS_IMETHOD GetBaseVal(nsAString& aResult)
+      { mVal->GetBaseValue(aResult, mSVGElement); return NS_OK; }
+    NS_IMETHOD SetBaseVal(const nsAString& aValue)
+      { mVal->SetBaseValue(aValue, mSVGElement, PR_TRUE); return NS_OK; }
+
+    NS_IMETHOD GetAnimVal(nsAString& aResult)
+    { 
+#ifdef MOZ_SMIL
+      mSVGElement->FlushAnimations();
+#endif
+      mVal->GetAnimValue(aResult, mSVGElement); return NS_OK;
+    }
+
+  };
+#ifdef MOZ_SMIL
+  struct SMILString : public nsISMILAttr
+  {
+  public:
+    SMILString(nsSVGClass *aVal, nsSVGElement *aSVGElement)
+      : mVal(aVal), mSVGElement(aSVGElement) {}
+
+    // These will stay alive because a nsISMILAttr only lives as long
+    // as the Compositing step, and DOM elements don't get a chance to
+    // die during that.
+    nsSVGClass* mVal;
+    nsSVGElement* mSVGElement;
+
+    // nsISMILAttr methods
+    virtual nsresult ValueFromString(const nsAString& aStr,
+                                     const nsISMILAnimationElement *aSrcElement,
+                                     nsSMILValue& aValue,
+                                     PRBool& aPreventCachingOfSandwich) const;
+    virtual nsSMILValue GetBaseValue() const;
+    virtual void ClearAnimValue();
+    virtual nsresult SetAnimValue(const nsSMILValue& aValue);
+  };
+#endif // MOZ_SMIL
+};
+#endif //__NS_SVGCLASS_H__
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -69,16 +69,17 @@
 #include "nsSVGLength2.h"
 #include "nsSVGNumber2.h"
 #include "nsSVGInteger.h"
 #include "nsSVGAngle.h"
 #include "nsSVGBoolean.h"
 #include "nsSVGEnum.h"
 #include "nsSVGViewBox.h"
 #include "nsSVGString.h"
+#include "nsSVGClass.h"
 #include "SVGAnimatedNumberList.h"
 #include "SVGAnimatedLengthList.h"
 #include "SVGAnimatedPointList.h"
 #include "SVGAnimatedPathSegList.h"
 #include "nsIDOMSVGUnitTypes.h"
 #include "nsIDOMSVGPointList.h"
 #include "nsIDOMSVGAnimatedPoints.h"
 #include "nsIDOMSVGTransformList.h"
@@ -542,16 +543,24 @@ nsSVGElement::ParseAttribute(PRInt32 aNa
           GetPreserveAspectRatio();
         if (preserveAspectRatio) {
           rv = preserveAspectRatio->SetBaseValueString(aValue, this, PR_FALSE);
           if (NS_FAILED(rv)) {
             preserveAspectRatio->Init();
           }
           foundMatch = PR_TRUE;
         }
+      // Check for class attribute
+      } else if (aAttribute == nsGkAtoms::_class) {
+        nsSVGClass *svgClass = GetClass();
+        if (svgClass) {
+          svgClass->SetBaseValue(aValue, this, PR_FALSE);
+          aResult.ParseAtomArray(aValue);
+          return PR_TRUE;
+        }
       }
     }
   }
 
   if (!foundMatch) {
     // Check for nsSVGString attribute
     StringAttributesInfo stringInfo = GetStringInfo();
     for (PRUint32 i = 0; i < stringInfo.mStringCount; i++) {
@@ -580,226 +589,199 @@ nsSVGElement::ParseAttribute(PRInt32 aNa
 nsresult
 nsSVGElement::UnsetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
                         PRBool aNotify)
 {
   // XXXbz there's a bunch of redundancy here with AfterSetAttr.
   // Maybe consolidate?
   nsresult rv = nsSVGElementBase::UnsetAttr(aNamespaceID, aName, aNotify);
 
-  PRBool foundMatch = PR_FALSE;
-
   if (aNamespaceID == kNameSpaceID_None) {
     // If this is an svg presentation attribute, remove rule to force an update
     if (IsAttributeMapped(aName))
       mContentStyleRule = nsnull;
 
     if (IsEventName(aName)) {
       nsIEventListenerManager* manager = GetListenerManager(PR_FALSE);
       if (manager) {
         nsIAtom* eventName = GetEventNameForAttr(aName);
         manager->RemoveScriptEventListener(eventName);
       }
-      foundMatch = PR_TRUE;
+      return rv;
     }
     
-    if (!foundMatch) {
-      // Check if this is a length attribute going away
-      LengthAttributesInfo lenInfo = GetLengthInfo();
+    // Check if this is a length attribute going away
+    LengthAttributesInfo lenInfo = GetLengthInfo();
 
-      for (PRUint32 i = 0; i < lenInfo.mLengthCount; i++) {
-        if (aName == *lenInfo.mLengthInfo[i].mName) {
-          lenInfo.Reset(i);
-          DidChangeLength(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
-        }
+    for (PRUint32 i = 0; i < lenInfo.mLengthCount; i++) {
+      if (aName == *lenInfo.mLengthInfo[i].mName) {
+        lenInfo.Reset(i);
+        DidChangeLength(i, PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is a length list attribute going away
-      LengthListAttributesInfo lengthListInfo = GetLengthListInfo();
+    // Check if this is a length list attribute going away
+    LengthListAttributesInfo lengthListInfo = GetLengthListInfo();
 
-      for (PRUint32 i = 0; i < lengthListInfo.mLengthListCount; i++) {
-        if (aName == *lengthListInfo.mLengthListInfo[i].mName) {
-          lengthListInfo.Reset(i);
-          DidChangeLengthList(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
-        }
+    for (PRUint32 i = 0; i < lengthListInfo.mLengthListCount; i++) {
+      if (aName == *lengthListInfo.mLengthListInfo[i].mName) {
+        lengthListInfo.Reset(i);
+        DidChangeLengthList(i, PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is a number list attribute going away
-      NumberListAttributesInfo numberListInfo = GetNumberListInfo();
+    // Check if this is a number list attribute going away
+    NumberListAttributesInfo numberListInfo = GetNumberListInfo();
 
-      for (PRUint32 i = 0; i < numberListInfo.mNumberListCount; i++) {
-        if (aName == *numberListInfo.mNumberListInfo[i].mName) {
-          numberListInfo.Reset(i);
-          DidChangeNumberList(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
-        }
+    for (PRUint32 i = 0; i < numberListInfo.mNumberListCount; i++) {
+      if (aName == *numberListInfo.mNumberListInfo[i].mName) {
+        numberListInfo.Reset(i);
+        DidChangeNumberList(i, PR_FALSE);
+        return rv;
+      }
+    }
+
+    // Check if this is a point list attribute going away
+    if (GetPointListAttrName() == aName) {
+      SVGAnimatedPointList *pointList = GetAnimatedPointList();
+      if (pointList) {
+        pointList->ClearBaseValue();
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is a point list attribute going away
-      if (GetPointListAttrName() == aName) {
-        SVGAnimatedPointList *pointList = GetAnimatedPointList();
-        if (pointList) {
-          pointList->ClearBaseValue();
-          DidChangePointList(PR_FALSE);
-          foundMatch = PR_TRUE;
-        }
+    // Check if this is a path segment list attribute going away
+    if (GetPathDataAttrName() == aName) {
+      SVGAnimatedPathSegList *segList = GetAnimPathSegList();
+      if (segList) {
+        segList->ClearBaseValue();
+        DidChangePathSegList(PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is a path segment list attribute going away
-      if (GetPathDataAttrName() == aName) {
-        SVGAnimatedPathSegList *segList = GetAnimPathSegList();
-        if (segList) {
-          segList->ClearBaseValue();
-          DidChangePathSegList(PR_FALSE);
-          foundMatch = PR_TRUE;
+    // Check if this is a number attribute going away
+    NumberAttributesInfo numInfo = GetNumberInfo();
+
+    for (PRUint32 i = 0; i < numInfo.mNumberCount; i++) {
+      if (aName == *numInfo.mNumberInfo[i].mName) {
+        if (i + 1 < numInfo.mNumberCount &&
+            aName == *numInfo.mNumberInfo[i + 1].mName) {
+          // found a number-optional-number
+          numInfo.Reset(i + 1);
+          DidChangeNumber(i + 1, PR_FALSE);
         }
+        numInfo.Reset(i);
+        DidChangeNumber(i, PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is a number attribute going away
-      NumberAttributesInfo numInfo = GetNumberInfo();
+    // Check if this is an integer attribute going away
+    IntegerAttributesInfo intInfo = GetIntegerInfo();
 
-      for (PRUint32 i = 0; i < numInfo.mNumberCount; i++) {
-        if (aName == *numInfo.mNumberInfo[i].mName) {
-          if (i + 1 < numInfo.mNumberCount &&
-              aName == *numInfo.mNumberInfo[i + 1].mName) {
-            // found a number-optional-number
-            numInfo.Reset(i + 1);
-            DidChangeNumber(i + 1, PR_FALSE);
-          }
-          numInfo.Reset(i);
-          DidChangeNumber(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
+    for (PRUint32 i = 0; i < intInfo.mIntegerCount; i++) {
+      if (aName == *intInfo.mIntegerInfo[i].mName) {
+        if (i + 1 < intInfo.mIntegerCount &&
+            aName == *intInfo.mIntegerInfo[i + 1].mName) {
+          // found a number-optional-number
+          intInfo.Reset(i + 1);
+          DidChangeNumber(i + 1, PR_FALSE);
         }
+        intInfo.Reset(i);
+        DidChangeInteger(i, PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is an integer attribute going away
-      IntegerAttributesInfo intInfo = GetIntegerInfo();
+    // Check if this is an angle attribute going away
+    AngleAttributesInfo angleInfo = GetAngleInfo();
 
-      for (PRUint32 i = 0; i < intInfo.mIntegerCount; i++) {
-        if (aName == *intInfo.mIntegerInfo[i].mName) {
-          if (i + 1 < intInfo.mIntegerCount &&
-              aName == *intInfo.mIntegerInfo[i + 1].mName) {
-            // found a number-optional-number
-            intInfo.Reset(i + 1);
-            DidChangeNumber(i + 1, PR_FALSE);
-          }
-          intInfo.Reset(i);
-          DidChangeInteger(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
-        }
+    for (PRUint32 i = 0; i < angleInfo.mAngleCount; i++) {
+      if (aName == *angleInfo.mAngleInfo[i].mName) {
+        angleInfo.Reset(i);
+        DidChangeAngle(i, PR_FALSE);
+        return rv;
+      }
+    }
+
+    // Check if this is a boolean attribute going away
+    BooleanAttributesInfo boolInfo = GetBooleanInfo();
+
+    for (PRUint32 i = 0; i < boolInfo.mBooleanCount; i++) {
+      if (aName == *boolInfo.mBooleanInfo[i].mName) {
+        boolInfo.Reset(i);
+        DidChangeBoolean(i, PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is an angle attribute going away
-      AngleAttributesInfo angleInfo = GetAngleInfo();
+    // Check if this is an enum attribute going away
+    EnumAttributesInfo enumInfo = GetEnumInfo();
 
-      for (PRUint32 i = 0; i < angleInfo.mAngleCount; i++) {
-        if (aName == *angleInfo.mAngleInfo[i].mName) {
-          angleInfo.Reset(i);
-          DidChangeAngle(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
-        }
-      }
-    }
-
-    if (!foundMatch) {
-      // Check if this is a boolean attribute going away
-      BooleanAttributesInfo boolInfo = GetBooleanInfo();
-
-      for (PRUint32 i = 0; i < boolInfo.mBooleanCount; i++) {
-        if (aName == *boolInfo.mBooleanInfo[i].mName) {
-          boolInfo.Reset(i);
-          DidChangeBoolean(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-        }
+    for (PRUint32 i = 0; i < enumInfo.mEnumCount; i++) {
+      if (aName == *enumInfo.mEnumInfo[i].mName) {
+        enumInfo.Reset(i);
+        DidChangeEnum(i, PR_FALSE);
+        return rv;
       }
     }
 
-    if (!foundMatch) {
-      // Check if this is an enum attribute going away
-      EnumAttributesInfo enumInfo = GetEnumInfo();
-
-      for (PRUint32 i = 0; i < enumInfo.mEnumCount; i++) {
-        if (aName == *enumInfo.mEnumInfo[i].mName) {
-          enumInfo.Reset(i);
-          DidChangeEnum(i, PR_FALSE);
-          foundMatch = PR_TRUE;
-          break;
-        }
+    // Check if this is a nsViewBox attribute going away
+    if (aName == nsGkAtoms::viewBox) {
+      nsSVGViewBox* viewBox = GetViewBox();
+      if (viewBox) {
+        viewBox->Init();
+        DidChangeViewBox(PR_FALSE);
+        return rv;
       }
     }
+    // Check if this is a preserveAspectRatio attribute going away
+    if (aName == nsGkAtoms::preserveAspectRatio) {
+      SVGAnimatedPreserveAspectRatio *preserveAspectRatio =
+        GetPreserveAspectRatio();
 
-    if (!foundMatch) {
-      // Check if this is a nsViewBox attribute going away
-      if (aName == nsGkAtoms::viewBox) {
-        nsSVGViewBox* viewBox = GetViewBox();
-        if (viewBox) {
-          viewBox->Init();
-          DidChangeViewBox(PR_FALSE);
-          foundMatch = PR_TRUE;
-        }
-      // Check if this is a preserveAspectRatio attribute going away
-      } else if (aName == nsGkAtoms::preserveAspectRatio) {
-        SVGAnimatedPreserveAspectRatio *preserveAspectRatio =
-          GetPreserveAspectRatio();
+      if (preserveAspectRatio) {
+        preserveAspectRatio->Init();
+        DidChangePreserveAspectRatio(PR_FALSE);
+        return rv;
+      }
+    }
+    // Check if this is a class attribute going away
+    if (aName == nsGkAtoms::_class) {
+      nsSVGClass *svgClass = GetClass();
 
-        if (preserveAspectRatio) {
-          preserveAspectRatio->Init();
-          DidChangePreserveAspectRatio(PR_FALSE);
-          foundMatch = PR_TRUE;
-        }
+      if (svgClass) {
+        svgClass->Init();
+        return rv;
       }
     }
   }
 
-  if (!foundMatch) {
-    // Check if this is a string attribute going away
-    StringAttributesInfo stringInfo = GetStringInfo();
+  // Check if this is a string attribute going away
+  StringAttributesInfo stringInfo = GetStringInfo();
 
-    for (PRUint32 i = 0; i < stringInfo.mStringCount; i++) {
-      if (aNamespaceID == stringInfo.mStringInfo[i].mNamespaceID &&
-          aName == *stringInfo.mStringInfo[i].mName) {
-        stringInfo.Reset(i);
-        DidChangeString(i);
-        foundMatch = PR_TRUE;
-        break;
-      }
+  for (PRUint32 i = 0; i < stringInfo.mStringCount; i++) {
+    if (aNamespaceID == stringInfo.mStringInfo[i].mNamespaceID &&
+        aName == *stringInfo.mStringInfo[i].mName) {
+      stringInfo.Reset(i);
+      DidChangeString(i);
+      return rv;
     }
   }
 
-  if (!foundMatch) {
-    // Now check for one of the old style basetypes going away
-    nsCOMPtr<nsISVGValue> svg_value = GetMappedAttribute(aNamespaceID, aName);
+  // Now check for one of the old style basetypes going away
+  nsCOMPtr<nsISVGValue> svg_value = GetMappedAttribute(aNamespaceID, aName);
 
-    if (svg_value) {
-      mSuppressNotification = PR_TRUE;
-      ResetOldStyleBaseType(svg_value);
-      mSuppressNotification = PR_FALSE;
-    }
+  if (svg_value) {
+    mSuppressNotification = PR_TRUE;
+    ResetOldStyleBaseType(svg_value);
+    mSuppressNotification = PR_FALSE;
   }
 
   return rv;
 }
 
 void
 nsSVGElement::ResetOldStyleBaseType(nsISVGValue *svg_value)
 {
@@ -2161,16 +2143,33 @@ nsSVGElement::DidAnimateString(PRUint8 a
   if (frame) {
     StringAttributesInfo info = GetStringInfo();
     frame->AttributeChanged(info.mStringInfo[aAttrEnum].mNamespaceID,
                             *info.mStringInfo[aAttrEnum].mName,
                             nsIDOMMutationEvent::MODIFICATION);
   }
 }
 
+nsSVGClass *
+nsSVGElement::GetClass()
+{
+  return nsnull;
+}
+
+void
+nsSVGElement::DidAnimateClass()
+{
+  nsIFrame* frame = GetPrimaryFrame();
+
+  if (frame) {
+    frame->AttributeChanged(kNameSpaceID_None, nsGkAtoms::_class,
+                            nsIDOMMutationEvent::MODIFICATION);
+  }
+}
+
 nsresult
 nsSVGElement::ParseNumberOptionalNumber(const nsAString& aValue,
                                         PRUint32 aIndex1, PRUint32 aIndex2)
 {
   NS_ConvertUTF16toUTF8 value(aValue);
   const char *str = value.get();
 
   if (NS_IsAsciiWhitespace(*str))
@@ -2406,16 +2405,21 @@ nsSVGElement::GetAnimatedAttr(PRInt32 aN
     // preserveAspectRatio:
     if (aName == nsGkAtoms::preserveAspectRatio) {
       SVGAnimatedPreserveAspectRatio *preserveAspectRatio =
         GetPreserveAspectRatio();
       return preserveAspectRatio ?
         preserveAspectRatio->ToSMILAttr(this) : nsnull;
     }
 
+    if (aName == nsGkAtoms::_class) {
+      nsSVGClass *svgClass = GetClass();
+      return svgClass ? svgClass->ToSMILAttr(this) : nsnull;
+    }
+
     // NumberLists:
     {
       NumberListAttributesInfo info = GetNumberListInfo();
       for (PRUint32 i = 0; i < info.mNumberListCount; i++) {
         if (aName == *info.mNumberListInfo[i].mName) {
           NS_ABORT_IF_FALSE(i <= UCHAR_MAX, "Too many attributes");
           return info.mNumberLists[i].ToSMILAttr(this, PRUint8(i));
         }
@@ -2430,61 +2434,61 @@ nsSVGElement::GetAnimatedAttr(PRInt32 aN
           NS_ABORT_IF_FALSE(i <= UCHAR_MAX, "Too many attributes");
           return info.mLengthLists[i].ToSMILAttr(this,
                                                  PRUint8(i),
                                                  info.mLengthListInfo[i].mAxis,
                                                  info.mLengthListInfo[i].mCouldZeroPadList);
         }
       }
     }
+
+    // PointLists:
+    {
+      if (GetPointListAttrName() == aName) {
+        SVGAnimatedPointList *pointList = GetAnimatedPointList();
+        if (pointList) {
+          return pointList->ToSMILAttr(this);
+        }
+      }
+    }
+
+    // PathSegLists:
+    {
+      if (GetPathDataAttrName() == aName) {
+        SVGAnimatedPathSegList *segList = GetAnimPathSegList();
+        if (segList) {
+          return segList->ToSMILAttr(this);
+        }
+      }
+    }
+
+    // Mapped attributes:
+    if (IsAttributeMapped(aName)) {
+      nsCSSProperty prop =
+        nsCSSProps::LookupProperty(nsDependentAtomString(aName));
+      // 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);
+      }
+    }
   }
 
   // Strings
   {
     StringAttributesInfo info = GetStringInfo();
     for (PRUint32 i = 0; i < info.mStringCount; i++) {
       if (aNamespaceID == info.mStringInfo[i].mNamespaceID &&
           aName == *info.mStringInfo[i].mName) {
         return info.mStrings[i].ToSMILAttr(this);
       }
     }
   }
 
-  // PointLists:
-  {
-    if (GetPointListAttrName() == aName) {
-      SVGAnimatedPointList *pointList = GetAnimatedPointList();
-      if (pointList) {
-        return pointList->ToSMILAttr(this);
-      }
-    }
-  }
-
-  // PathSegLists:
-  {
-    if (GetPathDataAttrName() == aName) {
-      SVGAnimatedPathSegList *segList = GetAnimPathSegList();
-      if (segList) {
-        return segList->ToSMILAttr(this);
-      }
-    }
-  }
-
-  // Mapped attributes:
-  if (IsAttributeMapped(aName)) {
-    nsCSSProperty prop =
-      nsCSSProps::LookupProperty(nsDependentAtomString(aName));
-    // 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);
-    }
-  }
-
   return nsnull;
 }
 
 void
 nsSVGElement::AnimationNeedsResample()
 {
   nsIDocument* doc = GetCurrentDoc();
   if (doc && doc->HasAnimationController()) {
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -63,16 +63,17 @@ class nsSVGLength2;
 class nsSVGNumber2;
 class nsSVGInteger;
 class nsSVGAngle;
 class nsSVGBoolean;
 class nsSVGEnum;
 struct nsSVGEnumMapping;
 class nsSVGViewBox;
 class nsSVGString;
+class nsSVGClass;
 struct gfxMatrix;
 namespace mozilla {
 class SVGAnimatedNumberList;
 class SVGNumberList;
 class SVGAnimatedLengthList;
 class SVGUserUnitList;
 class SVGAnimatedPointList;
 class SVGAnimatedPathSegList;
@@ -193,16 +194,17 @@ public:
   virtual void DidAnimateViewBox();
   virtual void DidAnimatePreserveAspectRatio();
   virtual void DidAnimateNumberList(PRUint8 aAttrEnum);
   virtual void DidAnimateLengthList(PRUint8 aAttrEnum);
   virtual void DidAnimatePointList();
   virtual void DidAnimatePathSegList();
   virtual void DidAnimateTransform();
   virtual void DidAnimateString(PRUint8 aAttrEnum);
+  virtual void DidAnimateClass();
 
   void GetAnimatedLengthValues(float *aFirst, ...);
   void GetAnimatedNumberValues(float *aFirst, ...);
   void GetAnimatedIntegerValues(PRInt32 *aFirst, ...);
   SVGAnimatedNumberList* GetAnimatedNumberList(PRUint8 aAttrEnum);
   SVGAnimatedNumberList* GetAnimatedNumberList(nsIAtom *aAttrName);
   void GetAnimatedLengthListValues(SVGUserUnitList *aFirst, ...);
   SVGAnimatedLengthList* GetAnimatedLengthList(PRUint8 aAttrEnum);
@@ -461,16 +463,17 @@ protected:
   virtual EnumAttributesInfo GetEnumInfo();
   // We assume all viewboxes and preserveAspectRatios are alike
   // so we don't need to wrap the class
   virtual nsSVGViewBox *GetViewBox();
   virtual SVGAnimatedPreserveAspectRatio *GetPreserveAspectRatio();
   virtual NumberListAttributesInfo GetNumberListInfo();
   virtual LengthListAttributesInfo GetLengthListInfo();
   virtual StringAttributesInfo GetStringInfo();
+  virtual nsSVGClass *GetClass();
 
   static nsSVGEnumMapping sSVGUnitTypesMap[];
 
 private:
   /* read <number-optional-number> */
   nsresult
   ParseNumberOptionalNumber(const nsAString& aValue,
                             PRUint32 aIndex1, PRUint32 aIndex2);
--- a/content/svg/content/src/nsSVGStylableElement.cpp
+++ b/content/svg/content/src/nsSVGStylableElement.cpp
@@ -34,32 +34,20 @@
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsSVGStylableElement.h"
 #include "nsGkAtoms.h"
 #include "nsDOMCSSDeclaration.h"
-#include "nsContentUtils.h"
 
 //----------------------------------------------------------------------
 // nsISupports methods
 
-NS_SVG_VAL_IMPL_CYCLE_COLLECTION(nsSVGStylableElement::DOMAnimatedClassString, mSVGElement)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsSVGStylableElement::DOMAnimatedClassString)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsSVGStylableElement::DOMAnimatedClassString)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsSVGStylableElement::DOMAnimatedClassString)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMSVGAnimatedString)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGAnimatedString)
-NS_INTERFACE_MAP_END
-
 NS_IMPL_ADDREF_INHERITED(nsSVGStylableElement, nsSVGStylableElementBase)
 NS_IMPL_RELEASE_INHERITED(nsSVGStylableElement, nsSVGStylableElementBase)
 
 NS_INTERFACE_MAP_BEGIN(nsSVGStylableElement)
   NS_INTERFACE_MAP_ENTRY(nsIDOMSVGStylable)
 NS_INTERFACE_MAP_END_INHERITING(nsSVGStylableElementBase)
 
 //----------------------------------------------------------------------
@@ -71,31 +59,30 @@ nsSVGStylableElement::nsSVGStylableEleme
 }
 
 //----------------------------------------------------------------------
 // nsIContent methods
 
 const nsAttrValue*
 nsSVGStylableElement::DoGetClasses() const
 {
-  return GetClassAnimAttr();
+  if (mClassAttribute.IsAnimated()) {
+    return mClassAnimAttr;
+  }
+  return nsSVGStylableElementBase::DoGetClasses();
 }
 
 //----------------------------------------------------------------------
 // nsIDOMSVGStylable methods
 
 /* readonly attribute nsIDOMSVGAnimatedString className; */
 NS_IMETHODIMP
 nsSVGStylableElement::GetClassName(nsIDOMSVGAnimatedString** aClassName)
 {
-  *aClassName = new DOMAnimatedClassString(this);
-  NS_ENSURE_TRUE(*aClassName, NS_ERROR_OUT_OF_MEMORY);
-
-  NS_ADDREF(*aClassName);
-  return NS_OK;
+  return mClassAttribute.ToDOMAnimatedString(aClassName, this);
 }
 
 /* readonly attribute nsIDOMCSSStyleDeclaration style; */
 NS_IMETHODIMP
 nsSVGStylableElement::GetStyle(nsIDOMCSSStyleDeclaration** aStyle)
 {
   nsresult rv;
   *aStyle = GetStyle(&rv);
@@ -116,71 +103,29 @@ nsSVGStylableElement::GetPresentationAtt
   // http://lists.w3.org/Archives/Public/www-style/2003Oct/0347.html
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 //----------------------------------------------------------------------
 // nsSVGElement methods
 
-PRBool
-nsSVGStylableElement::ParseAttribute(PRInt32 aNamespaceID,
-                                     nsIAtom* aAttribute,
-                                     const nsAString& aValue,
-                                     nsAttrValue& aResult)
+void
+nsSVGStylableElement::DidAnimateClass()
 {
-  if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::_class) {
-    mClassAnimAttr = nsnull;
-    // let the rest be handled in nsStyledElement
+  nsAutoString src;
+  mClassAttribute.GetAnimValue(src, this);
+  if (!mClassAnimAttr) {
+    mClassAnimAttr = new nsAttrValue();
   }
+  mClassAnimAttr->ParseAtomArray(src);
 
-  return nsSVGStylableElementBase::ParseAttribute(aNamespaceID, aAttribute,
-                                                   aValue, aResult);
-}
-
-nsresult
-nsSVGStylableElement::UnsetAttr(PRInt32 aNamespaceID, nsIAtom* aName,
-                                PRBool aNotify)
-{
-  if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::_class) {
-    mClassAnimAttr = nsnull;
+  nsIDocument* doc = GetOwnerDoc();
+  NS_ASSERTION(doc, "If we're animating we should have an owner");
+  if (doc) {
+    nsIPresShell* shell = doc->GetShell();
+    if (shell) {
+      shell->RestyleForAnimation(this, eRestyle_Self);
+    }
   }
 
-  return nsSVGStylableElementBase::UnsetAttr(aNamespaceID, aName, aNotify);
-}
-
-//----------------------------------------------------------------------
-// Methods for managing the class attribute
-
-const nsAttrValue*
-nsSVGStylableElement::GetClassAnimAttr() const
-{
-  if (mClassAnimAttr)
-    return mClassAnimAttr;
-
-  return mAttrsAndChildren.GetAttr(nsGkAtoms::_class, kNameSpaceID_None);
-}
-
-void
-nsSVGStylableElement::GetClassBaseValString(nsAString& aResult) const
-{
-  GetAttr(kNameSpaceID_None, nsGkAtoms::_class, aResult);
+  nsSVGStylableElementBase::DidAnimateClass();
 }
-
-void
-nsSVGStylableElement::SetClassBaseValString(const nsAString& aValue)
-{
-  mClassAnimAttr = nsnull;
-  SetAttr(kNameSpaceID_None, nsGkAtoms::_class, aValue, PR_TRUE); 
-}
-
-void
-nsSVGStylableElement::GetClassAnimValString(nsAString& aResult) const
-{
-  const nsAttrValue* attr = GetClassAnimAttr();
-
-  if (!attr) {
-    aResult.Truncate();
-    return;
-  }
-
-  attr->ToString(aResult);
-}
--- a/content/svg/content/src/nsSVGStylableElement.h
+++ b/content/svg/content/src/nsSVGStylableElement.h
@@ -36,17 +36,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef __NS_SVGSTYLABLEELEMENT_H__
 #define __NS_SVGSTYLABLEELEMENT_H__
 
 #include "nsSVGElement.h"
 #include "nsIDOMSVGStylable.h"
-#include "nsIDOMSVGAnimatedString.h"
+#include "nsSVGClass.h"
 #include "nsAutoPtr.h"
 
 typedef nsSVGElement nsSVGStylableElementBase;
 
 class nsSVGStylableElement : public nsSVGStylableElementBase,
                              public nsIDOMSVGStylable
 {
 protected:
@@ -55,54 +55,26 @@ protected:
 public:
   // interfaces:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMSVGSTYLABLE
 
   // nsIContent
   virtual const nsAttrValue* DoGetClasses() const;
 
-  // nsSVGElement
-  virtual nsresult UnsetAttr(PRInt32 aNamespaceID, nsIAtom* aAttribute,
-                             PRBool aNotify);
-
   nsIDOMCSSStyleDeclaration* GetStyle(nsresult* retval)
   {
     return nsSVGStylableElementBase::GetStyle(retval);
   }
 
 protected:
-
-  // nsSVGElement
-  virtual PRBool ParseAttribute(PRInt32 aNamespaceID, nsIAtom* aName,
-                                const nsAString& aValue,
-                                nsAttrValue& aResult);
-
-private:
-  nsAutoPtr<nsAttrValue> mClassAnimAttr;
-
-  const nsAttrValue* GetClassAnimAttr() const;
- 
-  void GetClassBaseValString(nsAString &aResult) const;
-  void SetClassBaseValString(const nsAString& aValue);
-  void GetClassAnimValString(nsAString& aResult) const;
+  virtual nsSVGClass *GetClass()
+  {
+    return &mClassAttribute;
+  }
+  virtual void DidAnimateClass();
 
-  struct DOMAnimatedClassString : public nsIDOMSVGAnimatedString
-  {
-    NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-    NS_DECL_CYCLE_COLLECTION_CLASS(DOMAnimatedClassString)
-
-    DOMAnimatedClassString(nsSVGStylableElement *aSVGElement)
-      : mSVGElement(aSVGElement) {}
-
-    nsRefPtr<nsSVGStylableElement> mSVGElement;
-
-    NS_IMETHOD GetBaseVal(nsAString& aResult)
-      { mSVGElement->GetClassBaseValString(aResult); return NS_OK; }
-    NS_IMETHOD SetBaseVal(const nsAString& aValue)
-      { mSVGElement->SetClassBaseValString(aValue); return NS_OK; }
-    NS_IMETHOD GetAnimVal(nsAString& aResult)
-      { mSVGElement->GetClassAnimValString(aResult); return NS_OK; }
-  };
+  nsSVGClass mClassAttribute;
+  nsAutoPtr<nsAttrValue> mClassAnimAttr;
 };
 
 
 #endif // __NS_SVGSTYLABLEELEMENT_H__
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/dynamic-class-01.svg
@@ -0,0 +1,24 @@
+<!--
+    Any copyright is dedicated to the Public Domain.
+    http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+    xmlns:xlink="http://www.w3.org/1999/xlink"
+    class="reftest-wait" onload="set_lime_class();">
+ <title>Test invalidation on setting .className.baseVal</title>
+ <style type="text/css">
+
+   .lime { fill: lime; }
+
+ </style>
+ <script>
+
+function set_lime_class()
+{
+ document.getElementById('rect').className.baseVal = 'lime';
+ document.documentElement.removeAttribute('class');
+}
+
+ </script>
+ <rect id="rect" width="100%" height="100%" fill="red"/>
+</svg>
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -39,16 +39,17 @@ include svg-integration/reftest.list
 == conditions-04.svg pass.svg
 == conditions-05.svg about:blank
 == currentColor-01.svg pass.svg
 == currentColor-02.svg pass.svg
 == currentColor-03.svg pass.svg
 == dynamic-attr-removal-1.svg pass.svg
 == dynamic-attr-removal-2.svg pass.svg
 == dynamic-attr-change-1.svg pass.svg
+== dynamic-class-01.svg pass.svg
 == dynamic-conditions-01.svg pass.svg
 == dynamic-conditions-02.svg about:blank
 == dynamic-conditions-03.svg pass.svg
 == dynamic-conditions-04.svg about:blank
 == dynamic-conditions-05.svg pass.svg
 == dynamic-conditions-06.svg about:blank
 == dynamic-conditions-07.svg pass.svg
 == dynamic-conditions-08.svg pass.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/anim-class-01.svg
@@ -0,0 +1,23 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait" onload="setTimeAndSnapshot(1, true)">
+  <title>Test animation of the class attribute on "rect" elements</title>
+  <script xlink:href="smil-util.js" type="text/javascript"/>
+  <style type="text/css">
+
+    .start {visibility: hidden; fill: red; }
+    .midway {visibility: visible; fill: red; }
+    .final {fill: lime; }
+
+  </style>
+
+  <rect width="100%" height="100%" fill="red"/>
+  <rect width="100%" height="100%">
+    <animate attributeName="class" attributeType="XML"
+         from="midway" to="final midway" begin="0.5s" dur="1s" fill="freeze"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/anim-class-02.svg
@@ -0,0 +1,22 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait" onload="setTimeAndSnapshot(1, true)">
+  <title>Test animation of the class attribute on "rect" elements</title>
+  <script xlink:href="smil-util.js" type="text/javascript"/>
+  <style type="text/css">
+
+    * {fill: lime;}
+    .start {visibility: visible; fill: red; }
+
+  </style>
+
+  <rect width="100%" height="100%" fill="red"/>
+  <rect width="100%" height="100%" class="start">
+    <set attributeName="class" attributeType="XML"
+         to="" begin="0.5s" dur="1s" fill="freeze"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/anim-class-03.svg
@@ -0,0 +1,22 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait" onload="setTimeAndSnapshot(1, true)">
+  <title>Test animation of the class attribute on "rect" elements</title>
+  <script xlink:href="smil-util.js" type="text/javascript"/>
+  <style type="text/css">
+
+    .start {fill: red; }
+    .finish {fill: lime;}
+
+  </style>
+
+  <rect width="100%" height="100%" fill="red"/>
+  <rect width="100%" height="100%" class="start">
+    <set attributeName="class" attributeType="XML"
+         to="finish" begin="0.5s" dur="1s" fill="freeze"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/anim-class-04-ref.svg
@@ -0,0 +1,8 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Reference for animation of the class attribute to an invalid value on "rect" elements</title>
+  <rect width="100%" height="100%" fill="black"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/anim-class-04.svg
@@ -0,0 +1,21 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait" onload="setTimeAndSnapshot(1, true)">
+  <title>Test animation of the class attribute on "rect" elements</title>
+  <script xlink:href="smil-util.js" type="text/javascript"/>
+  <style type="text/css">
+
+    .start {fill: red; }
+
+  </style>
+
+  <rect width="100%" height="100%" fill="red"/>
+  <rect width="100%" height="100%" class="start">
+    <set attributeName="class" attributeType="XML"
+         to="#ThisIsAnInvalidClassName" begin="0.5s" dur="1s" fill="freeze"/>
+  </rect>
+</svg>
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -125,16 +125,22 @@ fails == anim-fillcolor-1.svg      anim-
 
 # animate some string attributes:
 == anim-filter-href-01.svg lime.svg
 == anim-gradient-href-01.svg lime.svg
 == anim-image-href-01.svg lime.svg
 == anim-pattern-href-01.svg lime.svg
 asserts(9) == anim-use-href-01.svg lime.svg  # the asserts here are bug 563481
 
+# animate the class attribute
+== anim-class-01.svg lime.svg
+== anim-class-02.svg lime.svg
+== anim-class-03.svg lime.svg
+== anim-class-04.svg anim-class-04-ref.svg
+
 # animate with some paint server values
 == anim-paintserver-1.svg anim-paintserver-1-ref.svg
 
 # animate where the base value is non-interpolatable but will be replaced anyway
 == anim-fill-overpaintserver-1.svg lime.svg
 == anim-fill-overpaintserver-2.svg lime.svg
 
 # animate where we fallback from 'additive' animation to non-additive