Bug 602759 part 2 - Add tests for transform lists; r=jwatt
authorBrian Birtles <birtles@gmail.com>
Sun, 25 Sep 2011 22:03:26 +0100
changeset 77546 1d681a73cfc762de9494381e612d61c7d415ad29
parent 77545 c7924111705c5bba6b950667a1ed4a4ef4d2d2b9
child 77547 38e6f7980e5edfab1c8c30202253bb9b7a6f42f8
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewersjwatt
bugs602759
milestone9.0a1
Bug 602759 part 2 - Add tests for transform lists; r=jwatt
content/svg/content/test/Makefile.in
content/svg/content/test/matrixUtils.js
content/svg/content/test/test_SVGMatrix.xhtml
content/svg/content/test/test_SVGTransformList.xhtml
content/svg/content/test/test_SVGTransformListAddition.xhtml
content/svg/content/test/test_SVGxxxList.xhtml
content/svg/content/test/test_SVGxxxListIndexing.xhtml
content/svg/content/test/test_transform.xhtml
layout/reftests/svg/smil/transform/additive-1.svg
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -44,16 +44,17 @@ relativesrcdir  = content/svg/content/te
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 # Disabled:
 #		bbox-helper.svg \
 #		test_length.xhtml \
 
 _TEST_FILES = \
+		matrixUtils.js \
 		test_a_href_01.xhtml \
 		test_a_href_02.xhtml \
 		a_href_destination.svg \
 		a_href_helper_01.svg \
 		a_href_helper_02_03.svg \
 		a_href_helper_04.svg \
 		test_animLengthObjectIdentity.xhtml \
 		test_animLengthReadonly.xhtml \
@@ -79,18 +80,21 @@ include $(topsrcdir)/config/rules.mk
 		test_scientific.html \
 		scientific-helper.svg \
 		test_SVGAnimatedImageSMILDisabled.html \
 		animated-svg-image-helper.html \
 		animated-svg-image-helper.svg \
 		test_stroke-linecap-hit-testing.xhtml \
 		test_SVGLengthList.xhtml \
 		test_SVGLengthList-2.xhtml \
+		test_SVGMatrix.xhtml \
 		test_SVGPathSegList.xhtml \
 		test_SVGStyleElement.xhtml \
