Bug 468996. Implement SMIL animateTransform element. r+sr=roc
authorBrian Birtles <birtles@gmail.com>
Mon, 19 Jan 2009 22:14:16 +1300
changeset 23931 c8da73bbcb1b3a028e98c377103a1491944b051b
parent 23930 d9bbceb06cdc091fd1d9eee326bd01fcebb3b973
child 23932 b2c58375f633ec8bc83824ae241308ade86f9694
push id4800
push userrocallahan@mozilla.com
push dateMon, 19 Jan 2009 09:37:39 +0000
treeherdermozilla-central@afec2ee5b993 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs468996
milestone1.9.2a1pre
Bug 468996. Implement SMIL animateTransform element. r+sr=roc
content/smil/nsISMILType.h
content/smil/nsSMILAnimationFunction.cpp
content/smil/nsSMILValue.cpp
content/smil/nsSMILValue.h
content/svg/content/src/Makefile.in
content/svg/content/src/nsSVGAnimateTransformElement.cpp
content/svg/content/src/nsSVGAnimatedTransformList.cpp
content/svg/content/src/nsSVGAnimatedTransformList.h
content/svg/content/src/nsSVGElement.cpp
content/svg/content/src/nsSVGElementFactory.cpp
content/svg/content/src/nsSVGSMILTransform.h
content/svg/content/src/nsSVGTransform.cpp
content/svg/content/src/nsSVGTransform.h
content/svg/content/src/nsSVGTransformSMILAttr.cpp
content/svg/content/src/nsSVGTransformSMILAttr.h
content/svg/content/src/nsSVGTransformSMILType.cpp
content/svg/content/src/nsSVGTransformSMILType.h
dom/public/idl/svg/Makefile.in
dom/public/idl/svg/nsIDOMSVGAnimateTransformElement.idl
dom/public/nsDOMClassInfoID.h
dom/src/base/nsDOMClassInfo.cpp
layout/reftests/svg/smil/reftest.list
layout/reftests/svg/smil/transform/additive-1-ref.svg
layout/reftests/svg/smil/transform/additive-1.svg
layout/reftests/svg/smil/transform/paced-1-ref.svg
layout/reftests/svg/smil/transform/paced-1.svg
layout/reftests/svg/smil/transform/reftest.list
layout/reftests/svg/smil/transform/rotate-angle-1.svg
layout/reftests/svg/smil/transform/rotate-angle-2.svg
layout/reftests/svg/smil/transform/rotate-angle-3.svg
layout/reftests/svg/smil/transform/rotate-angle-4.svg
layout/reftests/svg/smil/transform/rotate-angle-5.svg
layout/reftests/svg/smil/transform/rotate-angle-ref.svg
layout/reftests/svg/smil/transform/scale-1-ref.svg
layout/reftests/svg/smil/transform/scale-1.svg
layout/reftests/svg/smil/transform/skew-1-ref.svg
layout/reftests/svg/smil/transform/skew-1.svg
--- a/content/smil/nsISMILType.h
+++ b/content/smil/nsISMILType.h
@@ -62,16 +62,17 @@ class nsSMILValue;
 // +---------------------+---------------+-------------+------------------+
 // | CATEGORY:           | DISCRETE      | LINEAR      | ADDITIVE         |
 // +---------------------+---------------+-------------+------------------+
 // | Example:            | strings,      | path data?  | lengths,         |
 // |                     | color k/words?|             | RGB color values |
 // |                     |               |             |                  |
 // | -- Assign?          |     X         |    X        |    X             |
 // | -- Add?             |     -         |    X?       |    X             |
+// | -- SandwichAdd?     |     -         |    -?       |    X             |
 // | -- ComputeDistance? |     -         |    -        |    X?            |
 // | -- Interpolate?     |     -         |    X        |    X             |
 // +---------------------+---------------+-------------+------------------+
 //
 
 class nsISMILType
 {
 public:
@@ -140,16 +141,40 @@ public:
    *
    * @pre aValueToAdd.mType == aDest.mType == this
    */
   virtual nsresult Add(nsSMILValue& aDest,
                        const nsSMILValue& aValueToAdd,
                        PRUint32 aCount) const = 0;
 
   /**
+   * Adds aValueToAdd to the underlying value in the animation sandwich, aDest.
+   *
+   * For most types this operation is identical to a regular Add() but for some
+   * types (notably <animateTransform>) the operation differs. For
+   * <animateTransform> Add() corresponds to simply adding together the
+   * transform parameters and is used when calculating cumulative values or
+   * by-animation values. On the other hand SandwichAdd() is used when adding to
+   * the underlying value and requires matrix post-multiplication. (This
+   * distinction is most clearly indicated by the SVGT1.2 test suite. It is not
+   * obvious within the SMIL specifications.)
+   *
+   * @param   aDest       The value to add to.
+   * @param   aValueToAdd The value to add.
+   * @return  NS_OK on success, an error code on failure.
+   *
+   * @pre aValueToAdd.mType == aDest.mType == this
+   */
+  virtual nsresult SandwichAdd(nsSMILValue& aDest,
+                               const nsSMILValue& aValueToAdd) const
+  {
+    return Add(aDest, aValueToAdd, 1);
+  }
+
+  /**
    * Calculates the 'distance' between two values. This is the distance used in
    * paced interpolation.
    *
    * @param   aFrom     The start of the interval for which the distance should
    *                    be calculated.
    * @param   aTo       The end of the interval for which the distance should be
    *                    calculated.
    * @param   aDistance The result of the calculation.
--- a/content/smil/nsSMILAnimationFunction.cpp
+++ b/content/smil/nsSMILAnimationFunction.cpp
@@ -287,18 +287,19 @@ nsSMILAnimationFunction::ComposeResult(c
     NS_ENSURE_SUCCESS(AccumulateResult(values, result),);
 
     if (IsToAnimation() && mIsFrozen) {
       mFrozenValue = result;
     }
   }
 
   // If additive animation isn't required or isn't supported, set the value.
-  if (!IsAdditive() || NS_FAILED(aResult.Add(result)))
+  if (!IsAdditive() || NS_FAILED(aResult.SandwichAdd(result))) {
     aResult = result;
+  }
 }
 
 PRInt8
 nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const
 {
   NS_ENSURE_TRUE(aOther, 0);
 
   NS_ASSERTION(aOther != this, "Trying to compare to self.");
--- a/content/smil/nsSMILValue.cpp
+++ b/content/smil/nsSMILValue.cpp
@@ -83,41 +83,55 @@ nsSMILValue::operator=(const nsSMILValue
 }
 
 nsresult
 nsSMILValue::Add(const nsSMILValue& aValueToAdd, PRUint32 aCount)
 {
   if (aValueToAdd.IsNull()) return NS_OK;
 
   if (aValueToAdd.mType != mType) {
-    NS_WARNING("Trying to add incompatible types.");
+    NS_ERROR("Trying to add incompatible types.");
     return NS_ERROR_FAILURE;
   }
 
   return mType->Add(*this, aValueToAdd, aCount);
 }
 
 nsresult
+nsSMILValue::SandwichAdd(const nsSMILValue& aValueToAdd)
+{
+  if (aValueToAdd.IsNull())
+    return NS_OK;
+
+  if (aValueToAdd.mType != mType) {
+    NS_ERROR("Trying to add incompatible types.");
+    return NS_ERROR_FAILURE;
+  }
+
+  return mType->SandwichAdd(*this, aValueToAdd);
+}
+
+nsresult
 nsSMILValue::ComputeDistance(const nsSMILValue& aTo, double& aDistance) const
 {
   if (aTo.mType != mType) {
-    NS_WARNING("Trying to calculate distance between incompatible types.");
+    NS_ERROR("Trying to calculate distance between incompatible types.");
     return NS_ERROR_FAILURE;
   }
 
   return mType->ComputeDistance(*this, aTo, aDistance);
 }
 
 nsresult
 nsSMILValue::Interpolate(const nsSMILValue& aEndVal,
                          double aUnitDistance,
                          nsSMILValue& aResult) const
 {
   if (aEndVal.mType != mType) {
-    NS_WARNING("Trying to interpolate between incompatible types.");
+    NS_ERROR("Trying to interpolate between incompatible types.");
     return NS_ERROR_FAILURE;
   }
 
   if (aResult.mType != mType) {
     aResult.mType->Destroy(aResult);
     NS_POSTCONDITION(aResult.IsNull(), "nsSMILValue not null after destroying");
     nsresult rv = mType->Init(aResult);
     NS_POSTCONDITION(aResult.mType == mType
--- a/content/smil/nsSMILValue.h
+++ b/content/smil/nsSMILValue.h
@@ -57,16 +57,17 @@ public:
   const nsSMILValue& operator=(const nsSMILValue& aVal);
 
   PRBool IsNull() const
   {
     return (mType == &nsSMILNullType::sSingleton);
   }
 
   nsresult Add(const nsSMILValue& aValueToAdd, PRUint32 aCount = 1);
+  nsresult SandwichAdd(const nsSMILValue& aValueToAdd);
   nsresult ComputeDistance(const nsSMILValue& aTo, double& aDistance) const;
   nsresult Interpolate(const nsSMILValue& aEndVal,
                        double aUnitDistance,
                        nsSMILValue& aResult) const;
 
   union {
     PRInt64 mInt;
     double mDouble;
--- a/content/svg/content/src/Makefile.in
+++ b/content/svg/content/src/Makefile.in
@@ -135,18 +135,21 @@ CPPSRCS		= \
 		nsSVGTransformList.cpp \
 		nsSVGTransformListParser.cpp \
 		nsSVGUseElement.cpp \
 		nsSVGValue.cpp \
 		$(NULL)
 
 ifdef MOZ_SMIL
 CPPSRCS += nsSVGAnimateElement.cpp \
+           nsSVGAnimateTransformElement.cpp \
            nsSVGAnimationElement.cpp \
            nsSVGSetElement.cpp \
+           nsSVGTransformSMILType.cpp \
+           nsSVGTransformSMILAttr.cpp \
            $(NULL)
 endif
 
 include $(topsrcdir)/config/config.mk
 
 # we don't want the shared lib, but we want to force the creation of a static lib.
 FORCE_STATIC_LIB = 1
 
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGAnimateTransformElement.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *   Chris Double  <chris.double@double.co.nz>
+ *   Daniel Holbert <dholbert@mozilla.com>
+ *
+ * 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 ***** */
+
+#include "nsSVGAnimationElement.h"
+#include "nsIDOMSVGAnimateTransformElement.h"
+#include "nsSVGEnum.h"
+#include "nsIDOMSVGTransform.h"
+#include "nsIDOMSVGTransformable.h"
+#include "nsSVGAnimatedTransformList.h"
+#include "nsSVGTransformSMILAttr.h"
+#include "nsSMILAnimationFunction.h"
+
+typedef nsSVGAnimationElement nsSVGAnimateTransformElementBase;
+
+class nsSVGAnimateTransformElement : public nsSVGAnimateTransformElementBase,
+                                     public nsIDOMSVGAnimateTransformElement
+{
+protected:
+  friend nsresult NS_NewSVGAnimateTransformElement(nsIContent **aResult,
+                                                   nsINodeInfo *aNodeInfo);
+  nsSVGAnimateTransformElement(nsINodeInfo* aNodeInfo);
+
+  nsSMILAnimationFunction mAnimationFunction;
+
+public:
+  // interfaces:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_NSIDOMSVGANIMATETRANSFORMELEMENT
+
+  NS_FORWARD_NSIDOMNODE(nsSVGAnimateTransformElementBase::)
+  NS_FORWARD_NSIDOMELEMENT(nsSVGAnimateTransformElementBase::)
+  NS_FORWARD_NSIDOMSVGELEMENT(nsSVGAnimateTransformElementBase::)
+  NS_FORWARD_NSIDOMSVGANIMATIONELEMENT(nsSVGAnimateTransformElementBase::)
+
+  // nsIDOMNode specializations
+  virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
+
+  // nsGenericElement specializations
+  PRBool ParseAttribute(PRInt32 aNamespaceID,
+                        nsIAtom* aAttribute,
+                        const nsAString& aValue,
+                        nsAttrValue& aResult);
+
+  // nsISMILAnimationElement
+  virtual nsSMILAnimationFunction& AnimationFunction();
+};
+
+NS_IMPL_NS_NEW_SVG_ELEMENT(AnimateTransform)
+
+//----------------------------------------------------------------------
+// nsISupports methods
+
+NS_IMPL_ADDREF_INHERITED(nsSVGAnimateTransformElement,nsSVGAnimateTransformElementBase)
+NS_IMPL_RELEASE_INHERITED(nsSVGAnimateTransformElement,nsSVGAnimateTransformElementBase)
+
+NS_INTERFACE_TABLE_HEAD(nsSVGAnimateTransformElement)
+  NS_NODE_INTERFACE_TABLE5(nsSVGAnimateTransformElement, nsIDOMNode,
+                           nsIDOMElement, nsIDOMSVGElement,
+                           nsIDOMSVGAnimationElement,
+                           nsIDOMSVGAnimateTransformElement)
+  NS_INTERFACE_MAP_ENTRY_CONTENT_CLASSINFO(SVGAnimateElement)
+NS_INTERFACE_MAP_END_INHERITING(nsSVGAnimateTransformElementBase)
+
+//----------------------------------------------------------------------
+// Implementation
+
+nsSVGAnimateTransformElement::nsSVGAnimateTransformElement(nsINodeInfo *aNodeInfo)
+  : nsSVGAnimateTransformElementBase(aNodeInfo)
+{
+}
+
+PRBool
+nsSVGAnimateTransformElement::ParseAttribute(PRInt32 aNamespaceID,
+                                             nsIAtom* aAttribute,
+                                             const nsAString& aValue,
+                                             nsAttrValue& aResult)
+{
+  // 'type' is an <animateTransform>-specific attribute, and we'll handle it
+  // specially.
+  if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::type) {
+    aResult.ParseAtom(aValue);
+    nsIAtom* atom = aResult.GetAtomValue();
+    if (atom != nsGkAtoms::translate &&
+        atom != nsGkAtoms::scale &&
+        atom != nsGkAtoms::rotate &&
+        atom != nsGkAtoms::skewX &&
+        atom != nsGkAtoms::skewY) {
+      ReportAttributeParseFailure(GetOwnerDoc(), aAttribute, aValue);
+    }
+    return PR_TRUE;
+  }
+
+  return nsSVGAnimateTransformElementBase::ParseAttribute(aNamespaceID, 
+                                                          aAttribute, aValue,
+                                                          aResult);
+}
+
+//----------------------------------------------------------------------
+// nsIDOMNode methods
+
+NS_IMPL_ELEMENT_CLONE_WITH_INIT(nsSVGAnimateTransformElement)
+
+//----------------------------------------------------------------------
+// nsISMILAnimationElement methods
+
+nsSMILAnimationFunction&
+nsSVGAnimateTransformElement::AnimationFunction()
+{
+  return mAnimationFunction;
+}
--- a/content/svg/content/src/nsSVGAnimatedTransformList.cpp
+++ b/content/svg/content/src/nsSVGAnimatedTransformList.cpp
@@ -33,72 +33,25 @@
  * 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 "nsSVGAnimatedTransformList.h"
 #include "nsSVGTransformList.h"
