dom/smil/test/smilTestUtils.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

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 }

mercurial