Bug 436418, patch D: SVG/SMIL animateMotion - reftests & mochitests.
authorDaniel Holbert <dholbert@cs.stanford.edu>
Wed, 28 Apr 2010 16:00:54 -0700
changeset 41508 2d5ccb713cd2b774e11b90e54818a2d048671c1e
parent 41507 c49f111bb95b8b105c08161d6a79024f6a8df557
child 41509 172c98ff378e82dc8373f0db0c4219fed5c0632f
push idunknown
push userunknown
push dateunknown
bugs436418
milestone1.9.3a5pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
Bug 436418, patch D: SVG/SMIL animateMotion - reftests & mochitests.
content/smil/test/Makefile.in
content/smil/test/db_smilAnimateMotion.js
content/smil/test/smilAnimateMotionValueLists.js
content/smil/test/smilTestUtils.js
content/smil/test/test_smilAnimateMotion.xhtml
content/smil/test/test_smilAnimateMotionInvalidValues.xhtml
content/smil/test/test_smilAnimateMotionOverrideRules.xhtml
layout/reftests/svg/smil/motion/animateMotion-by-1.svg
layout/reftests/svg/smil/motion/animateMotion-from-to-1.svg
layout/reftests/svg/smil/motion/animateMotion-mpath-targetChange-1.svg
layout/reftests/svg/smil/motion/animateMotion-rotate-1.svg
layout/reftests/svg/smil/motion/animateMotion-rotate-2.svg
layout/reftests/svg/smil/motion/animateMotion-values-linear-1-ref.svg
layout/reftests/svg/smil/motion/animateMotion-values-linear-1.svg
layout/reftests/svg/smil/motion/animateMotion-values-paced-1-ref.svg
layout/reftests/svg/smil/motion/animateMotion-values-paced-1a.svg
layout/reftests/svg/smil/motion/animateMotion-values-paced-1b.svg
layout/reftests/svg/smil/motion/lime.svg
layout/reftests/svg/smil/motion/reftest.list
--- a/content/smil/test/Makefile.in
+++ b/content/smil/test/Makefile.in
@@ -40,23 +40,28 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = content/smil/test
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _TEST_FILES = \
+	  db_smilAnimateMotion.js \
 	  db_smilCSSFromBy.js \
 	  db_smilCSSFromTo.js \
 	  db_smilCSSPaced.js \
 	  db_smilCSSPropertyList.js \
 	  db_smilMappedAttrList.js \
+	  smilAnimateMotionValueLists.js \
 	  smilTestUtils.js \
 	  smilXHR_helper.svg \
+	  test_smilAnimateMotion.xhtml \
+	  test_smilAnimateMotionInvalidValues.xhtml \
+	  test_smilAnimateMotionOverrideRules.xhtml \
 	  test_smilChangeAfterFrozen.xhtml \
 	  test_smilContainerBinding.xhtml \
 	  test_smilCrossContainer.xhtml \
 	  test_smilCSSFontStretchRelative.xhtml \
 	  test_smilCSSFromBy.xhtml \
 	  test_smilCSSFromTo.xhtml \
 	  test_smilCSSInherit.xhtml \
 	  test_smilCSSInvalidValues.xhtml \
