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 78586 1d681a73cfc762de9494381e612d61c7d415ad29
parent 78585 c7924111705c5bba6b950667a1ed4a4ef4d2d2b9
child 78587 38e6f7980e5edfab1c8c30202253bb9b7a6f42f8
push id340
push userclegnitto@mozilla.com
push dateTue, 08 Nov 2011 22:56:33 +0000
treeherdermozilla-beta@f745dc151615 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwatt
bugs602759
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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>