+		test_SVGTransformList.xhtml \
+		test_SVGTransformListAddition.xhtml \
 		test_SVGxxxList.xhtml \
 		test_SVGxxxListIndexing.xhtml \
 		test_switch.xhtml \
 		switch-helper.svg \
 		test_text.html \
 		text-helper.svg \
 		test_transform.xhtml \
 		test_valueAsString.xhtml \
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/matrixUtils.js
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * ***** 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 SVG Test Code.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * 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 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 ***** */
+
+/*
+ * Utilities for testing SVG matrices
+ */
+
+function createMatrix(a, b, c, d, e, f)
+{
+  var svg = document.getElementsByTagName("svg")[0];
+  var m = svg.createSVGMatrix();
+  m.a = a;
+  m.b = b;
+  m.c = c;
+  m.d = d;
+  m.e = e;
+  m.f = f;
+  return m;
+}
+
+// Lightweight dummy Matrix class for representing arrays that get passed in
+function MatrixFromArray(a)
+{
+  this.a = a[0];
+  this.b = a[1];
+  this.c = a[2];
+  this.d = a[3];
+  this.e = a[4];
+  this.f = a[5];
+}
+
+function cmpMatrix(a, b, msg)
+{
+  if (a.constructor === Array)
+    a = new MatrixFromArray(a);
+  if (b.constructor === Array)
+    b = new MatrixFromArray(b);
+
+  ok(a.a == b.a &&
+     a.b == b.b &&
+     a.c == b.c &&
+     a.d == b.d &&
+     a.e == b.e &&
+     a.f == b.f,
+     msg + " - got " + formatMatrix(a)
+         + ", expected" + formatMatrix(b));
+}
+
+function roughCmpMatrix(a, b, msg)
+{
+  if (a.constructor === Array)
+    a = new MatrixFromArray(a);
+  if (b.constructor === Array)
+    b = new MatrixFromArray(b);
+
+  const tolerance = 1 / 65535;
+  ok(Math.abs(b.a - a.a) < tolerance &&
+     Math.abs(b.b - a.b) < tolerance &&
+     Math.abs(b.c - a.c) < tolerance &&
+     Math.abs(b.d - a.d) < tolerance &&
+     Math.abs(b.e - a.e) < tolerance &&
+     Math.abs(b.f - a.f) < tolerance,
+     msg + " - got " + formatMatrix(a)
+         + ", expected" + formatMatrix(b));
+}
+
+function formatMatrix(m)
+{
+  if (m.constructor != Array)
+    return "(" + [m.a, m.b, m.c, m.d, m.e, m.f].join(', ') + ")";
+
+  return "(" + m.join(', ') + ")";
+}
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_SVGMatrix.xhtml
@@ -0,0 +1,194 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Test SVGMatrix 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="matrixUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content">
+  <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="1" id="svg">
+    <g id="g" transform="translate(10, 20)"/>
+  </svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+function main()
+{
+  var tests =
+    [ testCreateMatrix,
+      testMultiply,
+      testInverse,
+      testTranslate,
+      testScale,
+      testScaleNonUniform,
+      testRotate,
+      testRotateFromVector,
+      testFlipX,
+      testFlipY,
+      testSkewX,
+      testSkewY
+    ];
+  for (var i = 0; i < tests.length; i++) {
+    tests[i]();
+  }
+  SimpleTest.finish();
+}
+
+function testCreateMatrix()
+{
+  svg = $('svg');
+  var m = svg.createSVGMatrix();
+  
+  // Should be initialised to identity
+  cmpMatrix(m, [1, 0, 0, 1, 0, 0],
+            "createMatrix should produce identity matrix");
+
+  // Should return a new object each time;
+  ok(m != svg.createSVGMatrix(),
+     "Got identical objects when creating new matrix");
+}
+
+// SVGMatrix multiply(in SVGMatrix secondMatrix);
+function testMultiply()
+{
+  // This is the example from SVG 1.1 section 7.5
+  var m1 = createMatrix(1, 0, 0, 1, 50, 90);
+  var m2 = createMatrix(0.707, -0.707, 0.707, 0.707, 0, 0);
+  var m3 = createMatrix(1, 0, 0, 1, 130, 160);
+  var result = m1.multiply(m2).multiply(m3);
+  roughCmpMatrix(result, [0.707, -0.707, 0.707, 0.707, 255.03, 111.21],
+    "Unexpected result after multiplying matrices");
+
+  // Check orig matrices are unchanged
+  cmpMatrix(m1, [1, 0, 0, 1, 50, 90], "Matrix changed after multiplication");
+  roughCmpMatrix(m2, [0.707, -0.707, 0.707, 0.707, 0, 0],
+                 "Matrix changed after multiplication");
+  cmpMatrix(m3, [1, 0, 0, 1, 130, 160], "Matrix changed after multiplication");
+}
+
+// SVGMatrix inverse() raises(SVGException);
+function testInverse()
+{
+  // Test inversion
+  var m = createMatrix(2, 0, 0, 4, 110, -50);
+  roughCmpMatrix(m.inverse(), [0.5, 0, 0, 0.25, -55, 12.5],
+    "Unexpected result after inverting matrix");
+
+  // Test non-invertable
+  m = createMatrix(0, 0, 1, 0, 0, 0);
+  try {
+    m.inverse();
+    ok(false, "Failed to throw exception when inverting singular matrix");
+  } catch (e) {
+    is(e.code, SVGException.SVG_MATRIX_NOT_INVERTABLE,
+      "Got unexpected exception " + e + ", expected SVG_MATRIX_NOT_INVERTABLE");
+  }
+}
+
+// SVGMatrix translate(in float x, in float y);
+function testTranslate()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  roughCmpMatrix(m.translate(100, -50), [2, 0, 0, 1, 320, 50],
+    "Unexpected result after translate");
+}
+
+// SVGMatrix scale(in float scaleFactor);
+function testScale()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  roughCmpMatrix(m.scale(0.5), [1, 0, 0, 0.5, 120, 100],
+    "Unexpected result after scale");
+}
+
+// SVGMatrix scaleNonUniform(in float scaleFactorX, in float scaleFactorY);
+function testScaleNonUniform()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  roughCmpMatrix(m.scaleNonUniform(0.5, -3), [1, 0, 0, -3, 120, 100],
+    "Unexpected result after scaleNonUniform");
+}
+
+// SVGMatrix rotate(in float angle);
+function testRotate()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  roughCmpMatrix(m.rotate(45),
+                 [2*Math.cos(Math.PI/4), Math.sin(Math.PI/4),
+                  2*-Math.sin(Math.PI/4), Math.cos(Math.PI/4),
+                  120, 100],
+                 "Unexpected result after rotate");
+}
+
+// SVGMatrix rotateFromVector(in float x, in float y) raises(SVGException);
+function testRotateFromVector()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  // Make a 150 degree angle
+  var result = m.rotateFromVector(-2, 1.1547);
+  roughCmpMatrix(result,
+                 [2*Math.cos(5*Math.PI/6), Math.sin(5*Math.PI/6),
+                  2*-Math.sin(5*Math.PI/6), Math.cos(5*Math.PI/6),
+                  120, 100],
+                 "Unexpected result after rotateFromVector");
+
+  // Test bad input (1)
+  try {
+    m.rotateFromVector(1, 0);
+    ok(false, "Failed to throw exception with zero coord for rotateFromVector");
+  } catch (e) {
+    is(e.code, SVGException.SVG_INVALID_VALUE_ERR,
+      "Got unexpected exception " + e + ", expected SVG_INVALID_VALUE_ERR");
+  }
+
+  // Test bad input (2)
+  try {
+    m.rotateFromVector(0, 1);
+    ok(false, "Failed to throw exception with zero coord for rotateFromVector");
+  } catch (e) { }
+}
+
+// SVGMatrix flipX();
+function testFlipX()
+{
+  var m = createMatrix(1, 2, 3, 4, 5, 6);
+  cmpMatrix(m.flipX(), [-1, -2, 3, 4, 5, 6], "Unexpected result after flipX");
+}
+
+// SVGMatrix flipY();
+function testFlipY()
+{
+  var m = createMatrix(1, 2, 3, 4, 5, 6);
+  cmpMatrix(m.flipY(), [1, 2, -3, -4, 5, 6], "Unexpected result after flipY");
+}
+
+// SVGMatrix skewX(in float angle);
+function testSkewX()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  roughCmpMatrix(m.skewX(30), [2, 0, 2*Math.tan(Math.PI/6), 1, 120, 100],
+                 "Unexpected result after skewX");
+}
+
+// SVGMatrix skewY(in float angle);
+function testSkewY()
+{
+  var m = createMatrix(2, 0, 0, 1, 120, 100);
+  roughCmpMatrix(m.skewY(30), [2, Math.tan(Math.PI/6), 0, 1, 120, 100],
+                 "Unexpected result after skewY");
+}
+
+window.addEventListener("load", main, false);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_SVGTransformList.xhtml
@@ -0,0 +1,368 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602759
+-->
+<head>
+  <title>Tests specific to SVGTransformList</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="matrixUtils.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=602759">
+  Mozilla Bug 602759</a>
+<p id="display"></p>
+<div id="content" style="display:none;">
+<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"
+     onload="this.pauseAnimations();">
+  <g id="g"/>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+This file runs a series of SVGTransformList specific tests. Generic SVGXxxList
+tests can be found in test_SVGxxxList.xhtml. Anything that can be generalized
+to other list types belongs there.
+*/
+
+function main()
+{
+  var g = $('g');
+  var tests =
+    [ testConsolidateMatrix,
+      testConsolidateMatrixOneElem,
+      testConsolidateMatrixZeroElem,
+      testCreateSVGTransformFromMatrix,
+      testReadOnly,
+      testOrphan,
+      testFailedSet
+    ];
+  for (var i = 0; i < tests.length; i++) {
+    tests[i](g);
+  }
+  SimpleTest.finish();
+}
+
+function testConsolidateMatrix(g)
+{
+  // This is the example from SVG 1.1 section 7.5
+  g.setAttribute("transform",
+                 "translate(50 90) rotate(-45) translate(130 160)");
+  var list = g.transform.baseVal;
+  is(list.numberOfItems, 3, "Unexpected length of unconsolidated list");
+
+  // Sanity check -- take ref to first item in list and validate it
+  var first_item = list.getItem(0);
+  is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
+     "Unexpected type of first item in list");
+  cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90],
+     "Unexpected value for first item in list");
+
+  // Consolidate
+  var consolidated = list.consolidate();
+  is(list.numberOfItems, 1, "Unexpected length of consolidated list");
+  ok(consolidated === list.getItem(0),
+     "Consolidate return value should be first item in list, not a copy");
+  is(consolidated.type, SVGTransform.SVG_TRANSFORM_MATRIX,
+     "Consolidated transform not of type matrix");
+  const angle = -Math.PI/4;
+  roughCmpMatrix(consolidated.matrix,
+    [Math.cos(angle), Math.sin(angle),
+     -Math.sin(angle), Math.cos(angle),
+     130 * Math.cos(angle) - 160 * Math.sin(angle) + 50,
+     160 * Math.cos(angle) + 130 * Math.sin(angle) + 90],
+    "Unexpected result after consolidating matrices");
+
+  // Check ref to first item in list
+  // a) should not have changed
+  is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
+     "Unexpected type of cached first item in list after consolidating");
+  cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90],
+     "Unexpected value for cached first item in list after consolidating");
+  // b) should still be useable
+  first_item.setScale(2, 3);
+  is(first_item.type, SVGTransform.SVG_TRANSFORM_SCALE,
+     "Cached first item in list not useable after consolidating");
+
+  // Check consolidated is live
+  // a) Changes to 'consolidated' affect list
+  consolidated.setSkewX(45);
+  is(list.getItem(0).type, SVGTransform.SVG_TRANSFORM_SKEWX,
+     "Changing return value from consolidate doesn't affect list");
+  // b) Changes to list affect 'consolidated'
+  list.getItem(0).setRotate(90, 0, 0);
+  is(consolidated.type, SVGTransform.SVG_TRANSFORM_ROTATE,
+     "Changing list doesn't affect return value from consolidate");
+}
+
+function testConsolidateMatrixOneElem(g)
+{
+  // Check that even if we only have one item in the list it becomes a matrix
+  // transform (as per the spec)
+  g.setAttribute("transform", "translate(50 90)");
+  var list = g.transform.baseVal;
+  is(list.numberOfItems, 1, "Unexpected length of unconsolidated list");
+  var first_item = list.getItem(0);
+  is(first_item.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
+     "Unexpected type of first item in list");
+  cmpMatrix(first_item.matrix, [1, 0, 0, 1, 50, 90],
+     "Unexpected value for first item in list");
+
+  // Consolidate
+  var consolidated = list.consolidate();
+  is(list.numberOfItems, 1, "Unexpected length of consolidated list");
+  ok(consolidated === list.getItem(0),
+     "Consolidate return value should be first item in list, not a copy");
+  todo_is(consolidated.type, SVGTransform.SVG_TRANSFORM_MATRIX,
+     "Consolidated transform not of type matrix");
+  cmpMatrix(consolidated.matrix, [1, 0, 0, 1, 50, 90],
+     "Unexpected consolidated matrix value");
+}
+  
+function testConsolidateMatrixZeroElem(g)
+{
+  // Check that zero items returns null
+  g.setAttribute("transform", "");
+  var list = g.transform.baseVal;
+  is(list.numberOfItems, 0, "Unexpected length of unconsolidated list");
+  var consolidated = list.consolidate();
+  ok(consolidated === null,
+     "consolidate() should return null for a zero-length transform list");
+}
+
+function testCreateSVGTransformFromMatrix(g)
+{
+  var m = createMatrix(1, 2, 3, 4, 5, 6);
+
+  // "Creates an SVGTransform object which is initialized to transform of type
+  // SVG_TRANSFORM_MATRIX and whose values are the given matrix. The values from
+  // the parameter matrix are copied, the matrix parameter is not adopted as
+  // SVGTransform::matrix."
+  var list = g.transform.baseVal;
+  list.clear();
+  var t = list.createSVGTransformFromMatrix(m);
+
+  // Check that list hasn't changed
+  is(list.numberOfItems, 0,
+     "Transform list changed after calling createSVGTransformFromMatrix");
+
+  // Check return value
+  is(t.type, SVGTransform.SVG_TRANSFORM_MATRIX,
+     "Returned transform not of type matrix");
+  cmpMatrix(t.matrix, [1, 2, 3, 4, 5, 6],
+     "Unexpected returned matrix value");
+
+  // Check values are copied
+  ok(t.matrix != m, "Matrix should be copied not adopted");
+  m.a = 2;
+  is(t.matrix.a, 1,
+     "Changing source matrix should not affect newly created transform");
+
+  // Try passing in bad values (null, "undefined" etc.)
+  var exception = null;
+  try {
+    t = list.createSVGTransformFromMatrix(null);
+  } catch(e) { exception = e; }
+  ok(exception,
+    "Failed to throw for null input to createSVGTransformFromMatrix");
+  exception = null;
+  try {
+    t = list.createSVGTransformFromMatrix("undefined");
+  } catch(e) { exception = e; }
+  ok(exception,
+    "Failed to throw for string input to createSVGTransformFromMatrix");
+  exception = null;
+  try {
+    t = list.createSVGTransformFromMatrix(SVGMatrix(t));
+  } catch(e) { exception = e; }
+  ok(exception,
+    "Failed to throw for bad input to createSVGTransformFromMatrix");
+  exception = null;
+}
+
+function testReadOnly(g)
+{
+  var SVG_NS = 'http://www.w3.org/2000/svg';
+
+  // Just some data to work with
+  g.setAttribute("transform", "translate(50 90)");
+
+  // baseVal / animVal are readonly attributes
+  //   Create another (empty) transform list
+  var otherg = document.createElementNS(SVG_NS, 'g');
+  g.parentNode.appendChild(otherg);
+  is(g.transform.baseVal.numberOfItems, 1,
+    "Unexpected number of items in transform list before attempting to set");
+  is(otherg.transform.baseVal.numberOfItems, 0,
+    "Unexpected number of items in source transform list before attempting to"
+    + " set");
+  //   Attempt to set the base value and check nothing changes
+  g.transform.baseVal = otherg.transform.baseVal;
+  is(g.transform.baseVal.numberOfItems, 1,
+    "baseVal should be read-only but its value has changed");
+  is(otherg.transform.baseVal.numberOfItems, 0,
+    "baseVal changed after attempting to use it set another value");
+ 
+  // Read-only SVGTransformList:
+  // Standard list methods are covered in test_SVGxxxList.xhtml so here we
+  // just add tests for SVGTransformList-specific methods
+  var roList = g.transform.animVal;
+  // consolidate()
+  var threw = false;
+  try {
+    roList.consolidate();
+  } catch (e) {
+    is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR,
+      "Got unexpected exception " + e +
+      ", expected NO_MODIFICATION_ALLOWED_ERR");
+    threw = true;
+  }
+  todo(threw,
+     "Failed to throw exception when calling consolidate on read-only list");
+
+  // Read-only SVGTransform:
+  // read-only attributes are tested in test_transform.xhtml. Here we are
+  // concerned with methods that throw because this *object* is read-only
+  // (since it belongs to a read-only transform list)
+  var roTransform = roList.getItem(0);
+  // setMatrix
+  threw = false;
+  try {
+    var m = createMatrix(1, 2, 3, 4, 5, 6);
+    roTransform.setMatrix(m);
+  } catch (e) {
+    is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR,
+      "Got unexpected exception " + e +
+      ", expected NO_MODIFICATION_ALLOWED_ERR");
+    threw = true;
+  }
+  todo(threw, "Failed to throw exception when calling setMatrix on read-only"
+            + " transform");
+  // setTranslate
+  threw = false;
+  try {
+    roTransform.setTranslate(2, 3);
+  } catch(e) {
+    threw = true;
+  }
+  todo(threw, "Failed to throw when calling setTranslate on read-only"
+             + " transform");
+  // setScale
+  threw = false;
+  try {
+    roTransform.setScale(2, 3);
+  } catch(e) {
+    threw = true;
+  }
+  todo(threw,
+          "Failed to throw when calling setScale on read-only transform");
+  // setRotate
+  threw = false;
+  try {
+    roTransform.setRotate(1, 2, 3);
+  } catch(e) {
+    threw = true;
+  }
+  todo(threw,
+          "Failed to throw when calling setRotate on read-only transform");
+  // setSkewX
+  threw = false;
+  try {
+    roTransform.setSkewX(2);
+  } catch(e) {
+    threw = true;
+  }
+  todo(threw,
+          "Failed to throw when calling setSkewX on read-only transform");
+  // setSkewY
+  threw = false;
+  try {
+    roTransform.setSkewY(2);
+  } catch(e) {
+    threw = true;
+  }
+  todo(threw,
+          "Failed to throw when calling setSkewY on read-only transform");
+
+  // Read-only SVGMatrix
+  var roMatrix = roTransform.matrix;
+  threw = false;
+  try {
+    roMatrix.a = 1;
+  } catch (e) {
+    is(e.code, DOMException.NO_MODIFICATION_ALLOWED_ERR,
+      "Got unexpected exception " + e +
+      ", expected NO_MODIFICATION_ALLOWED_ERR");
+    threw = true;
+  }
+  todo(threw, "Failed to throw exception when modifying read-only matrix");
+}
+
+function testOrphan(g)
+{
+  // Although this isn't defined, if a read-only object becomes orphaned
+  // (detached from it's parent), then presumably it should become editable
+  // again.
+
+  // As with the read-only test set a value to test with
+  g.setAttribute("transform", "translate(50 90)");
+
+  var roList = g.transform.animVal;
+  var roTransform = roList.getItem(0);
+  var roMatrix = roTransform.matrix;
+
+  // Orphan transform list contents by re-setting transform attribute
+  g.setAttribute("transform", "");
+
+  // Transform should now be editable
+  var exception = null;
+  try {
+    roTransform.setTranslate(5, 3);
+  } catch(e) {
+    exception = e;
+  }
+  ok(exception===null,
+     "Unexpected exception " + exception + " modifying orphaned transform");
+  uexception = null;
+
+  // So should matrix
+  exception = null;
+  try {
+    roMatrix.a = 1;
+  } catch(e) {
+    exception = e;
+  }
+  ok(exception===null,
+     "Unexpected exception " + exception + " modifying orphaned matrix");
+}
+
+function testFailedSet(g)
+{
+  // Check that a parse failure results in the attribute being empty
+
+  // Set initial value
+  g.setAttribute("transform", "translate(50 90)");
+  var list = g.transform.baseVal;
+  is(list.numberOfItems, 1, "Unexpected initial length of list");
+
+  // Attempt to set bad value
+  g.setAttribute("transform", "translate(40 50) scale(a)");
+  is(list.numberOfItems, 0,
+     "Transform list should be empty after setting bad value");
+  is(g.transform.animVal.numberOfItems, 0,
+     "Animated transform list should also be empty after setting bad value");
+}
+
+window.addEventListener("load", main, false);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/svg/content/test/test_SVGTransformListAddition.xhtml
@@ -0,0 +1,192 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=602759
+-->
+<head>
+  <title>Tests specific to SVGLengthList addition</title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.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=602759">
+  Mozilla Bug 602759</a>
+<p id="display"></p>
+<div id="content" style="display:none;">
+<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"
+     onload="this.pauseAnimations();">
+  <g id="g"/>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+/*
+This file runs a series of tests specific to addition of SVGTransformList in
+animation.
+*/
+
+function AdditionTestCase(desc, baseVal, animSpecs, expectedTransformList)
+{
+  this.desc = desc;
+  this.baseVal = baseVal;
+  this.animSpecs = animSpecs;
+  this.expectedTransformList = expectedTransformList;
+}
+
+function Transform(type, angle)
+{
+  this.type = type;
+  this.angle = angle;
+}
+
+function main(g)
+{
+  var cases = [
+    new AdditionTestCase("Not additive",
+          "translate(150 50)",
+          {type: 'rotate', from: '0', to: '90'},
+          [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)]
+    ),
+    new AdditionTestCase("To animation",
+          "rotate(-90)",
+          {type: 'rotate', to: '90'},
+          [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)]
+    ),
+    new AdditionTestCase("By animation",
+          "rotate(-90)",
+          {type: 'rotate', by: '180'},
+          [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, -90),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 180)]
+    ),
+    new AdditionTestCase("Normal additive: same type",
+          "rotate(45)",
+          {type: 'rotate', from: '0', to: '45', additive: 'sum'},
+          [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)]
+    ),
+    new AdditionTestCase("Normal additive: different type",
+          "translate(50)",
+          {type: 'rotate', from: '0', to: '90', additive: 'sum'},
+          [new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)]
+    ),
+    new AdditionTestCase("Stacked additive: same type",
+          "rotate(-90)",
+          [{type: 'rotate', from: '0', to: '90', additive: 'sum'},
+           {type: 'rotate', from: '0', to: '90', additive: 'sum'}],
+          [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, -90),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90)]
+    ),
+    new AdditionTestCase("Stacked additive: different types #1",
+          "translate(50)",
+          [{type: 'rotate', from: '0', to: '45', additive: 'sum'},
+           {type: 'rotate', from: '0', to: '45', additive: 'sum'}],
+          [new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)]
+    ),
+    new AdditionTestCase("Stacked additive: different types #2",
+          "skewX(20) translate(50)",
+          [{type: 'rotate', from: '0', to: '45', additive: 'sum'},
+           {type: 'rotate', from: '0', to: '45', additive: 'sum'}],
+          [new Transform(SVGTransform.SVG_TRANSFORM_SKEWX, 20),
+           new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)]
+    ),
+    new AdditionTestCase("Stacked additive: different types #3",
+          "skewX(20) translate(50)",
+          [{type: 'rotate', from: '0', to: '45', additive: 'sum'},
+           {type: 'translate', from: '0', to: '30', additive: 'sum'},
+           {type: 'translate', from: '0', to: '-30', additive: 'sum'},
+           {type: 'rotate', from: '0', to: '45', additive: 'sum'}],
+          [new Transform(SVGTransform.SVG_TRANSFORM_SKEWX, 20),
+           new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45),
+           new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0),
+           new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0),
+           new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 45)]
+    ),
+    new AdditionTestCase("Base value with rotation around a centre",
+          "rotate(90 50 50)",
+          {type: 'translate', from: '0 0', to: '0 -50', additive: 'sum'},
+          [new Transform(SVGTransform.SVG_TRANSFORM_ROTATE, 90),
+           new Transform(SVGTransform.SVG_TRANSFORM_TRANSLATE, 0)]
+    ),
+  ];
+
+  for (var i = 0; i < cases.length; i++) {
+    runAdditionTestCase(cases[i], $('g'), $('svg'));
+  }
+
+  SimpleTest.finish();
+}
+
+function runAdditionTestCase(test, elem, svg)
+{
+  var anims = createAnims(test.animSpecs);
+
+  elem.setAttribute('transform', test.baseVal);
+  elem.appendChild(anims);
+
+  svg.setCurrentTime(1);
+  var expected = test.expectedTransformList; // Array of Transform objects
+  var actual   = elem.transform.animVal;     // SVGTransformList
+  is(actual.numberOfItems, expected.length,
+     "Unexpected number of transforms");
+
+  if (actual.numberOfItems == expected.length) {
+    for (var i = 0; i < actual.numberOfItems; i++) {
+      var transform = actual.getItem(i);
+      var testDesc = " for transform " + i + " in '" + test.desc + "' test";
+      is(transform.type,  expected[i].type,
+         "Unexpected transform type" + testDesc);
+      is(transform.angle, expected[i].angle,
+         "Unexpected transform angle" + testDesc);
+    }
+  }
+  // We assume the only children of elem are the animation elements we've just
+  // added.
+  while (elem.firstChild) {
+    elem.removeChild(elem.firstChild);
+  }
+}
+
+function createAnims(specs)
+{
+  if (specs.constructor == Array) {
+    var frag = document.createDocumentFragment();
+    for (var i = 0; i < specs.length; ++i) {
+      frag.appendChild(createAnim(specs[i]));
+    }
+    return frag;
+  }
+
+  return createAnim(specs);
+}
+
+function createAnim(attrs)
+{
+  var SVG_NS = 'http://www.w3.org/2000/svg';
+  var anim = document.createElementNS(SVG_NS, 'animateTransform');
+  anim.setAttribute('attributeName', 'transform');
+  anim.setAttribute('dur', '1s');
+  anim.setAttribute('fill', 'freeze');
+  for (attr in attrs) {
+    anim.setAttribute(attr, attrs[attr]);
+  }
+  return anim;
+}
+
+window.addEventListener("load", main, false);
+
+]]>
+</script>
+</pre>
+</body>
+</html>
--- a/content/svg/content/test/test_SVGxxxList.xhtml
+++ b/content/svg/content/test/test_SVGxxxList.xhtml
@@ -1,15 +1,16 @@
 <html xmlns="http://www.w3.org/1999/xhtml">
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=515116
 -->
 <head>
   <title>Generic tests for SVG animated length lists</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="matrixUtils.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=515116">Mozilla Bug 515116</a>
 <p id="display"></p>
 <div id="content" style="display:none;">
 <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"
      onload="this.pauseAnimations();">
