SVG:Lists
This page contains notes relevant to the implementation of SVG DOM lists (e.g. SVGLengthList) and their animated list "owners" (e.g. SVGAnimatedLengthList).
SVG 1.1 DOM lists
* SVGStringList SVGTests (many elements), view * SVGNumberList text, tspan, feColorMatrix, feComponentTransfer, feConvolveMatrix * SVGLengthList SVGTextPositioningElement (text, tspan, tref, altGlyph) * SVGPointList polyline, polygon * SVGTransformList gradient, pattern, SVGTransformable (lots) * SVGPathSegList path
These list interfaces all have the same members, except for SVGTransformList which has two extra methods (no big deal).
SVGXxxList's do NOT always have a SVGAnimatedXxxList "owner" object. Specifically: SVGICCColor.color is just an SVGNumberList, SVGViewSpec.transform is just an SVGTransformList, and there is no SVGAnimatedStringList interface (none of the SVGStringList attributes are animatable).
Note that the "animated" interfaces corresponding to SVGPointList and SVGPathSegList have unusual naming (SVGAnimatedPoints and SVGAnimatedPathData respectively), they have different properties (not baseVal and animVal), and these interfaces are inherited by elements rather than the elements having properties of those types.
SVGPathSeg is just a base interface for many other types that can be in a SVGPathSegList. (Other "list item" interfaces such as SVGTransform are not.)
Note how much simpler DOMSVGStringList (the only list class not no maintain its own DOM wrapper items) is compared to DOMSVGPathSegList (neither have a parent "SVGAnimated" object, so are a good comparison). This is partly because string lists are not animatable, but it's a lot to do with the fact that DOMSVGStringList does not have to maintain its own DOM items, and can just create a new string every time it's asked for a string. There's also some added complexity in path seg lists due to there being different types of path segment, and having those encoded in an array. Still, the lesson is that keeping the items live adds a lot of complexity.
Behavioral observations
If an attribute is not being animated, then animVal has the same number of items as baseVal, and the corresponding items in baseVal and animVal have the same values. At the same time both animVal and baseVal are different objects from each other, as are the corresponding items they contain.
If an animation is in progress, animVal may have a different number of items than baseVal. (I.e. if the animation is due to a 'set' element, or if the animation element has its 'from' or 'values' attribute set. In both cases the SMIL code does not need or use the base value.)
Even while animation is in progress, the number of items in animVal can change. (I.e. if a new animation begins and overrides an existing lower priority animation, or conversely, if an animation ends and gives way to a running lower priority animation.)
An animation of a list attribute may result in a different number of items in animVal than the number of items during a previous animations, independent of whether the underlying attribute changed or not.
baseVal lists and their items are mutable and may be changed by script.
animVal lists and their items are readonly to script and may only be changed by SMIL (though script may affect animVal indirectly by starting/ending an animation, or otherwise affects SMIL inputs).
baseVal and animVal items keep their values when removed from their list. More specifically, they keep the value they had just prior to the change in list length.
baseVal items continue to be mutable after being removed from their list (without affecting an attribute/element).
animVal items continue to be readonly after being removed from their list (SVGWG issue).
Whenever script requests the SVGAnimatedXxxList, it should always be given the exact same object if there's any way in which not returning the same object could be detectable by script (this probably means "if script has elsewhere kept a reference to this object, its baseVal or animVal, or any of their items").
Whenever script accesses the baseVal or animVal of the SVGAnimatedXxxList property, it should always be given the exact same objects if there's any way in which not returning the same objects could be detectable by script (this probably means "if script has elsewhere kept a reference to baseVal or animVal, or any of their items").
Whenever script accesses items of the baseVal or animVal, it should always be given the exact same object for any given index if there's any way in which not returning the same object could be detectable by script (this probably means "if script has elsewhere kept a reference to the requested items").
The number of items in baseVal may change if script changes the attribute value, or if script directly manipulates the list.
The number of items in animVal may change if script changes the attribute value, or if the SMIL engine manipulates the list.
When the length of baseVal or animVal is changed by an attribute change, script, or animation, any excess items in the lists are detached/removed from the list. Modifying detached items will never again affect the list/element, unless script explicitly inserts them back into a list.
Implementation observations
List items for ALL list types (except possibly SVGStringList, assuming its Strings are immutable) are going to need to have a reference back to their element (or to their list or some other object providing a path back to their element) in order to read their values from their corresponding internal list item and to notify of changes to their state. SVGLength additionally needs this reference in order to resolve (and convert between) certain unit types.
Additionally, keeping the old values when an object is detached means that the DOM objects must have space to store their state. That's not the place where we want to normally store their state though. Most times the DOM object will only be created on demand, and even if it has been created we don't want to be reaching into DOM objects from the internal code to get state.
Outstanding questions
Should a list item that has been removed from an animVal list be allowed to become mutable, and be allowed to be inserted into a baseVal list?
Detached SVGLength items are not going to be able to resolve percentage values or certain other units to user units. What error should we throw if the user tries to read the user unit value from such a length?
One of the questions that has to be resolved in the design of SVG lists is whether list items that are passed into list methods for insertion into the list should be copied or inserted. Specifically, should the four methods initialize, insertItemBefore, replaceItem and appendItem create a copy of the item and insert that copy into the list, or should the item that's passed to them itself be inserted into the list? Different implementations do different things. The current 1.1 errata says that the object itself must be inserted. The cases that need to be carefully considered by implementers are:
* inserting an item that already belongs in the list into which it is being inserted * inserting an item that already belongs in a different list * inserting an item that belongs to another element, but not a list (e.g. on SVGLength associated with the 'x' attribute on a 'rect' element)
The errata item for this seems to have assumed that "the item must be removed from any other list" text eliminated any issues with items belonging in more than one place. But what happens if someone takes e.g. an SVGLength from an SVGLength property and inserts that into a list? The SVGLength's property should presumably continue to point to the same object, but now so must the list. And what about the item? Does it need to maintain knowledge of *two* elements it needs to inform about changes to itself? This also raises hard implementation questions about where the object's state is stored. For performance reasons some implementations generally implement SVG's heavy DOM using DOM wrappers - the DOM objects have to query for their state elsewhere. Now where should the object get its state from? The state for the property or the state for the list? If the underlying attribute corresponding to the property changes, then these will be different since the DOM object for a property is not throw away when it's attribute changes (unlike for lists). jwatt: I'm strongly in favor of saying that if the item belongs to an existing attribute/element, then we copy, otherwise we insert the object. That satisfies the case of having the item created by createXxx() being inserted, but also eliminates the problem of having objects belonging to more than one attribute.
The number of items in a list can change for a variety of reasons, including the attribute being set, or animation starting/ending (either overriding/falling back to the baseVal/an existing lower priority animation). Clearly if the length of the list decreases then excess items need to be removed from the list, but does it perhaps make more sense to remove all items when the attribute is set to a completely new value? In that case references to existing items may no longer make much sense. If that's a good thing to do, should all the animVal items be detached at the same time, for the same reasons? Should that perhaps depend on whether an animation is in progress, of whether the animation depends on the baseVal or not? What about when a new animation begins/ends, either overriding/falling back to the baseVal/an existing lower priority animation? Should the animVal items all be detached since it's a completely new animation? Although the "detach all items" approach would make implementation very slightly simpler in general, it does lead to a problem in the animation case - the animVal code in Mozilla can't tell when it's being given a new value due to a new sample point of the same animation, or due to a new animation that has just overridden an existing animation. jwatt: my opinion is that the implementation should only ever remove excess items, regardless of what changes the list.
Should the SVGAnimatedXxxList objects be discarded if the attribute is set, and should a new object be created for the new attribute value? Or should they always be the same object? What about when animation starts/ends? Perhaps just throw away the animVal object? Would allowing objects to be thrown away like this make implementation easier? jwatt: not really, since all the DOM objects need to have their link to their element broken.
Why does SVGNumber even exist rather than just using float??!!
Related thoughts
Do pure "to" animations (without a "from") work correctly in implementations when the default value for the attribute is something unusual (e.g. 100% for some lengths)?
Animation and lists of different length
In the general case it does not make sense to allow continuous interpolation between lists of different length. For example, consider the 'path' element. How do you animate between specified and missing path segments? In the general case of lists of different length, Mozilla simply disallows continuous animation and instead treats the animation as if it had calcMode="discrete".
There is one exception to this rule though: length lists. The 'x', 'y', 'dx' and 'dy' attributes on text elements are length list values, and for these attributes it can make sense to allow continuous animation between lists of different length. In the case of the 'dx' and 'dy' attributes, it always makes sense, since the shorter list can simply be padded with zeros for the purposes of the animation. This is because the values for these attributes are offsets. However, in the case of 'x' and 'y', it doesn't make sense to pad the shorter list with zeros during interpolation, since the values for these attributes are relative to the coordinate system, and we would want to pad the list with the position in that coordinate system that the glyphs would otherwise be at, and not zero! However, when compositing animation sandwich layers together it *can* make sense to allow lists for 'x' and 'y' to have different lengths, but only if the higher priority animation contains the shorter list. This is because adding zero to specified positions is okay, but conversely adding values to unknown positions that we pretend are zero, is not.