-#include "nsSVGValue.h"
-#include "nsWeakReference.h"
 #include "nsContentUtils.h"
 
-////////////////////////////////////////////////////////////////////////
-// nsSVGAnimatedTransformList
-
-class nsSVGAnimatedTransformList : public nsIDOMSVGAnimatedTransformList,
-                                   public nsSVGValue,
-                                   public nsISVGValueObserver
-{  
-protected:
-  friend nsresult
-  NS_NewSVGAnimatedTransformList(nsIDOMSVGAnimatedTransformList** result,
-                                 nsIDOMSVGTransformList* baseVal);
-
-  nsSVGAnimatedTransformList();
-  ~nsSVGAnimatedTransformList();
-  void Init(nsIDOMSVGTransformList* baseVal);
-  
-public:
-  // nsISupports interface:
-  NS_DECL_ISUPPORTS
-
-  // nsIDOMSVGAnimatedTransformList interface:
-  NS_DECL_NSIDOMSVGANIMATEDTRANSFORMLIST
-
-  // remainder of nsISVGValue interface:
-  NS_IMETHOD SetValueString(const nsAString& aValue);
-  NS_IMETHOD GetValueString(nsAString& aValue);
-
-  // nsISVGValueObserver
-  NS_IMETHOD WillModifySVGObservable(nsISVGValue* observable,
-                                     modificationType aModType);
-  NS_IMETHOD DidModifySVGObservable (nsISVGValue* observable,
-                                     modificationType aModType);
-  
-  // nsISupportsWeakReference
-  // implementation inherited from nsSupportsWeakReference
-  
-protected:
-  nsCOMPtr<nsIDOMSVGTransformList> mBaseVal;
-};
-
-
 //----------------------------------------------------------------------
 // Implementation
 
-nsSVGAnimatedTransformList::nsSVGAnimatedTransformList()
-{
-}
-
 nsSVGAnimatedTransformList::~nsSVGAnimatedTransformList()
 {
   if (!mBaseVal) return;
-    nsCOMPtr<nsISVGValue> val = do_QueryInterface(mBaseVal);
+  nsCOMPtr<nsISVGValue> val = do_QueryInterface(mBaseVal);
   if (!val) return;
   val->RemoveObserver(this);
 }
 
 void
 nsSVGAnimatedTransformList::Init(nsIDOMSVGTransformList* baseVal)
 {
   mBaseVal = baseVal;
@@ -141,28 +94,28 @@ nsSVGAnimatedTransformList::GetValueStri
   return value->GetValueString(aValue);
 }
 
 //----------------------------------------------------------------------
 // nsIDOMSVGAnimatedTransformList methods:
 
 /* readonly attribute nsIDOMSVGTransformList baseVal; */
 NS_IMETHODIMP
-nsSVGAnimatedTransformList::GetBaseVal(nsIDOMSVGTransformList * *aBaseVal)
+nsSVGAnimatedTransformList::GetBaseVal(nsIDOMSVGTransformList** aBaseVal)
 {
   *aBaseVal = mBaseVal;
   NS_ADDREF(*aBaseVal);
   return NS_OK;
 }
 
 /* readonly attribute nsIDOMSVGTransformList animVal; */
 NS_IMETHODIMP
-nsSVGAnimatedTransformList::GetAnimVal(nsIDOMSVGTransformList * *aAnimVal)
+nsSVGAnimatedTransformList::GetAnimVal(nsIDOMSVGTransformList** aAnimVal)
 {
-  *aAnimVal = mBaseVal;
+  *aAnimVal = mAnimVal ? mAnimVal : mBaseVal;
   NS_ADDREF(*aAnimVal);
   return NS_OK;
 }
 
 //----------------------------------------------------------------------
 // nsISVGValueObserver methods
 
 NS_IMETHODIMP
--- a/content/svg/content/src/nsSVGAnimatedTransformList.h
+++ b/content/svg/content/src/nsSVGAnimatedTransformList.h
@@ -36,14 +36,60 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef __NS_SVGANIMATEDTRANSFORMLIST_H__
 #define __NS_SVGANIMATEDTRANSFORMLIST_H__
 
 #include "nsIDOMSVGAnimTransformList.h"
 #include "nsIDOMSVGTransformList.h"
+#include "nsSVGValue.h"
+
+////////////////////////////////////////////////////////////////////////
+// nsSVGAnimatedTransformList
+
+class nsSVGTransformSMILAttr;
+
+class nsSVGAnimatedTransformList : public nsIDOMSVGAnimatedTransformList,
+                                   public nsSVGValue,
+                                   public nsISVGValueObserver
+{  
+protected:
+  friend nsresult
+  NS_NewSVGAnimatedTransformList(nsIDOMSVGAnimatedTransformList** result,
+                                 nsIDOMSVGTransformList* baseVal);
+
+  ~nsSVGAnimatedTransformList();
+  void Init(nsIDOMSVGTransformList* baseVal);
+
+public:
+  // nsISupports interface:
+  NS_DECL_ISUPPORTS
+
+  // nsIDOMSVGAnimatedTransformList interface:
+  NS_DECL_NSIDOMSVGANIMATEDTRANSFORMLIST
+
+  // remainder of nsISVGValue interface:
+  NS_IMETHOD SetValueString(const nsAString& aValue);
+  NS_IMETHOD GetValueString(nsAString& aValue);
+
+  // nsISVGValueObserver
+  NS_IMETHOD WillModifySVGObservable(nsISVGValue* observable,
+                                     modificationType aModType);
+  NS_IMETHOD DidModifySVGObservable (nsISVGValue* observable,
+                                     modificationType aModType);
+
+  // nsISupportsWeakReference
+  // implementation inherited from nsSupportsWeakReference
+
+protected:
+  friend class nsSVGTransformSMILAttr;
+
+  nsCOMPtr<nsIDOMSVGTransformList> mBaseVal;
+  // XXX This should be read-only, i.e. its setters should throw
+  nsCOMPtr<nsIDOMSVGTransformList> mAnimVal;
+};
 
 nsresult
 NS_NewSVGAnimatedTransformList(nsIDOMSVGAnimatedTransformList** result,
                                nsIDOMSVGTransformList* baseVal);
 
 #endif //__NS_SVGANIMATEDTRANSFORMLIST_H__
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -86,16 +86,18 @@
 #include "nsIDOMSVGTransformList.h"
 #include "nsIDOMSVGAnimTransformList.h"
 #include "nsIDOMSVGAnimatedRect.h"
 #include "nsSVGRect.h"
 #include "nsIFrame.h"
 #include "prdtoa.h"
 #include <stdarg.h>
 #ifdef MOZ_SMIL
+#include "nsSVGTransformSMILAttr.h"
+#include "nsSVGAnimatedTransformList.h"
 #include "nsIDOMSVGTransformable.h"
 #endif // MOZ_SMIL
 
 nsSVGEnumMapping nsSVGElement::sSVGUnitTypesMap[] = {
   {&nsGkAtoms::userSpaceOnUse, nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_USERSPACEONUSE},
   {&nsGkAtoms::objectBoundingBox, nsIDOMSVGUnitTypes::SVG_UNIT_TYPE_OBJECTBOUNDINGBOX},
   {nsnull, 0}
 };