new file mode 100644
--- /dev/null
+++ b/content/smil/test/db_smilAnimateMotion.js
@@ -0,0 +1,286 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et: */
+/* ***** 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 Mozilla SMIL Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Daniel Holbert <dholbert@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* testcase data for <animateMotion> */
+
+// Fake motion 'attribute', to satisfy testing code that expects an attribute.
+var gMotionAttr = new AdditiveAttribute(SMILUtil.getMotionFakeAttributeName(),
+                                        "XML", "rect");
+
+// CTM-summary-definitions, for re-use by multiple testcase bundles below.
+var _reusedCTMLists = {
+  pacedBasic:     { ctm0:   [100, 200, 0],
+                    ctm1_6: [105, 205, 0],
+                    ctm1_3: [110, 210, 0],
+                    ctm2_3: [120, 220, 0],
+                    ctm1:   [130, 210, 0]
+  },
+  pacedR60:       { ctm0:   [100, 200, Math.PI/3],
+                    ctm1_6: [105, 205, Math.PI/3],
+                    ctm1_3: [110, 210, Math.PI/3],
+                    ctm2_3: [120, 220, Math.PI/3],
+                    ctm1:   [130, 210, Math.PI/3]
+  },
+  pacedRAuto:     { ctm0:   [100, 200, Math.PI/4],
+                    ctm1_6: [105, 205, Math.PI/4],
+                    ctm1_3: [110, 210, Math.PI/4],
+                    ctm2_3: [120, 220, Math.PI/4],
+                    ctm1:   [130, 210, -Math.PI/4]
+  },
+  pacedRAutoReverse : { ctm0:   [100, 200, 5*Math.PI/4],
+                        ctm1_6: [105, 205, 5*Math.PI/4],
+                        ctm1_3: [110, 210, 5*Math.PI/4],
+                        ctm2_3: [120, 220, 5*Math.PI/4],
+                        ctm1:   [130, 210, 3*Math.PI/4]
+  },
+  
+  discreteBasic : { ctm0:   [100, 200, 0],
+                    ctm1_6: [100, 200, 0],
+                    ctm1_3: [120, 220, 0],
+                    ctm2_3: [130, 210, 0],
+                    ctm1:   [130, 210, 0]
+  },
+  discreteRAuto : { ctm0:   [100, 200, Math.PI/4],
+                    ctm1_6: [100, 200, Math.PI/4],
+                    ctm1_3: [120, 220, Math.PI/4],
+                    ctm2_3: [130, 210, -Math.PI/4],
+                    ctm1:   [130, 210, -Math.PI/4]
+  },
+  justMoveBasic : { ctm0:   [40, 80, 0],
+                    ctm1_6: [40, 80, 0],
+                    ctm1_3: [40, 80, 0],
+                    ctm2_3: [40, 80, 0],
+                    ctm1:   [40, 80, 0]
+  },
+  justMoveR60 :   { ctm0:   [40, 80, Math.PI/3],
+                    ctm1_6: [40, 80, Math.PI/3],
+                    ctm1_3: [40, 80, Math.PI/3],
+                    ctm2_3: [40, 80, Math.PI/3],
+                    ctm1:   [40, 80, Math.PI/3]
+  },
+  justMoveRAuto : { ctm0:   [40, 80, Math.atan(2)],
+                    ctm1_6: [40, 80, Math.atan(2)],
+                    ctm1_3: [40, 80, Math.atan(2)],
+                    ctm2_3: [40, 80, Math.atan(2)],
+                    ctm1:   [40, 80, Math.atan(2)]
+  },
+  justMoveRAutoReverse : { ctm0:   [40, 80, Math.PI + Math.atan(2)],
+                           ctm1_6: [40, 80, Math.PI + Math.atan(2)],
+                           ctm1_3: [40, 80, Math.PI + Math.atan(2)],
+                           ctm2_3: [40, 80, Math.PI + Math.atan(2)],
+                           ctm1:   [40, 80, Math.PI + Math.atan(2)]
+  },
+  nullMoveBasic : { ctm0:   [0, 0, 0],
+                    ctm1_6: [0, 0, 0],
+                    ctm1_3: [0, 0, 0],
+                    ctm2_3: [0, 0, 0],
+                    ctm1:   [0, 0, 0]
+  },
+  nullMoveRAutoReverse : { ctm0:   [0, 0, Math.PI],
+                           ctm1_6: [0, 0, Math.PI],
+                           ctm1_3: [0, 0, Math.PI],
+                           ctm2_3: [0, 0, Math.PI],
+                           ctm1:   [0, 0, Math.PI]
+  },
+};
+
+var gMotionBundles =
+[
+  // Bundle to test basic functionality (using default calcMode='paced')
+  new TestcaseBundle(gMotionAttr, [
+    // Basic paced-mode (default) test, with values/mpath/path
+    new AnimMotionTestcase({ "values": "100, 200; 120, 220; 130, 210" },
+                           _reusedCTMLists.pacedBasic),
+    new AnimMotionTestcase({ "path":  "M100 200 L120 220 L130 210" },
+                           _reusedCTMLists.pacedBasic),
+    new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210" },
+                           _reusedCTMLists.pacedBasic),
+
+    // ..and now with rotate=constant value in degrees
+    new AnimMotionTestcase({ "values": "100,200; 120,220; 130, 210",
+                             "rotate": "60" },
+                           _reusedCTMLists.pacedR60),
+    new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210",
+                             "rotate": "60" },
+                           _reusedCTMLists.pacedR60),
+    new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210",
+                             "rotate": "60" },
+                           _reusedCTMLists.pacedR60),
+
+    // ..and now with rotate=constant value in radians
+    new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210",
+                             "rotate": "1.0471975512rad" }, // pi/3
+                           _reusedCTMLists.pacedR60),
+
+    // ..and now with rotate=auto
+    new AnimMotionTestcase({ "values": "100,200; 120,220; 130, 210",
+                             "rotate": "auto" },
+                           _reusedCTMLists.pacedRAuto),
+    new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210",
+                             "rotate": "auto" },
+                           _reusedCTMLists.pacedRAuto),
+    new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210",
+                             "rotate": "auto" },
+                           _reusedCTMLists.pacedRAuto),
+
+    // ..and now with rotate=auto-reverse
+    new AnimMotionTestcase({ "values": "100,200; 120,220; 130, 210",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.pacedRAutoReverse),
+    new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.pacedRAutoReverse),
+    new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.pacedRAutoReverse),
+
+  ]),
+
+  // Bundle to test calcMode='discrete'
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "100, 200; 120, 220; 130, 210",
+                             "calcMode": "discrete" },
+                           _reusedCTMLists.discreteBasic),
+    new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210",
+                             "calcMode": "discrete" },
+                           _reusedCTMLists.discreteBasic),
+    new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210",
+                             "calcMode": "discrete" },
+                           _reusedCTMLists.discreteBasic),
+    // ..and now with rotate=auto
+    new AnimMotionTestcase({ "values": "100, 200; 120, 220; 130, 210",
+                             "calcMode": "discrete",
+                             "rotate": "auto" },
+                           _reusedCTMLists.discreteRAuto),
+    new AnimMotionTestcase({ "path": "M100 200 L120 220 L130 210",
+                             "calcMode": "discrete",
+                             "rotate": "auto" },
+                           _reusedCTMLists.discreteRAuto),
+    new AnimMotionTestcase({ "mpath": "M100 200 L120 220 L130 210",
+                             "calcMode": "discrete",
+                             "rotate": "auto" },
+                           _reusedCTMLists.discreteRAuto),
+  ]),
+
+  // Bundle to test relative units ('em')
+  new TestcaseBundle(gMotionAttr, [
+    // First with unitless values from->by...
+    new AnimMotionTestcase({ "from": "10, 10",
+                             "by":   "30, 60" },
+                           { ctm0:   [10, 10, 0],
+                             ctm1_6: [15, 20, 0],
+                             ctm1_3: [20, 30, 0],
+                             ctm2_3: [30, 50, 0],
+                             ctm1:   [40, 70, 0]
+                           }),
+    // ... then add 'em' units (with 1em=10px) on half the values
+    new AnimMotionTestcase({ "from": "1em, 10",
+                             "by":   "30, 6em" },
+                           { ctm0:   [10, 10, 0],
+                             ctm1_6: [15, 20, 0],
+                             ctm1_3: [20, 30, 0],
+                             ctm2_3: [30, 50, 0],
+                             ctm1:   [40, 70, 0]
+                           }),
+  ]),
+
+  // Bundle to test a path with just a "move" command and nothing else
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "40, 80" },
+                           _reusedCTMLists.justMoveBasic),
+    new AnimMotionTestcase({ "path":  "M40 80" },
+                           _reusedCTMLists.justMoveBasic),
+    new AnimMotionTestcase({ "mpath": "m40 80" },
+                           _reusedCTMLists.justMoveBasic),
+  ]),
+  // ... and now with a fixed rotate-angle
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "40, 80",
+                             "rotate": "60" },
+                           _reusedCTMLists.justMoveR60),
+    new AnimMotionTestcase({ "path":  "M40 80",
+                             "rotate": "60" },
+                           _reusedCTMLists.justMoveR60),
+    new AnimMotionTestcase({ "mpath": "m40 80",
+                             "rotate": "60" },
+                           _reusedCTMLists.justMoveR60),
+  ]),
+  // ... and now with 'auto' (should use the move itself as
+  // our tangent angle, I think)
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "40, 80",
+                             "rotate": "auto" },
+                           _reusedCTMLists.justMoveRAuto),
+    new AnimMotionTestcase({ "path":  "M40 80",
+                             "rotate": "auto" },
+                           _reusedCTMLists.justMoveRAuto),
+    new AnimMotionTestcase({ "mpath": "m40 80",
+                             "rotate": "auto" },
+                           _reusedCTMLists.justMoveRAuto),
+  ]),
+  // ... and now with 'auto-reverse'
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "40, 80",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.justMoveRAutoReverse),
+    new AnimMotionTestcase({ "path":  "M40 80",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.justMoveRAutoReverse),
+    new AnimMotionTestcase({ "mpath": "m40 80",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.justMoveRAutoReverse),
+  ]),
+  // ... and now with a null move to make sure 'auto'/'auto-reverse' don't
+  // blow up
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "0, 0",
+                             "rotate": "auto" },
+                           _reusedCTMLists.nullMoveBasic),
+  ]),
+  new TestcaseBundle(gMotionAttr, [
+    new AnimMotionTestcase({ "values": "0, 0",
+                             "rotate": "auto-reverse" },
+                           _reusedCTMLists.nullMoveRAutoReverse),
+  ]),
+];
+
+// XXXdholbert Add more tests:
+//  - keyPoints/keyTimes
+//  - paths with curves
+//  - Control path with from/by/to
new file mode 100644
--- /dev/null
+++ b/content/smil/test/smilAnimateMotionValueLists.js
@@ -0,0 +1,124 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et: */
+/* ***** 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 Mozilla SMIL Test Code.
+ *
+ * The Initial Developer of the Original Code is
+ * the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Daniel Holbert <dholbert@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/* Lists of valid & invalid values for the various <animateMotion> attributes */
+const gValidValues = [
+  "10 10",
+  "   10   10em  ",
+  "1 2  ; 3,4",
+  "1,2;3,4",
+  "0 0",
+  "0,0",
+];
+
+const gInvalidValues = [
+  "10 10;",  // We treat semicolon-terminated value-lists as failure cases
+  "1 2 3",
+  "1 2 3 4",
+  "1,2;3,4 ,",
+  ",", ";", "a", "", " ",
+];
+
+const gInvalidValuesTodo = [
+  "1,2;3,4,",
+  "1,,2",
+  ",1,2",
+];
+
+const gValidRotate = [
+  "10",
+  "20.1",
+  "30.5deg",
+  "0.5rad",
+  "auto",
+  "auto-reverse"
+];
+
+const gInvalidRotate = [
+  " 10 ",
+  "  10deg",
+  "10 deg",
+  "10deg ",
+  "10 rad    ",
+  "aaa",
+  " 10.1 ",
+];
+
+const gValidToBy = [
+ "0 0",
+ "1em,2",
+ "50.3em 0.2in"
+];
+
+const gInvalidToBy = [
+ "0 0 0",
+ "0 0,0",
+ "0,0,0",
+ "1emm 2",
+ "1 2;",
+ " 1,2 ,",
+ "abc",
+ ",",
+ ""
+];
+
+const gInvalidToByTodo = [
+ "1,,2",
+ "1,2,",
+ " 1,2",
+ "1 2 "
+];
+
+const gValidPath = [
+ "m0 0     L30 30",
+ "M20,20L10    10",
+ "M20,20 L30, 30h20",
+ "m50 50", "M50 50",
+ "m0 0", "M0, 0"
+];
+
+const gInvalidPath = [
+ "m0 0 L30,,30",
+ "M20 20em",
+ "M20in 20",
+ "h30",
+ "L50 50",
+ "abc",
+ "M10 10 L50 50 abc",
+];
--- a/content/smil/test/smilTestUtils.js
+++ b/content/smil/test/smilTestUtils.js
@@ -34,16 +34,19 @@
  * 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 ***** */
 
 // Note: Class syntax roughly based on:
 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Inheritance
 const SVG_NS = "http://www.w3.org/2000/svg";