@@ -18,16 +19,17 @@ https://bugzilla.mozilla.org/show_bug.cg
       <feComponentTransfer>
         <feFuncR id="feFuncR" type="table"/>
       </feComponentTransfer>
     </filter>
   </desc>
   <text id="text">text</text>
   <path id="path"/>
   <polyline id="polyline"/>
+  <g id="g"/>
 </svg>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 
 SimpleTest.waitForExplicitFinish();
@@ -338,39 +340,62 @@ var tests = [
     attr_val_4 : '',
     attr_val_5a: '',
     attr_val_5b: '',
     item_constructor: function() {
       // XXX return a meaningful string
       return ;
     }
   },
+  */
+  /* Bug 602759 - Implement transform lists properly
   {
     // SVGTransformList test:
-
-    // XXX SVGTransformList has two extra methods, so add special case code to
-    // the test_xxx_API() functions below.
-
     target_element_id: 'g',
     attr_name: 'transform', // gradientTransform, patternTransform
     prop_name: 'transform',
     bv_name: 'baseVal',
     av_name: 'animVal',
     el_type: 'SVGGElement',
     prop_type: 'SVGAnimatedTransformList',
     list_type: 'SVGTransformList',
     item_type: 'SVGTransform',
-    attr_val_3a: '',
-    attr_val_3b: '',
-    attr_val_4 : '',
-    attr_val_5a: '',
-    attr_val_5b: '',
+    attr_val_3a: 'translate(20 10) rotate(90 10 10) skewX(45)',
+    attr_val_3b: 'translate(30 40) scale(2) matrix(1 2 3 4 5 6)',
+    attr_val_4 : 'scale(3 2) translate(19) skewY(2) rotate(-10)',
+    attr_val_5a:
+      'translate(20) rotate(-10) skewY(3) matrix(1 2 3 4 5 6) scale(0.5)',
+    attr_val_5b:
+      'skewX(45) rotate(45 -10 -10) skewX(-45) scale(2) matrix(6 5 4 3 2 1)',
+    // SVGTransformList animation addition is tested in
+    // test_SVGTransformListAddition.xhtml so we don't need:
+    // - attr_val_3b
+    // - attr_val_3b
+    // - attr_val_5b_firstItem_x3_constructor
+    // But we populate the first two anyway just in case they are later used for
+    // something other than testing animation.
+    // attr_val_5b_firstItem_x3_constructor is only used for animation
     item_constructor: function() {
       // XXX populate the matrix with different values each time
       return document.getElementById('svg').createSVGTransform();
+    },
+    item_is: function(itemA, itemB, message) {
+      ok(typeof(itemA.type) != 'undefined' &&
+         typeof(itemB.type) != 'undefined',
+         'expecting type property');
+      ok(typeof(itemA.matrix) != 'undefined' &&
+         typeof(itemB.matrix) != 'undefined',
+         'expecting matrix property');
+      ok(typeof(itemA.angle) != 'undefined' &&
+         typeof(itemB.angle) != 'undefined',
+         'expecting matrix property');
+
+      is(itemA.type, itemB.type, message);
+      is(itemA.angle, itemB.angle, message);
+      cmpMatrix(itemA.matrix, itemB.matrix, message);
     }
   },
   */
 ];
 
 
 /*
 This function returns a DocumentFragment with three 'animate' element children. The duration of the three animations is as follows:
@@ -392,33 +417,29 @@ At t=0s and t=1s we test the effect of a
 
 At t=10s we programatically remove the fill="freeze" from animation 1.
 */
 function create_animate_elements(test)
 {
   var SVG_NS = 'http://www.w3.org/2000/svg';
   var df = document.createDocumentFragment();
 
-  if (test.attr_name == 'transform' ||
-      test.attr_name == 'gradientTransform' ||
-      test.attr_name == 'patternTransform') {
-    ok(false, 'Need to update create_animate_element to handle transforms?')
-    throw new Error('Don\'t know how to handle transforms');
-    var animate1 =
-      document.createElementNS(SVG_NS, 'animateTransform');
-    var animate2 =
-      document.createElementNS(SVG_NS, 'animateTransform');
-    var animate3 =
-      document.createElementNS(SVG_NS, 'animateTransform');
-  } else {
-    var animate1 = document.createElementNS(SVG_NS, 'animate');
-    var animate2 = document.createElementNS(SVG_NS, 'animate');
-    var animate3 = document.createElementNS(SVG_NS, 'animate');
+  if (is_transform_attr(test.attr_name)) {
+    // animateTransform is "special". Although it targets an
+    // nsSVGAnimatedTransformList it only takes nsSVGTransform values as
+    // animation values. Therefore all the assumptions we're testing about the
+    // length of lists don't apply. We simply have to test it separately.
+    // This is done in test_SVGTransformListAddition.xhtml.
+    return df; // Return the empty document fragment
   }
 
+  var animate1 = document.createElementNS(SVG_NS, 'animate');
+  var animate2 = document.createElementNS(SVG_NS, 'animate');
+  var animate3 = document.createElementNS(SVG_NS, 'animate');
+
   animate1.setAttribute('attributeName', test.attr_name);
   animate1.setAttribute('from', test.attr_val_5a);
   animate1.setAttribute('to', test.attr_val_5b);
   animate1.setAttribute('begin', '1s');
   animate1.setAttribute('dur', '4s');
   animate1.setAttribute('repeatCount', '3');
   animate1.setAttribute('accumulate', 'sum');
   animate1.setAttribute('fill', 'freeze');
@@ -437,16 +458,22 @@ function create_animate_elements(test)
   animate3.setAttribute('begin', '7s');
   animate3.setAttribute('dur', '1s');
   animate3.setAttribute('additive', 'sum');
   df.appendChild(animate3);
 
   return df;
 }
 
+function is_transform_attr(attr_name) {
+  return attr_name == 'transform' ||
+         attr_name == 'gradientTransform' ||
+         attr_name == 'patternTransform';
+}
+
 function get_array_of_list_items(list)
 {
   array = [];
   for (var i = 0; i < list.numberOfItems; ++i) {
     array.push(list.getItem(i));
   }
   return array;
 }
@@ -1070,18 +1097,19 @@ function run_list_mutation_tests()
  * See the comment for create_animate_elements() for details of the animations
  * and their timings.
  */
 function run_animation_timeline_tests()
 {
   var svg = document.getElementById('svg');
 
   for each (var t in tests) {
-    // Skip if there is no animVal for this test
-    if (!t.animVal)
+    // Skip if there is no animVal for this test or if it is a transform list
+    // since these are handled specially
+    if (!t.animVal || is_transform_attr(t.attr_name))
       continue;
 
     svg.setCurrentTime(0); // reset timeline
 
     // Reset attributes before moving along the timeline and triggering SMIL:
     t.element.setAttribute(t.attr_name, t.attr_val_4);
     t.old_baseVal_items = get_array_of_list_items(t.baseVal);
     t.old_animVal_items = get_array_of_list_items(t.animVal);
--- a/content/svg/content/test/test_SVGxxxListIndexing.xhtml
+++ b/content/svg/content/test/test_SVGxxxListIndexing.xhtml
@@ -8,21 +8,23 @@ https://bugzilla.mozilla.org/show_bug.cg
   <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=631437">Mozilla Bug 631437</a>
 <svg xmlns="http://www.w3.org/2000/svg" id="svg">
   <text id="text" x="10 20 30" rotate="40 50 60">abcde</text>
   <path id="path" d="M0,0 L100,100"/>
   <polygon id="poly" points="50,50 70,70 90,50"/>
+  <g id="g" transform="translate(20 30) rotate(50 60 70) scale(2)"/>
 </svg>
 <script type="text/javascript;version=1.8"><![CDATA[
 var text = document.getElementById("text"),
     path = document.getElementById("path"),
     poly = document.getElementById("poly");
+    g    = document.getElementById("g");
 
 function CheckList(aListObject, aExpectedListLength, aListDescription)
 {
   is(aListObject.numberOfItems, aExpectedListLength, aListDescription + ".numberOfItems");
   is(aListObject.numberOfItems, aExpectedListLength, aListDescription + ".length");
   for (let i = 0; i < aListObject.length; i++) {
     let item = aListObject.getItem(i);
     ok(aListObject[i] === item, aListDescription + "[" + i + "]");
@@ -53,17 +55,25 @@ var tests = [
                 { values: "M50,50", length: 1 },
                 { values: "M0,0 h10 v20 h30 v40", length: 5 } ] },
   { element: poly,
     attribute: "points",
     listProperty: "animatedPoints",
     type: "SVGPointList",
     subtests: [ { values: null, length: 3 },
                 { values: "100,100", length: 1 },
-                { values: "0,0 10,10 20,0 30,10 40,0", length: 5 } ] }
+                { values: "0,0 10,10 20,0 30,10 40,0", length: 5 } ] },
+  { element: g,
+    attribute: "transform",
+    listProperty: "transform.baseVal",
+    type: "SVGTransformList",
+    subtests: [ { values: null, length: 3 },
+                { values: "skewX(45)", length: 1 },
+                { values: "translate(1 2) rotate(3) scale(4) skewY(5) skewX(6)",
+                  length: 5 } ] }
 ];
 
 for each (let test in tests) {
   let list = test.element;
   for each (let property in test.listProperty.split(".")) {
     list = list[property];
   }
 
--- a/content/svg/content/test/test_transform.xhtml
+++ b/content/svg/content/test/test_transform.xhtml
@@ -18,124 +18,167 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
-var tolerance = 1 / 65535;
-      
-function isequal( value, expected, tolerance )
-{
-  ok(Math.abs(value - expected) < tolerance, 'matrix value expected:' +expected + ' actual:' + value);
-}
-
 function run()
 {
   var g, svg, t, m, m2;
 
   svg = $('svg');
   g = $('g');
 
   t = g.transform.baseVal.getItem(0);
   m = t.matrix;
 
   // test that the SVGTransform correctly reflects the translate()
-  is(t.type, SVGTransform.SVG_TRANSFORM_TRANSLATE, 't.type for translate');
-  is(m.a, 1, 'm.a for translate');
-  is(m.b, 0, 'm.b for translate');
-  is(m.c, 0, 'm.c for translate');
-  is(m.d, 1, 'm.d for translate');
-  is(m.e, 10, 'm.e for translate');
-  is(m.f, 20, 'm.f for translate');
-  is(t.angle, 0, 't.angle for translate');
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_TRANSLATE,
+                 1, 0,
+                 0, 1,
+                 10, 20,
+                 0, "translate");
 
   // set the SVGTransform to be a scale()
   t.setScale(2, 3);
 
   // test that the matrix is live and now reflects the scale()
-  is(t.type, SVGTransform.SVG_TRANSFORM_SCALE, 't.type for scale');
-  is(m.a, 2, 'm.a for scale');
-  is(m.b, 0, 'm.b for scale');
-  is(m.c, 0, 'm.c for scale');
-  is(m.d, 3, 'm.d for scale');
-  is(m.e, 0, 'm.e for scale');
-  is(m.f, 0, 'm.f for scale');
-  is(t.angle, 0, 't.angle for scale');
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_SCALE,
+                 2, 0,
+                 0, 3,
+                 0, 0,
+                 0, "scale");
 
   // set the SVGTransform to be a matrix()
   m2 = svg.createSVGMatrix();
   m2.a = 1;
   m2.b = 2;
   m2.c = 3;
   m2.d = 4;
   m2.e = 5;
   m2.f = 6;
   t.setMatrix(m2);
 
   // check that setMatrix() took a copy of m
   ok(m != m2, 't.matrix identity');
 
   // test that the SVGTransform now reflects the matrix value
-  is(t.type, SVGTransform.SVG_TRANSFORM_MATRIX, 't.type for matrix');
-  is(m.a, 1, 'm.a for matrix');
-  is(m.b, 2, 'm.b for matrix');
-  is(m.c, 3, 'm.c for matrix');
-  is(m.d, 4, 'm.d for matrix');
-  is(m.e, 5, 'm.e for matrix');
-  is(m.f, 6, 'm.f for matrix');
-  is(t.angle, 0, 't.angle for matrix');
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX,
+                 1, 2,
+                 3, 4,
+                 5, 6,
+                 0, "matrix");
 
   // set the SVGTransform to be a translate() then convert to a matrix
   t.setTranslate(0, 0);
   m.a = 2;
 
   // test that the SVGTransform now reflects the matrix value
   is(t.type, SVGTransform.SVG_TRANSFORM_MATRIX, 't.type for matrix');
 
   // set the SVGTransform to be a rotate()
   t.setRotate(90, 0, 0);
 
   // test that the SVGTransform now reflects the matrix value
-  is(t.type, SVGTransform.SVG_TRANSFORM_ROTATE, 't.type for rotate');
-  isequal(m.a, Math.cos(Math.PI/2), tolerance);
-  isequal(m.b, Math.sin(Math.PI/2), tolerance);
-  isequal(m.c, -Math.sin(Math.PI/2), tolerance);
-  isequal(m.d, Math.cos(Math.PI/2), tolerance);
-  isequal(m.e, 0, tolerance);
-  isequal(m.f, 0, tolerance);
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_ROTATE,
+                 Math.cos(Math.PI/2), Math.sin(Math.PI/2),
+                 -Math.sin(Math.PI/2), Math.cos(Math.PI/2),
+                 0, 0,
+                 90, "rotate");
 
   // set the SVGTransform to be a skewX()
   t.setSkewX(45);
 
   // test that the SVGTransform now reflects the matrix value
-  is(t.type, SVGTransform.SVG_TRANSFORM_SKEWX, 't.type for skewx');
-  isequal(m.a, 1, tolerance);
-  isequal(m.b, 0, tolerance);
-  isequal(m.c, Math.tan(Math.PI/4), tolerance);
-  isequal(m.d, Math.tan(Math.PI/4), tolerance);
-  isequal(m.e, 0, tolerance);
-  isequal(m.f, 0, tolerance);
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_SKEWX,
+                 1, 0,
+                 Math.tan(Math.PI/4), Math.tan(Math.PI/4),
+                 0, 0,
+                 45, "skewX");
 
   // set the SVGTransform to be a skewY()
   t.setSkewY(45);
 
   // test that the SVGTransform now reflects the matrix value