@@ -1645,16 +1647,32 @@ nsSVGElement::RecompileScriptEventListen
     AddScriptEventListener(GetEventNameForAttr(attr), value, PR_TRUE);
   }
 }
 
 #ifdef MOZ_SMIL
 nsISMILAttr*
 nsSVGElement::GetAnimatedAttr(const nsIAtom* aName)
 {
+  // Transforms:
+  if (aName == nsGkAtoms::transform) {
+    nsCOMPtr<nsIDOMSVGTransformable> transformable(
+            do_QueryInterface(static_cast<nsIContent*>(this)));
+    if (!transformable)
+      return nsnull;
+    nsCOMPtr<nsIDOMSVGAnimatedTransformList> transformList;
+    nsresult rv = transformable->GetTransform(getter_AddRefs(transformList));
+    NS_ENSURE_SUCCESS(rv, nsnull);
+    nsSVGAnimatedTransformList* list
+      = static_cast<nsSVGAnimatedTransformList*>(transformList.get());
+    NS_ENSURE_TRUE(list, nsnull);
+
+    return new nsSVGTransformSMILAttr(list, this);
+  }
+
   // Lengths:
   LengthAttributesInfo info = GetLengthInfo();
   for (PRUint32 i = 0; i < info.mLengthCount; i++) {
     if (aName == *info.mLengthInfo[i].mName) {
       return info.mLengths[i].ToSMILAttr(this);
     }
   }
 
--- a/content/svg/content/src/nsSVGElementFactory.cpp
+++ b/content/svg/content/src/nsSVGElementFactory.cpp
@@ -160,16 +160,18 @@ nsresult
 NS_NewSVGFEImageElement(nsIContent **aResult, nsINodeInfo *aNodeInfo);
 nsresult
 NS_NewSVGFEDisplacementMapElement(nsIContent **aResult, nsINodeInfo *aNodeInfo);
 
 #ifdef MOZ_SMIL
 nsresult
 NS_NewSVGAnimateElement(nsIContent **aResult, nsINodeInfo *aNodeInfo);
 nsresult
+NS_NewSVGAnimateTransformElement(nsIContent **aResult, nsINodeInfo *aNodeInfo);
+nsresult
 NS_NewSVGSetElement(nsIContent **aResult, nsINodeInfo *aNodeInfo);
 #endif // MOZ_SMIL
 
 nsresult
 NS_NewSVGElement(nsIContent** aResult, nsINodeInfo *aNodeInfo,
                  PRBool aFromParser)
 {
   NS_PRECONDITION(NS_SVGEnabled(),
@@ -294,16 +296,18 @@ NS_NewSVGElement(nsIContent** aResult, n
     return NS_NewSVGPatternElement(aResult, aNodeInfo);
   if (name == nsGkAtoms::mask)
     return NS_NewSVGMaskElement(aResult, aNodeInfo);
   if (name == nsGkAtoms::svgSwitch)
     return NS_NewSVGSwitchElement(aResult, aNodeInfo);
 #ifdef MOZ_SMIL
   if (name == nsGkAtoms::animate)
     return NS_NewSVGAnimateElement(aResult, aNodeInfo);
+  if (name == nsGkAtoms::animateTransform)
+    return NS_NewSVGAnimateTransformElement(aResult, aNodeInfo);
   if (name == nsGkAtoms::set)
     return NS_NewSVGSetElement(aResult, aNodeInfo);
 #endif // MOZ_SMIL
 
   // if we don't know what to create, just create a standard xml element:
   return NS_NewXMLElement(aResult, aNodeInfo);
 }
 
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGSMILTransform.h
@@ -0,0 +1,110 @@
+/* -*- 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * 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_SVGSMILTRANSFORM_H_
+#define NS_SVGSMILTRANSFORM_H_
+
+////////////////////////////////////////////////////////////////////////
+// nsSVGSMILTransform
+//
+// A pared-down representation of an SVG transform used in SMIL animation. We
+// just store the most basic facts about the transform such that we can add the
+// transform parameters together and later reconstruct a full SVG transform from
+// this information.
+//
+// The meaning of the mParams array depends on the transform type as follows:
+//
+// Type                | mParams[0], mParams[1], mParams[2], ...
+// --------------------+-----------------------------------------
+// TRANSFORM_TRANSLATE | tx, ty
+// TRANSFORM_SCALE     | sx, sy
+// TRANSFORM_ROTATE    | rotation-angle (in degrees), cx, cy
+// TRANSFORM_SKEWX     | skew-angle (in degrees)
+// TRANSFORM_SKEWY     | skew-angle (in degrees)
+// TRANSFORM_MATRIX    | a, b, c, d, e, f
+//
+// TRANSFORM_MATRIX is never generated by animation code (it is only produced
+// when the user inserts one via the DOM) and often requires special handling
+// when we do encounter it. Therefore many users of this class are only
+// interested in the first three parameters and so we provide a special
+// constructor for setting those parameters only.
+class nsSVGSMILTransform
+{
+public:
+  enum TransformType
+  {
+    TRANSFORM_TRANSLATE,
+    TRANSFORM_SCALE,
+    TRANSFORM_ROTATE,
+    TRANSFORM_SKEWX,
+    TRANSFORM_SKEWY,
+    TRANSFORM_MATRIX
+  };
+
+  nsSVGSMILTransform(TransformType aType)
+  : mTransformType(aType)
+  {
+    for (int i = 0; i < 6; ++i) {
+      mParams[i] = 0;
+    }
+  }
+
+  nsSVGSMILTransform(TransformType aType, float (&aParams)[3])
+  : mTransformType(aType)
+  {
+    for (int i = 0; i < 3; ++i) {
+      mParams[i] = aParams[i];
+    }
+    for (int i = 3; i < 6; ++i) {
+      mParams[i] = 0;
+    }
+  }
+
+  nsSVGSMILTransform(float (&aParams)[6])
+  : mTransformType(TRANSFORM_MATRIX)
+  {
+    for (int i = 0; i < 6; ++i) {
+      mParams[i] = aParams[i];
+    }
+  }
+    
+  TransformType mTransformType;
+  
+  float mParams[6];
+};
+
+#endif // NS_SVGSMILTRANSFORM_H_
--- a/content/svg/content/src/nsSVGTransform.cpp
+++ b/content/svg/content/src/nsSVGTransform.cpp
@@ -34,64 +34,23 @@
  * 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 "nsSVGTransform.h"
 #include "prdtoa.h"
 #include "nsSVGMatrix.h"
-#include "nsSVGValue.h"
 #include "nsISVGValueUtils.h"
-#include "nsISVGValueObserver.h"
 #include "nsWeakReference.h"
 #include "nsSVGMatrix.h"
 #include "nsTextFormatter.h"
 #include "nsContentUtils.h"
 #include "nsDOMError.h"
 
-
-////////////////////////////////////////////////////////////////////////
-// nsSVGTransform
-
-class nsSVGTransform : public nsIDOMSVGTransform,
-                       public nsSVGValue,
-                       public nsISVGValueObserver
-{
-public:
-  static nsresult Create(nsIDOMSVGTransform** aResult);
-  
-protected:
-  nsSVGTransform();
-  ~nsSVGTransform();
-  nsresult Init();
-public:
-  // nsISupports interface:
-  NS_DECL_ISUPPORTS
-
-  // nsIDOMSVGTransform interface:
-  NS_DECL_NSIDOMSVGTRANSFORM
-
-  // nsISVGValue interface:
-  NS_IMETHOD SetValueString(const nsAString& aValue);
-  NS_IMETHOD GetValueString(nsAString& aValue);
-
-  // nsISVGValueObserver
-  NS_IMETHOD WillModifySVGObservable(nsISVGValue* observable,
-                                     modificationType aModType);
-  NS_IMETHOD DidModifySVGObservable (nsISVGValue* observable,
-                                     modificationType aModType);
-
-protected:
-  nsCOMPtr<nsIDOMSVGMatrix> mMatrix;
-  float mAngle, mOriginX, mOriginY;
-  PRUint16 mType;
-};
-
-
 //----------------------------------------------------------------------
 // Implementation
 
 nsresult
 nsSVGTransform::Create(nsIDOMSVGTransform** aResult)
 {
   nsSVGTransform *pl = new nsSVGTransform();
   NS_ENSURE_TRUE(pl, NS_ERROR_OUT_OF_MEMORY);
--- a/content/svg/content/src/nsSVGTransform.h
+++ b/content/svg/content/src/nsSVGTransform.h
@@ -35,16 +35,64 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef __NS_SVGTRANSFORM_H__
 #define __NS_SVGTRANSFORM_H__
 
 #include "nsIDOMSVGTransform.h"
+#include "nsSVGValue.h"
+#include "nsISVGValueObserver.h"
+
+////////////////////////////////////////////////////////////////////////
+// nsSVGTransform
+
+class nsSVGTransform : public nsIDOMSVGTransform,
+                       public nsSVGValue,
+                       public nsISVGValueObserver
+{
+public:
+  static nsresult Create(nsIDOMSVGTransform** aResult);
+  
+protected:
+  nsSVGTransform();
+  ~nsSVGTransform();
+  nsresult Init();
+public:
+  // nsISupports interface:
+  NS_DECL_ISUPPORTS
+
+  // nsIDOMSVGTransform interface:
+  NS_DECL_NSIDOMSVGTRANSFORM
+
+  // nsISVGValue interface:
+  NS_IMETHOD SetValueString(const nsAString& aValue);
+  NS_IMETHOD GetValueString(nsAString& aValue);
+
+  // nsISVGValueObserver
+  NS_IMETHOD WillModifySVGObservable(nsISVGValue* observable,
+                                     modificationType aModType);
+  NS_IMETHOD DidModifySVGObservable (nsISVGValue* observable,
+                                     modificationType aModType);
+
+#ifdef MOZ_SMIL
+  // Additional methods needed for animation
+  void GetRotationOrigin(float& aOriginX, float& aOriginY) const
+  {
+    aOriginX = mOriginX;
+    aOriginY = mOriginY;
+  }
+#endif // MOZ_SMIL
+
+protected:
+  nsCOMPtr<nsIDOMSVGMatrix> mMatrix;
+  float mAngle, mOriginX, mOriginY;
+  PRUint16 mType;
+};
 
 nsresult
 NS_NewSVGTransform(nsIDOMSVGTransform** result);
 
 // XXX we'll need this prototype-based stuff to support unsetting:
 //nsresult NS_NewSVGTransform(nsIDOMSVGTransform** result,
 //                            nsIDOMSVGTransform*  prototype);
 
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGTransformSMILAttr.cpp
@@ -0,0 +1,439 @@
+/* -*- 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * 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 ***** */
+
+#include "nsSVGTransformSMILAttr.h"
+#include "nsSVGTransformSMILType.h"
+#include "nsSVGAnimatedTransformList.h"
+#include "nsSVGTransformList.h"
+#include "nsSVGTransform.h"
+#include "nsIDOMSVGTransform.h"
+#include "nsIDOMSVGMatrix.h"
+#include "nsSVGMatrix.h"
+#include "nsSMILValue.h"
+#include "nsSMILNullType.h"
+#include "nsISMILAnimationElement.h"
+#include "nsSVGElement.h"
+#include "nsISVGValue.h"
+#include "prdtoa.h"
+
+nsISMILType*
+nsSVGTransformSMILAttr::GetSMILType() const
+{
+  return &nsSVGTransformSMILType::sSingleton;
+}
+
+nsresult
+nsSVGTransformSMILAttr::ValueFromString(const nsAString& aStr,
+                                     const nsISMILAnimationElement* aSrcElement,
+                                     nsSMILValue& aValue) const
+{
+  NS_ENSURE_TRUE(aSrcElement, NS_ERROR_FAILURE);
+  NS_ASSERTION(aValue.IsNull(),
+    "aValue should have been cleared before calling ValueFromString.");
+
+  nsSMILValue val(&nsSVGTransformSMILType::sSingleton);
+  if (val.IsNull())
+    return NS_ERROR_FAILURE;
+
+  const nsAttrValue* typeAttr = aSrcElement->GetAnimAttr(nsGkAtoms::type);
+
+  const nsIAtom* transformType = typeAttr
+                               ? typeAttr->GetAtomValue()
+                               : nsGkAtoms::translate;
+
+  nsresult rv = ParseValue(aStr, transformType, val);
+  if (NS_FAILED(rv))
+    return rv;
+
+  aValue = val;
+
+  return NS_OK;
+}
+
+nsSMILValue
+nsSVGTransformSMILAttr::GetBaseValue() const
+{
+  nsSVGTransformSMILType *type = &nsSVGTransformSMILType::sSingleton;
+  nsSMILValue val(type);
+  if (val.IsNull())
+    return val;
+ 
+  nsIDOMSVGTransformList *list = mVal->mBaseVal.get();
+
+  PRUint32 numItems = 0;
+  list->GetNumberOfItems(&numItems);
+  for (PRUint32 i = 0; i < numItems; i++) {
+    nsCOMPtr<nsIDOMSVGTransform> transform;
+    nsresult rv = list->GetItem(i, getter_AddRefs(transform));
+    if (NS_SUCCEEDED(rv) && transform) {
+      rv = AppendSVGTransformToSMILValue(transform.get(), val);
+      NS_ENSURE_SUCCESS(rv,nsSMILValue());
+    }
+  }
+  
+  return val;
+}
+
+nsresult
+nsSVGTransformSMILAttr::SetAnimValue(const nsSMILValue& aValue)
+{
+  if (aValue.mType != &nsSVGTransformSMILType::sSingleton) {
+    NS_WARNING("Unexpected SMIL Type");
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = NS_OK;
+
+  // Create the anim value if necessary
+  mVal->WillModify(nsISVGValue::mod_other);
+  if (!mVal->mAnimVal) {
+    rv = nsSVGTransformList::Create(getter_AddRefs(mVal->mAnimVal));
+    NS_ENSURE_SUCCESS(rv,rv);
+  }
+
+  // Do a minimal update on the anim value and if anything fails, set the anim
+  // value to null so that calls to nsSVGAnimatedTransformList::GetAnimVal will
+  // return the base value instead.
+  rv = UpdateFromSMILValue(mVal->mAnimVal, aValue);
+  if (NS_FAILED(rv)) {
+    mVal->mAnimVal = nsnull;
+  }
+  NS_ENSURE_SUCCESS(rv,rv);
+
+  mVal->DidModify(nsISVGValue::mod_other);
+  return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// Implementation helpers
+
+nsresult
+nsSVGTransformSMILAttr::ParseValue(const nsAString& aSpec,
+                                   const nsIAtom* aTransformType,
+                                   nsSMILValue& aResult) const
+{
+  nsSVGTransformSMILType* type = &nsSVGTransformSMILType::sSingleton;
+  NS_ASSERTION(
+      type == static_cast<nsSVGTransformSMILType const *>(aResult.mType),
+      "Unexpected type for SMIL value result.");
+
+  // Reset the result so we can just append to it
+  nsresult rv = type->Init(aResult);
+  NS_ENSURE_SUCCESS(rv,rv);
+
+  float params[3] = { 0.f };
+  PRInt32 numParsed = ParseParameterList(aSpec, params, 3);
+  nsSVGSMILTransform::TransformType transformType;
+   
+  if (aTransformType == nsGkAtoms::translate) {
+    // tx [ty=0]
+    if (numParsed != 1 && numParsed != 2)
+      return NS_ERROR_FAILURE;
+    transformType = nsSVGSMILTransform::TRANSFORM_TRANSLATE;
+  } else if (aTransformType == nsGkAtoms::scale) {
+    // sx [sy=sx]
+    if (numParsed != 1 && numParsed != 2)
+      return NS_ERROR_FAILURE;
+    if (numParsed == 1) {
+      params[1] = params[0];
+    }
+    transformType = nsSVGSMILTransform::TRANSFORM_SCALE;
+  } else if (aTransformType == nsGkAtoms::rotate) {
+    // r [cx=0 cy=0]
+    if (numParsed != 1 && numParsed != 3)
+      return NS_ERROR_FAILURE;
+    transformType = nsSVGSMILTransform::TRANSFORM_ROTATE;
+  } else if (aTransformType == nsGkAtoms::skewX) {
+    // x-angle
+    if (numParsed != 1)
+      return NS_ERROR_FAILURE;
+    transformType = nsSVGSMILTransform::TRANSFORM_SKEWX;
+  } else if (aTransformType == nsGkAtoms::skewY) {
+    // y-angle
+    if (numParsed != 1)
+      return NS_ERROR_FAILURE;
+    transformType = nsSVGSMILTransform::TRANSFORM_SKEWY;
+  } else {
+    return NS_ERROR_FAILURE;
+  }
+
+  return type->AppendTransform(nsSVGSMILTransform(transformType, params),
+                               aResult);
+}
+
+inline PRBool
+nsSVGTransformSMILAttr::IsSpace(const char c) const
+{
+  return (c == 0x9 || c == 0xA || c == 0xD || c == 0x20);
+}
+
+inline void
+nsSVGTransformSMILAttr::SkipWsp(nsACString::const_iterator& aIter,
+                               const nsACString::const_iterator& aIterEnd) const
+{
+  while (aIter != aIterEnd && IsSpace(*aIter))
+    ++aIter;
+}
+
+PRInt32
+nsSVGTransformSMILAttr::ParseParameterList(const nsAString& aSpec,
+                                           float* aVars,
+                                           PRInt32 aNVars) const
+{
+  NS_ConvertUTF16toUTF8 spec(aSpec);
+
+  nsACString::const_iterator start, end;
+  spec.BeginReading(start);
+  spec.EndReading(end);
+
+  SkipWsp(start, end);
+
+  int numArgsFound = 0;
+
+  while (start != end) {
+    char const *arg = start.get();
+    char *argend;
+    float f = float(PR_strtod(arg, &argend));
+    if (arg == argend || argend > end.get())
+      return -1;
+
+    if (numArgsFound < aNVars) {
+      aVars[numArgsFound] = f;
+    }
+
+    start.advance(argend - arg);
+    numArgsFound++;
+
+    SkipWsp(start, end);
+    if (*start == ',') {
+      ++start;
+      SkipWsp(start, end);
+    }
+  }
+
+  return numArgsFound;
+}
+
+nsresult
+nsSVGTransformSMILAttr::AppendSVGTransformToSMILValue(
+  nsIDOMSVGTransform* aTransform, nsSMILValue& aValue) const
+{
+  nsSVGTransformSMILType* type = &nsSVGTransformSMILType::sSingleton;
+
+  PRUint16 svgTransformType = nsIDOMSVGTransform::SVG_TRANSFORM_MATRIX;
+  aTransform->GetType(&svgTransformType);
+
+  nsCOMPtr<nsIDOMSVGMatrix> matrix;
+  nsresult rv = aTransform->GetMatrix(getter_AddRefs(matrix));
+  if (NS_FAILED(rv) || !matrix)
+    return NS_ERROR_FAILURE;
+
+  float params[3] = { 0.f };
+  nsSVGSMILTransform::TransformType transformType;
+
+  switch (svgTransformType)
+  {
+    case nsIDOMSVGTransform::SVG_TRANSFORM_TRANSLATE:
+      {
+        matrix->GetE(&params[0]);
+        matrix->GetF(&params[1]);
+        transformType = nsSVGSMILTransform::TRANSFORM_TRANSLATE;
+      }
+      break;
+
+    case nsIDOMSVGTransform::SVG_TRANSFORM_SCALE:
+      {
+        matrix->GetA(&params[0]);
+        matrix->GetD(&params[1]);
+        transformType = nsSVGSMILTransform::TRANSFORM_SCALE;
+      }
+      break;
+
+    case nsIDOMSVGTransform::SVG_TRANSFORM_ROTATE:
+      {
+        /*
+         * Unfortunately the SVG 1.1 DOM API for transforms doesn't allow us to
+         * query the center of rotation so we do some dirty casting to make up
+         * for it.
+         */
+        nsSVGTransform* svgTransform = static_cast<nsSVGTransform*>(aTransform);
+        svgTransform->GetAngle(&params[0]);
+        svgTransform->GetRotationOrigin(params[1], params[2]);
+        transformType = nsSVGSMILTransform::TRANSFORM_ROTATE;
+      }
+      break;
+
+    case nsIDOMSVGTransform::SVG_TRANSFORM_SKEWX:
+      {
+        aTransform->GetAngle(&params[0]);
+        transformType = nsSVGSMILTransform::TRANSFORM_SKEWX;
+      }
+      break;
+
+    case nsIDOMSVGTransform::SVG_TRANSFORM_SKEWY:
+      {
+        aTransform->GetAngle(&params[0]);
+        transformType = nsSVGSMILTransform::TRANSFORM_SKEWY;
+      }
+      break;
+
+    case nsIDOMSVGTransform::SVG_TRANSFORM_MATRIX:
+      {
+        float mx[6];
+        matrix->GetA(&mx[0]);
+        matrix->GetB(&mx[1]);
+        matrix->GetC(&mx[2]);
+        matrix->GetD(&mx[3]);
+        matrix->GetE(&mx[4]);
+        matrix->GetF(&mx[5]);
+        rv = type->AppendTransform(nsSVGSMILTransform(mx), aValue);
+        transformType = nsSVGSMILTransform::TRANSFORM_MATRIX;
+      }
+      break;
+
+    case nsIDOMSVGTransform::SVG_TRANSFORM_UNKNOWN:
+      // If it's 'unknown', it's probably not initialised, so just skip it.
+      return NS_OK;
+
+    default:
+      NS_WARNING("Trying to convert unrecognised SVG transform type.");
+      return NS_ERROR_FAILURE;
+  }
+
+  if (transformType != nsSVGSMILTransform::TRANSFORM_MATRIX) {
+    rv = 
+      type->AppendTransform(nsSVGSMILTransform(transformType, params), aValue);
+  }
+
+  return rv;
+}
+
+nsresult
+nsSVGTransformSMILAttr::UpdateFromSMILValue(
+  nsIDOMSVGTransformList* aTransformList, const nsSMILValue& aValue)
+{
+  PRUint32 svgLength = -1;
+  aTransformList->GetNumberOfItems(&svgLength);
+
+  nsSVGTransformSMILType* type = &nsSVGTransformSMILType::sSingleton;
+  PRUint32 smilLength = type->GetNumTransforms(aValue);
+
+  nsresult rv = NS_OK;
+
+  for (PRUint32 i = 0; i < smilLength; i++) {
+    nsCOMPtr<nsIDOMSVGTransform> transform;
+    if (i < svgLength) {
+      // Get the transform to update
+      rv = aTransformList->GetItem(i, getter_AddRefs(transform));
+      NS_ENSURE_SUCCESS(rv,rv);
+    } else {
+      // Append another transform to the list
+      nsresult rv = NS_NewSVGTransform(getter_AddRefs(transform));
+      NS_ENSURE_SUCCESS(rv,rv);
+
+      nsCOMPtr<nsIDOMSVGTransform> result;
+      rv = aTransformList->AppendItem(transform, getter_AddRefs(result));
+      NS_ENSURE_SUCCESS(rv,rv);
+    }
+    // Set the value
+    const nsSVGSMILTransform* smilTransform = type->GetTransformAt(i, aValue);
+    rv = GetSVGTransformFromSMILValue(*smilTransform, transform);
+    NS_ENSURE_SUCCESS(rv,rv);
+  }
+
+  // Trim excess elements
+  while (svgLength > smilLength) {
+    nsCOMPtr<nsIDOMSVGTransform> removed;
+    rv = aTransformList->RemoveItem(--svgLength, getter_AddRefs(removed));
+    NS_ENSURE_SUCCESS(rv,rv);
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsSVGTransformSMILAttr::GetSVGTransformFromSMILValue(
+    const nsSVGSMILTransform& aSMILTransform,
+    nsIDOMSVGTransform* aSVGTransform) const
+{
+  nsresult rv = NS_ERROR_FAILURE;
+
+  switch (aSMILTransform.mTransformType)
+  {
+    case nsSVGSMILTransform::TRANSFORM_TRANSLATE:
+      rv = aSVGTransform->SetTranslate(aSMILTransform.mParams[0],
+                                       aSMILTransform.mParams[1]);
+      break;
+
+    case nsSVGSMILTransform::TRANSFORM_SCALE:
+      rv = aSVGTransform->SetScale(aSMILTransform.mParams[0],
+                                   aSMILTransform.mParams[1]);
+      break;
+
+    case nsSVGSMILTransform::TRANSFORM_ROTATE:
+      rv = aSVGTransform->SetRotate(aSMILTransform.mParams[0],
+                                    aSMILTransform.mParams[1],
+                                    aSMILTransform.mParams[2]);
+      break;
+
+    case nsSVGSMILTransform::TRANSFORM_SKEWX:
+      rv = aSVGTransform->SetSkewX(aSMILTransform.mParams[0]);
+      break;
+
+    case nsSVGSMILTransform::TRANSFORM_SKEWY:
+      rv = aSVGTransform->SetSkewY(aSMILTransform.mParams[0]);
+      break;
+
+    case nsSVGSMILTransform::TRANSFORM_MATRIX:
+      {
+        nsCOMPtr<nsIDOMSVGMatrix> svgMatrix;
+        rv = NS_NewSVGMatrix(getter_AddRefs(svgMatrix),
+                             aSMILTransform.mParams[0],
+                             aSMILTransform.mParams[1],
+                             aSMILTransform.mParams[2],
+                             aSMILTransform.mParams[3],
+                             aSMILTransform.mParams[4],
+                             aSMILTransform.mParams[5]);
+        NS_ENSURE_SUCCESS(rv,rv);
+        NS_ENSURE_TRUE(svgMatrix,NS_ERROR_FAILURE);
+        rv = aSVGTransform->SetMatrix(svgMatrix);
+      }
+      break;
+  }
+
+  return rv;
+}
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGTransformSMILAttr.h
@@ -0,0 +1,95 @@
+/* -*- 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * 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_SVGTRANSFORMSMILATTR_H_
+#define NS_SVGTRANSFORMSMILATTR_H_
+
+#include "nsISMILAttr.h"
+#include "nsIAtom.h"
+#include "nsString.h"
+
+class nsSVGElement;
+class nsSVGAnimatedTransformList;
+class nsISMILType;
+class nsIDOMSVGTransform;
+class nsIDOMSVGTransformList;
+class nsSVGSMILTransform;
+
+class nsSVGTransformSMILAttr : public nsISMILAttr
+{
+public:
+  nsSVGTransformSMILAttr(nsSVGAnimatedTransformList* aTransform,
+                         nsSVGElement* aSVGElement)
+    : mVal(aTransform),
+      mSVGElement(aSVGElement) {}
+
+  // nsISMILAttr methods
+  virtual nsISMILType* GetSMILType() const;
+  virtual nsresult     ValueFromString(const nsAString& aStr,
+                                   const nsISMILAnimationElement* aSrcElement,
+                                   nsSMILValue& aValue) const;
+  virtual nsSMILValue  GetBaseValue() const;
+  virtual nsresult     SetAnimValue(const nsSMILValue& aValue);
+
+protected:
+  nsresult ParseValue(const nsAString& aSpec,
+                      const nsIAtom* aTransformType,
+                      nsSMILValue& aResult) const;
+  PRInt32  ParseParameterList(const nsAString& aSpec, float* aVars,
+                              PRInt32 aNVars) const;
+  PRBool   IsSpace(const char c) const;
+  void     SkipWsp(nsACString::const_iterator& aIter,
+                   const nsACString::const_iterator& aIterEnd) const;
+  nsresult AppendSVGTransformToSMILValue(nsIDOMSVGTransform* transform,
+                                         nsSMILValue& aValue) const;
+  nsresult UpdateFromSMILValue(nsIDOMSVGTransformList* aTransformList,
+                               const nsSMILValue& aValue);
+  nsresult GetSVGTransformFromSMILValue(
+                        const nsSVGSMILTransform& aSMILTransform,
+                        nsIDOMSVGTransform* aSVGTransform) const;
+  already_AddRefed<nsIDOMSVGTransform> GetSVGTransformFromSMILValue(
+                                        const nsSMILValue& aValue) const;
+
+private:
+  // Raw pointers are OK here because this nsSVGTransformSMILAttr is both
+  // created & destroyed during a SMIL sample-step, during which time the DOM
+  // isn't modified.
+  nsSVGAnimatedTransformList* mVal;
+  nsSVGElement* mSVGElement;
+};
+
+#endif // NS_SVGTRANSFORMSMILATTR_H_
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGTransformSMILType.cpp
@@ -0,0 +1,356 @@
+/* -*- 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * 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 ***** */
+
+#include "nsSVGTransformSMILType.h"
+#include "nsSMILValue.h"
+#include "nsCRT.h"
+#include <math.h>
+
+/*static*/ nsSVGTransformSMILType nsSVGTransformSMILType::sSingleton;
+
+//----------------------------------------------------------------------
+// nsISMILType implementation
+
+nsresult
+nsSVGTransformSMILType::Init(nsSMILValue &aValue) const
+{
+  NS_PRECONDITION(aValue.mType == this || aValue.IsNull(),
+                  "Unexpected value type");
+  NS_ASSERTION(aValue.mType != this || aValue.mU.mPtr,
+               "Invalid nsSMILValue of SVG transform type: NULL data member.");
+
+  if (aValue.mType != this || !aValue.mU.mPtr) {
+    // Different type, or no data member: allocate memory and set type 
+    TransformArray* transforms = new TransformArray(1);
+    NS_ENSURE_TRUE(transforms, NS_ERROR_OUT_OF_MEMORY);
+    aValue.mU.mPtr = transforms;
+    aValue.mType = this;
+  } else {
+    // Same type, just set clear
+    TransformArray* transforms = static_cast<TransformArray*>(aValue.mU.mPtr);
+    transforms->Clear();
+  }
+
+  return NS_OK;
+}
+
+void
+nsSVGTransformSMILType::Destroy(nsSMILValue& aValue) const
+{
+  NS_PRECONDITION(aValue.mType == this, "Unexpected SMIL value type.");
+  TransformArray* params = static_cast<TransformArray*>(aValue.mU.mPtr);
+  delete params;
+  aValue.mU.mPtr = nsnull;
+  aValue.mType = &nsSMILNullType::sSingleton;
+}
+
+nsresult
+nsSVGTransformSMILType::Assign(nsSMILValue& aDest,
+                               const nsSMILValue& aSrc) const
+{
+  NS_PRECONDITION(aDest.mType == aSrc.mType, "Incompatible SMIL types.");
+  NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL value.");
+
+  const TransformArray* srcTransforms = 
+    static_cast<const TransformArray*>(aSrc.mU.mPtr);
+  TransformArray* dstTransforms = static_cast<TransformArray*>(aDest.mU.mPtr);
+
+  // Before we assign, ensure we have sufficient memory
+  PRBool result = dstTransforms->SetCapacity(srcTransforms->Length());
+  NS_ENSURE_TRUE(result,NS_ERROR_OUT_OF_MEMORY);
+
+  *dstTransforms = *srcTransforms;
+
+  return NS_OK;
+}
+
+nsresult
+nsSVGTransformSMILType::Add(nsSMILValue& aDest, const nsSMILValue& aValueToAdd,
+                            PRUint32 aCount) const
+{
+  NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL type.");
+  NS_PRECONDITION(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types.");
+
+  TransformArray& dstTransforms(*static_cast<TransformArray*>(aDest.mU.mPtr));
+  const TransformArray& srcTransforms
+    (*static_cast<const TransformArray*>(aValueToAdd.mU.mPtr));
+
+  // We're doing a simple add here (as opposed to a sandwich add below).
+  // We only do this when we're accumulating a repeat result or calculating
+  // a by-animation value.
+  //
+  // In either case we should have 1 transform in the source array.
+  NS_ASSERTION(srcTransforms.Length() == 1,
+    "Invalid source transform list to add.");
+
+  // And we should have 0 or 1 transforms in the dest array.
+  // (We can have 0 transforms in the case of by-animation when we are
+  // calculating the by-value as "0 + by". Zero being represented by an
+  // nsSMILValue with an empty transform array.)
+  NS_ASSERTION(dstTransforms.Length() < 2,
+    "Invalid dest transform list to add to.");
+
+  // Get the individual transforms to add
+  const nsSVGSMILTransform& srcTransform = srcTransforms[0];
+  if (dstTransforms.IsEmpty()) {
+    nsSVGSMILTransform* result = dstTransforms.AppendElement(
+      nsSVGSMILTransform(srcTransform.mTransformType));
+    NS_ENSURE_TRUE(result,NS_ERROR_OUT_OF_MEMORY);
+  }
+  nsSVGSMILTransform& dstTransform = dstTransforms[0];
+
+  // The types must be the same
+  NS_ASSERTION(srcTransform.mTransformType == dstTransform.mTransformType,
+    "Trying to perform simple add of different transform types.");
+
+  // And it should be impossible that one of them is of matrix type
+  NS_ASSERTION(
+    srcTransform.mTransformType != nsSVGSMILTransform::TRANSFORM_MATRIX,
+    "Trying to perform simple add with matrix transform.");
+
+  // Add the parameters
+  for (int i = 0; i <= 2; ++i) {
+    dstTransform.mParams[i] += srcTransform.mParams[i] * aCount;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsSVGTransformSMILType::SandwichAdd(nsSMILValue& aDest,
+                                    const nsSMILValue& aValueToAdd) const
+{
+  NS_PRECONDITION(aDest.mType == this, "Unexpected SMIL type.");
+  NS_PRECONDITION(aDest.mType == aValueToAdd.mType, "Incompatible SMIL types.");
+
+  // For <animateTransform> a sandwich add means a matrix post-multiplication
+  // which just means to put the additional transform on the end of the array
+
+  TransformArray& dstTransforms(*static_cast<TransformArray*>(aDest.mU.mPtr));
+  const TransformArray& srcTransforms
+    (*static_cast<const TransformArray*>(aValueToAdd.mU.mPtr));
+
+  // We're only expecting to be adding 1 src transform on to the list
+  NS_ASSERTION(srcTransforms.Length() == 1,
+    "Trying to do sandwich add of more than one value.");
+
+  // Stick the src on the end of the array
+  const nsSVGSMILTransform& srcTransform = srcTransforms[0];
+  nsSVGSMILTransform* result = dstTransforms.AppendElement(srcTransform);
+  NS_ENSURE_TRUE(result,NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
+
+nsresult
+nsSVGTransformSMILType::ComputeDistance(const nsSMILValue& aFrom,
+                                        const nsSMILValue& aTo,
+                                        double& aDistance) const
+{
+  NS_PRECONDITION(aFrom.mType == aTo.mType, 
+      "Can't compute difference between different SMIL types.");
+  NS_PRECONDITION(aFrom.mType == this, "Unexpected SMIL type.");
+
+  const TransformArray* fromTransforms = 
+    static_cast<const TransformArray*>(aFrom.mU.mPtr);
+  const TransformArray* toTransforms = 
+    static_cast<const TransformArray*>(aTo.mU.mPtr);
+
+  // ComputeDistance is only used for calculating distances between single
+  // values in a values array which necessarily have the same type
+  //
+  // So we should only have one transform in each array and they should be of
+  // the same type
+  NS_ASSERTION(fromTransforms->Length() == 1,
+    "Wrong number of elements in from value.");
+  NS_ASSERTION(toTransforms->Length() == 1,
+    "Wrong number of elements in to value.");
+
+  const nsSVGSMILTransform& fromTransform = (*fromTransforms)[0];
+  const nsSVGSMILTransform& toTransform = (*toTransforms)[0];
+  NS_ASSERTION(fromTransform.mTransformType == toTransform.mTransformType,
+    "Incompatible transform types to calculate distance between.");
+
+  switch (fromTransform.mTransformType)
+  {
+    // We adopt the SVGT1.2 notions of distance here
+    // See: http://www.w3.org/TR/SVGTiny12/animate.html#complexDistances
+    // (As discussed in bug #469040)
+    case nsSVGSMILTransform::TRANSFORM_TRANSLATE:
+    case nsSVGSMILTransform::TRANSFORM_SCALE:
+      {
+        const float& a_tx = fromTransform.mParams[0];
+        const float& a_ty = fromTransform.mParams[1];
+        const float& b_tx = toTransform.mParams[0];
+        const float& b_ty = toTransform.mParams[1];
+        aDistance = sqrt(pow(a_tx - b_tx, 2) + (pow(a_ty - b_ty, 2)));
+      }
+      break;
+
+    case nsSVGSMILTransform::TRANSFORM_ROTATE:
+    case nsSVGSMILTransform::TRANSFORM_SKEWX:
+    case nsSVGSMILTransform::TRANSFORM_SKEWY:
+      {
+        const float& a = fromTransform.mParams[0];
+        const float& b = toTransform.mParams[0];
+        aDistance = fabs(a-b);
+      }
+      break;
+
+    default:
+      NS_ERROR("Got bad transform types for calculating distances.");
+      aDistance = 1.0;
+      return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
+nsSVGTransformSMILType::Interpolate(const nsSMILValue& aStartVal,
+                                    const nsSMILValue& aEndVal,
+                                    double aUnitDistance,
+                                    nsSMILValue& aResult) const
+{
+  NS_PRECONDITION(aStartVal.mType == aEndVal.mType,
+      "Can't interpolate between different SMIL types.");
+  NS_PRECONDITION(aStartVal.mType == this,
+      "Unexpected type for interpolation.");
+  NS_PRECONDITION(aResult.mType == this, "Unexpected result type.");
+
+  const TransformArray& startTransforms = 
+    (*static_cast<const TransformArray*>(aStartVal.mU.mPtr));
+  const TransformArray& endTransforms 
+    (*static_cast<const TransformArray*>(aEndVal.mU.mPtr));
+
+  // We may have 0..n transforms in the start transform array (the base
+  // value) but we should only have 1 transform in the end transform array
+  NS_ASSERTION(endTransforms.Length() == 1,
+    "Invalid end-point for interpolating between transform values.");
+
+  // The end point should never be a matrix transform
+  const nsSVGSMILTransform& endTransform = endTransforms[0];
+  NS_ASSERTION(
+    endTransform.mTransformType != nsSVGSMILTransform::TRANSFORM_MATRIX,
+    "End point for interpolation should not be a matrix transform.");
+
+  // If we have 0 or more than 1 transform in the start transform array then we
+  // just interpolate from 0, 0, 0
+  // Likewise, even if there's only 1 transform in the start transform array
+  // then if the type of the start transform doesn't match the end then we
+  // can't interpolate and should just use 0, 0, 0
+  static float identityParams[3] = { 0.f };
+  const float* startParams = nsnull;
+  if (startTransforms.Length() == 1) {
+    const nsSVGSMILTransform& startTransform = startTransforms[0];
+    if (startTransform.mTransformType == endTransform.mTransformType) {
+      startParams = startTransform.mParams;
+    }
+  }
+  if (!startParams) {
+    startParams = identityParams;
+  }
+  
+  const float* endParams = endTransform.mParams;
+
+  // Interpolate between the params
+  float newParams[3];
+  for (int i = 0; i <= 2; ++i) {
+    const float& a = startParams[i];
+    const float& b = endParams[i];
+    newParams[i] = a + (b - a) * aUnitDistance;
+  }
+
+  // Make the result
+  nsSVGSMILTransform resultTransform(endTransform.mTransformType, newParams);
+
+  // Clear the way for it in the result array
+  TransformArray& dstTransforms = 
+    (*static_cast<TransformArray*>(aResult.mU.mPtr));
+  dstTransforms.Clear();
+
+  // Assign the result
+  nsSVGSMILTransform* transform = dstTransforms.AppendElement(resultTransform);
+  NS_ENSURE_TRUE(transform,NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
+
+//----------------------------------------------------------------------
+// Transform array accessors
+
+PRUint32
+nsSVGTransformSMILType::GetNumTransforms(const nsSMILValue& aValue) const
+{
+  NS_PRECONDITION(aValue.mType == this, "Unexpected SMIL value.");
+
+  const TransformArray& transforms = 
+    *static_cast<const TransformArray*>(aValue.mU.mPtr);
+  
+  return transforms.Length();
+}
+
+const nsSVGSMILTransform*
+nsSVGTransformSMILType::GetTransformAt(PRUint32 aIndex,
+                                       const nsSMILValue& aValue) const
+{
+  NS_PRECONDITION(aValue.mType == this, "Unexpected SMIL value.");
+
+  const TransformArray& transforms = 
+    *static_cast<const TransformArray*>(aValue.mU.mPtr);
+
+  if (aIndex >= transforms.Length()) {
+    NS_ERROR("Attempting to access invalid transform.");
+    return nsnull;
+  }
+  
+  return &transforms[aIndex];
+}
+
+nsresult
+nsSVGTransformSMILType::AppendTransform(const nsSVGSMILTransform& aTransform,
+                                        nsSMILValue& aValue) const
+{
+  NS_PRECONDITION(aValue.mType == this, "Unexpected SMIL value.");
+
+  TransformArray& transforms = *static_cast<TransformArray*>(aValue.mU.mPtr);
+
+  nsSVGSMILTransform* transform = transforms.AppendElement(aTransform);
+  NS_ENSURE_TRUE(transform,NS_ERROR_OUT_OF_MEMORY);
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/content/svg/content/src/nsSVGTransformSMILType.h
@@ -0,0 +1,141 @@
+/* -*- 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2006
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * 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_SVGTRANSFORMSMILTYPE_H_
+#define NS_SVGTRANSFORMSMILTYPE_H_
+
+#include "nsISMILType.h"
+#include "nsSVGSMILTransform.h"
+#include "nsTArray.h"
+
+struct nsSMILValue;
+
+////////////////////////////////////////////////////////////////////////
+// nsSVGTransformSMILType
+//
+// Operations for animating an nsSVGTransformList.
+//
+// This class is confused somewhat by the fact that:
+// (i)  An <animateTransform> element animates an SVGTransformList
+// (ii) BUT <animateTransform> only allows the user to specify animation values
+//      for an SVGTransform
+//
+// This may be rectified in a future edition of SVG but for now it means that
+// the underlying value of an animation may be something of the form:
+//
+//   rotate(90) scale(2) skewX(50)
+//
+// BUT the animation values can only ever be SINGLE transform operations such as
+//
+//   rotate(90)
+//
+//   (actually the syntax here is:
+//      <animateTransform type="rotate" from="0" to="90"...
+//      OR maybe
+//      <animateTransform type="rotate" values="0; 90; 30; 50"...
+//      OR even (with a rotation centre)
+//      <animateTransform type="rotate" values="0 50 20; 30 50 20; 70 0 0"...)
+//
+// This has many implications for the number of elements we expect in the
+// transform array supplied for each operation.
+//
+// For example, Add() only ever operates on the values specified on an
+// <animateTransform> element and so these values can only ever contain 0 or
+// 1 TRANSFORM elements as the syntax doesn't allow more. (A "value" here is
+// a single element in the values array such as "0 50 20" above.)
+//
+// Likewise ComputeDistance() only ever operates within the values specified on
+// an <animateTransform> element so similar conditions hold.
+//
+// However, SandwichAdd() combines with a base value which may contain 0..n
+// transforms either because the base value of the attribute specifies a series
+// of transforms, e.g.
+//
+//   <circle transform="translate(30) rotate(50)"... >
+//     <animateTransform.../>
+//   </circle>
+//
+// or because several animations target the same attribute and are additive and
+// so are simply appended on to the transformation array, e.g.
+//
+//   <circle transform="translate(30)"... >
+//     <animateTransform type="rotate" additive="sum".../>
+//     <animateTransform type="scale" additive="sum".../>
+//     <animateTransform type="skewX" additive="sum".../>
+//   </circle>
+//
+// Similar conditions hold for Interpolate() which in cases such as to-animation
+// may have use a start-value the base value of the target attribute (which as
+// we have seen above can contain 0..n elements) whilst the end-value comes from
+// the <animateTransform> and so can only hold 1 transform.
+//
+class nsSVGTransformSMILType : public nsISMILType
+{
+public:
+  // nsISMILType
+  virtual nsresult Init(nsSMILValue& aValue) const;
+  virtual void     Destroy(nsSMILValue& aValue) const;
+  virtual nsresult Assign(nsSMILValue& aDest, const nsSMILValue& aSrc) const;
+  virtual nsresult Add(nsSMILValue& aDest,
+                       const nsSMILValue& aValueToAdd,
+                       PRUint32 aCount) const;
+  virtual nsresult SandwichAdd(nsSMILValue& aDest,
+                               const nsSMILValue& aValueToAdd) const;
+  virtual nsresult ComputeDistance(const nsSMILValue& aFrom,
+                                   const nsSMILValue& aTo,
+                                   double& aDistance) const;
+  virtual nsresult Interpolate(const nsSMILValue& aStartVal,
+                               const nsSMILValue& aEndVal,
+                               double aUnitDistance,
+                               nsSMILValue& aResult) const;
+  // Transform array accessors
+  PRUint32 GetNumTransforms(const nsSMILValue& aValue) const;
+  const nsSVGSMILTransform* GetTransformAt(PRUint32 aIndex,
+                                           const nsSMILValue& aValue) const;
+  nsresult AppendTransform(const nsSVGSMILTransform& aTransform,
+                           nsSMILValue& aValue) const;
+
+  static nsSVGTransformSMILType sSingleton;
+
+protected:
+  typedef nsTArray<nsSVGSMILTransform> TransformArray;
+
+private:
+  nsSVGTransformSMILType() {}
+};
+
+#endif // NS_SVGTRANSFORMSMILTYPE_H_
--- a/dom/public/idl/svg/Makefile.in
+++ b/dom/public/idl/svg/Makefile.in
@@ -126,14 +126,15 @@ XPIDLSRCS	= \
 		nsIDOMSVGViewSpec.idl \
 		nsIDOMSVGZoomAndPan.idl \
 		nsIDOMSVGZoomEvent.idl \
 		$(NULL)
 
 ifdef MOZ_SMIL
 XPIDLSRCS	+= \
 		nsIDOMSVGAnimateElement.idl \
+		nsIDOMSVGAnimateTransformElement.idl \
 		nsIDOMSVGAnimationElement.idl \
 		nsIDOMSVGSetElement.idl \
 		$(NULL)
 endif
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/public/idl/svg/nsIDOMSVGAnimateTransformElement.idl
@@ -0,0 +1,41 @@
+/* -*- Mode: IDL; 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 Brian Birtles.
+ * Portions created by the Initial Developer are Copyright (C) 2005
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * 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 ***** */
+
+#include "nsIDOMSVGAnimationElement.idl"
+
+[scriptable, uuid(735e0f75-c6aa-4aee-bcd2-46426d6ac90c)]
+interface nsIDOMSVGAnimateTransformElement : nsIDOMSVGAnimationElement {};
--- a/dom/public/nsDOMClassInfoID.h
+++ b/dom/public/nsDOMClassInfoID.h
@@ -240,16 +240,17 @@ enum nsDOMClassInfoID {
 #ifdef MOZ_SVG
   // The SVG document
   eDOMClassInfo_SVGDocument_id,
 
   // SVG element classes
   eDOMClassInfo_SVGAElement_id,
 #ifdef MOZ_SMIL
   eDOMClassInfo_SVGAnimateElement_id,
+  eDOMClassInfo_SVGAnimateTransformElement_id,
   eDOMClassInfo_SVGSetElement_id,
 #endif // MOZ_SMIL
   eDOMClassInfo_SVGCircleElement_id,
   eDOMClassInfo_SVGClipPathElement_id,
   eDOMClassInfo_SVGDefsElement_id,
   eDOMClassInfo_SVGDescElement_id,
   eDOMClassInfo_SVGEllipseElement_id,
   eDOMClassInfo_SVGFEBlendElement_id,
--- a/dom/src/base/nsDOMClassInfo.cpp
+++ b/dom/src/base/nsDOMClassInfo.cpp
@@ -373,16 +373,17 @@
 #include "nsIDOMSVGAnimatedNumberList.h"
 #include "nsIDOMSVGAnimatedPathData.h"
 #include "nsIDOMSVGAnimatedPoints.h"
 #include "nsIDOMSVGAnimPresAspRatio.h"
 #include "nsIDOMSVGAnimatedRect.h"
 #include "nsIDOMSVGAnimatedString.h"
 #ifdef MOZ_SMIL
 #include "nsIDOMSVGAnimateElement.h"
+#include "nsIDOMSVGAnimateTransformElement.h"
 #include "nsIDOMSVGSetElement.h"
 #include "nsIDOMSVGAnimationElement.h"
 #include "nsIDOMElementTimeControl.h"
 #endif // MOZ_SMIL
 #include "nsIDOMSVGAnimTransformList.h"
 #include "nsIDOMSVGCircleElement.h"
 #include "nsIDOMSVGClipPathElement.h"
 #include "nsIDOMSVGDefsElement.h"
@@ -943,16 +944,18 @@ static nsDOMClassInfoData sClassInfoData
                            DOCUMENT_SCRIPTABLE_FLAGS)
 
   // SVG element classes
   NS_DEFINE_CLASSINFO_DATA(SVGAElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
 #ifdef MOZ_SMIL
   NS_DEFINE_CLASSINFO_DATA(SVGAnimateElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(SVGAnimateTransformElement, nsElementSH,
+                           ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGSetElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
 #endif // MOZ_SMIL
   NS_DEFINE_CLASSINFO_DATA(SVGCircleElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGClipPathElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGDefsElement, nsElementSH,
@@ -2766,16 +2769,24 @@ nsDOMClassInfo::Init()
 #ifdef MOZ_SMIL
   DOM_CLASSINFO_MAP_BEGIN(SVGAnimateElement, nsIDOMSVGAnimateElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGAnimationElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGAnimateElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMElementTimeControl)
     DOM_CLASSINFO_SVG_ELEMENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(SVGAnimateTransformElement,
+                          nsIDOMSVGAnimateTransformElement)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGAnimationElement)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGAnimateTransformElement)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMElementTimeControl)
+    DOM_CLASSINFO_SVG_ELEMENT_MAP_ENTRIES
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(SVGSetElement,
                           nsIDOMSVGSetElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGAnimationElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGSetElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMElementTimeControl)
     DOM_CLASSINFO_SVG_ELEMENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 #endif // MOZ_SMIL
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -14,16 +14,19 @@
 #    * Animation-related bits of http://www.w3.org/TR/SVG/struct.html#DOMInterfaces
 
 # animation sort-order tests
 include sort/reftest.list
 
 # style tests
 include style/reftest.list
 
+# animateTransform tests
+include transform/reftest.list
+
 # time-dependent tests
 # XXXdholbert Disabling this class of tests for now, because most of them
 # can & should be converted so they don't depend on specific timeout values.
 # (to prevent sporadic failures due to nondeterminism)
 # include timed/reftest.list
 
 # time container tests
 include container/reftest.list
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/additive-1-ref.svg
@@ -0,0 +1,42 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <g transform="translate(50 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(50 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(50 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(50 350)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/additive-1.svg
@@ -0,0 +1,209 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="setupSnapshot(1.5)">
+  <script type="text/ecmascript"><![CDATA[
+    function setupSnapshot(timeInSeconds) {
+      var svg = document.documentElement;
+      svg.pauseAnimations();
+      svg.setCurrentTime(timeInSeconds);
+      // Use setTimeout to allow SMIL to update the animation before we check
+      // the values and take a snapshot
+      setTimeout('checkAnimVals()');
+    }
+    function checkAnimVals() {
+      var svg = document.documentElement;
+      var paths = svg.getElementsByTagName("path");
+      for (var i = 0; i < paths.length; i++) {
+        var path = paths[i];
+        checkAnimVal(path, path.transform.animVal, i);
+      }
+      svg.removeAttribute("class");
+    }
+    function Transform(type, angle) {
+      this.type = type;
+      this.angle = angle;
+    }
+    function checkAnimVal(path, val, index) {
+      var expected = [];
+      switch (index) {
+      case 0:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90));
+        break;
+
+      case 1:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90));
+        break;
+
+      case 2:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, -90));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 180));
+        break;
+
+      case 3:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        break;
+
+      case 4:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90));
+        break;
+
+      case 5:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, -90));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90));
+        break;
+
+      case 6:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        break;
+
+      case 7:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_SKEWX, 20));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        break;
+
+      case 8:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_SKEWX, 20));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45));
+        break;
+
+      case 9:
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90));
+        expected.push(new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0));
+        break;
+      }
+
+      var ok = true;
+      if (val.numberOfItems == expected.length) {
+        for (var i = 0; i < val.numberOfItems; i++) {
+          var transform = val.getItem(i);
+          if (transform.type != expected[i].type ||
+              transform.angle != expected[i].angle) {
+            ok = false;
+          }
+        }
+      } else {
+        ok = false;
+      }
+
+      if (!ok) {
+        path.style.visibility = 'hidden';
+      }
+    }
+  ]]></script>
+  <!-- not additive -->
+  <g transform="translate(50 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="90" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- to-animation: special additive -->
+  <g transform="translate(150 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" to="90" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- by-animation: special additive -->
+  <g transform="translate(250 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" by="180" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- normal additive: same type -->
+  <g transform="translate(50 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(45)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- normal additive: different type -->
+  <g transform="translate(100 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="translate(50)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="90" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- stacked additive: same type -->
+  <g transform="translate(250 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="90" dur="1s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="90" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- stacked additive: different types #1 -->
+  <g transform="translate(0 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="translate(50)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- stacked additive: different types #2 -->
+  <g transform="translate(100 250) skewX(-20)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="skewX(20) translate(50)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- stacked additive: different types #3 -->
+  <g transform="translate(200 250) skewX(-20)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="skewX(20) translate(50)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="translate" from="0" to="30" dur="1s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="translate" from="0" to="-30" dur="1s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- base value with rotation around a centre -->
+  <g transform="translate(-50 300)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(90 50 50)">
+      <animateTransform attributeName="transform"
+        type="translate" from="0 0" to="0 -50" dur="1s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/paced-1-ref.svg
@@ -0,0 +1,31 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <g id="smiley">
+      <circle fill="yellow" stroke="black" stroke-width="2" cx="0" cy="0"
+        r="40"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="-14" cy="-14"
+        r="14"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="14" cy="-14"
+        r="14"/>
+      <circle cx="-10" cy="-14" r="4"/>
+      <circle cx="10" cy="-14" r="4"/>
+      <path d="m-11 14a13,13 0 0,0 22,0" fill="none" stroke="black"/>
+    </g>
+  </defs>
+  <g transform="translate(50 50)">
+    <use xlink:href="#smiley"/>
+  </g>
+  <g transform="translate(150 50)">
+    <use xlink:href="#smiley"/>
+  </g>
+  <g transform="translate(250 50)">
+    <use xlink:href="#smiley"/>
+  </g>
+  <g transform="translate(50 150)">
+    <use xlink:href="#smiley"/>
+  </g>
+  <g transform="translate(150 150)">
+    <use xlink:href="#smiley"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/paced-1.svg
@@ -0,0 +1,64 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <defs>
+    <g id="smiley">
+      <circle fill="yellow" stroke="black" stroke-width="2" cx="0" cy="0"
+        r="40"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="-14" cy="-14"
+        r="14"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="14" cy="-14"
+        r="14"/>
+      <circle cx="-10" cy="-14" r="4"/>
+      <circle cx="10" cy="-14" r="4"/>
+      <path d="m-11 14a13,13 0 0,0 22,0" fill="none" stroke="black"/>
+    </g>
+  </defs>
+  <!-- translation -->
+  <g transform="translate(0 50)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="translate" fill="freeze" dur="2s" calcMode="paced"
+        values="-5 -10; 35 20; 95 -60"/>
+    </use>
+  </g>
+  <!-- rotation -->
+  <g transform="translate(150 50) rotate(-90)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="rotate" fill="freeze" dur="2s" calcMode="paced"
+        values="20 10 10; 40 -15 25; 160 21 -35"/>
+    </use>
+  </g>
+  <!-- skewY -->
+  <g transform="translate(250 50) skewY(-30)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewY" fill="freeze" dur="2s" calcMode="paced"
+        values="10; 40; 50"/>
+    </use>
+  </g>
+  <!-- scale -->
+  <g transform="translate(50 150) scale(0.5)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="scale" fill="freeze" dur="2s" calcMode="paced"
+        values="-4 -2.5; 4 3.5; 8 0.5"/>
+    </use>
+  </g>
+  <!-- to-animation
+
+       You can't have to-animation with a paced calcMode. This test should just
+       produce regular to-animation without any assertions. This is a white-box
+       test.
+   -->
+  <g transform="translate(100 150)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="translate" fill="freeze" dur="2s" calcMode="paced"
+        to="100"/>
+    </use>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/reftest.list
@@ -0,0 +1,12 @@
+# Tests related to SVG Animation (using SMIL), focusing on the animateTransform
+# element.
+
+== rotate-angle-1.svg rotate-angle-ref.svg
+== rotate-angle-2.svg rotate-angle-ref.svg
+== rotate-angle-3.svg rotate-angle-ref.svg
+== rotate-angle-4.svg rotate-angle-ref.svg
+== rotate-angle-5.svg rotate-angle-ref.svg
+== scale-1.svg scale-1-ref.svg
+== skew-1.svg skew-1-ref.svg
+== paced-1.svg paced-1-ref.svg
+== additive-1.svg additive-1-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/rotate-angle-1.svg
@@ -0,0 +1,60 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <g transform="translate(50 50) rotate(90)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="0" dur="4s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(150 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="90" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(250 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="180" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(50 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="270" dur="3s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(150 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="360" dur="4s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(250 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="540" dur="6s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(50 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="3600" dur="40s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(150 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="-270" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(250 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="-540" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/rotate-angle-2.svg
@@ -0,0 +1,60 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <g transform="translate(44 0) rotate(90 6 50)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="0 6 50" dur="4s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(144 0)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0,6,50" to="90 6 50" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(244,0)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="180,6,50" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(44,100)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0,6,50" to="270,6,50" dur="3s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(144,100)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="360 6 50" dur="4s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(244,100)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="540 6 50" dur="6s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(44,200)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="3600 6 50" dur="40s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(144,200)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="-270 6 50" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(244,200)">
+    <path d="M4 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 6 50" to="-540 6 50" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/rotate-angle-3.svg
@@ -0,0 +1,60 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <g transform="translate(0 0) rotate(90 50 50)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="0 200 50" dur="4s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(100 0)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="90 50 50" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(200 0)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="180 100 50" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(0 100)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="270 150 50" dur="3s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(100 100)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="360 200 50" dur="4s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(200 100)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="540 300 50" dur="6s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(0 200)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="3600 2000 50" dur="40s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(100 200)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="-270 50 50" dur="1s" fill="freeze"/>
+    </path>
+  </g>
+  <g transform="translate(200 200)">
+    <path d="M48 100h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0 0 50" to="-540 100 50" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/rotate-angle-4.svg
@@ -0,0 +1,79 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <!-- no animation -->
+  <g transform="translate(50 50) rotate(90)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"/>
+  </g>
+  <!-- accumulate: sum -->
+  <g transform="translate(150 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="45" dur="0.5s" fill="freeze"
+        repeatCount="4" accumulate="sum"/>
+    </path>
+  </g>
+  <!-- accumulate: none -->
+  <g transform="translate(250 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="270" dur="0.75s" fill="freeze"
+        repeatCount="2"/>
+    </path>
+  </g>
+  <!-- additive: replace -->
+  <g transform="translate(50 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="180" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- additive: sum (adding to base value) -->
+  <g transform="translate(150 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="270" dur="1.5s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- additive: sum (adding to other animations) -->
+  <g transform="translate(250 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="60" dur="2s" fill="freeze"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="60" dur="2s" fill="freeze"
+        additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="60" dur="2s" fill="freeze"
+        additive="sum"/>
+    </path>
+  </g>
+  <!-- to animation -->
+  <g transform="translate(50 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(45)">
+      <animateTransform attributeName="transform"
+        type="rotate" to="135" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- by animation -->
+  <g transform="translate(150 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(45)">
+      <animateTransform attributeName="transform"
+        type="rotate" by="90" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- values animation -->
+  <g transform="translate(250 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue">
+      <animateTransform attributeName="transform"
+        type="rotate" values="0; 135; 0" dur="1.5s" fill="freeze"/>
+    </path>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/rotate-angle-5.svg
@@ -0,0 +1,86 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <!-- Tests to-animation behaviour for a lot of undefined situations.
+       SVG 1.1 doesn't define what should happen here but the behaviour we
+       expect is based on other browsers. -->
+  <!-- to animation: rotation from base value -90 to final value 180 -->
+  <g transform="translate(50 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90)">
+      <animateTransform attributeName="transform"
+        type="rotate" to="180" dur="1.5s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- to animation: rotation from base value -810 to final value 990 -->
+  <g transform="translate(150 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-810)">
+      <animateTransform attributeName="transform"
+        type="rotate" to="990" dur="2s" fill="freeze"/>
+    </path>
+  </g>
+  <!-- to animation: rotation from base value -180 to final value 90 but with
+       other animations combined.
+
+       What happens here is that the rotation animation can't interpolate from
+       the base value as it's not a rotation transformation, so instead it
+       assumes an underlying zero matrix as the base value. (see next comment)
+   -->
+  <g transform="translate(250 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90) translate(0 50) scale(2)">
+      <animateTransform attributeName="transform"
+        type="rotate" to="180" dur="2s" fill="freeze" additive="sum"/>
+    </path>
+  </g>
+  <!-- to animation: rotate and scale
+       
+       Here again the scale animation can't interpolate from its base value
+       which is of a different type so it assumes a zero matrix NOT an identity
+       matrix (this is what the SVG WG have decided in the SVGT1.2 Tiny test
+       suite).
+   -->
+  <g transform="translate(50 150) rotate(90)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="scale(2)">
+      <animateTransform attributeName="transform"
+        type="rotate" to="180" dur="1s" fill="freeze" additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="scale" to="2" dur="2s" fill="freeze" additive="sum"/>
+    </path>
+  </g>
+  <!-- to animation: translate and rotate
+       
+       Likewise here we end up rotating from 0 to 180 because we can't
+       interpolate from the underlying translation transformation.
+   -->
+  <g transform="translate(150 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+    transform="rotate(-90) translate(0 50) scale(2)">
+      <animateTransform attributeName="transform"
+        type="translate" to="0" dur="1s" fill="freeze" additive="sum"/>
+      <animateTransform attributeName="transform"
+        type="rotate" to="180" dur="2s" fill="freeze" additive="sum"/>
+    </path>
+  </g>
+  <!-- The following are from the reference image -->
+  <g transform="translate(250 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(50 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/rotate-angle-ref.svg
@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <g transform="translate(50 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 50)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(50 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 150)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(50 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(150 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+  <g transform="translate(250 250)">
+    <path d="M-2 50h4v -90h4l -6 -10 -6 10h4z" fill="blue"
+      transform="rotate(90)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/scale-1-ref.svg
@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <linearGradient id="grad">
+      <stop offset="5%" stop-color="#F66"/>
+      <stop offset="95%" stop-color="#FF6"/>
+    </linearGradient>
+    <circle fill="url(#grad)" stroke="black" stroke-width="1" cx="0" cy="0"
+      r="20" id="circle"/>
+  </defs>
+  <g transform="translate(50 50)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(150 50)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(250 50)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(50 150)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(150 150)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(250 150)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(50 250)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(150 250)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+  <g transform="translate(250 250)">
+    <use xlink:href="#circle" transform="scale(2)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/scale-1.svg
@@ -0,0 +1,108 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <defs>
+    <linearGradient id="grad">
+      <stop offset="5%" stop-color="#F66"/>
+      <stop offset="95%" stop-color="#FF6"/>
+    </linearGradient>
+    <circle fill="url(#grad)" stroke="black" stroke-width="1" cx="0" cy="0"
+      r="20" id="circle"/>
+  </defs>
+  <!-- to animation 
+
+       This should interpolate from 0 (not 1) to 4 to match the behaviour
+       required by the SVGT1.2 test suite and Opera's behaviour.
+  -->
+  <g transform="translate(50 50)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform"
+        type="scale" to="4" dur="2s" fill="freeze"/>
+    </use>
+  </g>
+  <!-- from-to animation -->
+  <g transform="translate(150 50)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform"
+        type="scale" from="-5" to="9" dur="2s" fill="freeze"/>
+    </use>
+  </g>
+  <!-- negative to-animation
+
+       Should go from 0 to -4 over 2s, therefore at t=1s, the scale factor
+       should be -2, so we add a rotation animation to correct the gradient
+   -->
+  <g transform="translate(250 50)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform"
+        type="scale" to="-4" dur="2s" fill="freeze"/>
+      <animateTransform attributeName="transform"
+        type="rotate" from="0" to="360" dur="2s" fill="freeze" additive="sum"/>
+    </use>
+  </g>
+  <!-- by animation
+
+       The behaviour at this point is not clear. The definition of by-animation
+       is:
+
+       "simple animation in which the animation function is defined to offset
+       the underlying value for the attribute, using a delta that varies over
+       the course of the simple duration, starting from a delta of 0 and ending
+       with the delta specified with the by attribute." (SMILANIM 3.2.2)
+
+       Therefore it might seem like by-animation of by="1" means to ADD to the
+       underlying scale factor. Furthermore, the underlying scale factor when
+       not otherwise specified might seemt to be 1, but the SVG WG have decided
+       it's 0. This is inconsistent with the definition of addition for
+       animateTransform (post-multiplication of matrices) but it is the
+       behaviour required by SVGT1.2 test suite and used by Opera.
+
+       The following animation should go from 0 to 4, over 2s so at t=1s, the
+       scale factor should be 2.
+   -->
+  <g transform="translate(50 150)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform"
+        type="scale" by="4" dur="2s" fill="freeze"/>
+    </use>
+  </g>
+  <!-- by animation #2 -->
+  <g transform="translate(150 150)">
+    <use xlink:href="#circle" transform="scale(4)">
+      <animateTransform attributeName="transform"
+        type="scale" by="1" dur="2s" fill="freeze" additive="sum"/>
+    </use>
+  </g>
+  <!-- from-by animation -->
+  <g transform="translate(250 150)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform"
+        type="scale" from="2" by="0" dur="2s" fill="freeze"/>
+    </use>
+  </g>
+  <!-- values animation -->
+  <g transform="translate(50 250)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform"
+        type="scale" values="0; 10; 2; 5; -1" dur="2s" fill="freeze"/>
+    </use>
+  </g>
+  <!-- repetition -->
+  <g transform="translate(150 250)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform" type="scale" from="0"
+      to="0.6" dur="0.3s" repeatCount="4" accumulate="sum"
+      fill="freeze"/>
+    </use>
+  </g>
+  <!-- repeated to-animation (should NOT accumulate) -->
+  <g transform="translate(250 250)">
+    <use xlink:href="#circle">
+      <animateTransform attributeName="transform" type="scale"
+      to="6" dur="0.75" repeatCount="2" accumulate="sum"
+      fill="freeze"/>
+    </use>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/skew-1-ref.svg
@@ -0,0 +1,43 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <g id="smiley">
+      <circle fill="yellow" stroke="black" stroke-width="2" cx="0" cy="0"
+        r="40"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="-14" cy="-14"
+        r="14"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="14" cy="-14"
+        r="14"/>
+      <circle cx="-10" cy="-14" r="4"/>
+      <circle cx="10" cy="-14" r="4"/>
+      <path d="m-11 14a13,13 0 0,0 22,0" fill="none" stroke="black"/>
+    </g>
+  </defs>
+  <g transform="translate(50 50)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(150 50)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(250 50)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(50 150)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(150 150)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(250 150)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(50 250)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(150 250)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+  <g transform="translate(250 250)">
+    <use xlink:href="#smiley" transform="skewX(30)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/transform/skew-1.svg
@@ -0,0 +1,86 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="setTimeAndSnapshot(1, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <defs>
+    <g id="smiley">
+      <circle fill="yellow" stroke="black" stroke-width="2" cx="0" cy="0"
+        r="40"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="-14" cy="-14"
+        r="14"/>
+      <circle fill="white" stroke="black" stroke-width="1" cx="14" cy="-14"
+        r="14"/>
+      <circle cx="-10" cy="-14" r="4"/>
+      <circle cx="10" cy="-14" r="4"/>
+      <path d="m-11 14a13,13 0 0,0 22,0" fill="none" stroke="black"/>
+    </g>
+  </defs>
+  <!-- from-to animation -->
+  <g transform="translate(50 50)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" from="-30" to="90" dur="2s"/>
+    </use>
+  </g>
+  <!-- from-by animation -->
+  <g transform="translate(150 50)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" from="60" by="-60" dur="2s"/>
+    </use>
+  </g>
+  <!-- by animation -->
+  <g transform="translate(250 50) skewX(-15)">
+    <use xlink:href="#smiley" transform="skewX(15)">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" by="60" dur="2s"/>
+    </use>
+  </g>
+  <!-- to animation -->
+  <g transform="translate(50 150)">
+    <use xlink:href="#smiley" transform="skewX(15)">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" to="45" dur="2s"/>
+    </use>
+  </g>
+  <!-- values animation -->
+  <g transform="translate(150 150)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" values="10; 40; 20; 60" dur="2s"/>
+    </use>
+  </g>
+  <!-- additive -->
+  <g transform="translate(250 150)">
+    <use xlink:href="#smiley" transform="skewY(-30)">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewY" fill="freeze" to="30" dur="2s"/>
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" from="-30" to="90" dur="2s" additive="sum"/>
+    </use>
+  </g>
+  <!-- accumulate: none -->
+  <g transform="translate(50 250)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" from="20" to="50" dur="0.75s"
+        repeatCount="3"/>
+    </use>
+  </g>
+  <!-- accumulate: sum -->
+  <g transform="translate(150 250)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" from="0" to="22.5" dur="0.75"
+        accumulate="sum" repeatCount="3"/>
+    </use>
+  </g>
+  <!-- angles > 360 -->
+  <g transform="translate(250 250)">
+    <use xlink:href="#smiley">
+      <animateTransform attributeName="transform" attributeType="XML"
+        type="skewX" fill="freeze" from="-690" to="750" dur="2s"/>
+    </use>
+  </g>
+</svg>