+const XLINK_NS = "http://www.w3.org/1999/xlink";
+
+const MPATH_TARGET_ID = "smilTestUtilsTestingPath";
 
 function extend(child, supertype)
 {
    child.prototype.__proto__ = supertype.prototype;
 }
 
 // General Utility Methods
 var SMILUtil =
@@ -80,21 +83,26 @@ var SMILUtil =
   // Simple wrapper for getComputedStyle
   getComputedStyleSimple: function(elem, prop)
   {
     return window.getComputedStyle(elem, null).getPropertyValue(prop);
   },
 
   getAttributeValue: function(elem, attr)
   {
+    if (attr.attrName == SMILUtil.getMotionFakeAttributeName()) {
+      // Fake motion "attribute" -- "computed value" is the element's CTM
+      return elem.getCTM();
+    }
     if (attr.attrType == "CSS") {
       return SMILUtil.getComputedStyleWrapper(elem, attr.attrName);
-    } else if (attr.attrType == "XML") {
+    }
+    if (attr.attrType == "XML") {
       // XXXdholbert This is appropriate for mapped attributes, but not
-      // for others.
+      // for other attributes.
       return SMILUtil.getComputedStyleWrapper(elem, attr.attrName);
     }
   },
 
   // Smart wrapper for getComputedStyle, which will generate a "fake" computed
   // style for recognized shorthand properties (font, overflow, marker)
   getComputedStyleWrapper : function(elem, propName)
   {
@@ -163,17 +171,92 @@ var SMILUtil =
 
     // Hide node's descendents
     var child = node.firstChild;
     while (child) {
       SMILUtil.hideSubtree(child, true, useXMLAttribute);
       child = child.nextSibling;
     }
   },
-}
+
+  getMotionFakeAttributeName : function() {
+    return "_motion";
+  },
+};
+
+
+var CTMUtil =
+{
+  CTM_COMPONENTS_ALL    : ["a", "b", "c", "d", "e", "f"],
+  CTM_COMPONENTS_ROTATE : ["a", "b", "c", "d" ],
+
+  // Function to generate a CTM Matrix from a "summary"
+  // (a 3-tuple containing [tX, tY, theta])
+  generateCTM : function(aCtmSummary)
+  {
+    if (!aCtmSummary || aCtmSummary.length != 3) {
+      ok(false, "Unexpected CTM summary tuple length: " + aCtmSummary.length);
+    }
+    var tX = aCtmSummary[0];
+    var tY = aCtmSummary[1];
+    var theta = aCtmSummary[2];
+    var cosTheta = Math.cos(theta);
+    var sinTheta = Math.sin(theta);
+    var newCtm = { a : cosTheta,  c: -sinTheta,  e: tX,
+                   b : sinTheta,  d:  cosTheta,  f: tY  };
+    return newCtm;
+  },
+
+  /// Helper for isCtmEqual
+  isWithinDelta : function(aTestVal, aExpectedVal, aErrMsg, aIsTodo) {
+    var testFunc = aIsTodo ? todo : ok;
+    const delta = 0.000001; // allowing margin of error = 10^-6
+    ok(aTestVal >= aExpectedVal - delta &&
+       aTestVal <= aExpectedVal + delta,
+       aErrMsg + " | got: " + aTestVal + ", expected: " + aExpectedVal);
+  },
+
+  assertCTMEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck,
+                            aErrMsg, aIsTodo) {
+    var foundCTMDifference = false;
+    for (var j in aComponentsToCheck) {
+      var curComponent = aComponentsToCheck[j];
+      if (!aIsTodo) {
+        CTMUtil.isWithinDelta(aLeftCtm[curComponent], aRightCtm[curComponent],
+                              aErrMsg + " | component: " + curComponent, false);
+      } else if (aLeftCtm[curComponent] != aRightCtm[curComponent]) {
+        foundCTMDifference = true;
+      }
+    }
+
+    if (aIsTodo) {
+      todo(!foundCTMDifference, aErrMsg + " | (currently marked todo)");
+    }
+  },
+
+  assertCTMNotEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck,
+                               aErrMsg, aIsTodo) {
+    // CTM should not match initial one
+    var foundCTMDifference = false;
+    for (var j in aComponentsToCheck) {
+      var curComponent = aComponentsToCheck[j];
+      if (aLeftCtm[curComponent] != aRightCtm[curComponent]) {
+        foundCTMDifference = true;
+        break; // We found a difference, as expected. Success!
+      }
+    }
+
+    if (aIsTodo) {
+      todo(foundCTMDifference, aErrMsg + " | (currently marked todo)");
+    } else {
+      ok(foundCTMDifference, aErrMsg);
+    }
+  },
+};
+
 
 // Wrapper for timing information
 function SMILTimingData(aBegin, aDur)
 {
   this._begin = aBegin;
   this._dur = aDur;
 }
 SMILTimingData.prototype =
@@ -680,15 +763,163 @@ AnimTestcasePaced.prototype =
                    msgPrefix + "(at animation end) - " + aReasonStatic]);
     seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal,
                    msgPrefix + "(after animation end) - " + aReasonStatic]);
     return seekList;
   },
 };
 extend(AnimTestcasePaced, AnimTestcase);
 
