tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_SVGxxxList.xhtml (51856B)


      1 <html xmlns="http://www.w3.org/1999/xhtml">
      2 <!--
      3 https://bugzilla.mozilla.org/show_bug.cgi?id=515116
      4 -->
      5 <head>
      6  <title>Generic tests for SVG animated length lists</title>
      7  <script src="/tests/SimpleTest/SimpleTest.js"></script>
      8  <script type="text/javascript" src="matrixUtils.js"></script>
      9  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
     10 </head>
     11 <body>
     12 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=515116">Mozilla Bug 515116</a>
     13 <p id="display"></p>
     14 <div id="content">
     15 <svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100" height="100"
     16     onload="this.pauseAnimations();">
     17  <defs>
     18    <filter>
     19      <feComponentTransfer>
     20        <feFuncR id="feFuncR" type="table"/>
     21      </feComponentTransfer>
     22    </filter>
     23  </defs>
     24  <text id="text">text</text>
     25  <path id="path"/>
     26  <polyline id="polyline"/>
     27  <g id="g"/>
     28 </svg>
     29 </div>
     30 <pre id="test">
     31 <script class="testbody" type="text/javascript">
     32 <![CDATA[
     33 
     34 
     35 SimpleTest.waitForExplicitFinish();
     36 
     37 /*
     38 This file runs a series of type-agnostic tests to check the state of the mini DOM trees that represent various SVG 'list' attributes (including checking the "object identity" of the objects in those trees) in the face of various changes, both with and without the complication of SMIL animation being active.
     39 
     40 For additional high level information on the tests that are run, see the comment for 'create_animate_elements' below.
     41 
     42 To have the battery of generic tests run for a new list attribute, add an element with that attribute to the document, then add a JavaScript object literal to the following 'tests' array with the following properties:
     43 
     44  target_element_id
     45    The ID of the element that has the attribute that is to be tested.
     46  attr_name
     47    The name of the attribute that is to be tested.
     48  prop_name
     49    The name of the DOM property that corresponds to the attribute that is to
     50    be tested. For some list types the SVGAnimatedXxxList interface is
     51    inherited by the element interface rather than the element having a
     52    property of that type, and in others the list type is not animatable so
     53    there is no SVGAnimatedXxxList interface for that list type. In these
     54    cases this property should be set to null.
     55  bv_name
     56    The name of the DOM base value property for the attribute that is to be
     57    tested. This is usually 'baseVal', but not always. In the case of
     58    SVGStringList, which is not animatable, this is the name of the
     59    SVGStringList property.
     60  av_name
     61    The name of the DOM anim value property for the attribute that is to be
     62    tested. This is usually 'animVal' but not always. In the case of
     63    SVGStringList, which is not animatable, this should be set to null.
     64  el_type
     65    The name of the SVGXxxElement interface on which the property corresponding
     66    to the attribute being tested is defined.
     67  prop_type
     68    The name of the SVGAnimatedXxxList interface (e.g. SVGAnimatedLengthList),
     69    if it exists, and if the element has a property is of this type (as
     70    opposed to the element interface inheriting it).
     71  list_type
     72    The name of the SVGXxxList interface implemented by the baseVal and
     73    animVal objects.
     74  item_type
     75    The name of the SVGXxx interface implemented by the list items.
     76  attr_val_3a:
     77  attr_val_3b:
     78    Two attribute values containing three different items.
     79  attr_val_4
     80    An attribute value containing four items.
     81  attr_val_5a:
     82  attr_val_5b:
     83    Two attribute values containing five different items.
     84  attr_val_5b_firstItem_x3_constructor:
     85    Function to construct a list-item that should match the first item in a
     86    SVGXxxList after three repeats of a cumulative animation to attr_val_5b.
     87    This function takes t.item_constructor as its only argument.
     88  item_constructor:
     89    Function to create a dummy list item.
     90  item_is:
     91    Function to compare two list items for equality, like "is()". If this
     92    property is omitted, it is assumed that we can just compare
     93    "item.value" (which is the case for most list types).
     94 */
     95 // helper method
     96 function keys(obj) {
     97    var rval = [];
     98    for (var prop in obj) {
     99        rval.push(prop);
    100    }
    101    return rval;
    102 }
    103 
    104 var tests = [
    105  {
    106    // SVGLengthList test:
    107    target_element_id: "text",
    108    attr_name: "x",
    109    prop_name: "x",
    110    bv_name: "baseVal",
    111    av_name: "animVal",
    112    el_type: "SVGTextElement",
    113    prop_type: "SVGAnimatedLengthList",
    114    list_type: "SVGLengthList",
    115    item_type: "SVGLength",
    116    attr_val_3a: "10 20ex, 30in",
    117    attr_val_3b: "30in 10, 20ex",
    118    attr_val_4: "10 20ex, 30in ,40cm",
    119    attr_val_5a: "10 20ex, 30in ,40cm , 50%",
    120    attr_val_5b: "20 50%, 20ex ,30in , 40cm",
    121    attr_val_5b_firstItem_x3_constructor(constructor) {
    122      var expected = constructor();
    123      expected.value = 60;
    124      return expected;
    125    },
    126    item_constructor() {
    127      // We need this function literal to avoid "Illegal operation on
    128      // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO.
    129      return document.getElementById("svg").createSVGLength();
    130    },
    131  },
    132  {
    133    // SVGNumberList test:
    134    target_element_id: "text",
    135    attr_name: "rotate",
    136    prop_name: "rotate",
    137    bv_name: "baseVal",
    138    av_name: "animVal",
    139    el_type: "SVGTextElement",
    140    prop_type: "SVGAnimatedNumberList",
    141    list_type: "SVGNumberList",
    142    item_type: "SVGNumber",
    143    attr_val_3a: "0 20 40",
    144    attr_val_3b: "60 40 20",
    145    attr_val_4: "40 20 10 80",
    146    attr_val_5a: "90 30 60 20 70",
    147    attr_val_5b: "30 20 70 30 90",
    148    attr_val_5b_firstItem_x3_constructor(constructor) {
    149      var expected = constructor();
    150      expected.value = 90;
    151      return expected;
    152    },
    153    item_constructor() {
    154      // We need this function literal to avoid "Illegal operation on
    155      // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO.
    156      return document.getElementById("svg").createSVGNumber();
    157    },
    158  },
    159  {
    160    // SVGNumberList test:
    161    target_element_id: "feFuncR",
    162    attr_name: "tableValues",
    163    prop_name: "tableValues",
    164    bv_name: "baseVal",
    165    av_name: "animVal",
    166    el_type: "SVGFEComponentTransferElement",
    167    prop_type: "SVGAnimatedNumberList",
    168    list_type: "SVGNumberList",
    169    item_type: "SVGNumber",
    170    attr_val_3a: "0 .5 .2",
    171    attr_val_3b: "1 .7 .1",
    172    attr_val_4: ".5 .3 .8 .2",
    173    attr_val_5a: "3 4 5 6 7",
    174    attr_val_5b: "7 6 5 4 3",
    175    attr_val_5b_firstItem_x3_constructor(constructor) {
    176      var expected = constructor();
    177      expected.value = 21;
    178      return expected;
    179    },
    180    item_constructor() {
    181      // We need this function literal to avoid "Illegal operation on
    182      // WrappedNative prototype object" NS_ERROR_XPC_BAD_OP_ON_WN_PROTO.
    183      return document.getElementById("svg").createSVGNumber();
    184    },
    185  },
    186  {
    187    // SVGPointList test:
    188    target_element_id: "polyline",
    189    attr_name: "points",
    190    prop_name: null, // SVGAnimatedPoints is an inherited interface!
    191    bv_name: "points",
    192    av_name: "animatedPoints",
    193    el_type: "SVGPolylineElement",
    194    prop_type: null,
    195    list_type: "SVGPointList",
    196    item_type: "SVGPoint",
    197    attr_val_3a: " 10,10 50,50 90,10 ",
    198    attr_val_3b: " 10,50 50,10 90,50 ",
    199    attr_val_4: " 10,10 50,50 90,10 200,100 ",
    200    attr_val_5a: " 10,10 50,50 90,10 130,50 170,10 ",
    201    attr_val_5b: " 50,10 50,10 90,50 130,10 170,50 ",
    202    attr_val_5b_firstItem_x3_constructor(constructor) {
    203      var expected = constructor();
    204      expected.x = 150;
    205      expected.y = 30;
    206      return expected;
    207    },
    208    item_constructor() {
    209      // XXX return different values each time
    210      return document.getElementById("svg").createSVGPoint();
    211    },
    212    item_is(itemA, itemB, message) {
    213      ok(typeof(itemA.x) != "undefined" &&
    214         typeof(itemB.x) != "undefined",
    215         "expecting x property");
    216      ok(typeof(itemA.y) != "undefined" &&
    217         typeof(itemB.y) != "undefined",
    218         "expecting y property");
    219 
    220      is(itemA.x, itemB.x, message);
    221      is(itemA.y, itemB.y, message);
    222    },
    223  },
    224  {
    225    // SVGStringList test:
    226    target_element_id: "g",
    227    attr_name: "requiredExtensions", // systemLanguage, viewTarget
    228    prop_name: null, // SVGStringList attributes are not animatable
    229    bv_name: "requiredExtensions",
    230    av_name: null,
    231    el_type: "SVGGElement",
    232    prop_type: null,
    233    list_type: "SVGStringList",
    234    item_type: "DOMString",
    235    attr_val_3a: "http://www.w3.org/TR/SVG11/feature#Shape http://www.w3.org/TR/SVG11/feature#Image " +
    236                 "http://www.w3.org/TR/SVG11/feature#Style",
    237    attr_val_3b: "http://www.w3.org/TR/SVG11/feature#CoreAttribute http://www.w3.org/TR/SVG11/feature#Structure " +
    238                 "http://www.w3.org/TR/SVG11/feature#Gradient",
    239    attr_val_4: "http://www.w3.org/TR/SVG11/feature#Pattern http://www.w3.org/TR/SVG11/feature#Clip " +
    240                 "http://www.w3.org/TR/SVG11/feature#Mask http://www.w3.org/TR/SVG11/feature#Extensibility",
    241    attr_val_5a: "http://www.w3.org/TR/SVG11/feature#BasicStructure http://www.w3.org/TR/SVG11/feature#BasicText " +
    242                 "http://www.w3.org/TR/SVG11/feature#BasicPaintAttribute http://www.w3.org/TR/SVG11/feature#BasicGraphicsAttribute " +
    243                 "http://www.w3.org/TR/SVG11/feature#BasicClip",
    244    attr_val_5b: "http://www.w3.org/TR/SVG11/feature#DocumentEventsAttribute http://www.w3.org/TR/SVG11/feature#GraphicalEventsAttribute " +
    245                 "http://www.w3.org/TR/SVG11/feature#AnimationEventsAttribute http://www.w3.org/TR/SVG11/feature#Hyperlinking " +
    246                 "http://www.w3.org/TR/SVG11/feature#XlinkAttribute",
    247    item_constructor() {
    248      return "http://www.w3.org/TR/SVG11/feature#XlinkAttribute";
    249    },
    250  },
    251  {
    252    // SVGTransformList test:
    253    target_element_id: "g",
    254    attr_name: "transform", // gradientTransform, patternTransform
    255    prop_name: "transform",
    256    bv_name: "baseVal",
    257    av_name: "animVal",
    258    el_type: "SVGGElement",
    259    prop_type: "SVGAnimatedTransformList",
    260    list_type: "SVGTransformList",
    261    item_type: "SVGTransform",
    262    attr_val_3a: "translate(20 10) rotate(90 10 10) skewX(45)",
    263    attr_val_3b: "translate(30 40) scale(2) matrix(1 2 3 4 5 6)",
    264    attr_val_4: "scale(3 2) translate(19) skewY(2) rotate(-10)",
    265    attr_val_5a:
    266      "translate(20) rotate(-10) skewY(3) matrix(1 2 3 4 5 6) scale(0.5)",
    267    attr_val_5b:
    268      "skewX(45) rotate(45 -10 -10) skewX(-45) scale(2) matrix(6 5 4 3 2 1)",
    269    // SVGTransformList animation addition is tested in
    270    // test_SVGTransformListAddition.xhtml so we don't need:
    271    // - attr_val_3b
    272    // - attr_val_3b
    273    // - attr_val_5b_firstItem_x3_constructor
    274    // But we populate the first two anyway just in case they are later used for
    275    // something other than testing animation.
    276    // attr_val_5b_firstItem_x3_constructor is only used for animation
    277    item_constructor() {
    278      // XXX populate the matrix with different values each time
    279      return document.getElementById("svg").createSVGTransform();
    280    },
    281    item_is(itemA, itemB, message) {
    282      ok(typeof(itemA.type) != "undefined" &&
    283         typeof(itemB.type) != "undefined",
    284         "expecting type property");
    285      ok(typeof(itemA.matrix) != "undefined" &&
    286         typeof(itemB.matrix) != "undefined",
    287         "expecting matrix property");
    288      ok(typeof(itemA.angle) != "undefined" &&
    289         typeof(itemB.angle) != "undefined",
    290         "expecting matrix property");
    291 
    292      is(itemA.type, itemB.type, message);
    293      is(itemA.angle, itemB.angle, message);
    294      cmpMatrix(itemA.matrix, itemB.matrix, message);
    295    },
    296  },
    297 ];
    298 
    299 
    300 /*
    301 This function returns a DocumentFragment with three 'animate' element children. The duration of the three animations is as follows:
    302 
    303  animation 1: |  *-----------*-----------*-----------*
    304  animation 2: |     *--*
    305  animation 3: |                    *--*
    306               |___________________________________________> time (s)
    307               |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
    308               0  1  2  3  4  5  6  7  8  9 10 11 12 13 14
    309 
    310 The first animation repeats once so that we can test state on a repeat animation.
    311 
    312 The second animation overrides the first animation for a short time, and has fewer list items than the first animation. This allows us to test object identity and other state on and after an overriding animation. Specifically, it allows us to check whether animVal list items are kept or discarded after the end of an overriding animation that has fewer items.
    313 
    314 The third animation has additive="sum", with fewer items than the lower priority animation 1, allowing us to test object identity and other state in that scenario. TODO: some type aware tests to check whether the composite fails or works?
    315 
    316 At t=0s and t=1s we test the effect of an attribute value changes in the absence and presence of SMIL animation respectively.
    317 
    318 At t=10s we programmatically remove the fill="freeze" from animation 1.
    319 */
    320 function create_animate_elements(test) {
    321  var SVG_NS = "http://www.w3.org/2000/svg";
    322  var df = document.createDocumentFragment();
    323 
    324  if (is_transform_attr(test.attr_name)) {
    325    // animateTransform is "special". Although it targets an
    326    // SVGAnimatedTransformList it only takes SVGTransform values as
    327    // animation values. Therefore all the assumptions we're testing about the
    328    // length of lists don't apply. We simply have to test it separately.
    329    // This is done in test_SVGTransformListAddition.xhtml.
    330    return df; // Return the empty document fragment
    331  }
    332 
    333  var animate1 = document.createElementNS(SVG_NS, "animate");
    334  var animate2 = document.createElementNS(SVG_NS, "animate");
    335  var animate3 = document.createElementNS(SVG_NS, "animate");
    336 
    337  animate1.setAttribute("attributeName", test.attr_name);
    338  animate1.setAttribute("from", test.attr_val_5a);
    339  animate1.setAttribute("to", test.attr_val_5b);
    340  animate1.setAttribute("begin", "1s");
    341  animate1.setAttribute("dur", "4s");
    342  animate1.setAttribute("repeatCount", "3");
    343  animate1.setAttribute("accumulate", "sum");
    344  animate1.setAttribute("fill", "freeze");
    345  df.appendChild(animate1);
    346 
    347  animate2.setAttribute("attributeName", test.attr_name);
    348  animate2.setAttribute("from", test.attr_val_3a);
    349  animate2.setAttribute("to", test.attr_val_3b);
    350  animate2.setAttribute("begin", "2s");
    351  animate2.setAttribute("dur", "1s");
    352  df.appendChild(animate2);
    353 
    354  animate3.setAttribute("attributeName", test.attr_name);
    355  animate3.setAttribute("from", test.attr_val_3a);
    356  animate3.setAttribute("to", test.attr_val_3b);
    357  animate3.setAttribute("begin", "7s");
    358  animate3.setAttribute("dur", "1s");
    359  animate3.setAttribute("additive", "sum");
    360  df.appendChild(animate3);
    361 
    362  return df;
    363 }
    364 
    365 function is_transform_attr(attr_name) {
    366  return attr_name == "transform" ||
    367         attr_name == "gradientTransform" ||
    368         attr_name == "patternTransform";
    369 }
    370 
    371 function get_array_of_list_items(list) {
    372  let array = [];
    373  for (var i = 0; i < list.numberOfItems; ++i) {
    374    array.push(list.getItem(i));
    375  }
    376  return array;
    377 }
    378 
    379 
    380 /**
    381 * This function tests the SVGXxxList API for the base val list. This means
    382 * running tests for the following property and methods:
    383 *
    384 *   numberOfItems
    385 *   clear()
    386 *   SVGLength initialize(in SVGLength newItem)
    387 *   SVGLength getItem(in unsigned long index)
    388 *   SVGLength insertItemBefore(in SVGLength newItem, in unsigned long index)
    389 *   SVGLength replaceItem(in SVGLength newItem, in unsigned long index)
    390 *   SVGLength removeItem(in unsigned long index)
    391 *   SVGLength appendItem(in SVGLength newItem)
    392 *
    393 * @param t A test from the 'tests' array.
    394 */
    395 function run_baseVal_API_tests() {
    396  var res, threw;
    397 
    398  for (var t of tests) {
    399    // Test .clear():
    400 
    401    t.element.setAttribute(t.attr_name, t.attr_val_4);
    402 
    403    is(t.baseVal.numberOfItems, 4,
    404       "The " + t.list_type + " object should contain four list items.");
    405 
    406    res = t.baseVal.clear();
    407 
    408    is(t.baseVal.numberOfItems, 0,
    409       "The method " + t.list_type + ".clear() should clear the " + t.list_type +
    410       " object.");
    411    is(res, undefined,
    412       "The method " + t.list_type + ".clear() should not return a value.");
    413    ok(t.element.hasAttribute(t.attr_name),
    414       "The method " + t.list_type + ".clear() should not remove the attribute.");
    415    ok(t.element.getAttribute(t.attr_name) === "",
    416       "Cleared " + t.attr_name + " (" + t.list_type + ") but did not get an " +
    417       "empty string back.");
    418 
    419    t.baseVal.clear();
    420 
    421    // Test empty strings
    422 
    423    t.element.setAttribute(t.attr_name, "");
    424    ok(t.element.getAttribute(t.attr_name) === "",
    425       "Set an empty attribute value for " + t.attr_name + " (" + t.list_type +
    426       ") but did not get an empty string back.");
    427 
    428    // Test removed attributes
    429 
    430    t.element.removeAttribute(t.attr_name);
    431    ok(t.element.getAttribute(t.attr_name) === null,
    432       "Removed attribute value for " + t.attr_name + " (" + t.list_type +
    433       ") but did not get null back.");
    434    ok(!t.element.hasAttribute(t.attr_name),
    435       "Removed attribute value for " + t.attr_name + " (" + t.list_type +
    436       ") but hasAttribute still returns true.");
    437 
    438    // Test .initialize():
    439 
    440    t.element.setAttribute(t.attr_name, t.attr_val_4);
    441 
    442    var item = t.item_constructor();
    443    // Our current implementation of 'initialize' for most list types performs
    444    // a 'clear' followed by an 'insertItemBefore'. This results in two
    445    // modification events being dispatched. SVGStringList however avoids the
    446    // additional clear.
    447    res = t.baseVal.initialize(item);
    448 
    449 
    450    is(t.baseVal.numberOfItems, 1,
    451       "The " + t.list_type + " object should contain one list item.");
    452    ok(res === item,
    453       "The list item returned by " + t.list_type + ".initialize() should be the " +
    454       "exact same object as the item that was passed to that method, since " +
    455       "the item that was passed to that method did not already belong to a " +
    456       "list.");
    457    ok(t.baseVal.getItem(0) === item,
    458       "The list item at index 0 should be the exact same object as the " +
    459       "object that was passed to the " + t.list_type + ".initialize() method, " +
    460       "since the item that was passed to that method did not already " +
    461       "belong to a list.");
    462 
    463    t.element.setAttribute(t.attr_name, t.attr_val_4);
    464 
    465    if (t.item_type != "DOMString") {
    466      var old_items = get_array_of_list_items(t.baseVal);
    467      item = t.baseVal.getItem(3);
    468      res = t.baseVal.initialize(item);
    469 
    470      ok(res !== item &&
    471         t.baseVal.getItem(0) !== item &&
    472         t.baseVal.getItem(0) !== old_items[0] &&
    473         res === t.baseVal.getItem(0),
    474         "The method " + t.list_type + ".initialize() should clone the object that " +
    475         "is passed in if that object is already in a list.");
    476      // [SVGWG issue] not what the spec currently says
    477 
    478 
    479      item = t.baseVal.getItem(0);
    480      res = t.baseVal.initialize(item);
    481 
    482      ok(res !== item &&
    483         t.baseVal.getItem(0) !== item,
    484         "The method " + t.list_type + ".initialize() should clone the object that " +
    485         "is passed in, even if that object is the only item in that list.");
    486      // [SVGWG issue] not what the spec currently says
    487 
    488      threw = false;
    489      try {
    490        t.baseVal.initialize({});
    491      } catch (e) {
    492        threw = true;
    493      }
    494      ok(threw,
    495         "The method " + t.list_type + ".initialize() should throw if passed an " +
    496         "object of the wrong type.");
    497    }
    498 
    499    // Test .insertItemBefore():
    500 
    501    t.element.setAttribute(t.attr_name, t.attr_val_4);
    502 
    503    old_items = get_array_of_list_items(t.baseVal);
    504    item = t.item_constructor();
    505    res = t.baseVal.insertItemBefore(item, 2);
    506 
    507    is(t.baseVal.numberOfItems, 5,
    508       "The " + t.list_type + " object should contain five list items.");
    509    ok(res === item,
    510       "The list item returned by " + t.list_type + ".insertItemBefore() should " +
    511       "be the exact same object as the item that was passed to that method, " +
    512       "since the item that was passed to that method did not already belong " +
    513       "to a list.");
    514    ok(t.baseVal.getItem(2) === item,
    515       "The list item at index 2 should be the exact same object as the " +
    516       "object that was passed to the " + t.list_type + ".insertItemBefore() " +
    517       "method, since the item that was passed to that method did not " +
    518       "already belong to a list.");
    519    ok(t.baseVal.getItem(3) === old_items[2],
    520       "The list item that was at index 2 should be at index 3 after " +
    521       "inserting a new item at index 2 using the " + t.list_type +
    522       ".insertItemBefore() method.");
    523 
    524    item = t.item_constructor();
    525    t.baseVal.insertItemBefore(item, 100);
    526 
    527    ok(t.baseVal.getItem(5) === item,
    528       "When the index passed to the " + t.list_type + ".insertItemBefore() " +
    529       "method is out of bounds, the supplied list item should be appended " +
    530       "to the list.");
    531 
    532    item = t.baseVal.getItem(4);
    533    res = t.baseVal.insertItemBefore(item, 2);
    534 
    535    is(t.baseVal.numberOfItems, 7,
    536       "The " + t.list_type + " object should contain seven list items.");
    537    if (t.item_type != "DOMString") {
    538      ok(res !== item &&
    539         t.baseVal.getItem(2) !== item &&
    540         t.baseVal.getItem(2) !== old_items[2] &&
    541         res === t.baseVal.getItem(2),
    542         "The method " + t.list_type + ".insertItemBefore() should clone the " +
    543         "object that is passed in if that object is already in a list.");
    544      // [SVGWG issue] not what the spec currently says
    545    }
    546 
    547    item = t.baseVal.getItem(2);
    548    res = t.baseVal.insertItemBefore(item, 2);
    549 
    550    is(t.baseVal.numberOfItems, 8,
    551       "The " + t.list_type + " object should contain eight list items.");
    552    if (t.item_type != "DOMString") {
    553      ok(res !== item &&
    554         t.baseVal.getItem(2) !== item,
    555         "The method " + t.list_type + ".insertItemBefore() should clone the " +
    556         "object that is passed in, even if that object is the item in " +
    557         "the list at the index specified.");
    558      // [SVGWG issue] not what the spec currently says
    559 
    560      threw = false;
    561      try {
    562        t.baseVal.insertItemBefore({}, 2);
    563      } catch (e) {
    564        threw = true;
    565      }
    566      ok(threw,
    567         "The method " + t.list_type + ".insertItemBefore() should throw if passed " +
    568         "an object of the wrong type.");
    569    }
    570 
    571    // Test .replaceItem():
    572 
    573    t.element.setAttribute(t.attr_name, t.attr_val_4);
    574 
    575    old_items = get_array_of_list_items(t.baseVal);
    576    item = t.item_constructor();
    577    res = t.baseVal.replaceItem(item, 2);
    578 
    579    is(t.baseVal.numberOfItems, 4,
    580       "The " + t.list_type + " object should contain four list items.");
    581    if (t.item_type != "DOMString") {
    582      ok(res === item,
    583         "The list item returned by " + t.list_type + ".replaceItem() should be " +
    584         "the exact same object as the item that was passed to that method, " +
    585         "since the item that was passed to that method did not already belong " +
    586         "to a list.");
    587    }
    588    ok(t.baseVal.getItem(2) === item,
    589       "The list item at index 2 should be the exact same object as the " +
    590       "object that was passed to the " + t.list_type + ".replaceItem() method, " +
    591       "since the item that was passed to that method did not already belong " +
    592       "to a list.");
    593    ok(t.baseVal.getItem(3) === old_items[3],
    594       "The list item that was at index 3 should still be at index 3 after " +
    595       "the item at index 2 was replaced using the " + t.list_type +
    596       ".replaceItem() method.");
    597 
    598    item = t.item_constructor();
    599 
    600    threw = false;
    601    try {
    602      t.baseVal.replaceItem(item, 100);
    603    } catch (e) {
    604      threw = true;
    605    }
    606    ok(threw,
    607       "The method " + t.list_type + ".replaceItem() should throw if passed " +
    608       "an index that is out of bounds.");
    609 
    610    old_items = get_array_of_list_items(t.baseVal);
    611    item = t.baseVal.getItem(3);
    612    res = t.baseVal.replaceItem(item, 1);
    613 
    614    is(t.baseVal.numberOfItems, 4,
    615       "The " + t.list_type + " object should contain four list items.");
    616    if (t.item_type != "DOMString") {
    617      ok(res !== item &&
    618         t.baseVal.getItem(1) !== item &&
    619         t.baseVal.getItem(1) !== old_items[1] &&
    620         res === t.baseVal.getItem(1),
    621         "The method " + t.list_type + ".replaceItem() should clone the object " +
    622         "that is passed in if that object is already in a list.");
    623      // [SVGWG issue] not what the spec currently says
    624    }
    625 
    626    item = t.baseVal.getItem(1);
    627    res = t.baseVal.replaceItem(item, 1);
    628 
    629    is(t.baseVal.numberOfItems, 4,
    630       "The " + t.list_type + " object should contain four list items.");
    631    if (t.item_type != "DOMString") {
    632      ok(res !== item &&
    633         t.baseVal.getItem(1) !== item,
    634         "The method " + t.list_type + ".replaceItem() should clone the object " +
    635         "that is passed in, even if the object that object and the object " +
    636         "that is being replaced are the exact same objects.");
    637      // [SVGWG issue] not what the spec currently says
    638 
    639      threw = false;
    640      try {
    641        t.baseVal.replaceItem({}, 2);
    642      } catch (e) {
    643        threw = true;
    644      }
    645      ok(threw,
    646         "The method " + t.list_type + ".replaceItem() should throw if passed " +
    647         "an object of the wrong type.");
    648    }
    649 
    650    // Test .removeItem():
    651 
    652    t.element.setAttribute(t.attr_name, t.attr_val_4);
    653 
    654    old_items = get_array_of_list_items(t.baseVal);
    655    item = t.baseVal.getItem(2);
    656    res = t.baseVal.removeItem(2);
    657 
    658    is(t.baseVal.numberOfItems, 3,
    659       "The " + t.list_type + " object should contain three list items.");
    660    if (t.item_type != "DOMString") {
    661      ok(res === item,
    662         "The list item returned by " + t.list_type + ".removeItem() should be the " +
    663         "exact same object as the item that was at the specified index.");
    664    }
    665    ok(t.baseVal.getItem(1) === old_items[1],
    666       "The list item that was at index 1 should still be at index 1 after " +
    667       "the item at index 2 was removed using the " + t.list_type +
    668       ".replaceItem() method.");
    669    ok(t.baseVal.getItem(2) === old_items[3],
    670       "The list item that was at index 3 should still be at index 2 after " +
    671       "the item at index 2 was removed using the " + t.list_type +
    672       ".replaceItem() method.");
    673 
    674    threw = false;
    675    try {
    676      t.baseVal.removeItem(100);
    677    } catch (e) {
    678      threw = true;
    679    }
    680    ok(threw,
    681       "The method " + t.list_type + ".removeItem() should throw if passed " +
    682       "an index that is out of bounds.");
    683 
    684    // Test .appendItem():
    685 
    686    t.element.setAttribute(t.attr_name, t.attr_val_4);
    687 
    688    old_items = get_array_of_list_items(t.baseVal);
    689    item = t.item_constructor();
    690    res = t.baseVal.appendItem(item);
    691 
    692    is(t.baseVal.numberOfItems, 5,
    693       "The " + t.list_type + " object should contain five list items.");
    694    ok(res === item,
    695       "The list item returned by " + t.list_type + ".appendItem() should be the " +
    696       "exact same object as the item that was passed to that method, since " +
    697       "the item that was passed to that method did not already belong " +
    698       "to a list.");
    699    ok(t.baseVal.getItem(4) === item,
    700       "The last list item should be the exact same object as the object " +
    701       "that was passed to the " + t.list_type + ".appendItem() method, since " +
    702       "the item that was passed to that method did not already belong to " +
    703       "a list.");
    704    ok(t.baseVal.getItem(3) === old_items[3],
    705       "The list item that was at index 4 should still be at index 4 after " +
    706       "appending a new item using the " + t.list_type + ".appendItem() " +
    707       "method.");
    708 
    709    item = t.baseVal.getItem(2);
    710    res = t.baseVal.appendItem(item);
    711 
    712    is(t.baseVal.numberOfItems, 6,
    713       "The " + t.list_type + " object should contain six list items.");
    714    if (t.item_type != "DOMString") {
    715      ok(res !== item &&
    716         t.baseVal.getItem(5) !== item &&
    717         res === t.baseVal.getItem(5),
    718         "The method " + t.list_type + ".appendItem() should clone the object " +
    719         "that is passed in if that object is already in a list.");
    720      // [SVGWG issue] not what the spec currently says
    721    }
    722 
    723    item = t.baseVal.getItem(5);
    724    res = t.baseVal.appendItem(item);
    725 
    726    is(t.baseVal.numberOfItems, 7,
    727       "The " + t.list_type + " object should contain seven list items.");
    728    if (t.item_type != "DOMString") {
    729      ok(res !== item &&
    730         t.baseVal.getItem(6) !== item,
    731         "The method " + t.list_type + ".appendItem() should clone the object " +
    732         "that is passed in, if that object is already the last item in " +
    733         "that list.");
    734      // [SVGWG issue] not what the spec currently says
    735 
    736      threw = false;
    737      try {
    738        t.baseVal.appendItem({});
    739      } catch (e) {
    740        threw = true;
    741      }
    742      ok(threw,
    743         "The method " + t.list_type + ".appendItem() should throw if passed " +
    744         "an object of the wrong type.");
    745    }
    746 
    747    // Test removal and addition events
    748 
    749    t.element.removeAttribute(t.attr_name);
    750    t.element.removeAttributeNS(null, t.attr_name);
    751    res = t.baseVal.appendItem(item);
    752  }
    753 }
    754 
    755 
    756 /**
    757 * This function tests the SVGXxxList API for the anim val list (see also the
    758 * comment for test_baseVal_API).
    759 */
    760 function run_animVal_API_tests() {
    761  var threw, item;
    762 
    763  for (var t of tests) {
    764    if (!t.animVal)
    765      continue; // SVGStringList isn't animatable
    766 
    767    item = t.item_constructor();
    768 
    769    t.element.setAttribute(t.attr_name, t.attr_val_4);
    770 
    771    is(t.animVal.numberOfItems, 4,
    772       "The " + t.list_type + " object should contain four list items.");
    773 
    774    // Test .clear():
    775 
    776    threw = false;
    777    try {
    778      t.animVal.clear();
    779    } catch (e) {
    780      threw = true;
    781    }
    782    ok(threw,
    783       "The method " + t.list_type + ".clear() should throw when called on an " +
    784       "anim val list, since anim val lists should be readonly.");
    785 
    786    // Test .getItem():
    787 
    788    item = t.animVal.getItem(2);
    789    ok(item != null && item === t.animVal.getItem(2),
    790       "The method " + t.list_type + ".getItem() should work when called on an " +
    791       "anim val list, and always return the exact same object.");
    792 
    793    // .initialize()
    794 
    795    threw = false;
    796    try {
    797      t.animVal.initialize(item);
    798    } catch (e) {
    799      threw = true;
    800    }
    801    ok(threw,
    802       "The method " + t.list_type + ".initialize() should throw when called on " +
    803       "an anim val list, since anim val lists should be readonly.");
    804 
    805    // Test .insertItemBefore():
    806 
    807    threw = false;
    808    try {
    809      t.animVal.insertItemBefore(item, 2);
    810    } catch (e) {
    811      threw = true;
    812    }
    813    ok(threw,
    814       "The method " + t.list_type + ".insertItemBefore() should throw when " +
    815       "called on an anim val list, since anim val lists should be readonly.");
    816 
    817    // Test .replaceItem():
    818 
    819    threw = false;
    820    try {
    821      t.animVal.replaceItem(item, 2);
    822    } catch (e) {
    823      threw = true;
    824    }
    825    ok(threw,
    826       "The method " + t.list_type + ".replaceItem() should throw when called " +
    827       "on an anim val list, since anim val lists should be readonly.");
    828 
    829    // Test .removeItem():
    830 
    831    threw = false;
    832    try {
    833      t.animVal.removeItem(2);
    834    } catch (e) {
    835      threw = true;
    836    }
    837    ok(threw,
    838       "The method " + t.list_type + ".removeItem() should throw when called " +
    839       "on an anim val list, since anim val lists should be readonly.");
    840 
    841    // Test .appendItem():
    842 
    843    threw = false;
    844    try {
    845      t.animVal.appendItem(item);
    846    } catch (e) {
    847      threw = true;
    848    }
    849    ok(threw,
    850       "The method " + t.list_type + ".appendItem() should throw when called " +
    851       "on an anim val list, since anim val lists should be readonly.");
    852  }
    853 }
    854 
    855 
    856 /**
    857 * This function runs some basic tests to check the effect of setAttribute()
    858 * calls on object identity, without taking SMIL animation into consideration.
    859 */
    860 function run_basic_setAttribute_tests() {
    861  for (var t of tests) {
    862    // Since the t.prop, t.baseVal and t.animVal objects should never ever
    863    // change, we leave testing of them to our caller so that it can check
    864    // them after all the other mutations such as SMIL changes.
    865 
    866    t.element.setAttribute(t.attr_name, t.attr_val_4);
    867 
    868    ok(t.baseVal.numberOfItems == 4 && t.baseVal.getItem(3) != null,
    869       "The length of the " + t.list_type + " object for " + t.bv_path + " should " +
    870       "have been set to 4 by the setAttribute() call.");
    871 
    872    if (t.animVal) {
    873      ok(t.baseVal.numberOfItems == t.animVal.numberOfItems,
    874         "When no animations are active, the " + t.list_type + " objects for " +
    875         t.bv_path + " and " + t.av_path + " should be the same length (4).");
    876 
    877      ok(t.baseVal !== t.animVal,
    878         "The " + t.list_type + " objects for " + t.bv_path + " and " + t.av_path +
    879         " should be different objects.");
    880 
    881      ok(t.baseVal.getItem(0) !== t.animVal.getItem(0),
    882         "The " + t.item_type + " list items in the " + t.list_type + " objects for " +
    883         t.bv_path + " and " + t.av_path + " should be different objects.");
    884    }
    885 
    886    // eslint-disable-next-line no-self-compare
    887    ok(t.baseVal.getItem(0) === t.baseVal.getItem(0),
    888       "The exact same " + t.item_type + " DOM object should be returned each " +
    889       "time the item at a given index in the " + t.list_type + " for " +
    890       t.bv_path + " is accessed, given that the index was not made invalid " +
    891       "by a change in list length between the successive accesses.");
    892 
    893    if (t.animVal) {
    894      // eslint-disable-next-line no-self-compare
    895      ok(t.animVal.getItem(0) === t.animVal.getItem(0),
    896         "The exact same " + t.item_type + " DOM object should be returned each " +
    897         "time the item at a given index in the " + t.list_type + " for " +
    898         t.av_path + " is accessed, given that the index was not made invalid " +
    899         "by a change in list length between the successive accesses.");
    900    }
    901 
    902    // Test the effect of setting the attribute to new values:
    903 
    904    t.old_baseVal_items = get_array_of_list_items(t.baseVal);
    905    if (t.animVal) {
    906      t.old_animVal_items = get_array_of_list_items(t.animVal);
    907    }
    908 
    909    t.element.setAttribute(t.attr_name, t.attr_val_3a);
    910    t.element.setAttribute(t.attr_name, t.attr_val_5a);
    911 
    912    ok(t.baseVal.numberOfItems == 5 && t.baseVal.getItem(4) != null,
    913       "The length of the " + t.list_type + " object for " + t.bv_path + " should " +
    914       "have been set to 5 by the setAttribute() call.");
    915 
    916    if (t.animVal) {
    917      ok(t.baseVal.numberOfItems == t.animVal.numberOfItems,
    918         "Since no animations are active, the length of the " + t.list_type + " " +
    919         "objects for " + t.bv_path + " and " + t.av_path + " should be the same " +
    920         "(5).");
    921    }
    922 
    923    if (t.item_type != "DOMString") {
    924      ok(t.baseVal.getItem(2) === t.old_baseVal_items[2],
    925         "After its attribute changes, list items in the " + t.list_type + " for " +
    926         t.bv_path + " that are at indexes that existed prior to the attribute " +
    927         "change should be the exact same objects as the objects that were " +
    928         "at those indexes prior to the attribute change.");
    929 
    930      ok(t.baseVal.getItem(3) !== t.old_baseVal_items[3],
    931         "After its attribute changes, list items in the " + t.list_type + " for " +
    932         t.bv_path + " that are at indexes that did not exist prior to the " +
    933         "attribute change should not be the same objects as any objects that " +
    934         "were at those indexes at some earlier time.");
    935    }
    936 
    937    if (t.animVal) {
    938      ok(t.animVal.getItem(2) === t.old_animVal_items[2],
    939         "After its attribute changes, list items in the " + t.list_type + " for " +
    940         t.av_path + " that are at indexes that existed prior to the attribute " +
    941         "change should be the exact same objects as the objects that were " +
    942         "at those indexes prior to the attribute change.");
    943 
    944      ok(t.animVal.getItem(3) !== t.old_animVal_items[3],
    945         "After its attribute changes, list items in the " + t.list_type + " for " +
    946         t.av_path + " that are at indexes that did not exist prior to the " +
    947         "attribute change should not be the same objects as any objects " +
    948         "that were at those indexes at some earlier time.");
    949    }
    950  }
    951 }
    952 
    953 /**
    954 * This function verifies that a list's animVal is kept in sync with its
    955 * baseVal, when we add & remove items from the baseVal.
    956 */
    957 function run_list_mutation_tests() {
    958  for (var t of tests) {
    959    if (t.animVal) {
    960      // Test removeItem()
    961      // =================
    962      // Save second item in baseVal list; then make it the first item, and
    963      // check that animVal is updated accordingly.
    964      t.element.setAttribute(t.attr_name, t.attr_val_4);
    965 
    966      var secondVal = t.baseVal.getItem(1);
    967      var removedFirstVal = t.baseVal.removeItem(0);
    968      t.item_is(t.animVal.getItem(0), secondVal,
    969                "animVal for " + t.attr_name + " needs update after first item " +
    970                "removed");
    971 
    972      // Repeat with last item
    973      var secondToLastVal = t.baseVal.getItem(1);
    974      var removedLastVal = t.baseVal.removeItem(2);
    975 
    976      var threw = false;
    977      try {
    978        t.animVal.getItem(2);
    979      } catch (e) {
    980        threw = true;
    981      }
    982      ok(threw,
    983         "The method " + t.attr_name + ".animVal.getItem() for previously-final " +
    984         "index should throw after final item is removed from baseVal.");
    985 
    986      t.item_is(t.animVal.getItem(1), secondToLastVal,
    987                "animVal for " + t.attr_name + " needs update after last item " +
    988                "removed");
    989 
    990      // Test insertItemBefore()
    991      // =======================
    992      // Reset base value, insert value @ start, check that animVal is updated.
    993      t.element.setAttribute(t.attr_name, t.attr_val_3a);
    994      t.baseVal.insertItemBefore(removedLastVal, 0);
    995      t.item_is(t.animVal.getItem(0), removedLastVal,
    996                "animVal for " + t.attr_name + " needs update after insert at " +
    997                "beginning");
    998 
    999      // Repeat with insert at end
   1000      t.element.setAttribute(t.attr_name, t.attr_val_3a);
   1001      t.baseVal.insertItemBefore(removedFirstVal, t.baseVal.numberOfItems);
   1002      t.item_is(t.animVal.getItem(t.baseVal.numberOfItems - 1),
   1003                removedFirstVal,
   1004                "animVal for " + t.attr_name + " needs update after insert at end");
   1005 
   1006      // Test appendItem()
   1007      // =================
   1008      var dummy = t.item_constructor();
   1009      t.baseVal.appendItem(dummy);
   1010      t.item_is(t.animVal.getItem(t.baseVal.numberOfItems - 1), dummy,
   1011                "animVal for " + t.attr_name + " needs update after appendItem");
   1012 
   1013      // Test clear()
   1014      // ============
   1015      t.baseVal.clear();
   1016      threw = false;
   1017      try {
   1018        t.animVal.getItem(0);
   1019      } catch (e) {
   1020        threw = true;
   1021      }
   1022      ok(threw,
   1023         "The method " + t.attr_name + ".animVal.getItem() should throw after " +
   1024         "we've cleared baseVal.");
   1025 
   1026      is(t.animVal.numberOfItems, 0,
   1027         "animVal for " + t.attr_name + " should be empty after baseVal cleared");
   1028 
   1029      // Test initialize()
   1030      // =================
   1031      t.element.setAttribute(t.attr_name, t.attr_val_3a);
   1032      t.baseVal.initialize(dummy);
   1033 
   1034      is(t.animVal.numberOfItems, 1,
   1035         "animVal for " + t.attr_name + " should have length 1 after initialize");
   1036      t.item_is(t.animVal.getItem(0), dummy,
   1037                "animVal for " + t.attr_name + " needs update after initialize");
   1038    }
   1039  }
   1040 }
   1041 
   1042 /**
   1043 * In this function we run a series of tests at various points along the SMIL
   1044 * animation timeline, using SVGSVGElement.setCurrentTime() to move forward
   1045 * along the timeline.
   1046 *
   1047 * See the comment for create_animate_elements() for details of the animations
   1048 * and their timings.
   1049 */
   1050 function run_animation_timeline_tests() {
   1051  var svg = document.getElementById("svg");
   1052 
   1053  for (var t of tests) {
   1054    // Skip if there is no animVal for this test or if it is a transform list
   1055    // since these are handled specially
   1056    if (!t.animVal || is_transform_attr(t.attr_name))
   1057      continue;
   1058 
   1059    svg.setCurrentTime(0); // reset timeline
   1060 
   1061    // Reset attributes before moving along the timeline and triggering SMIL:
   1062    t.element.setAttribute(t.attr_name, t.attr_val_4);
   1063    t.old_baseVal_items = get_array_of_list_items(t.baseVal);
   1064    t.old_animVal_items = get_array_of_list_items(t.animVal);
   1065 
   1066 
   1067    /********************    t = 1s    ********************/
   1068 
   1069    svg.setCurrentTime(1); // begin first animation
   1070 
   1071    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1072       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1073       "The start of an animation should never affect the " + t.list_type +
   1074       " for " + t.bv_path + ", or its list items.");
   1075 
   1076    ok(t.animVal.numberOfItems == 5 && t.animVal.getItem(4) != null,
   1077       "The start of the animation should have changed the number of items " +
   1078       "in the " + t.list_type + " for " + t.bv_path + " to 5.");
   1079 
   1080    // TODO
   1081    ok(t.animVal.getItem(3) === t.old_animVal_items[3],
   1082       "When affected by SMIL animation, list items in the " + t.list_type +
   1083       " for " + t.bv_path + " that are at indexes that existed prior to the " +
   1084       "start of the animation should be the exact same objects as the " +
   1085       "objects that were at those indexes prior to the start of the " +
   1086       "animation.");
   1087 
   1088    t.old_animVal_items = get_array_of_list_items(t.animVal);
   1089 
   1090    t.element.setAttribute(t.attr_name, t.attr_val_3a);
   1091 
   1092    ok(t.baseVal.numberOfItems == 3 &&
   1093       t.baseVal.getItem(2) === t.old_baseVal_items[2],
   1094       "Setting the underlying attribute should change the items in the " +
   1095       t.list_type + " for " + t.bv_path + ", including when an animation is " +
   1096       "in progress.");
   1097 
   1098    ok(t.animVal.numberOfItems == 5 &&
   1099       t.animVal.getItem(4) === t.old_animVal_items[4],
   1100       "Setting the underlying attribute should not change the " + t.list_type +
   1101       " for " + t.bv_path + " when an animation that does not depend on the " +
   1102       "base val is in progress.");
   1103 
   1104    t.element.setAttribute(t.attr_name, t.attr_val_4); // reset
   1105 
   1106    t.old_baseVal_items = get_array_of_list_items(t.baseVal);
   1107    t.old_animVal_items = get_array_of_list_items(t.animVal);
   1108 
   1109 
   1110    /********************    t = 2s    ********************/
   1111 
   1112    svg.setCurrentTime(2); // begin override animation
   1113 
   1114    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1115       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1116       "The start of an override animation should never affect the " +
   1117       t.list_type + " for " + t.bv_path + ", or its list items.");
   1118 
   1119    is(t.animVal.numberOfItems, 3,
   1120       "The start of the override animation should have changed the number " +
   1121       "of items in the " + t.list_type + " for " + t.bv_path + " to 3.");
   1122 
   1123    ok(t.animVal.getItem(2) === t.old_animVal_items[2],
   1124       "When affected by an override SMIL animation, list items in the " +
   1125       t.list_type + " for " + t.bv_path + " that are at indexes that existed " +
   1126       "prior to the start of the animation should be the exact same " +
   1127       "objects as the objects that were at those indexes prior to the " +
   1128       "start of that animation.");
   1129 
   1130    t.old_animVal_items = get_array_of_list_items(t.animVal);
   1131 
   1132 
   1133    /********************    t = 3s    ********************/
   1134 
   1135    svg.setCurrentTime(3); // end of override animation
   1136 
   1137    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1138       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1139       "The end of an override animation should never affect the " +
   1140       t.list_type + " for " + t.bv_path + ", or its list items.");
   1141 
   1142    is(t.animVal.numberOfItems, 5,
   1143       "At the end of the override animation, the number of items in the " +
   1144       t.list_type + " for " + t.bv_path + " should have reverted to 5.");
   1145 
   1146    ok(t.animVal.getItem(2) === t.old_animVal_items[2],
   1147       "At the end of the override animation, list items in the " +
   1148       t.list_type + " for " + t.bv_path + " that are at indexes that existed " +
   1149       "prior to the end of the animation should be the exact same " +
   1150       "objects as the objects that were at those indexes prior to the " +
   1151       "end of that animation.");
   1152 
   1153    t.old_animVal_items = get_array_of_list_items(t.animVal);
   1154 
   1155 
   1156    /********************    t = 5s    ********************/
   1157 
   1158    svg.setCurrentTime(5); // animation repeat point
   1159 
   1160    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1161       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1162       "When a SMIL animation repeats, it should never affect the " +
   1163       t.list_type + " for " + t.bv_path + ", or its list items.");
   1164 
   1165    ok(t.animVal.numberOfItems == t.old_animVal_items.length &&
   1166       t.animVal.getItem(4) === t.old_animVal_items[4],
   1167       "When an animation repeats, the list items that are at a given " +
   1168       "index in the " + t.list_type + " for " + t.av_path + " should be the exact " +
   1169       "same objects as were at that index before the repeat occurred.");
   1170 
   1171 
   1172    /********************    t = 6s    ********************/
   1173 
   1174    svg.setCurrentTime(6); // inside animation repeat
   1175 
   1176    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1177       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1178       "When a SMIL animation repeats, it should never affect the " +
   1179       t.list_type + " for " + t.bv_path + ", or its list items.");
   1180 
   1181    ok(t.animVal.numberOfItems == t.old_animVal_items.length &&
   1182       t.animVal.getItem(4) === t.old_animVal_items[4],
   1183       "When an animation repeats, the list items that are at a given " +
   1184       "index in the " + t.list_type + " for " + t.av_path + " should be the exact " +
   1185       "same objects as were at that index before the repeat occurred.");
   1186 
   1187 
   1188    /********************    t = 7s    ********************/
   1189 
   1190    svg.setCurrentTime(7); // start of additive="sum" animation
   1191 
   1192    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1193       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1194       "When a new SMIL animation starts and should blend with an " +
   1195       "underlying animation, it should never affect the " +
   1196       t.list_type + " for " + t.bv_path + ", or its list items.");
   1197 
   1198    if (t.list_type == "SVGLengthList") {
   1199      // Length lists are a special case where it makes sense to allow shorter
   1200      // lists to be composed on top of longer lists (but not necessarily vice
   1201      // versa - see comment below).
   1202 
   1203      ok(t.animVal.numberOfItems == t.old_animVal_items.length &&
   1204         t.animVal.getItem(3) === t.old_animVal_items[3],
   1205         'When an animation with additive="sum" is added on top of an ' +
   1206         "existing animation that has more list items, the length of the " +
   1207         t.list_type + " for " + t.av_path + " should not change.");
   1208    } else {
   1209 
   1210 /* TODO
   1211      ok(false,
   1212         'Decide what to do here - see ' +
   1213         'https://bugzilla.mozilla.org/show_bug.cgi?id=573716 - we ' +
   1214         'probably should be discarding any animation sandwich layers from ' +
   1215         'a layer that fails to add, on up.');
   1216 */
   1217 
   1218      // In other words, we wouldn't need the if-else check here.
   1219 
   1220    }
   1221 
   1222    // XXX what if the higher priority sandwich layer has *more* list items
   1223    // than the underlying animation? In that case we would need to
   1224    // distinguish between different SVGLengthList attributes, since although
   1225    // all SVGLengthList attributes allow a short list to be added to longer
   1226    // list, they do not all allow a longer list to be added to shorter list.
   1227    // Specifically that would not be good for 'x' and 'y' on <text> since
   1228    // lengths there are not naturally zero. See the comment in
   1229    // SVGLengthListSMILAttr::Add().
   1230 
   1231 
   1232    /********************    t = 13s    ********************/
   1233 
   1234    svg.setCurrentTime(13); // all animations have finished, but one is frozen
   1235 
   1236    ok(t.baseVal.numberOfItems == t.old_baseVal_items.length &&
   1237       t.baseVal.getItem(3) === t.old_baseVal_items[3],
   1238       "When a SMIL animation ends, it should never affect the " +
   1239       t.list_type + " for " + t.bv_path + ", or its list items.");
   1240 
   1241    is(t.animVal.numberOfItems, 5,
   1242       "Even though all SMIL animation have finished, the number " +
   1243       "of items in the " + t.list_type + " for " + t.av_path +
   1244       " should still be more than the same as the number of items in " +
   1245       t.bv_path + " since one of the animations is still frozen.");
   1246 
   1247    var expected = t.attr_val_5b_firstItem_x3_constructor(t.item_constructor);
   1248    t.item_is(t.animVal.getItem(0), expected,
   1249      'animation with accumulate="sum" and repeatCount="3" for attribute "' +
   1250       t.attr_name + '" should end up at 3x the "to" value.');
   1251 
   1252    // Unfreeze frozen animation (removing its effects)
   1253    var frozen_animate_element =
   1254      t.element.querySelector('animate[fill][attributeName="' + t.attr_name + '"]');
   1255    frozen_animate_element.removeAttribute("fill");
   1256 
   1257    ok(t.animVal.numberOfItems == t.baseVal.numberOfItems,
   1258       "Once all SMIL animation have finished and been un-frozen, the number " +
   1259       "of items in the " + t.list_type + " for " + t.av_path +
   1260       " should be the same as the number of items in " + t.bv_path + ".");
   1261 
   1262    ok(t.animVal.getItem(2) === t.old_animVal_items[2],
   1263       "Even after an animation finishes and is un-frozen, the list items " +
   1264       "that are at a given index in the " + t.list_type + " for " + t.av_path +
   1265       " should be the exact same objects as were at that index before the " +
   1266       "end and unfreezing of the animation occurred.");
   1267   }
   1268 }
   1269 
   1270 
   1271 function run_tests() {
   1272  // Initialize each test object with some useful properties, and create their
   1273  // 'animate' elements. Note that 'prop' and 'animVal' may be null.
   1274  for (let t of tests) {
   1275    t.element = document.getElementById(t.target_element_id);
   1276    t.prop = t.prop_name ? t.element[t.prop_name] : null;
   1277    t.baseVal = ( t.prop || t.element )[t.bv_name];
   1278    t.animVal = t.av_name ? ( t.prop || t.element )[t.av_name] : null;
   1279    t.bv_path = t.el_type + "." +
   1280                (t.prop ? t.prop_name + "." : "") +
   1281                t.bv_name; // e.g. 'SVGTextElement.x.baseVal'
   1282    if (t.animVal) {
   1283      t.av_path = t.el_type + "." +
   1284                  (t.prop ? t.prop_name + "." : "") +
   1285                  t.av_name;
   1286    }
   1287    t.prop_type = t.prop_type || null;
   1288 
   1289    // use fallback 'is' function, if none was provided.
   1290    if (!t.item_is) {
   1291      t.item_is = function(itemA, itemB, message) {
   1292      ok(typeof(itemA.value) != "undefined" &&
   1293         typeof(itemB.value) != "undefined",
   1294         "expecting value property");
   1295        is(itemA.value, itemB.value, message);
   1296      };
   1297    }
   1298 
   1299    if (t.animVal) {
   1300      t.element.appendChild(create_animate_elements(t));
   1301    }
   1302  }
   1303 
   1304  // Run the major test groups:
   1305 
   1306  run_baseVal_API_tests();
   1307  run_animVal_API_tests();
   1308  run_basic_setAttribute_tests();
   1309  run_list_mutation_tests();
   1310  run_animation_timeline_tests();
   1311 
   1312  // After all the other test manipulations, we check that the following
   1313  // objects have still not changed, since they never should:
   1314 
   1315  for (let t of tests) {
   1316    if (t.prop) {
   1317      ok(t.prop === t.element[t.prop_name],
   1318         "The same " + t.prop_type + " object should ALWAYS be returned for " +
   1319         t.el_type + "." + t.prop_name + " each time it is accessed.");
   1320    }
   1321 
   1322    ok(t.baseVal === ( t.prop || t.element )[t.bv_name],
   1323       "The same " + t.list_type + " object should ALWAYS be returned for " +
   1324       t.el_type + "." + t.prop_name + "." + t.bv_name + " each time it is accessed.");
   1325 
   1326    if (t.animVal) {
   1327      ok(t.animVal === ( t.prop || t.element )[t.av_name],
   1328         "The same " + t.list_type + " object should ALWAYS be returned for " +
   1329         t.el_type + "." + t.prop_name + "." + t.av_name + " each time it is accessed.");
   1330    }
   1331  }
   1332 
   1333  SimpleTest.finish();
   1334 }
   1335 
   1336 window.addEventListener("load",
   1337  () => SimpleTest.executeSoon(run_tests)
   1338 );
   1339 
   1340 ]]>
   1341 </script>
   1342 </pre>
   1343 </body>
   1344 </html>