-  is(t.type, SVGTransform.SVG_TRANSFORM_SKEWY, 't.type for skewy');
-  isequal(m.a, Math.tan(Math.PI/4), tolerance);
-  isequal(m.b, Math.tan(Math.PI/4), tolerance);
-  isequal(m.c, 0, tolerance);
-  isequal(m.d, 1, tolerance);
-  isequal(m.e, 0, tolerance);
-  isequal(m.f, 0, tolerance);
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_SKEWY,
+                 Math.tan(Math.PI/4), Math.tan(Math.PI/4),
+                 0, 1,
+                 0, 0,
+                 45, "skewY");
+
+  // check angle is reset after changing type
+  t.setTranslate(10, 20);
+  is(t.angle, 0, "Angle not reset after changing to translate type");
+
+  // check read-only properties
+  t.angle = 40;
+  is(t.angle, 0, "t.angle should be read-only");
+  t.type = 7;
+  is(t.type, SVGTransform.SVG_TRANSFORM_TRANSLATE,
+     "t.type should be read-only");
+  t.matrix = m2;
+  ok(t.matrix != m2 && t.matrix.b == 0, "t.matrix should be read-only");
+
+  // check transform object identity after manipulation
+  ok(t === g.transform.baseVal.getItem(0),
+     "Got different transform objects after manipulation");
+  ok(t.matrix === m,
+     "Got different matrix objects after manipulation");
+
+  testCreateTransform();
 
   SimpleTest.finish();
 }
 
