Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // Note: Class syntax roughly based on:
8 // https://developer.mozilla.org/en/Core_JavaScript_1.5_Guide/Inheritance
9 const SVG_NS = "http://www.w3.org/2000/svg";
10 const XLINK_NS = "http://www.w3.org/1999/xlink";
12 const MPATH_TARGET_ID = "smilTestUtilsTestingPath";
14 function extend(child, supertype)
15 {
16 child.prototype.__proto__ = supertype.prototype;
17 }
19 // General Utility Methods
20 var SMILUtil =
21 {
22 // Returns the first matched <svg> node in the document
23 getSVGRoot : function()
24 {
25 return SMILUtil.getFirstElemWithTag("svg");
26 },
28 // Returns the first element in the document with the matching tag
29 getFirstElemWithTag : function(aTargetTag)
30 {
31 var elemList = document.getElementsByTagName(aTargetTag);
32 return (elemList.length == 0 ? null : elemList[0]);
33 },
35 // Simple wrapper for getComputedStyle
36 getComputedStyleSimple: function(elem, prop)
37 {
38 return window.getComputedStyle(elem, null).getPropertyValue(prop);
39 },
41 getAttributeValue: function(elem, attr)
42 {
43 if (attr.attrName == SMILUtil.getMotionFakeAttributeName()) {
44 // Fake motion "attribute" -- "computed value" is the element's CTM
45 return elem.getCTM();
46 }
47 if (attr.attrType == "CSS") {
48 return SMILUtil.getComputedStyleWrapper(elem, attr.attrName);
49 }
50 if (attr.attrType == "XML") {
51 // XXXdholbert This is appropriate for mapped attributes, but not
52 // for other attributes.
53 return SMILUtil.getComputedStyleWrapper(elem, attr.attrName);
54 }
55 },
57 // Smart wrapper for getComputedStyle, which will generate a "fake" computed
58 // style for recognized shorthand properties (font, overflow, marker)
59 getComputedStyleWrapper : function(elem, propName)
60 {
61 // Special cases for shorthand properties (which aren't directly queriable
62 // via getComputedStyle)
63 var computedStyle;
64 if (propName == "font") {
65 var subProps = ["font-style", "font-variant", "font-weight",
66 "font-size", "line-height", "font-family"];
67 for (var i in subProps) {
68 var subPropStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]);
69 if (subPropStyle) {
70 if (subProps[i] == "line-height") {
71 // There needs to be a "/" before line-height
72 subPropStyle = "/ " + subPropStyle;
73 }
74 if (!computedStyle) {
75 computedStyle = subPropStyle;
76 } else {
77 computedStyle = computedStyle + " " + subPropStyle;
78 }
79 }
80 }
81 } else if (propName == "marker") {
82 var subProps = ["marker-end", "marker-mid", "marker-start"];
83 for (var i in subProps) {
84 if (!computedStyle) {
85 computedStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]);
86 } else {
87 is(computedStyle, SMILUtil.getComputedStyleSimple(elem, subProps[i]),
88 "marker sub-properties should match each other " +
89 "(they shouldn't be individually set)");
90 }
91 }
92 } else if (propName == "overflow") {
93 var subProps = ["overflow-x", "overflow-y"];
94 for (var i in subProps) {
95 if (!computedStyle) {
96 computedStyle = SMILUtil.getComputedStyleSimple(elem, subProps[i]);
97 } else {
98 is(computedStyle, SMILUtil.getComputedStyleSimple(elem, subProps[i]),
99 "overflow sub-properties should match each other " +
100 "(they shouldn't be individually set)");
101 }
102 }
103 } else {
104 computedStyle = SMILUtil.getComputedStyleSimple(elem, propName);
105 }
106 return computedStyle;
107 },
109 // This method hides (i.e. sets "display: none" on) all of the given node's
110 // descendents. It also hides the node itself, if requested.
111 hideSubtree : function(node, hideNodeItself, useXMLAttribute)
112 {
113 // Hide node, if requested
114 if (hideNodeItself) {
115 if (useXMLAttribute) {
116 if (node.setAttribute) {
117 node.setAttribute("display", "none");
118 }
119 } else if (node.style) {
120 node.style.display = "none";
121 }
122 }
124 // Hide node's descendents
125 var child = node.firstChild;
126 while (child) {
127 SMILUtil.hideSubtree(child, true, useXMLAttribute);
128 child = child.nextSibling;
129 }
130 },
132 getMotionFakeAttributeName : function() {
133 return "_motion";
134 },
135 };
138 var CTMUtil =
139 {
140 CTM_COMPONENTS_ALL : ["a", "b", "c", "d", "e", "f"],
141 CTM_COMPONENTS_ROTATE : ["a", "b", "c", "d" ],
143 // Function to generate a CTM Matrix from a "summary"
144 // (a 3-tuple containing [tX, tY, theta])
145 generateCTM : function(aCtmSummary)
146 {
147 if (!aCtmSummary || aCtmSummary.length != 3) {
148 ok(false, "Unexpected CTM summary tuple length: " + aCtmSummary.length);
149 }
150 var tX = aCtmSummary[0];
151 var tY = aCtmSummary[1];
152 var theta = aCtmSummary[2];
153 var cosTheta = Math.cos(theta);
154 var sinTheta = Math.sin(theta);
155 var newCtm = { a : cosTheta, c: -sinTheta, e: tX,
156 b : sinTheta, d: cosTheta, f: tY };
157 return newCtm;
158 },
160 /// Helper for isCtmEqual
161 isWithinDelta : function(aTestVal, aExpectedVal, aErrMsg, aIsTodo) {
162 var testFunc = aIsTodo ? todo : ok;
163 const delta = 0.00001; // allowing margin of error = 10^-5
164 ok(aTestVal >= aExpectedVal - delta &&
165 aTestVal <= aExpectedVal + delta,
166 aErrMsg + " | got: " + aTestVal + ", expected: " + aExpectedVal);
167 },
169 assertCTMEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck,
170 aErrMsg, aIsTodo) {
171 var foundCTMDifference = false;
172 for (var j in aComponentsToCheck) {
173 var curComponent = aComponentsToCheck[j];
174 if (!aIsTodo) {
175 CTMUtil.isWithinDelta(aLeftCtm[curComponent], aRightCtm[curComponent],
176 aErrMsg + " | component: " + curComponent, false);
177 } else if (aLeftCtm[curComponent] != aRightCtm[curComponent]) {
178 foundCTMDifference = true;
179 }
180 }
182 if (aIsTodo) {
183 todo(!foundCTMDifference, aErrMsg + " | (currently marked todo)");
184 }
185 },
187 assertCTMNotEqual : function(aLeftCtm, aRightCtm, aComponentsToCheck,
188 aErrMsg, aIsTodo) {
189 // CTM should not match initial one
190 var foundCTMDifference = false;
191 for (var j in aComponentsToCheck) {
192 var curComponent = aComponentsToCheck[j];
193 if (aLeftCtm[curComponent] != aRightCtm[curComponent]) {
194 foundCTMDifference = true;
195 break; // We found a difference, as expected. Success!
196 }
197 }
199 if (aIsTodo) {
200 todo(foundCTMDifference, aErrMsg + " | (currently marked todo)");
201 } else {
202 ok(foundCTMDifference, aErrMsg);
203 }
204 },
205 };
208 // Wrapper for timing information
209 function SMILTimingData(aBegin, aDur)
210 {
211 this._begin = aBegin;
212 this._dur = aDur;
213 }
214 SMILTimingData.prototype =
215 {
216 _begin: null,
217 _dur: null,
218 getBeginTime : function() { return this._begin; },
219 getDur : function() { return this._dur; },
220 getEndTime : function() { return this._begin + this._dur; },
221 getFractionalTime : function(aPortion)
222 {
223 return this._begin + aPortion * this._dur;
224 },
225 }
227 /**
228 * Attribute: a container for information about an attribute we'll
229 * attempt to animate with SMIL in our tests.
230 *
231 * See also the factory methods below: NonAnimatableAttribute(),
232 * NonAdditiveAttribute(), and AdditiveAttribute().
233 *
234 * @param aAttrName The name of the attribute
235 * @param aAttrType The type of the attribute ("CSS" vs "XML")
236 * @param aTargetTag The name of an element that this attribute could be
237 * applied to.
238 * @param aIsAnimatable A bool indicating whether this attribute is defined as
239 * animatable in the SVG spec.
240 * @param aIsAdditive A bool indicating whether this attribute is defined as
241 * additive (i.e. supports "by" animation) in the SVG spec.
242 */
243 function Attribute(aAttrName, aAttrType, aTargetTag,
244 aIsAnimatable, aIsAdditive)
245 {
246 this.attrName = aAttrName;
247 this.attrType = aAttrType;
248 this.targetTag = aTargetTag;
249 this.isAnimatable = aIsAnimatable;
250 this.isAdditive = aIsAdditive;
251 }
252 Attribute.prototype =
253 {
254 // Member variables
255 attrName : null,
256 attrType : null,
257 isAnimatable : null,
258 testcaseList : null,
259 };
261 // Generators for Attribute objects. These allow lists of attribute
262 // definitions to be more human-readible than if we were using Attribute() with
263 // boolean flags, e.g. "Attribute(..., true, true), Attribute(..., true, false)
264 function NonAnimatableAttribute(aAttrName, aAttrType, aTargetTag)
265 {
266 return new Attribute(aAttrName, aAttrType, aTargetTag, false, false);
267 }
268 function NonAdditiveAttribute(aAttrName, aAttrType, aTargetTag)
269 {
270 return new Attribute(aAttrName, aAttrType, aTargetTag, true, false);
271 }
272 function AdditiveAttribute(aAttrName, aAttrType, aTargetTag)
273 {
274 return new Attribute(aAttrName, aAttrType, aTargetTag, true, true);
275 }
277 /**
278 * TestcaseBundle: a container for a group of tests for a particular attribute
279 *
280 * @param aAttribute An Attribute object for the attribute
281 * @param aTestcaseList An array of AnimTestcase objects
282 */
283 function TestcaseBundle(aAttribute, aTestcaseList, aSkipReason)
284 {
285 this.animatedAttribute = aAttribute;
286 this.testcaseList = aTestcaseList;
287 this.skipReason = aSkipReason;
288 }
289 TestcaseBundle.prototype =
290 {
291 // Member variables
292 animatedAttribute : null,
293 testcaseList : null,
294 skipReason : null,
296 // Methods
297 go : function(aTimingData) {
298 if (this.skipReason) {
299 todo(false, "Skipping a bundle for '" + this.animatedAttribute.attrName +
300 "' because: " + this.skipReason);
301 } else {
302 // Sanity Check: Bundle should have > 0 testcases
303 if (!this.testcaseList || !this.testcaseList.length) {
304 ok(false, "a bundle for '" + this.animatedAttribute.attrName +
305 "' has no testcases");
306 }
308 var targetElem =
309 SMILUtil.getFirstElemWithTag(this.animatedAttribute.targetTag);
311 if (!targetElem) {
312 ok(false, "Error: can't find an element of type '" +
313 this.animatedAttribute.targetTag +
314 "', so I can't test property '" +
315 this.animatedAttribute.attrName + "'");
316 return;
317 }
319 for (var testcaseIdx in this.testcaseList) {
320 var testcase = this.testcaseList[testcaseIdx];
321 if (testcase.skipReason) {
322 todo(false, "Skipping a testcase for '" +
323 this.animatedAttribute.attrName +
324 "' because: " + testcase.skipReason);
325 } else {
326 testcase.runTest(targetElem, this.animatedAttribute,
327 aTimingData, false);
328 testcase.runTest(targetElem, this.animatedAttribute,
329 aTimingData, true);
330 }
331 }
332 }
333 },
334 };
336 /**
337 * AnimTestcase: an abstract class that represents an animation testcase.
338 * (e.g. a set of "from"/"to" values to test)
339 */
340 function AnimTestcase() {} // abstract => no constructor
341 AnimTestcase.prototype =
342 {
343 // Member variables
344 _animElementTagName : "animate", // Can be overridden for e.g. animateColor
345 computedValMap : null,
346 skipReason : null,
348 // Methods
349 /**
350 * runTest: Runs this AnimTestcase
351 *
352 * @param aTargetElem The node to be targeted in our test animation.
353 * @param aTargetAttr An Attribute object representing the attribute
354 * to be targeted in our test animation.
355 * @param aTimeData A SMILTimingData object with timing information for
356 * our test animation.
357 * @param aIsFreeze If true, indicates that our test animation should use
358 * fill="freeze"; otherwise, we'll default to fill="remove".
359 */
360 runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze)
361 {
362 // SANITY CHECKS
363 if (!SMILUtil.getSVGRoot().animationsPaused()) {
364 ok(false, "Should start each test with animations paused");
365 }
366 if (SMILUtil.getSVGRoot().getCurrentTime() != 0) {
367 ok(false, "Should start each test at time = 0");
368 }
370 // SET UP
371 // Cache initial computed value
372 var baseVal = SMILUtil.getAttributeValue(aTargetElem, aTargetAttr);
374 // Create & append animation element
375 var anim = this.setupAnimationElement(aTargetAttr, aTimeData, aIsFreeze);
376 aTargetElem.appendChild(anim);
378 // Build a list of [seek-time, expectedValue, errorMessage] triplets
379 var seekList = this.buildSeekList(aTargetAttr, baseVal, aTimeData, aIsFreeze);
381 // DO THE ACTUAL TESTING
382 this.seekAndTest(seekList, aTargetElem, aTargetAttr);
384 // CLEAN UP
385 aTargetElem.removeChild(anim);
386 SMILUtil.getSVGRoot().setCurrentTime(0);
387 },
389 // HELPER FUNCTIONS
390 // setupAnimationElement: <animate> element
391 // Subclasses should extend this parent method
392 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
393 {
394 var animElement = document.createElementNS(SVG_NS,
395 this._animElementTagName);
396 animElement.setAttribute("attributeName", aAnimAttr.attrName);
397 animElement.setAttribute("attributeType", aAnimAttr.attrType);
398 animElement.setAttribute("begin", aTimeData.getBeginTime());
399 animElement.setAttribute("dur", aTimeData.getDur());
400 if (aIsFreeze) {
401 animElement.setAttribute("fill", "freeze");
402 }
403 return animElement;
404 },
406 buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
407 {
408 if (!aAnimAttr.isAnimatable) {
409 return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData,
410 "defined as non-animatable in SVG spec");
411 }
412 if (this.computedValMap.noEffect) {
413 return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData,
414 "testcase specified to have no effect");
415 }
416 return this.buildSeekListAnimated(aAnimAttr, aBaseVal,
417 aTimeData, aIsFreeze)
418 },
420 seekAndTest : function(aSeekList, aTargetElem, aTargetAttr)
421 {
422 var svg = document.getElementById("svg");
423 for (var i in aSeekList) {
424 var entry = aSeekList[i];
425 SMILUtil.getSVGRoot().setCurrentTime(entry[0]);
426 is(SMILUtil.getAttributeValue(aTargetElem, aTargetAttr),
427 entry[1], entry[2]);
428 }
429 },
431 // methods that expect to be overridden in subclasses
432 buildSeekListStatic : function(aAnimAttr, aBaseVal,
433 aTimeData, aReasonStatic) {},
434 buildSeekListAnimated : function(aAnimAttr, aBaseVal,
435 aTimeData, aIsFreeze) {},
436 };
439 // Abstract parent class to share code between from-to & from-by testcases.
440 function AnimTestcaseFrom() {} // abstract => no constructor
441 AnimTestcaseFrom.prototype =
442 {
443 // Member variables
444 from : null,
446 // Methods
447 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
448 {
449 // Call super, and then add my own customization
450 var animElem = AnimTestcase.prototype.setupAnimationElement.apply(this,
451 [aAnimAttr, aTimeData, aIsFreeze]);
452 animElem.setAttribute("from", this.from)
453 return animElem;
454 },
456 buildSeekListStatic : function(aAnimAttr, aBaseVal, aTimeData, aReasonStatic)
457 {
458 var seekList = new Array();
459 var msgPrefix = aAnimAttr.attrName +
460 ": shouldn't be affected by animation ";
461 seekList.push([aTimeData.getBeginTime(), aBaseVal,
462 msgPrefix + "(at animation begin) - " + aReasonStatic]);
463 seekList.push([aTimeData.getFractionalTime(1/2), aBaseVal,
464 msgPrefix + "(at animation mid) - " + aReasonStatic]);
465 seekList.push([aTimeData.getEndTime(), aBaseVal,
466 msgPrefix + "(at animation end) - " + aReasonStatic]);
467 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal,
468 msgPrefix + "(after animation end) - " + aReasonStatic]);
469 return seekList;
470 },
472 buildSeekListAnimated : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
473 {
474 var seekList = new Array();
475 var msgPrefix = aAnimAttr.attrName + ": ";
476 if (aTimeData.getBeginTime() > 0.1) {
477 seekList.push([aTimeData.getBeginTime() - 0.1,
478 aBaseVal,
479 msgPrefix + "checking that base value is set " +
480 "before start of animation"]);
481 }
483 seekList.push([aTimeData.getBeginTime(),
484 this.computedValMap.fromComp || this.from,
485 msgPrefix + "checking that 'from' value is set " +
486 "at start of animation"]);
487 seekList.push([aTimeData.getFractionalTime(1/2),
488 this.computedValMap.midComp ||
489 this.computedValMap.toComp || this.to,
490 msgPrefix + "checking value halfway through animation"]);
492 var finalMsg;
493 var expectedEndVal;
494 if (aIsFreeze) {
495 expectedEndVal = this.computedValMap.toComp || this.to;
496 finalMsg = msgPrefix + "[freeze-mode] checking that final value is set ";
497 } else {
498 expectedEndVal = aBaseVal;
499 finalMsg = msgPrefix +
500 "[remove-mode] checking that animation is cleared ";
501 }
502 seekList.push([aTimeData.getEndTime(),
503 expectedEndVal, finalMsg + "at end of animation"]);
504 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
505 expectedEndVal, finalMsg + "after end of animation"]);
506 return seekList;
507 },
508 }
509 extend(AnimTestcaseFrom, AnimTestcase);
511 /*
512 * A testcase for a simple "from-to" animation
513 * @param aFrom The 'from' value
514 * @param aTo The 'to' value
515 * @param aComputedValMap A hash-map that contains some computed values,
516 * if they're needed, as follows:
517 * - fromComp: Computed value version of |aFrom| (if different from |aFrom|)
518 * - midComp: Computed value that we expect to visit halfway through the
519 * animation (if different from |aTo|)
520 * - toComp: Computed value version of |aTo| (if different from |aTo|)
521 * - noEffect: Special flag -- if set, indicates that this testcase is
522 * expected to have no effect on the computed value. (e.g. the
523 * given values are invalid.)
524 * @param aSkipReason If this test-case is known to currently fail, this
525 * parameter should be a string explaining why.
526 * Otherwise, this value should be null (or omitted).
527 *
528 */
529 function AnimTestcaseFromTo(aFrom, aTo, aComputedValMap, aSkipReason)
530 {
531 this.from = aFrom;
532 this.to = aTo;
533 this.computedValMap = aComputedValMap || {}; // Let aComputedValMap be omitted
534 this.skipReason = aSkipReason;
535 }
536 AnimTestcaseFromTo.prototype =
537 {
538 // Member variables
539 to : null,
541 // Methods
542 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
543 {
544 // Call super, and then add my own customization
545 var animElem = AnimTestcaseFrom.prototype.setupAnimationElement.apply(this,
546 [aAnimAttr, aTimeData, aIsFreeze]);
547 animElem.setAttribute("to", this.to)
548 return animElem;
549 },
550 }
551 extend(AnimTestcaseFromTo, AnimTestcaseFrom);
553 /*
554 * A testcase for a simple "from-by" animation.
555 *
556 * @param aFrom The 'from' value
557 * @param aBy The 'by' value
558 * @param aComputedValMap A hash-map that contains some computed values that
559 * we expect to visit, as follows:
560 * - fromComp: Computed value version of |aFrom| (if different from |aFrom|)
561 * - midComp: Computed value that we expect to visit halfway through the
562 * animation (|aFrom| + |aBy|/2)
563 * - toComp: Computed value of the animation endpoint (|aFrom| + |aBy|)
564 * - noEffect: Special flag -- if set, indicates that this testcase is
565 * expected to have no effect on the computed value. (e.g. the
566 * given values are invalid. Or the attribute may be animatable
567 * and additive, but the particular "from" & "by" values that
568 * are used don't support addition.)
569 * @param aSkipReason If this test-case is known to currently fail, this
570 * parameter should be a string explaining why.
571 * Otherwise, this value should be null (or omitted).
572 */
573 function AnimTestcaseFromBy(aFrom, aBy, aComputedValMap, aSkipReason)
574 {
575 this.from = aFrom;
576 this.by = aBy;
577 this.computedValMap = aComputedValMap;
578 this.skipReason = aSkipReason;
579 if (this.computedValMap &&
580 !this.computedValMap.noEffect && !this.computedValMap.toComp) {
581 ok(false, "AnimTestcaseFromBy needs expected computed final value");
582 }
583 }
584 AnimTestcaseFromBy.prototype =
585 {
586 // Member variables
587 by : null,
589 // Methods
590 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
591 {
592 // Call super, and then add my own customization
593 var animElem = AnimTestcaseFrom.prototype.setupAnimationElement.apply(this,
594 [aAnimAttr, aTimeData, aIsFreeze]);
595 animElem.setAttribute("by", this.by)
596 return animElem;
597 },
598 buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
599 {
600 if (!aAnimAttr.isAdditive) {
601 return this.buildSeekListStatic(aAnimAttr, aBaseVal, aTimeData,
602 "defined as non-additive in SVG spec");
603 }
604 // Just use inherited method
605 return AnimTestcaseFrom.prototype.buildSeekList.apply(this,
606 [aAnimAttr, aBaseVal, aTimeData, aIsFreeze]);
607 },
608 }
609 extend(AnimTestcaseFromBy, AnimTestcaseFrom);
611 /*
612 * A testcase for a "paced-mode" animation
613 * @param aValues An array of values, to be used as the "Values" list
614 * @param aComputedValMap A hash-map that contains some computed values,
615 * if they're needed, as follows:
616 * - comp0: The computed value at the start of the animation
617 * - comp1_6: The computed value exactly 1/6 through animation
618 * - comp1_3: The computed value exactly 1/3 through animation
619 * - comp2_3: The computed value exactly 2/3 through animation
620 * - comp1: The computed value of the animation endpoint
621 * The math works out easiest if...
622 * (a) aValuesString has 3 entries in its values list: vA, vB, vC
623 * (b) dist(vB, vC) = 2 * dist(vA, vB)
624 * With this setup, we can come up with expected intermediate values according
625 * to the following rules:
626 * - comp0 should be vA
627 * - comp1_6 should be us halfway between vA and vB
628 * - comp1_3 should be vB
629 * - comp2_3 should be halfway between vB and vC
630 * - comp1 should be vC
631 * @param aSkipReason If this test-case is known to currently fail, this
632 * parameter should be a string explaining why.
633 * Otherwise, this value should be null (or omitted).
634 */
635 function AnimTestcasePaced(aValuesString, aComputedValMap, aSkipReason)
636 {
637 this.valuesString = aValuesString;
638 this.computedValMap = aComputedValMap;
639 this.skipReason = aSkipReason;
640 if (this.computedValMap &&
641 (!this.computedValMap.comp0 ||
642 !this.computedValMap.comp1_6 ||
643 !this.computedValMap.comp1_3 ||
644 !this.computedValMap.comp2_3 ||
645 !this.computedValMap.comp1)) {
646 ok(false, "This AnimTestcasePaced has an incomplete computed value map");
647 }
648 }
649 AnimTestcasePaced.prototype =
650 {
651 // Member variables
652 valuesString : null,
654 // Methods
655 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
656 {
657 // Call super, and then add my own customization
658 var animElem = AnimTestcase.prototype.setupAnimationElement.apply(this,
659 [aAnimAttr, aTimeData, aIsFreeze]);
660 animElem.setAttribute("values", this.valuesString)
661 animElem.setAttribute("calcMode", "paced");
662 return animElem;
663 },
664 buildSeekListAnimated : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
665 {
666 var seekList = new Array();
667 var msgPrefix = aAnimAttr.attrName + ": checking value ";
668 seekList.push([aTimeData.getBeginTime(),
669 this.computedValMap.comp0,
670 msgPrefix + "at start of animation"]);
671 seekList.push([aTimeData.getFractionalTime(1/6),
672 this.computedValMap.comp1_6,
673 msgPrefix + "1/6 of the way through animation."]);
674 seekList.push([aTimeData.getFractionalTime(1/3),
675 this.computedValMap.comp1_3,
676 msgPrefix + "1/3 of the way through animation."]);
677 seekList.push([aTimeData.getFractionalTime(2/3),
678 this.computedValMap.comp2_3,
679 msgPrefix + "2/3 of the way through animation."]);
681 var finalMsg;
682 var expectedEndVal;
683 if (aIsFreeze) {
684 expectedEndVal = this.computedValMap.comp1;
685 finalMsg = aAnimAttr.attrName +
686 ": [freeze-mode] checking that final value is set ";
687 } else {
688 expectedEndVal = aBaseVal;
689 finalMsg = aAnimAttr.attrName +
690 ": [remove-mode] checking that animation is cleared ";
691 }
692 seekList.push([aTimeData.getEndTime(),
693 expectedEndVal, finalMsg + "at end of animation"]);
694 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
695 expectedEndVal, finalMsg + "after end of animation"]);
696 return seekList;
697 },
698 buildSeekListStatic : function(aAnimAttr, aBaseVal, aTimeData, aReasonStatic)
699 {
700 var seekList = new Array();
701 var msgPrefix =
702 aAnimAttr.attrName + ": shouldn't be affected by animation ";
703 seekList.push([aTimeData.getBeginTime(), aBaseVal,
704 msgPrefix + "(at animation begin) - " + aReasonStatic]);
705 seekList.push([aTimeData.getFractionalTime(1/6), aBaseVal,
706 msgPrefix + "(1/6 of the way through animation) - " +
707 aReasonStatic]);
708 seekList.push([aTimeData.getFractionalTime(1/3), aBaseVal,
709 msgPrefix + "(1/3 of the way through animation) - " +
710 aReasonStatic]);
711 seekList.push([aTimeData.getFractionalTime(2/3), aBaseVal,
712 msgPrefix + "(2/3 of the way through animation) - " +
713 aReasonStatic]);
714 seekList.push([aTimeData.getEndTime(), aBaseVal,
715 msgPrefix + "(at animation end) - " + aReasonStatic]);
716 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(), aBaseVal,
717 msgPrefix + "(after animation end) - " + aReasonStatic]);
718 return seekList;
719 },
720 };
721 extend(AnimTestcasePaced, AnimTestcase);
723 /*
724 * A testcase for an <animateMotion> animation.
725 *
726 * @param aAttrValueHash A hash-map mapping attribute names to values.
727 * Should include at least 'path', 'values', 'to'
728 * or 'by' to describe the motion path.
729 * @param aCtmMap A hash-map that contains summaries of the expected resulting
730 * CTM at various points during the animation. The CTM is
731 * summarized as a tuple of three numbers: [tX, tY, theta]
732 (indicating a translate(tX,tY) followed by a rotate(theta))
733 * - ctm0: The CTM summary at the start of the animation
734 * - ctm1_6: The CTM summary at exactly 1/6 through animation
735 * - ctm1_3: The CTM summary at exactly 1/3 through animation
736 * - ctm2_3: The CTM summary at exactly 2/3 through animation
737 * - ctm1: The CTM summary at the animation endpoint
738 *
739 * NOTE: For paced-mode animation (the default for animateMotion), the math
740 * works out easiest if:
741 * (a) our motion path has 3 points: vA, vB, vC
742 * (b) dist(vB, vC) = 2 * dist(vA, vB)
743 * (See discussion in header comment for AnimTestcasePaced.)
744 *
745 * @param aSkipReason If this test-case is known to currently fail, this
746 * parameter should be a string explaining why.
747 * Otherwise, this value should be null (or omitted).
748 */
749 function AnimMotionTestcase(aAttrValueHash, aCtmMap, aSkipReason)
750 {
751 this.attrValueHash = aAttrValueHash;
752 this.ctmMap = aCtmMap;
753 this.skipReason = aSkipReason;
754 if (this.ctmMap &&
755 (!this.ctmMap.ctm0 ||
756 !this.ctmMap.ctm1_6 ||
757 !this.ctmMap.ctm1_3 ||
758 !this.ctmMap.ctm2_3 ||
759 !this.ctmMap.ctm1)) {
760 ok(false, "This AnimMotionTestcase has an incomplete CTM map");
761 }
762 }
763 AnimMotionTestcase.prototype =
764 {
765 // Member variables
766 _animElementTagName : "animateMotion",
768 // Implementations of inherited methods that we need to override:
769 // --------------------------------------------------------------
770 setupAnimationElement : function(aAnimAttr, aTimeData, aIsFreeze)
771 {
772 var animElement = document.createElementNS(SVG_NS,
773 this._animElementTagName);
774 animElement.setAttribute("begin", aTimeData.getBeginTime());
775 animElement.setAttribute("dur", aTimeData.getDur());
776 if (aIsFreeze) {
777 animElement.setAttribute("fill", "freeze");
778 }
779 for (var attrName in this.attrValueHash) {
780 if (attrName == "mpath") {
781 this.createPath(this.attrValueHash[attrName]);
782 this.createMpath(animElement);
783 } else {
784 animElement.setAttribute(attrName, this.attrValueHash[attrName]);
785 }
786 }
787 return animElement;
788 },
790 createPath : function(aPathDescription)
791 {
792 var path = document.createElementNS(SVG_NS, "path");
793 path.setAttribute("d", aPathDescription);
794 path.setAttribute("id", MPATH_TARGET_ID);
795 return SMILUtil.getSVGRoot().appendChild(path);
796 },
798 createMpath : function(aAnimElement)
799 {
800 var mpath = document.createElementNS(SVG_NS, "mpath");
801 mpath.setAttributeNS(XLINK_NS, "href", "#" + MPATH_TARGET_ID);
802 return aAnimElement.appendChild(mpath);
803 },
805 // Override inherited seekAndTest method since...
806 // (a) it expects a computedValMap and we have a computed-CTM map instead
807 // and (b) it expects we might have no effect (for non-animatable attrs)
808 buildSeekList : function(aAnimAttr, aBaseVal, aTimeData, aIsFreeze)
809 {
810 var seekList = new Array();
811 var msgPrefix = "CTM mismatch ";
812 seekList.push([aTimeData.getBeginTime(),
813 CTMUtil.generateCTM(this.ctmMap.ctm0),
814 msgPrefix + "at start of animation"]);
815 seekList.push([aTimeData.getFractionalTime(1/6),
816 CTMUtil.generateCTM(this.ctmMap.ctm1_6),
817 msgPrefix + "1/6 of the way through animation."]);
818 seekList.push([aTimeData.getFractionalTime(1/3),
819 CTMUtil.generateCTM(this.ctmMap.ctm1_3),
820 msgPrefix + "1/3 of the way through animation."]);
821 seekList.push([aTimeData.getFractionalTime(2/3),
822 CTMUtil.generateCTM(this.ctmMap.ctm2_3),
823 msgPrefix + "2/3 of the way through animation."]);
825 var finalMsg;
826 var expectedEndVal;
827 if (aIsFreeze) {
828 expectedEndVal = CTMUtil.generateCTM(this.ctmMap.ctm1);
829 finalMsg = aAnimAttr.attrName +
830 ": [freeze-mode] checking that final value is set ";
831 } else {
832 expectedEndVal = aBaseVal;
833 finalMsg = aAnimAttr.attrName +
834 ": [remove-mode] checking that animation is cleared ";
835 }
836 seekList.push([aTimeData.getEndTime(),
837 expectedEndVal, finalMsg + "at end of animation"]);
838 seekList.push([aTimeData.getEndTime() + aTimeData.getDur(),
839 expectedEndVal, finalMsg + "after end of animation"]);
840 return seekList;
841 },
843 // Override inherited seekAndTest method
844 // (Have to use assertCTMEqual() instead of is() for comparison, to check each
845 // component of the CTM and to allow for a small margin of error.)
846 seekAndTest : function(aSeekList, aTargetElem, aTargetAttr)
847 {
848 var svg = document.getElementById("svg");
849 for (var i in aSeekList) {
850 var entry = aSeekList[i];
851 SMILUtil.getSVGRoot().setCurrentTime(entry[0]);
852 CTMUtil.assertCTMEqual(aTargetElem.getCTM(), entry[1],
853 CTMUtil.CTM_COMPONENTS_ALL, entry[2], false);
854 }
855 },
857 // Override "runTest" method so we can remove any <path> element that we
858 // created at the end of each test.
859 runTest : function(aTargetElem, aTargetAttr, aTimeData, aIsFreeze)
860 {
861 AnimTestcase.prototype.runTest.apply(this,
862 [aTargetElem, aTargetAttr, aTimeData, aIsFreeze]);
863 var pathElem = document.getElementById(MPATH_TARGET_ID);
864 if (pathElem) {
865 SMILUtil.getSVGRoot().removeChild(pathElem);
866 }
867 }
868 };
869 extend(AnimMotionTestcase, AnimTestcase);
871 // MAIN METHOD
872 function testBundleList(aBundleList, aTimingData)
873 {
874 for (var bundleIdx in aBundleList) {
875 aBundleList[bundleIdx].go(aTimingData);
876 }
877 }