+/*
+ * A testcase for an <animateMotion> animation.
+ *
+ * @param aAttrValueHash   A hash-map mapping attribute names to values.
+ *                         Should include at least 'path', 'values', 'to'
+ *                         or 'by' to describe the motion path.
+ * @param aCtmMap  A hash-map that contains summaries of the expected resulting
+ *                 CTM at various points during the animation. The CTM is
+ *                 summarized as a tuple of three numbers: [tX, tY, theta]
+                   (indicating a translate(tX,tY) followed by a rotate(theta))
+ *      - ctm0:   The CTM summary at the start of the animation
+ *      - ctm1_6: The CTM summary at exactly 1/6 through animation
+ *      - ctm1_3: The CTM summary at exactly 1/3 through animation
+ *      - ctm2_3: The CTM summary at exactly 2/3 through animation
+ *      - ctm1:   The CTM summary at the animation endpoint
+ *
+ *  NOTE: For paced-mode animation (the default for animateMotion), the math
+ *  works out easiest if:
+ *    (a) our motion path has 3 points: vA, vB, vC
+ *    (b) dist(vB, vC) = 2 * dist(vA, vB)
+ *  (See discussion in header comment for AnimTestcasePaced.)
+ *
+ * @param aSkipReason  If this test-case is known to currently fail, this
+ *                     parameter should be a string explaining why.
+ *                     Otherwise, this value should be null (or omitted).
+ */
+function AnimMotionTestcase(aAttrValueHash, aCtmMap, aSkipReason)
+{
+  this.attrValueHash = aAttrValueHash;
+  this.ctmMap        = aCtmMap;
+  this.skipReason    = aSkipReason;
+  if (this.ctmMap &&
+      (!this.ctmMap.ctm0 ||
+       !this.ctmMap.ctm1_6 ||
+       !this.ctmMap.ctm1_3 ||
+       !this.ctmMap.ctm2_3 ||
+       !this.ctmMap.ctm1)) {
+    ok(false, "This AnimMotionTestcase has an incomplete CTM map");
+  }
+}
+AnimMotionTestcase.prototype =
+{
+  // Member variables
+  _animElementTagName : "animateMotion",
+  
+  // Implementations of inherited methods that we need to override:
+  // --------------------------------------------------------------
+  setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
+  {
+    var animElement = document.createElementNS(SVG_NS,
+                                               this._animElementTagName);
+    animElement.setAttribute("begin", aTimeData.getBeginTime());
+    animElement.setAttribute("dur", aTimeData.getDur());
+    if (aIsFreeze) {
+      animElement.setAttribute("fill", "freeze");
+    }
+    for (var attrName in this.attrValueHash) {
+      if (attrName == "mpath") {
+        this.createPath(this.attrValueHash[attrName]);
+        this.createMpath(animElement);
+      } else {
+        animElement.setAttribute(attrName, this.attrValueHash[attrName]);
+      }
+    }
+    return animElement;
+  },
+
+  createPath : function(aPathDescription)
+  {
+    var path = document.createElementNS(SVG_NS, "path");
+    path.setAttribute("d", aPathDescription);
+    path.setAttribute("id", MPATH_TARGET_ID);
+    return SMILUtil.getSVGRoot().appendChild(path);
+  },
+
+  createMpath : function(aAnimElement)
+  {
+    var mpath = document.createElementNS(SVG_NS, "mpath");
+    mpath.setAttributeNS(XLINK_NS, "href", "#" + MPATH_TARGET_ID);
+    return aAnimElement.appendChild(mpath);
+  },
+
+  // Override inherited seekAndTest method since...
+  // (a) it expects a computedValMap and we have a computed-CTM map instead
+  // and (b) it expects we might have no effect (for non-animatable attrs)
+  buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
+  {
+    var seekList = new Array();
+    var msgPrefix = "CTM mismatch ";
+    seekList.push([aTimeData.getBeginTime(),
+                   CTMUtil.generateCTM(this.ctmMap.ctm0),
+                   msgPrefix + "at start of animation"]);
+    seekList.push([aTimeData.getFractionalTime(1/6),
+                   CTMUtil.generateCTM(this.ctmMap.ctm1_6),
+                   msgPrefix + "1/6 of the way through animation."]);
+    seekList.push([aTimeData.getFractionalTime(1/3),
+                   CTMUtil.generateCTM(this.ctmMap.ctm1_3),
+                   msgPrefix + "1/3 of the way through animation."]);
+    seekList.push([aTimeData.getFractionalTime(2/3),
+                   CTMUtil.generateCTM(this.ctmMap.ctm2_3),
+                   msgPrefix + "2/3 of the way through animation."]);
+
+    var finalMsg;
+    var expectedEndVal;
+    if (aIsFreeze) {
+      expectedEndVal = CTMUtil.generateCTM(this.ctmMap.ctm1);
+      finalMsg = aAnimAttr.attrName +
+        ": [freeze-mode] checking that final value is set ";
+    } else {
+      expectedEndVal = aBaseVal;
+      finalMsg = aAnimAttr.attrName +
+        ": [remove-mode] checking that animation is cleared ";
+    }
+    seekList.push([aTimeData.getEndTime(),
+                   expectedEndVal, finalMsg + "at end of animation"]);
+    seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
+                   expectedEndVal, finalMsg + "after end of animation"]);
+    return seekList;
+  },
+
+  // Override inherited seekAndTest method
+  // (Have to use assertCTMEqual() instead of is() for comparison, to check each
+  // component of the CTM and to allow for a small margin of error.)
+  seekAndTest : function(aSeekList, aTargetElem, aTargetAttr)
+  {
+    var svg = document.getElementById("svg");
+    for (var i in aSeekList) {
+      var entry = aSeekList[i];
+      SMILUtil.getSVGRoot().setCurrentTime(entry[0]);
+      CTMUtil.assertCTMEqual(aTargetElem.getCTM(), entry[1],
+                             CTMUtil.CTM_COMPONENTS_ALL, entry[2], false);
+    }
+  },
+
+  // Override "runTest" method so we can remove any <path> element that we
+  // created at the end of each test.
+  runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze)
+  {
+    AnimTestcase.prototype.runTest.apply(this,
+                             [aTargetElem, aTargetAttr, aTimeData, aIsFreeze]);
+    var pathElem = document.getElementById(MPATH_TARGET_ID);
+    if (pathElem) {
+      SMILUtil.getSVGRoot().removeChild(pathElem);
+    }
+  }
+};
+extend(AnimMotionTestcase, AnimTestcase);
+
 // MAIN METHOD
 function testBundleList(aBundleList, aTimingData)
 {
   for (var bundleIdx in aBundleList) {
     aBundleList[bundleIdx].go(aTimingData);
   }
 }