+function testCreateTransform()
+{
+  svg = $('svg');
+  var t = svg.createSVGTransform();
+  ok(t != svg.createSVGTransform(),
+     "Got identical objects when creating new transform");
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX,
+                 1, 0, 0, 1, 0, 0, 0, "createSVGTransform");
+
+  var m = svg.createSVGMatrix();
+  m.a = 1;
+  m.b = 2;
+  m.c = 3;
+  m.d = 4;
+  m.e = 5;
+  m.f = 6;
+  t = svg.createSVGTransformFromMatrix(m);
+  ok(t.matrix != m,
+     'createSVGTransformFromMatrix should copy matrix not adopt it');
+  m.a = 7; // Just to be sure, changing m should not affect t
+  checkTransform(t, SVGTransform.SVG_TRANSFORM_MATRIX,
+                 1, 2, 3, 4, 5, 6, 0, "createSVGTransformFromMatrix");
+}
+
+function checkTransform(transform, type, a, b, c, d, e, f, angle, forWhat)
+{
+  var m = transform.matrix;
+  is(transform.type, type, 'transform.type for ' + forWhat);
+  roughlyEqual(m.a, a, 'matrix.a for ' + forWhat);
+  roughlyEqual(m.b, b, 'matrix.b for ' + forWhat);
+  roughlyEqual(m.c, c, 'matrix.c for ' + forWhat);
+  roughlyEqual(m.d, d, 'matrix.d for ' + forWhat);
+  roughlyEqual(m.e, e, 'matrix.e for ' + forWhat);
+  roughlyEqual(m.f, f, 'matrix.f for ' + forWhat);
+  is(transform.angle, angle, 'transform.angle for ' + forWhat);
+}
+
+function roughlyEqual(value, expected, msg)
+{
+  const tolerance = 1 / 65535;
+  ok(Math.abs(value - expected) < tolerance,
+     msg + ' - got ' + value + ', expected ' + expected);
+}
+
 window.addEventListener("load", run, false);
 
 ]]>
 </script>
 </pre>
 </body>
 </html>
--- a/layout/reftests/svg/smil/transform/additive-1.svg
+++ b/layout/reftests/svg/smil/transform/additive-1.svg
@@ -1,105 +1,13 @@
 <svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
      class="reftest-wait"
-     onload="setupSnapshot(1.5)">
-  <script type="text/ecmascript"><![CDATA[
-    function setupSnapshot(timeInSeconds) {
-      var svg = document.documentElement;
-      svg.pauseAnimations();
-      svg.setCurrentTime(timeInSeconds);
-      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>
+     onload="setTimeAndSnapshot(1.5, true)">
+  <script xlink:href="../smil-util.js" type="text/javascript"/>
   <!-- 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>