new file mode 100644
--- /dev/null
+++ b/content/smil/test/test_smilAnimateMotion.xhtml
@@ -0,0 +1,58 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436418
+-->
+<head>
+  <title>Test for animateMotion behavior</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="smilTestUtils.js"></script>
+  <script type="text/javascript" src="db_smilAnimateMotion.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436418">Mozilla Bug 436418</a>
+<p id="display"></p>
+<div id="content" style="visibility: hidden">
+
+<!-- NOTE: Setting font-size so we can test 'em' units -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="200px" height="200px" style="font-size: 500px"
+     onload="this.pauseAnimations()">
+  <!-- XXXdholbert Right now, 'em' conversions are correct if we set font-size
+       on rect using the inline style attr. However, if we use 'font-size' attr,
+       then 'em' units end up using the inherited font-size instead. Bug? -->
+  <rect x="20" y="20" width="200" height="200" style="font-size: 10px"/>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function main()
+{
+  if (!SMILUtil.isSMILEnabled()) {
+    ok(false, "SMIL dosn't seem to be enabled");
+    SimpleTest.finish();
+    return;
+  }
+
+  // Start out with document paused
+  var svg = SMILUtil.getSVGRoot();
+  ok(svg.animationsPaused(), "should be paused by <svg> load handler");
+  is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
+
+  var timingData = new SMILTimingData(1.0, 6.0);
+  testBundleList(gMotionBundles, timingData);
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", main, false);
+]]>
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/smil/test/test_smilAnimateMotionInvalidValues.xhtml
@@ -0,0 +1,175 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436418
+-->
+<head>
+  <title>Test for animateMotion acceptance of invalid values</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="smilTestUtils.js" />
+  <script type="text/javascript" src="smilAnimateMotionValueLists.js" />
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436418">Mozilla Bug 436418</a>
+<p id="display"></p>
+<div id="content" style="visibility: hidden">
+<svg xmlns="http://www.w3.org/2000/svg" id="svg"
+     width="200px" height="200px"
+     onload="this.pauseAnimations()">
+  <rect id="rect" x="20" y="20" width="200" height="200"/>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+// Constant strings (& string-arrays)
+const SVGNS = "http://www.w3.org/2000/svg";
+const XLINKNS = "http://www.w3.org/1999/xlink";
+
+// Constant objects
+const gSvg = document.getElementById("svg");
+const gRect = document.getElementById("rect");
+const gUnAnimatedCTM = gRect.getCTM();
+
+SimpleTest.waitForExplicitFinish();
+
+function createAnim()
+{
+  var anim = document.createElementNS(SVGNS, "animateMotion");
+  anim.setAttribute("dur", "2s");
+  return gRect.appendChild(anim);
+}
+
+function removeElem(aElem)
+{
+  aElem.parentNode.removeChild(aElem);
+}
+
+function testAttr(aAttrName, aAttrValueArray, aIsValid, aIsTodo)
+{
+  var componentsToCheck;
+
+  for (var i in aAttrValueArray) {
+    var curVal = aAttrValueArray[i];
+    var anim = createAnim();
+    anim.setAttribute(aAttrName, curVal);
+    if (aAttrName == "rotate") {
+      // Apply a diagonal translation (so rotate='auto' will have an effect)
+      // and just test the rotation matrix components
+      anim.setAttribute("values", "0 0; 50 50");
+      componentsToCheck = CTMUtil.CTM_COMPONENTS_ROTATE;
+    } else {
+      // Apply a supplementary rotation to make sure that we don't apply it if
+      // our value is rejected.
+      anim.setAttribute("rotate", Math.PI/4);
+      componentsToCheck = CTMUtil.CTM_COMPONENTS_ALL;
+    }
+
+    var curCTM = gRect.getCTM();
+    if (aIsValid) {
+      var errMsg = "CTM should have changed when applying animateMotion " +
+        "with '" + aAttrName + "' set to valid value '" + curVal + "'";
+      CTMUtil.assertCTMNotEqual(curCTM, gUnAnimatedCTM, componentsToCheck,
+                                errMsg, aIsTodo);
+    } else {
+      var errMsg = "CTM should not have changed when applying animateMotion " +
+        "with '" + aAttrName + "' set to invalid value '" + curVal + "'";
+      CTMUtil.assertCTMEqual(curCTM, gUnAnimatedCTM, componentsToCheck,
+                             errMsg, aIsTodo);
+    }
+    removeElem(anim);
+  }
+}
+
+function createPath(aPathDescription)
+{
+  var path = document.createElementNS(SVGNS, "path");
+  path.setAttribute("d", aPathDescription);
+  path.setAttribute("id", "thePath");
+  return gSvg.appendChild(path);
+}
+
+function createMpath(aAnimElement)
+{
+  var mpath = document.createElementNS(SVGNS, "mpath");
+  mpath.setAttributeNS(XLINKNS, "href", "#thePath");
+  return aAnimElement.appendChild(mpath);
+}
+
+function testMpathElem(aPathValueArray, aIsValid, aIsTodo)
+{
+  for (var i in aPathValueArray) {
+    var curVal = aPathValueArray[i];
+    var anim = createAnim();
+    var mpath = createMpath(anim);
+    var path = createPath(curVal);
+
+    // Apply a supplementary rotation to make sure that we don't apply it if
+    // our value is rejected.
+    anim.setAttribute("rotate", Math.PI/4);
+    componentsToCheck = CTMUtil.CTM_COMPONENTS_ALL;
+
+    if (aIsValid) {
+      var errMsg = "CTM should have changed when applying animateMotion " +
+        "with mpath linking to a path with valid value '" + curVal + "'";
+
+      CTMUtil.assertCTMNotEqual(gRect.getCTM(), gUnAnimatedCTM,
+                                componentsToCheck, errMsg, aIsTodo);
+    } else {
+      var errMsg = "CTM should not have changed when applying animateMotion " +
+        "with mpath linking to a path with invalid value '" + curVal + "'";
+      CTMUtil.assertCTMEqual(gRect.getCTM(), gUnAnimatedCTM,
+                             componentsToCheck, errMsg, aIsTodo);
+    }
+    removeElem(anim);
+    removeElem(path);
+    removeElem(mpath);
+ } 
+}
+
+// Main Function
+function main()
+{
+  if (!SMILUtil.isSMILEnabled()) {
+    ok(false, "SMIL dosn't seem to be enabled");
+    SimpleTest.finish();
+    return;
+  }
+
+  // Start out with document paused
+  var svg = SMILUtil.getSVGRoot();
+  ok(svg.animationsPaused(), "should be paused by <svg> load handler");
+  is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
+
+  testAttr("values", gValidValues, true, false);
+  testAttr("values", gInvalidValues, false, false);
+  testAttr("values", gInvalidValuesTodo, false, true);
+
+  testAttr("rotate", gValidRotate, true, false);
+  testAttr("rotate", gInvalidRotate, false, false);
+
+  testAttr("to", gValidToBy, true, false);
+  testAttr("to", gInvalidToBy, false, false);
+  testAttr("to", gInvalidToByTodo, false, true);
+
+  testAttr("by", gValidToBy, true, false);
+  testAttr("by", gInvalidToBy, false, false);
+  testAttr("by", gInvalidToByTodo, false, true);
+
+  testAttr("path", gValidPath, true, false);
+  testAttr("path", gInvalidPath, false, false);
+
+  testMpathElem(gValidPath, true, false);
+  testMpathElem(gInvalidPath, false, false);
+
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", main, false);
+]]>
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/smil/test/test_smilAnimateMotionOverrideRules.xhtml
@@ -0,0 +1,222 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=436418
+-->
+<head>
+  <title>Test for overriding of path-defining attributes for animateMotion</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="smilTestUtils.js" />
+  <script type="text/javascript" src="smilAnimateMotionValueLists.js" />
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=436418">Mozilla Bug 436418</a>
+<p id="display"></p>
+<div id="content" style="visibility: hidden">
+<svg xmlns="http://www.w3.org/2000/svg" id="svg"
+     width="200px" height="200px"
+     onload="this.pauseAnimations()">
+  <!-- Paths for mpath to refer to -->
+  <path id="validPathElem"   d="M10 10 h-10"/>
+  <path id="invalidPathElem" d="abc"/>
+
+  <!-- The rect whose motion is animated -->
+  <rect id="rect" x="20" y="20" width="200" height="200"/>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+// Constant strings (& string-arrays)
+const SVGNS   = "http://www.w3.org/2000/svg";
+const XLINKNS = "http://www.w3.org/1999/xlink";
+
+// Constant objects
+const gSvg = document.getElementById("svg");
+const gRect = document.getElementById("rect");
+const gUnAnimatedCTM = gRect.getCTM();
+
+// Values for path-defining attributes, and their expected
+// CTMs halfway through the animation
+var gMpathValidTarget    = "#validPathElem";
+var gMpathCTM = CTMUtil.generateCTM([ 5, 10, 0 ]);
+
+var gMpathInvalidTargetA = "#invalidPathElem";
+var gMpathInvalidTargetB = "#nonExistentElem";
+
+var gInvalidAttrValue = "i-am-invalid"; // Invalid for all tested attributes
+
+var gPathValidValue = "M20 20 h10";
+var gPathCTM = CTMUtil.generateCTM([ 25, 20, 0 ]);
+
+var gValuesValidValue = "30 30; 40 30"
+var gValuesCTM = CTMUtil.generateCTM([ 35, 30, 0 ]);
+
+var gFromValidValue = "50 50";
+
+var gByValidValue =   "10 2";
+var gPureByCTM = CTMUtil.generateCTM([  5,  1, 0 ]);
+var gFromByCTM = CTMUtil.generateCTM([ 55, 51, 0 ]);
+
+var gToValidValue =   "80 60";
+var gPureToCTM = CTMUtil.generateCTM([ 40, 30, 0 ]);
+var gFromToCTM = CTMUtil.generateCTM([ 65, 55, 0 ]);
+
+
+SimpleTest.waitForExplicitFinish();
+
+function createAnim()
+{
+  var anim = document.createElementNS(SVGNS, "animateMotion");
+  return gRect.appendChild(anim);
+}
+
+function removeElem(aElem)
+{
+  aElem.parentNode.removeChild(aElem);
+}
+
+function createMpath(aAnimElement, aHrefVal)
+{
+  var mpath = document.createElementNS(SVGNS, "mpath");
+  mpath.setAttributeNS(XLINKNS, "href", aHrefVal);
+  return aAnimElement.appendChild(mpath);
+}
+
+function runTest() {
+  // Start out with valid values for all path-defining attributes
+  var attrSettings = {
+    "mpath"  : gMpathValidTarget,
+    "path"   : gPathValidValue,
+    "values" : gValuesValidValue,
+    "from"   : gFromValidValue,
+    "to"     : gToValidValue,
+    "by"     : gByValidValue,
+  };
+
+  // Test that <mpath> overrides everything below it
+  testAttrSettings(attrSettings, gMpathCTM,
+                   "<mpath> should win");
+  var mpathInvalidTargets = [gMpathInvalidTargetA, gMpathInvalidTargetB];
+  for (var i in mpathInvalidTargets) {
+    var curInvalidValue = mpathInvalidTargets[i];
+    attrSettings["mpath"] = curInvalidValue;
+    testAttrSettings(attrSettings, gUnAnimatedCTM,
+                     "invalid <mpath> should block animation");
+  }
+  delete attrSettings["mpath"];
+
+  // Test that 'path' overrides everything below it
+  testAttrSettings(attrSettings, gPathCTM,
+                   "'path' should win vs all but mpath");
+  attrSettings["path"] = gInvalidAttrValue;
+  testAttrSettings(attrSettings, gUnAnimatedCTM,
+                   "invalid 'path' should block animation vs all but mpath");
+  delete attrSettings["path"];
+
+  // Test that 'values' overrides everything below it
+  testAttrSettings(attrSettings, gValuesCTM,
+                   "'values' should win vs from/by/to");
+  attrSettings["values"] = gInvalidAttrValue;
+  testAttrSettings(attrSettings, gUnAnimatedCTM,
+                   "invalid 'values' should block animation vs from/by/to");
+  delete attrSettings["values"];
+
+  // Test that 'from' & 'to' overrides 'by'
+  testAttrSettings(attrSettings, gFromToCTM,
+                   "'from/to' should win vs 'by'");
+  attrSettings["to"] = gInvalidAttrValue;
+  testAttrSettings(attrSettings, gUnAnimatedCTM,
+                   "invalid 'to' should block animation vs 'by'");
+  delete attrSettings["to"];
+
+  // Test that 'from' & 'by' are effective
+  testAttrSettings(attrSettings, gFromByCTM,
+                   "'from/by' should be visible");
+  attrSettings["by"] = gInvalidAttrValue;
+  testAttrSettings(attrSettings, gUnAnimatedCTM,
+                   "invalid 'by' should block animation");
+  delete attrSettings["from"];
+
+  // REINSERT "to" & fix up "by" so we can test pure-"to" vs pure-"by"
+  attrSettings["to"] = gToValidValue;
+  attrSettings["by"] = gByValidValue;
+  testAttrSettings(attrSettings, gPureToCTM,
+                   "pure-'to' should be effective & beat pure-'by'");
+  attrSettings["to"] = gInvalidAttrValue;
+  testAttrSettings(attrSettings, gUnAnimatedCTM,
+                   "invalid pure-'to' should block animation vs pure-'by'");
+  delete attrSettings["to"];
+
+  // Test that pure-"by" is effective
+  testAttrSettings(attrSettings, gPureByCTM,
+                   "pure-by should be visible");
+  attrSettings["by"] = gInvalidAttrValue;
+  testAttrSettings(attrSettings, gUnAnimatedCTM,
+                   "invalid 'by' should block animation");
+  delete attrSettings["by"];
+
+  // Make sure that our hash is empty now.
+  for (var unexpectedKey in attrSettings) {
+    ok(false, "Unexpected mapping remains in attrSettings: " +
+       unexpectedKey + "-->" + unexpectedValue);
+  }
+}
+
+function testAttrSettings(aAttrValueHash, aExpectedCTM, aErrMsg)
+{
+  var isDebug = false; // XXdholbert
+  !isDebug || todo(false, "ENTERING testAttrSettings");
+  // Set up animateMotion element
+  var animElement = document.createElementNS(SVGNS, "animateMotion");
+  animElement.setAttribute("dur", "2s");
+  for (var attrName in aAttrValueHash) {
+    !isDebug || todo(false, "setting '" + attrName +"' to '" +
+                     aAttrValueHash[attrName] +"'");
+    if (attrName == "mpath") {
+      createMpath(animElement, aAttrValueHash[attrName]);
+    } else {
+      animElement.setAttribute(attrName, aAttrValueHash[attrName]);
+    }
+  }
+
+  gRect.appendChild(animElement);
+
+  // Seek to halfway through animation
+  SMILUtil.getSVGRoot().setCurrentTime(1); // Seek halfway through animation
+
+  // Check CTM against expected value
+  CTMUtil.assertCTMEqual(gRect.getCTM(), aExpectedCTM,
+                         CTMUtil.CTM_COMPONENTS_ALL, aErrMsg, false);
+
+  // CLEAN UP
+  SMILUtil.getSVGRoot().setCurrentTime(0);
+  removeElem(animElement);
+}
+
+// Main Function
+function main()
+{
+  if (!SMILUtil.isSMILEnabled()) {
+    ok(false, "SMIL dosn't seem to be enabled");
+    SimpleTest.finish();
+    return;
+  }
+
+  // Start out with document paused
+  var svg = SMILUtil.getSVGRoot();
+  ok(svg.animationsPaused(), "should be paused by <svg> load handler");
+  is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
+
+  runTest();
+  SimpleTest.finish();
+}
+
+window.addEventListener("load", main, false);
+]]>
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-by-1.svg
@@ -0,0 +1,41 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function doTest() {
+      setTimeAndSnapshot(1, true);
+    }
+    window.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+
+  <!-- Big green background to match lime.svg -->
+  <rect width="100%" height="100%" fill="lime"/>
+  <!-- Red "workspace" (should be covered up, if tests pass) -->
+  <rect x="100" y="100" width="100" height="100" fill="red"/>
+
+  <!-- FIRST ROW -->
+  <!-- Check that 'by' works at all -->
+  <rect fill="lime" x="0" y="0" width="50" height="50">
+    <animateMotion by="100, 100" begin="0" dur="1" fill="freeze"/>
+  </rect>
+
+  <!-- Check that 'by' is additive w/ 'by' -->
+  <rect fill="lime" x="50" y="50" width="50" height="50">
+    <animateMotion by="60, 75" begin="0" dur="1" fill="freeze"/>
+    <animateMotion by="40, -25" begin="0" dur="1" fill="freeze"/>
+  </rect>
+
+  <!-- SECOND ROW -->
+  <!-- Check that 'by' is additive w/ 'to' -->
+  <rect fill="lime" width="50" height="50">
+    <animateMotion to="50,100" begin="0" dur="1" fill="freeze"/>
+    <animateMotion by="50, 50" begin="0" dur="1" fill="freeze"/>
+  </rect>
+
+  <!-- Check that 'from-to' replaces 'by' -->
+  <rect fill="lime" width="50" height="50">
+    <animateMotion by="500, 500" begin="0" dur="1" fill="freeze"/>
+    <animateMotion from="300,300" to="150,150" begin="0" dur="1" fill="freeze"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-from-to-1.svg
@@ -0,0 +1,44 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function doTest() {
+      setTimeAndSnapshot(1, true);
+    }
+    window.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+
+  <!-- Big green background to match lime.svg -->
+  <rect width="100%" height="100%" fill="lime"/>
+  <!-- Red "workspace" (should be covered up, if tests pass) -->
+  <rect x="100" y="100" width="100" height="100" fill="red"/>
+
+  <!-- FIRST ROW -->
+  <!-- Check that 'from' gets applied at begin time -->
+  <rect fill="lime" x="0" y="0" width="50" height="50">
+    <animateMotion from="100, 100" to="500, 500" begin="1" dur="1"/>
+  </rect>
+
+  <!-- Check that 'to' gets hit at end time -->
+  <rect fill="lime" x="0" y="0" width="50" height="50">
+    <animateMotion from="200,200" to="150,100" begin="0" dur="1" fill="freeze"/>
+  </rect>
+
+  <!-- SECOND ROW -->
+  <!-- Check that animation effects are removed after end time
+       (note that fill="remove" is default; just specifying it for clarity -->
+  <rect fill="lime" x="100" y="150" width="50" height="50">
+    <animateMotion from="500,500" to="600,600" begin="0" dur="1" fill="remove"/>
+  </rect>
+  <rect fill="purple" x="-25" y="-25" width="25" height="25">
+    <!-- With the purple rect's x/y offsets, this animateMotion path moves us
+         around the 2nd row, 1st col -->
+    <animateMotion from="125,175" to="150,175" begin="0" dur="1" fill="remove"/>
+  </rect>
+
+  <!-- Check interpolation halfway through animation -->
+  <rect fill="lime" width="50" height="50">
+    <animateMotion from="200,100" to="100,200" begin="0.5" dur="1"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-mpath-targetChange-1.svg
@@ -0,0 +1,151 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait">
+  <style>
+    .background { fill: lime }
+    .workspace  { fill: red  }
+    .test       { fill: lime }
+  </style>
+  <defs>
+    <!-- 'Dummy' path -->
+    <path id="moveFarAway"      d="M300,300 h0"/>
+
+    <path id="moveToUpperLeft"  d="M100,100 h0"/>
+
+    <path id="pathWhoseDAttrChanges" d="M360,360 h0"/>
+
+    <!-- The first of these two elems w/ same ID should be used. -->
+    <path id="moveToMiddleLeft"  d="M100,150 h0"/>
+    <path id="moveToMiddleLeft"  d="M350,350 h0"/>
+
+    <!-- The first of these two elems w/ same ID initially wins, but then
+         it gets removed via script. -->
+    <path id="moveToMiddleCenter" d="M340,340 h0"/>
+    <path id="moveToMiddleCenter" d="M150,150 h0"/>
+
+    <!-- This elem doesn't do what its id would suggest, but we'll use JS to
+         add an earlier elem with the same ID that *does* do what it says. -->
+    <path id="moveToMiddleRight" d="M330,330 h0"/>
+
+    <path id="moveToLowerLeft"   d="M100,200 h0"/>
+    <path id="moveToLowerCenter" d="M150,200 h0"/>
+
+  </defs>
+
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    const SVGNS   = "http://www.w3.org/2000/svg";
+    const XLINKNS = "http://www.w3.org/1999/xlink";
+
+
+    function insertPathElem(aPathId, aPathSpec) {
+      var newPath = document.createElementNS(SVGNS, "path");
+      newPath.setAttribute("id", aPathId);
+      newPath.setAttribute("d",  aPathSpec);
+
+      // Insert new path into defs
+      var defsElem = document.getElementsByTagName("defs")[0];
+      defsElem.insertBefore(newPath, defsElem.firstChild);
+    }
+
+    function doTest() {
+      // Seek already, so we'll have sampled the initial 'stale' state
+      document.documentElement.setCurrentTime(1);
+
+      // Make tweaks
+      var mpathToModify = document.getElementById("modifyMyTarget");
+      mpathToModify.setAttributeNS(XLINKNS, "href", "#moveToUpperLeft");
+
+      var mpathWhoseHrefNeedsClearing = document.getElementById("unsetMyTarget");
+      mpathWhoseHrefNeedsClearing.removeAttributeNS(XLINKNS, "href");
+
+      var pathToTweak = document.getElementById("pathWhoseDAttrChanges");
+      pathToTweak.setAttribute("d", "M200 100 h0");
+
+      var mpathToDelete = document.getElementById("removeMe");
+      mpathToDelete.parentNode.removeChild(mpathToDelete);
+
+      var pathToDelete = document.getElementById("moveToMiddleCenter");
+      pathToDelete.parentNode.removeChild(pathToDelete);
+
+      insertPathElem("moveToMiddleRight", "M200,150 h0");
+      insertPathElem("moveToLowerRight", "M200,200 h0");
+
+      setTimeAndSnapshot(1, true);
+    }
+    window.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+
+  <!-- Big green background to match lime.svg -->
+  <rect class="background" width="100%" height="100%" />
+  <!-- Red "workspace" (should be covered up, if tests pass) -->
+  <rect class="workspace" x="100" y="100" width="150" height="150"/>
+
+  <!-- FIRST ROW: Test behavior... -->
+  <!-- ...when mpath's 'xlink:href' attr is modified. -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath id="modifyMyTarget" xlink:href="#moveFarAway"/>
+    </animateMotion>
+  </rect>
+
+  <!-- ...when mpath's 'xlink:href' is unset.  -->
+  <rect class="test" x="150" y="100" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath id="unsetMyTarget" xlink:href="#moveFarAway"/>
+    </animateMotion>
+  </rect>
+
+  <!-- ...when the target-path's "d" attr is modified. -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath xlink:href="#pathWhoseDAttrChanges"/>
+    </animateMotion>
+  </rect>
+
+  <!-- SECOND ROW: Test behavior... -->
+  <!--  ...when there are two paths with same ID (first should win) -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath xlink:href="#moveToMiddleLeft"/>
+    </animateMotion>
+  </rect>
+
+  <!--  ...when there are two paths with same ID, and the first is removed. -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath xlink:href="#moveToMiddleCenter"/>
+    </animateMotion>
+  </rect>
+
+  <!--  ...when an earlier path is added with our target ID. -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath xlink:href="#moveToMiddleRight"/>
+    </animateMotion>
+  </rect>
+
+  <!-- THIRD ROW: Test behavior... -->
+  <!--  ...when there are two mpath children (first should win). -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath xlink:href="#moveToLowerLeft"/>
+      <mpath xlink:href="#moveFarAway"/>
+    </animateMotion>
+  </rect>
+
+  <!--  ...when there are two mpath children, and the first is removed. -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath id="removeMe" xlink:href="#moveFarAway"/>
+      <mpath xlink:href="#moveToLowerCenter"/>
+    </animateMotion>
+  </rect>
+  <!--  ...when there's an mpath child that initially matches nothing, until
+        a node with the right ID is inserted into the DOM. -->
+  <rect class="test" x="0" y="0" width="50" height="50">
+    <animateMotion begin="1" dur="1" fill="freeze">
+      <mpath xlink:href="#moveToLowerRight"/>
+    </animateMotion>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-rotate-1.svg
@@ -0,0 +1,63 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait">
+  <style>
+    .background { fill: lime }
+    .workspace  { fill: red  }
+    .test       { fill: lime }
+    .filler     { fill: lime }
+  </style>
+
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function doTest() {
+      setTimeAndSnapshot(1, true);
+    }
+    window.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+
+  <!-- Big green background to match lime.svg -->
+  <rect class="background" width="100%" height="100%" />
+  <!-- Red "workspace" (should be covered up, if tests pass) -->
+  <rect class="workspace" x="100" y="100" width="100" height="100"/>
+
+  <!-- FIRST ROW -->
+  <!-- Check that 'rotate' gets applied at begin time -->
+  <g>
+    <animateMotion from="150, 100" to="500, 500" rotate="90"
+                   begin="1" dur="1"/>
+    <rect class="test" x="0" y="0" width="20" height="50"/>
+    <rect class="test" x="0" y="0" width="50" height="20"/>
+  </g>
+  <rect class="filler" x="100" y="120" width="30" height="30"/>
+
+  <!-- Check that 'rotate' gets applied at end time -->
+  <g>
+    <animateMotion from="600, 700" to="200, 150" rotate="180" begin="0"
+                   dur="1" fill="freeze"/>
+    <rect class="test" x="0" y="0" width="20" height="50"/>
+    <rect class="test" x="0" y="0" width="50" height="20"/>
+  </g>
+  <rect class="filler" x="150" y="100" width="30" height="30"/>
+
+  <!-- SECOND ROW -->
+  <!-- Check that rotate combines with existing rotate -->
+  <g transform="rotate(90)">
+    <animateMotion from="150,200" to="600,600" rotate="90"
+                   begin="1" dur="1"/>
+    <rect class="test" x="0" y="0" width="20" height="50"/>
+    <rect class="test" x="0" y="0" width="50" height="20"/>
+  </g>
+  <rect class="filler" x="100" y="150" width="30" height="30"/>
+
+  <!-- Check additivity of <animateMotion> "rotate" adds -->
+  <g>
+    <animateMotion from="100,100" to="100,200" rotate="90"
+                   begin="0.5" dur="1"/>
+    <animateMotion by="100,-200" rotate="90"
+                   begin="0.5" dur="1"/>
+    <rect class="test" x="0" y="0" width="20" height="50"/>
+    <rect class="test" x="0" y="0" width="50" height="20"/>
+  </g>
+  <rect class="filler" x="150" y="150" width="30" height="30"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-rotate-2.svg
@@ -0,0 +1,50 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait">
+  <!-- Tests for rotate="auto" and "auto-reverse" -->
+  <!-- The idea here is to create positioned red "holes" in the lime
+       background, and then hopefully use paused <animateMotion> elements to
+       position other elements exactly on top of the hole. -->
+  <style>
+    .background { fill:  lime   }
+    .hole       { color: red    }
+    .testBegin  { color: purple }
+    .testEnd    { color: orange }
+    .mask       { color: lime   }
+  </style>
+  <defs>
+    <!-- A 'pin' marker, just offscreen, pointing directly down at 0,0 -->
+    <!-- NOTE: The lime 2px-wide stroke is a hack to get around "seams" in
+         SVG when redrawing the same non-pixel-aligned shape on top of itself
+         in different colors. -->
+    <path id="marker" d="m0,0 l-10,-30  c-5,-20 25,-20 20,0 z"
+          style="fill: currentColor; stroke: lime; stroke-width: 2px"/>
+  </defs>
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function doTest() {
+      setTimeAndSnapshot(1, true);
+    }
+    window.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+
+  <!-- Big green background to match lime.svg -->
+  <rect class="background" width="100%" height="100%" />
+  <g transform="translate(50,50)">
+    <!-- Here's the hole -->
+    <use xlink:href="#marker" class="hole"
+         transform="translate(20,20) rotate(45)"/>
+
+    <!-- And here's a stack of elements animated with 'animateMotion' that
+         should end up there. -->
+    <use xlink:href="#marker" class="testBegin">
+      <animateMotion from="20,20" to="40,40" rotate="auto" begin="1s" dur="1s"/>
+    </use>
+    <use xlink:href="#marker" class="testEnd">
+      <animateMotion by="20,20" rotate="auto" dur="1s" fill="freeze"/>
+    </use>
+    <use xlink:href="#marker" class="mask">
+      <animateMotion by="40,40" rotate="auto" dur="2s"/>
+    </use>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-values-linear-1-ref.svg
@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <!-- First row -->
+  <g transform="translate(20,20)">
+    <g>
+      <rect width="15px" height="15px"/>
+    </g>
+    <g transform="translate(50,0)">
+      <rect x="0" y="10" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(100,0)">
+      <rect x="9" y="10" width="15px" height="15px"/>
+    </g>
+  </g>
+  <!-- Second row -->
+  <g transform="translate(20,70)">
+    <g>
+      <rect x="22.5" y="10" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(50,0)">
+      <rect x="29.7" y="10" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(100,0)">
+      <rect x="45" y="30"  width="15px" height="15px"/>
+    </g>
+  </g>
+  <!-- Third row -->
+  <g transform="translate(20,120)">
+    <g>
+      <rect x="48" y="34" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(50,0)">
+      <rect x="60" y="50" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(100,0)">
+      <rect x="60" y="50" width="15px" height="15px"/>
+    </g>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-values-linear-1.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="go()">
+  <!-- Tests for <animateMotion> with 'values' attribute -->
+  <script xlink:href="../smil-grid.js" type="text/javascript"/>
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script>
+    function go() {
+      var animAttrHash = { "values"    : "0,10; 30,10; 60,50",
+                           "calcMode"  : "linear" };
+      testAnimatedRectGrid("animateMotion", [animAttrHash]);
+    }
+  </script>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-values-paced-1-ref.svg
@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <!-- First row -->
+  <g transform="translate(20,20)">
+    <g>
+      <rect width="15px" height="15px"/>
+    </g>
+    <g transform="translate(50,0)">
+      <rect x="0" y="10" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(100,0)">
+      <rect x="12" y="10" width="15px" height="15px"/>
+    </g>
+  </g>
+  <!-- Second row -->
+  <g transform="translate(20,70)">
+    <g>
+      <rect x="30" y="10" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(50,0)">
+      <rect x="35.76" y="17.68" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(100,0)">
+      <rect x="48" y="34"  width="15px" height="15px"/>
+    </g>
+  </g>
+  <!-- Third row -->
+  <g transform="translate(20,120)">
+    <g>
+      <rect x="50.4" y="37.2" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(50,0)">
+      <rect x="60" y="50" width="15px" height="15px"/>
+    </g>
+    <g transform="translate(100,0)">
+      <rect x="60" y="50" width="15px" height="15px"/>
+    </g>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-values-paced-1a.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="go()">
+  <!-- Tests for <animateMotion> with 'values' attribute -->
+  <script xlink:href="../smil-grid.js" type="text/javascript"/>
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script>
+    function go() {
+      var animAttrHash = { "values"    : "0,10; 30,10; 60,50"};
+      testAnimatedRectGrid("animateMotion", [animAttrHash]);
+    }
+  </script>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/animateMotion-values-paced-1b.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="go()">
+  <!-- Tests for <animateMotion> with 'values' attribute -->
+  <script xlink:href="../smil-grid.js" type="text/javascript"/>
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
+  <script>
+    function go() {
+      var animAttrHash = { "values"    : "0,10; 30,10; 60,50",
+                           "calcMode"  : "paced" };
+      testAnimatedRectGrid("animateMotion", [animAttrHash]);
+    }
+  </script>
+</svg>
copy from layout/reftests/svg/smil/lime.svg
copy to layout/reftests/svg/smil/motion/lime.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/motion/reftest.list
@@ -0,0 +1,13 @@
+# Tests related to SVG Animation (using SMIL), focusing on the animateMotion
+# element.
+
+== animateMotion-by-1.svg      lime.svg
+== animateMotion-from-to-1.svg lime.svg
+== animateMotion-rotate-1.svg  lime.svg
+== animateMotion-rotate-2.svg  lime.svg
+== animateMotion-values-linear-1.svg animateMotion-values-linear-1-ref.svg
+== animateMotion-values-paced-1a.svg animateMotion-values-paced-1-ref.svg
+== animateMotion-values-paced-1b.svg animateMotion-values-paced-1-ref.svg
+
+# Tests involving <mpath> sub-element
+== animateMotion-mpath-targetChange-1.svg lime.svg