LCOV - code coverage report
Current view: top level - dom/smil - nsSMILAnimationFunction.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 0 447 0.0 %
Date: 2017-07-14 16:53:18 Functions: 0 41 0.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
       2             : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
       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/. */
       6             : 
       7             : #include "nsSMILAnimationFunction.h"
       8             : 
       9             : #include "mozilla/dom/SVGAnimationElement.h"
      10             : #include "mozilla/Move.h"
      11             : #include "nsISMILAttr.h"
      12             : #include "nsSMILCSSValueType.h"
      13             : #include "nsSMILParserUtils.h"
      14             : #include "nsSMILNullType.h"
      15             : #include "nsSMILTimedElement.h"
      16             : #include "nsAttrValueInlines.h"
      17             : #include "nsGkAtoms.h"
      18             : #include "nsCOMPtr.h"
      19             : #include "nsCOMArray.h"
      20             : #include "nsIContent.h"
      21             : #include "nsContentUtils.h"
      22             : #include "nsReadableUtils.h"
      23             : #include "nsString.h"
      24             : #include <math.h>
      25             : #include <algorithm>
      26             : 
      27             : using namespace mozilla::dom;
      28             : 
      29             : //----------------------------------------------------------------------
      30             : // Static members
      31             : 
      32             : nsAttrValue::EnumTable nsSMILAnimationFunction::sAccumulateTable[] = {
      33             :       {"none", false},
      34             :       {"sum", true},
      35             :       {nullptr, 0}
      36             : };
      37             : 
      38             : nsAttrValue::EnumTable nsSMILAnimationFunction::sAdditiveTable[] = {
      39             :       {"replace", false},
      40             :       {"sum", true},
      41             :       {nullptr, 0}
      42             : };
      43             : 
      44             : nsAttrValue::EnumTable nsSMILAnimationFunction::sCalcModeTable[] = {
      45             :       {"linear", CALC_LINEAR},
      46             :       {"discrete", CALC_DISCRETE},
      47             :       {"paced", CALC_PACED},
      48             :       {"spline", CALC_SPLINE},
      49             :       {nullptr, 0}
      50             : };
      51             : 
      52             : // Any negative number should be fine as a sentinel here,
      53             : // because valid distances are non-negative.
      54             : #define COMPUTE_DISTANCE_ERROR (-1)
      55             : 
      56             : //----------------------------------------------------------------------
      57             : // Constructors etc.
      58             : 
      59           0 : nsSMILAnimationFunction::nsSMILAnimationFunction()
      60             :   : mSampleTime(-1),
      61             :     mRepeatIteration(0),
      62             :     mBeginTime(INT64_MIN),
      63             :     mAnimationElement(nullptr),
      64             :     mErrorFlags(0),
      65             :     mIsActive(false),
      66             :     mIsFrozen(false),
      67             :     mLastValue(false),
      68             :     mHasChanged(true),
      69             :     mValueNeedsReparsingEverySample(false),
      70             :     mPrevSampleWasSingleValueAnimation(false),
      71           0 :     mWasSkippedInPrevSample(false)
      72             : {
      73           0 : }
      74             : 
      75             : void
      76           0 : nsSMILAnimationFunction::SetAnimationElement(
      77             :     SVGAnimationElement* aAnimationElement)
      78             : {
      79           0 :   mAnimationElement = aAnimationElement;
      80           0 : }
      81             : 
      82             : bool
      83           0 : nsSMILAnimationFunction::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
      84             :                                  nsAttrValue& aResult, nsresult* aParseResult)
      85             : {
      86           0 :   bool foundMatch = true;
      87           0 :   nsresult parseResult = NS_OK;
      88             : 
      89             :   // The attributes 'by', 'from', 'to', and 'values' may be parsed differently
      90             :   // depending on the element & attribute we're animating.  So instead of
      91             :   // parsing them now we re-parse them at every sample.
      92           0 :   if (aAttribute == nsGkAtoms::by ||
      93           0 :       aAttribute == nsGkAtoms::from ||
      94           0 :       aAttribute == nsGkAtoms::to ||
      95           0 :       aAttribute == nsGkAtoms::values) {
      96             :     // We parse to, from, by, values at sample time.
      97             :     // XXX Need to flag which attribute has changed and then when we parse it at
      98             :     // sample time, report any errors and reset the flag
      99           0 :     mHasChanged = true;
     100           0 :     aResult.SetTo(aValue);
     101           0 :   } else if (aAttribute == nsGkAtoms::accumulate) {
     102           0 :     parseResult = SetAccumulate(aValue, aResult);
     103           0 :   } else if (aAttribute == nsGkAtoms::additive) {
     104           0 :     parseResult = SetAdditive(aValue, aResult);
     105           0 :   } else if (aAttribute == nsGkAtoms::calcMode) {
     106           0 :     parseResult = SetCalcMode(aValue, aResult);
     107           0 :   } else if (aAttribute == nsGkAtoms::keyTimes) {
     108           0 :     parseResult = SetKeyTimes(aValue, aResult);
     109           0 :   } else if (aAttribute == nsGkAtoms::keySplines) {
     110           0 :     parseResult = SetKeySplines(aValue, aResult);
     111             :   } else {
     112           0 :     foundMatch = false;
     113             :   }
     114             : 
     115           0 :   if (foundMatch && aParseResult) {
     116           0 :     *aParseResult = parseResult;
     117             :   }
     118             : 
     119           0 :   return foundMatch;
     120             : }
     121             : 
     122             : bool
     123           0 : nsSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
     124             : {
     125           0 :   bool foundMatch = true;
     126             : 
     127           0 :   if (aAttribute == nsGkAtoms::by ||
     128           0 :       aAttribute == nsGkAtoms::from ||
     129           0 :       aAttribute == nsGkAtoms::to ||
     130           0 :       aAttribute == nsGkAtoms::values) {
     131           0 :     mHasChanged = true;
     132           0 :   } else if (aAttribute == nsGkAtoms::accumulate) {
     133           0 :     UnsetAccumulate();
     134           0 :   } else if (aAttribute == nsGkAtoms::additive) {
     135           0 :     UnsetAdditive();
     136           0 :   } else if (aAttribute == nsGkAtoms::calcMode) {
     137           0 :     UnsetCalcMode();
     138           0 :   } else if (aAttribute == nsGkAtoms::keyTimes) {
     139           0 :     UnsetKeyTimes();
     140           0 :   } else if (aAttribute == nsGkAtoms::keySplines) {
     141           0 :     UnsetKeySplines();
     142             :   } else {
     143           0 :     foundMatch = false;
     144             :   }
     145             : 
     146           0 :   return foundMatch;
     147             : }
     148             : 
     149             : void
     150           0 : nsSMILAnimationFunction::SampleAt(nsSMILTime aSampleTime,
     151             :                                   const nsSMILTimeValue& aSimpleDuration,
     152             :                                   uint32_t aRepeatIteration)
     153             : {
     154             :   // * Update mHasChanged ("Might this sample be different from prev one?")
     155             :   // Were we previously sampling a fill="freeze" final val? (We're not anymore.)
     156           0 :   mHasChanged |= mLastValue;
     157             : 
     158             :   // Are we sampling at a new point in simple duration? And does that matter?
     159           0 :   mHasChanged |=
     160           0 :     (mSampleTime != aSampleTime || mSimpleDuration != aSimpleDuration) &&
     161           0 :     !IsValueFixedForSimpleDuration();
     162             : 
     163             :   // Are we on a new repeat and accumulating across repeats?
     164           0 :   if (!mErrorFlags) { // (can't call GetAccumulate() if we've had parse errors)
     165           0 :     mHasChanged |= (mRepeatIteration != aRepeatIteration) && GetAccumulate();
     166             :   }
     167             : 
     168           0 :   mSampleTime       = aSampleTime;
     169           0 :   mSimpleDuration   = aSimpleDuration;
     170           0 :   mRepeatIteration  = aRepeatIteration;
     171           0 :   mLastValue        = false;
     172           0 : }
     173             : 
     174             : void
     175           0 : nsSMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration)
     176             : {
     177           0 :   if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) {
     178           0 :     mHasChanged = true;
     179             :   }
     180             : 
     181           0 :   mRepeatIteration  = aRepeatIteration;
     182           0 :   mLastValue        = true;
     183           0 : }
     184             : 
     185             : void
     186           0 : nsSMILAnimationFunction::Activate(nsSMILTime aBeginTime)
     187             : {
     188           0 :   mBeginTime = aBeginTime;
     189           0 :   mIsActive = true;
     190           0 :   mIsFrozen = false;
     191           0 :   mHasChanged = true;
     192           0 : }
     193             : 
     194             : void
     195           0 : nsSMILAnimationFunction::Inactivate(bool aIsFrozen)
     196             : {
     197           0 :   mIsActive = false;
     198           0 :   mIsFrozen = aIsFrozen;
     199           0 :   mHasChanged = true;
     200           0 : }
     201             : 
     202             : void
     203           0 : nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
     204             :                                        nsSMILValue& aResult)
     205             : {
     206           0 :   mHasChanged = false;
     207           0 :   mPrevSampleWasSingleValueAnimation = false;
     208           0 :   mWasSkippedInPrevSample = false;
     209             : 
     210             :   // Skip animations that are inactive or in error
     211           0 :   if (!IsActiveOrFrozen() || mErrorFlags != 0)
     212           0 :     return;
     213             : 
     214             :   // Get the animation values
     215           0 :   nsSMILValueArray values;
     216           0 :   nsresult rv = GetValues(aSMILAttr, values);
     217           0 :   if (NS_FAILED(rv))
     218           0 :     return;
     219             : 
     220             :   // Check that we have the right number of keySplines and keyTimes
     221           0 :   CheckValueListDependentAttrs(values.Length());
     222           0 :   if (mErrorFlags != 0)
     223           0 :     return;
     224             : 
     225             :   // If this interval is active, we must have a non-negative mSampleTime
     226           0 :   MOZ_ASSERT(mSampleTime >= 0 || !mIsActive,
     227             :              "Negative sample time for active animation");
     228           0 :   MOZ_ASSERT(mSimpleDuration.IsResolved() || mLastValue,
     229             :              "Unresolved simple duration for active or frozen animation");
     230             : 
     231             :   // If we want to add but don't have a base value then just fail outright.
     232             :   // This can happen when we skipped getting the base value because there's an
     233             :   // animation function in the sandwich that should replace it but that function
     234             :   // failed unexpectedly.
     235           0 :   bool isAdditive = IsAdditive();
     236           0 :   if (isAdditive && aResult.IsNull())
     237           0 :     return;
     238             : 
     239           0 :   nsSMILValue result;
     240             : 
     241           0 :   if (values.Length() == 1 && !IsToAnimation()) {
     242             : 
     243             :     // Single-valued animation
     244           0 :     result = values[0];
     245           0 :     mPrevSampleWasSingleValueAnimation = true;
     246             : 
     247           0 :   } else if (mLastValue) {
     248             : 
     249             :     // Sampling last value
     250           0 :     const nsSMILValue& last = values[values.Length() - 1];
     251           0 :     result = last;
     252             : 
     253             :     // See comment in AccumulateResult: to-animation does not accumulate
     254           0 :     if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
     255             :       // If the target attribute type doesn't support addition Add will
     256             :       // fail leaving result = last
     257           0 :       result.Add(last, mRepeatIteration);
     258             :     }
     259             : 
     260             :   } else {
     261             : 
     262             :     // Interpolation
     263           0 :     if (NS_FAILED(InterpolateResult(values, result, aResult)))
     264           0 :       return;
     265             : 
     266           0 :     if (NS_FAILED(AccumulateResult(values, result)))
     267           0 :       return;
     268             :   }
     269             : 
     270             :   // If additive animation isn't required or isn't supported, set the value.
     271           0 :   if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) {
     272           0 :     aResult = Move(result);
     273             :   }
     274             : }
     275             : 
     276             : int8_t
     277           0 : nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const
     278             : {
     279           0 :   NS_ENSURE_TRUE(aOther, 0);
     280             : 
     281           0 :   NS_ASSERTION(aOther != this, "Trying to compare to self");
     282             : 
     283             :   // Inactive animations sort first
     284           0 :   if (!IsActiveOrFrozen() && aOther->IsActiveOrFrozen())
     285           0 :     return -1;
     286             : 
     287           0 :   if (IsActiveOrFrozen() && !aOther->IsActiveOrFrozen())
     288           0 :     return 1;
     289             : 
     290             :   // Sort based on begin time
     291           0 :   if (mBeginTime != aOther->GetBeginTime())
     292           0 :     return mBeginTime > aOther->GetBeginTime() ? 1 : -1;
     293             : 
     294             :   // Next sort based on syncbase dependencies: the dependent element sorts after
     295             :   // its syncbase
     296             :   const nsSMILTimedElement& thisTimedElement =
     297           0 :     mAnimationElement->TimedElement();
     298             :   const nsSMILTimedElement& otherTimedElement =
     299           0 :     aOther->mAnimationElement->TimedElement();
     300           0 :   if (thisTimedElement.IsTimeDependent(otherTimedElement))
     301           0 :     return 1;
     302           0 :   if (otherTimedElement.IsTimeDependent(thisTimedElement))
     303           0 :     return -1;
     304             : 
     305             :   // Animations that appear later in the document sort after those earlier in
     306             :   // the document
     307           0 :   MOZ_ASSERT(mAnimationElement != aOther->mAnimationElement,
     308             :              "Two animations cannot have the same animation content element!");
     309             : 
     310           0 :   return (nsContentUtils::PositionIsBefore(mAnimationElement, aOther->mAnimationElement))
     311           0 :           ? -1 : 1;
     312             : }
     313             : 
     314             : bool
     315           0 : nsSMILAnimationFunction::WillReplace() const
     316             : {
     317             :   /*
     318             :    * In IsAdditive() we don't consider to-animation to be additive as it is
     319             :    * a special case that is dealt with differently in the compositing method.
     320             :    * Here, however, we return FALSE for to-animation (i.e. it will NOT replace
     321             :    * the underlying value) as it builds on the underlying value.
     322             :    */
     323           0 :   return !mErrorFlags && !(IsAdditive() || IsToAnimation());
     324             : }
     325             : 
     326             : bool
     327           0 : nsSMILAnimationFunction::HasChanged() const
     328             : {
     329           0 :   return mHasChanged || mValueNeedsReparsingEverySample;
     330             : }
     331             : 
     332             : bool
     333           0 : nsSMILAnimationFunction::UpdateCachedTarget(
     334             :   const nsSMILTargetIdentifier& aNewTarget)
     335             : {
     336           0 :   if (!mLastTarget.Equals(aNewTarget)) {
     337           0 :     mLastTarget = aNewTarget;
     338           0 :     return true;
     339             :   }
     340           0 :   return false;
     341             : }
     342             : 
     343             : //----------------------------------------------------------------------
     344             : // Implementation helpers
     345             : 
     346             : nsresult
     347           0 : nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
     348             :                                            nsSMILValue& aResult,
     349             :                                            nsSMILValue& aBaseValue)
     350             : {
     351             :   // Sanity check animation values
     352           0 :   if ((!IsToAnimation() && aValues.Length() < 2) ||
     353           0 :       (IsToAnimation()  && aValues.Length() != 1)) {
     354           0 :     NS_ERROR("Unexpected number of values");
     355           0 :     return NS_ERROR_FAILURE;
     356             :   }
     357             : 
     358           0 :   if (IsToAnimation() && aBaseValue.IsNull()) {
     359           0 :     return NS_ERROR_FAILURE;
     360             :   }
     361             : 
     362             :   // Get the normalised progress through the simple duration.
     363             :   //
     364             :   // If we have an indefinite simple duration, just set the progress to be
     365             :   // 0 which will give us the expected behaviour of the animation being fixed at
     366             :   // its starting point.
     367           0 :   double simpleProgress = 0.0;
     368             : 
     369           0 :   if (mSimpleDuration.IsDefinite()) {
     370           0 :     nsSMILTime dur = mSimpleDuration.GetMillis();
     371             : 
     372           0 :     MOZ_ASSERT(dur >= 0, "Simple duration should not be negative");
     373           0 :     MOZ_ASSERT(mSampleTime >= 0, "Sample time should not be negative");
     374             : 
     375           0 :     if (mSampleTime >= dur || mSampleTime < 0) {
     376           0 :       NS_ERROR("Animation sampled outside interval");
     377           0 :       return NS_ERROR_FAILURE;
     378             :     }
     379             : 
     380           0 :     if (dur > 0) {
     381           0 :       simpleProgress = (double)mSampleTime / dur;
     382             :     } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
     383             :   }
     384             : 
     385           0 :   nsresult rv = NS_OK;
     386           0 :   nsSMILCalcMode calcMode = GetCalcMode();
     387             : 
     388             :   // Force discrete calcMode for visibility since StyleAnimationValue will
     389             :   // try to interpolate it using the special clamping behavior defined for
     390             :   // CSS.
     391           0 :   if (nsSMILCSSValueType::PropertyFromValue(aValues[0])
     392             :         == eCSSProperty_visibility) {
     393           0 :     calcMode = CALC_DISCRETE;
     394             :   }
     395             : 
     396           0 :   if (calcMode != CALC_DISCRETE) {
     397             :     // Get the normalised progress between adjacent values
     398           0 :     const nsSMILValue* from = nullptr;
     399           0 :     const nsSMILValue* to = nullptr;
     400             :     // Init to -1 to make sure that if we ever forget to set this, the
     401             :     // MOZ_ASSERT that tests that intervalProgress is in range will fail.
     402           0 :     double intervalProgress = -1.f;
     403           0 :     if (IsToAnimation()) {
     404           0 :       from = &aBaseValue;
     405           0 :       to = &aValues[0];
     406           0 :       if (calcMode == CALC_PACED) {
     407             :         // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
     408           0 :         intervalProgress = simpleProgress;
     409             :       } else {
     410             :         double scaledSimpleProgress =
     411           0 :           ScaleSimpleProgress(simpleProgress, calcMode);
     412           0 :         intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
     413             :       }
     414           0 :     } else if (calcMode == CALC_PACED) {
     415             :       rv = ComputePacedPosition(aValues, simpleProgress,
     416           0 :                                 intervalProgress, from, to);
     417             :       // Note: If the above call fails, we'll skip the "from->Interpolate"
     418             :       // call below, and we'll drop into the CALC_DISCRETE section
     419             :       // instead. (as the spec says we should, because our failure was
     420             :       // presumably due to the values being non-additive)
     421             :     } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
     422             :       double scaledSimpleProgress =
     423           0 :         ScaleSimpleProgress(simpleProgress, calcMode);
     424           0 :       uint32_t index = (uint32_t)floor(scaledSimpleProgress *
     425           0 :                                        (aValues.Length() - 1));
     426           0 :       from = &aValues[index];
     427           0 :       to = &aValues[index + 1];
     428           0 :       intervalProgress =
     429           0 :         scaledSimpleProgress * (aValues.Length() - 1) - index;
     430           0 :       intervalProgress = ScaleIntervalProgress(intervalProgress, index);
     431             :     }
     432             : 
     433           0 :     if (NS_SUCCEEDED(rv)) {
     434           0 :       MOZ_ASSERT(from, "NULL from-value during interpolation");
     435           0 :       MOZ_ASSERT(to, "NULL to-value during interpolation");
     436           0 :       MOZ_ASSERT(0.0f <= intervalProgress && intervalProgress < 1.0f,
     437             :                  "Interval progress should be in the range [0, 1)");
     438           0 :       rv = from->Interpolate(*to, intervalProgress, aResult);
     439             :     }
     440             :   }
     441             : 
     442             :   // Discrete-CalcMode case
     443             :   // Note: If interpolation failed (isn't supported for this type), the SVG
     444             :   // spec says to force discrete mode.
     445           0 :   if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) {
     446             :     double scaledSimpleProgress =
     447           0 :       ScaleSimpleProgress(simpleProgress, CALC_DISCRETE);
     448             : 
     449             :     // Floating-point errors can mean that, for example, a sample time of 29s in
     450             :     // a 100s duration animation gives us a simple progress of 0.28999999999
     451             :     // instead of the 0.29 we'd expect. Normally this isn't a noticeable
     452             :     // problem, but when we have sudden jumps in animation values (such as is
     453             :     // the case here with discrete animation) we can get unexpected results.
     454             :     //
     455             :     // To counteract this, before we perform a floor() on the animation
     456             :     // progress, we add a tiny fudge factor to push us into the correct interval
     457             :     // in cases where floating-point errors might cause us to fall short.
     458             :     static const double kFloatingPointFudgeFactor = 1.0e-16;
     459           0 :     if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) {
     460           0 :       scaledSimpleProgress += kFloatingPointFudgeFactor;
     461             :     }
     462             : 
     463           0 :     if (IsToAnimation()) {
     464             :       // We don't follow SMIL 3, 12.6.4, where discrete to animations
     465             :       // are the same as <set> animations.  Instead, we treat it as a
     466             :       // discrete animation with two values (the underlying value and
     467             :       // the to="" value), and honor keyTimes="" as well.
     468           0 :       uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2);
     469           0 :       aResult = index == 0 ? aBaseValue : aValues[0];
     470             :     } else {
     471           0 :       uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length());
     472           0 :       aResult = aValues[index];
     473             :     }
     474           0 :     rv = NS_OK;
     475             :   }
     476           0 :   return rv;
     477             : }
     478             : 
     479             : nsresult
     480           0 : nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues,
     481             :                                           nsSMILValue& aResult)
     482             : {
     483           0 :   if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
     484           0 :     const nsSMILValue& lastValue = aValues[aValues.Length() - 1];
     485             : 
     486             :     // If the target attribute type doesn't support addition, Add will
     487             :     // fail and we leave aResult untouched.
     488           0 :     aResult.Add(lastValue, mRepeatIteration);
     489             :   }
     490             : 
     491           0 :   return NS_OK;
     492             : }
     493             : 
     494             : /*
     495             :  * Given the simple progress for a paced animation, this method:
     496             :  *  - determines which two elements of the values array we're in between
     497             :  *    (returned as aFrom and aTo)
     498             :  *  - determines where we are between them
     499             :  *    (returned as aIntervalProgress)
     500             :  *
     501             :  * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
     502             :  * computation.
     503             :  */
     504             : nsresult
     505           0 : nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues,
     506             :                                               double aSimpleProgress,
     507             :                                               double& aIntervalProgress,
     508             :                                               const nsSMILValue*& aFrom,
     509             :                                               const nsSMILValue*& aTo)
     510             : {
     511           0 :   NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
     512             :                "aSimpleProgress is out of bounds");
     513           0 :   NS_ASSERTION(GetCalcMode() == CALC_PACED,
     514             :                "Calling paced-specific function, but not in paced mode");
     515           0 :   MOZ_ASSERT(aValues.Length() >= 2, "Unexpected number of values");
     516             : 
     517             :   // Trivial case: If we have just 2 values, then there's only one interval
     518             :   // for us to traverse, and our progress across that interval is the exact
     519             :   // same as our overall progress.
     520           0 :   if (aValues.Length() == 2) {
     521           0 :     aIntervalProgress = aSimpleProgress;
     522           0 :     aFrom = &aValues[0];
     523           0 :     aTo = &aValues[1];
     524           0 :     return NS_OK;
     525             :   }
     526             : 
     527           0 :   double totalDistance = ComputePacedTotalDistance(aValues);
     528           0 :   if (totalDistance == COMPUTE_DISTANCE_ERROR)
     529           0 :     return NS_ERROR_FAILURE;
     530             : 
     531             :   // If we have 0 total distance, then it's unclear where our "paced" position
     532             :   // should be.  We can just fail, which drops us into discrete animation mode.
     533             :   // (That's fine, since our values are apparently indistinguishable anyway.)
     534           0 :   if (totalDistance == 0.0) {
     535           0 :     return NS_ERROR_FAILURE;
     536             :   }
     537             : 
     538             :   // total distance we should have moved at this point in time.
     539             :   // (called 'remainingDist' due to how it's used in loop below)
     540           0 :   double remainingDist = aSimpleProgress * totalDistance;
     541             : 
     542             :   // Must be satisfied, because totalDistance is a sum of (non-negative)
     543             :   // distances, and aSimpleProgress is non-negative
     544           0 :   NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
     545             : 
     546             :   // Find where remainingDist puts us in the list of values
     547             :   // Note: We could optimize this next loop by caching the
     548             :   // interval-distances in an array, but maybe that's excessive.
     549           0 :   for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
     550             :     // Note: The following assertion is valid because remainingDist should
     551             :     // start out non-negative, and this loop never shaves off more than its
     552             :     // current value.
     553           0 :     NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
     554             : 
     555             :     double curIntervalDist;
     556             : 
     557             : #ifdef DEBUG
     558             :     nsresult rv =
     559             : #endif
     560           0 :       aValues[i].ComputeDistance(aValues[i+1], curIntervalDist);
     561           0 :     MOZ_ASSERT(NS_SUCCEEDED(rv),
     562             :                "If we got through ComputePacedTotalDistance, we should "
     563             :                "be able to recompute each sub-distance without errors");
     564             : 
     565           0 :     NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
     566             :     // Clamp distance value at 0, just in case ComputeDistance is evil.
     567           0 :     curIntervalDist = std::max(curIntervalDist, 0.0);
     568             : 
     569           0 :     if (remainingDist >= curIntervalDist) {
     570           0 :       remainingDist -= curIntervalDist;
     571             :     } else {
     572             :       // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
     573             :       // Because this clause is only hit when remainingDist < curIntervalDist,
     574             :       // and if curIntervalDist were 0, that would mean remainingDist would
     575             :       // have to be < 0.  But that can't happen, because remainingDist (as
     576             :       // a distance) is non-negative by definition.
     577           0 :       NS_ASSERTION(curIntervalDist != 0,
     578             :                    "We should never get here with this set to 0...");
     579             : 
     580             :       // We found the right spot -- an interpolated position between
     581             :       // values i and i+1.
     582           0 :       aFrom = &aValues[i];
     583           0 :       aTo = &aValues[i+1];
     584           0 :       aIntervalProgress = remainingDist / curIntervalDist;
     585           0 :       return NS_OK;
     586             :     }
     587             :   }
     588             : 
     589           0 :   NS_NOTREACHED("shouldn't complete loop & get here -- if we do, "
     590             :                 "then aSimpleProgress was probably out of bounds");
     591           0 :   return NS_ERROR_FAILURE;
     592             : }
     593             : 
     594             : /*
     595             :  * Computes the total distance to be travelled by a paced animation.
     596             :  *
     597             :  * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
     598             :  * our values don't support distance computation.
     599             :  */
     600             : double
     601           0 : nsSMILAnimationFunction::ComputePacedTotalDistance(
     602             :     const nsSMILValueArray& aValues) const
     603             : {
     604           0 :   NS_ASSERTION(GetCalcMode() == CALC_PACED,
     605             :                "Calling paced-specific function, but not in paced mode");
     606             : 
     607           0 :   double totalDistance = 0.0;
     608           0 :   for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
     609             :     double tmpDist;
     610           0 :     nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist);
     611           0 :     if (NS_FAILED(rv)) {
     612           0 :       return COMPUTE_DISTANCE_ERROR;
     613             :     }
     614             : 
     615             :     // Clamp distance value to 0, just in case we have an evil ComputeDistance
     616             :     // implementation somewhere
     617           0 :     MOZ_ASSERT(tmpDist >= 0.0f, "distance values must be non-negative");
     618           0 :     tmpDist = std::max(tmpDist, 0.0);
     619             : 
     620           0 :     totalDistance += tmpDist;
     621             :   }
     622             : 
     623           0 :   return totalDistance;
     624             : }
     625             : 
     626             : double
     627           0 : nsSMILAnimationFunction::ScaleSimpleProgress(double aProgress,
     628             :                                              nsSMILCalcMode aCalcMode)
     629             : {
     630           0 :   if (!HasAttr(nsGkAtoms::keyTimes))
     631           0 :     return aProgress;
     632             : 
     633           0 :   uint32_t numTimes = mKeyTimes.Length();
     634             : 
     635           0 :   if (numTimes < 2)
     636           0 :     return aProgress;
     637             : 
     638           0 :   uint32_t i = 0;
     639           0 :   for (; i < numTimes - 2 && aProgress >= mKeyTimes[i+1]; ++i) { }
     640             : 
     641           0 :   if (aCalcMode == CALC_DISCRETE) {
     642             :     // discrete calcMode behaviour differs in that each keyTime defines the time
     643             :     // from when the corresponding value is set, and therefore the last value
     644             :     // needn't be 1. So check if we're in the last 'interval', that is, the
     645             :     // space between the final value and 1.0.
     646           0 :     if (aProgress >= mKeyTimes[i+1]) {
     647           0 :       MOZ_ASSERT(i == numTimes - 2,
     648             :                  "aProgress is not in range of the current interval, yet the "
     649             :                  "current interval is not the last bounded interval either.");
     650           0 :       ++i;
     651             :     }
     652           0 :     return (double)i / numTimes;
     653             :   }
     654             : 
     655           0 :   double& intervalStart = mKeyTimes[i];
     656           0 :   double& intervalEnd   = mKeyTimes[i+1];
     657             : 
     658           0 :   double intervalLength = intervalEnd - intervalStart;
     659           0 :   if (intervalLength <= 0.0)
     660           0 :     return intervalStart;
     661             : 
     662           0 :   return (i + (aProgress - intervalStart) / intervalLength) /
     663           0 :          double(numTimes - 1);
     664             : }
     665             : 
     666             : double
     667           0 : nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress,
     668             :                                                uint32_t aIntervalIndex)
     669             : {
     670           0 :   if (GetCalcMode() != CALC_SPLINE)
     671           0 :     return aProgress;
     672             : 
     673           0 :   if (!HasAttr(nsGkAtoms::keySplines))
     674           0 :     return aProgress;
     675             : 
     676           0 :   MOZ_ASSERT(aIntervalIndex < mKeySplines.Length(),
     677             :              "Invalid interval index");
     678             : 
     679           0 :   nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
     680           0 :   return spline.GetSplineValue(aProgress);
     681             : }
     682             : 
     683             : bool
     684           0 : nsSMILAnimationFunction::HasAttr(nsIAtom* aAttName) const
     685             : {
     686           0 :   return mAnimationElement->HasAnimAttr(aAttName);
     687             : }
     688             : 
     689             : const nsAttrValue*
     690           0 : nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName) const
     691             : {
     692           0 :   return mAnimationElement->GetAnimAttr(aAttName);
     693             : }
     694             : 
     695             : bool
     696           0 : nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const
     697             : {
     698           0 :   return mAnimationElement->GetAnimAttr(aAttName, aResult);
     699             : }
     700             : 
     701             : /*
     702             :  * A utility function to make querying an attribute that corresponds to an
     703             :  * nsSMILValue a little neater.
     704             :  *
     705             :  * @param aAttName    The attribute name (in the global namespace).
     706             :  * @param aSMILAttr   The SMIL attribute to perform the parsing.
     707             :  * @param[out] aResult        The resulting nsSMILValue.
     708             :  * @param[out] aPreventCachingOfSandwich
     709             :  *                    If |aResult| contains dependencies on its context that
     710             :  *                    should prevent the result of the animation sandwich from
     711             :  *                    being cached and reused in future samples (as reported
     712             :  *                    by nsISMILAttr::ValueFromString), then this outparam
     713             :  *                    will be set to true. Otherwise it is left unmodified.
     714             :  *
     715             :  * Returns false if a parse error occurred, otherwise returns true.
     716             :  */
     717             : bool
     718           0 : nsSMILAnimationFunction::ParseAttr(nsIAtom* aAttName,
     719             :                                    const nsISMILAttr& aSMILAttr,
     720             :                                    nsSMILValue& aResult,
     721             :                                    bool& aPreventCachingOfSandwich) const
     722             : {
     723           0 :   nsAutoString attValue;
     724           0 :   if (GetAttr(aAttName, attValue)) {
     725           0 :     bool preventCachingOfSandwich = false;
     726           0 :     nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement,
     727           0 :                                             aResult, preventCachingOfSandwich);
     728           0 :     if (NS_FAILED(rv))
     729           0 :       return false;
     730             : 
     731           0 :     if (preventCachingOfSandwich) {
     732           0 :       aPreventCachingOfSandwich = true;
     733             :     }
     734             :   }
     735           0 :   return true;
     736             : }
     737             : 
     738             : /*
     739             :  * SMILANIM specifies the following rules for animation function values:
     740             :  *
     741             :  * (1) if values is set, it overrides everything
     742             :  * (2) for from/to/by animation at least to or by must be specified, from on its
     743             :  *     own (or nothing) is an error--which we will ignore
     744             :  * (3) if both by and to are specified only to will be used, by will be ignored
     745             :  * (4) if by is specified without from (by animation), forces additive behaviour
     746             :  * (5) if to is specified without from (to animation), special care needs to be
     747             :  *     taken when compositing animation as such animations are composited last.
     748             :  *
     749             :  * This helper method applies these rules to fill in the values list and to set
     750             :  * some internal state.
     751             :  */
     752             : nsresult
     753           0 : nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
     754             :                                    nsSMILValueArray& aResult)
     755             : {
     756           0 :   if (!mAnimationElement)
     757           0 :     return NS_ERROR_FAILURE;
     758             : 
     759           0 :   mValueNeedsReparsingEverySample = false;
     760           0 :   nsSMILValueArray result;
     761             : 
     762             :   // If "values" is set, use it
     763           0 :   if (HasAttr(nsGkAtoms::values)) {
     764           0 :     nsAutoString attValue;
     765           0 :     GetAttr(nsGkAtoms::values, attValue);
     766           0 :     bool preventCachingOfSandwich = false;
     767           0 :     if (!nsSMILParserUtils::ParseValues(attValue, mAnimationElement,
     768             :                                         aSMILAttr, result,
     769             :                                         preventCachingOfSandwich)) {
     770           0 :       return NS_ERROR_FAILURE;
     771             :     }
     772             : 
     773           0 :     if (preventCachingOfSandwich) {
     774           0 :       mValueNeedsReparsingEverySample = true;
     775             :     }
     776             :   // Else try to/from/by
     777             :   } else {
     778           0 :     bool preventCachingOfSandwich = false;
     779           0 :     bool parseOk = true;
     780           0 :     nsSMILValue to, from, by;
     781           0 :     parseOk &= ParseAttr(nsGkAtoms::to,   aSMILAttr, to,
     782           0 :                          preventCachingOfSandwich);
     783           0 :     parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from,
     784           0 :                          preventCachingOfSandwich);
     785           0 :     parseOk &= ParseAttr(nsGkAtoms::by,   aSMILAttr, by,
     786           0 :                          preventCachingOfSandwich);
     787             : 
     788           0 :     if (preventCachingOfSandwich) {
     789           0 :       mValueNeedsReparsingEverySample = true;
     790             :     }
     791             : 
     792           0 :     if (!parseOk || !result.SetCapacity(2, mozilla::fallible)) {
     793           0 :       return NS_ERROR_FAILURE;
     794             :     }
     795             : 
     796             :     // AppendElement() below must succeed, because SetCapacity() succeeded.
     797           0 :     if (!to.IsNull()) {
     798           0 :       if (!from.IsNull()) {
     799           0 :         MOZ_ALWAYS_TRUE(result.AppendElement(from, mozilla::fallible));
     800           0 :         MOZ_ALWAYS_TRUE(result.AppendElement(to, mozilla::fallible));
     801             :       } else {
     802           0 :         MOZ_ALWAYS_TRUE(result.AppendElement(to, mozilla::fallible));
     803             :       }
     804           0 :     } else if (!by.IsNull()) {
     805           0 :       nsSMILValue effectiveFrom(by.mType);
     806           0 :       if (!from.IsNull())
     807           0 :         effectiveFrom = from;
     808             :       // Set values to 'from; from + by'
     809           0 :       MOZ_ALWAYS_TRUE(result.AppendElement(effectiveFrom, mozilla::fallible));
     810           0 :       nsSMILValue effectiveTo(effectiveFrom);
     811           0 :       if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
     812           0 :         MOZ_ALWAYS_TRUE(result.AppendElement(effectiveTo, mozilla::fallible));
     813             :       } else {
     814             :         // Using by-animation with non-additive type or bad base-value
     815           0 :         return NS_ERROR_FAILURE;
     816             :       }
     817             :     } else {
     818             :       // No values, no to, no by -- call it a day
     819           0 :       return NS_ERROR_FAILURE;
     820             :     }
     821             :   }
     822             : 
     823           0 :   result.SwapElements(aResult);
     824             : 
     825           0 :   return NS_OK;
     826             : }
     827             : 
     828             : void
     829           0 : nsSMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues)
     830             : {
     831           0 :   CheckKeyTimes(aNumValues);
     832           0 :   CheckKeySplines(aNumValues);
     833           0 : }
     834             : 
     835             : /**
     836             :  * Performs checks for the keyTimes attribute required by the SMIL spec but
     837             :  * which depend on other attributes and therefore needs to be updated as
     838             :  * dependent attributes are set.
     839             :  */
     840             : void
     841           0 : nsSMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues)
     842             : {
     843           0 :   if (!HasAttr(nsGkAtoms::keyTimes))
     844           0 :     return;
     845             : 
     846           0 :   nsSMILCalcMode calcMode = GetCalcMode();
     847             : 
     848             :   // attribute is ignored for calcMode = paced
     849           0 :   if (calcMode == CALC_PACED) {
     850           0 :     SetKeyTimesErrorFlag(false);
     851           0 :     return;
     852             :   }
     853             : 
     854           0 :   uint32_t numKeyTimes = mKeyTimes.Length();
     855           0 :   if (numKeyTimes < 1) {
     856             :     // keyTimes isn't set or failed preliminary checks
     857           0 :     SetKeyTimesErrorFlag(true);
     858           0 :     return;
     859             :   }
     860             : 
     861             :   // no. keyTimes == no. values
     862             :   // For to-animation the number of values is considered to be 2.
     863             :   bool matchingNumOfValues =
     864           0 :     numKeyTimes == (IsToAnimation() ? 2 : aNumValues);
     865           0 :   if (!matchingNumOfValues) {
     866           0 :     SetKeyTimesErrorFlag(true);
     867           0 :     return;
     868             :   }
     869             : 
     870             :   // first value must be 0
     871           0 :   if (mKeyTimes[0] != 0.0) {
     872           0 :     SetKeyTimesErrorFlag(true);
     873           0 :     return;
     874             :   }
     875             : 
     876             :   // last value must be 1 for linear or spline calcModes
     877           0 :   if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
     878           0 :       mKeyTimes[numKeyTimes - 1] != 1.0) {
     879           0 :     SetKeyTimesErrorFlag(true);
     880           0 :     return;
     881             :   }
     882             : 
     883           0 :   SetKeyTimesErrorFlag(false);
     884             : }
     885             : 
     886             : void
     887           0 : nsSMILAnimationFunction::CheckKeySplines(uint32_t aNumValues)
     888             : {
     889             :   // attribute is ignored if calc mode is not spline
     890           0 :   if (GetCalcMode() != CALC_SPLINE) {
     891           0 :     SetKeySplinesErrorFlag(false);
     892           0 :     return;
     893             :   }
     894             : 
     895             :   // calc mode is spline but the attribute is not set
     896           0 :   if (!HasAttr(nsGkAtoms::keySplines)) {
     897           0 :     SetKeySplinesErrorFlag(false);
     898           0 :     return;
     899             :   }
     900             : 
     901           0 :   if (mKeySplines.Length() < 1) {
     902             :     // keyTimes isn't set or failed preliminary checks
     903           0 :     SetKeySplinesErrorFlag(true);
     904           0 :     return;
     905             :   }
     906             : 
     907             :   // ignore splines if there's only one value
     908           0 :   if (aNumValues == 1 && !IsToAnimation()) {
     909           0 :     SetKeySplinesErrorFlag(false);
     910           0 :     return;
     911             :   }
     912             : 
     913             :   // no. keySpline specs == no. values - 1
     914           0 :   uint32_t splineSpecs = mKeySplines.Length();
     915           0 :   if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
     916           0 :       (IsToAnimation() && splineSpecs != 1)) {
     917           0 :     SetKeySplinesErrorFlag(true);
     918           0 :     return;
     919             :   }
     920             : 
     921           0 :   SetKeySplinesErrorFlag(false);
     922             : }
     923             : 
     924             : bool
     925           0 : nsSMILAnimationFunction::IsValueFixedForSimpleDuration() const
     926             : {
     927           0 :   return mSimpleDuration.IsIndefinite() ||
     928           0 :     (!mHasChanged && mPrevSampleWasSingleValueAnimation);
     929             : }
     930             : 
     931             : //----------------------------------------------------------------------
     932             : // Property getters
     933             : 
     934             : bool
     935           0 : nsSMILAnimationFunction::GetAccumulate() const
     936             : {
     937           0 :   const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate);
     938           0 :   if (!value)
     939           0 :     return false;
     940             : 
     941           0 :   return value->GetEnumValue();
     942             : }
     943             : 
     944             : bool
     945           0 : nsSMILAnimationFunction::GetAdditive() const
     946             : {
     947           0 :   const nsAttrValue* value = GetAttr(nsGkAtoms::additive);
     948           0 :   if (!value)
     949           0 :     return false;
     950             : 
     951           0 :   return value->GetEnumValue();
     952             : }
     953             : 
     954             : nsSMILAnimationFunction::nsSMILCalcMode
     955           0 : nsSMILAnimationFunction::GetCalcMode() const
     956             : {
     957           0 :   const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
     958           0 :   if (!value)
     959           0 :     return CALC_LINEAR;
     960             : 
     961           0 :   return nsSMILCalcMode(value->GetEnumValue());
     962             : }
     963             : 
     964             : //----------------------------------------------------------------------
     965             : // Property setters / un-setters:
     966             : 
     967             : nsresult
     968           0 : nsSMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate,
     969             :                                        nsAttrValue& aResult)
     970             : {
     971           0 :   mHasChanged = true;
     972             :   bool parseResult =
     973           0 :     aResult.ParseEnumValue(aAccumulate, sAccumulateTable, true);
     974           0 :   SetAccumulateErrorFlag(!parseResult);
     975           0 :   return parseResult ? NS_OK : NS_ERROR_FAILURE;
     976             : }
     977             : 
     978             : void
     979           0 : nsSMILAnimationFunction::UnsetAccumulate()
     980             : {
     981           0 :   SetAccumulateErrorFlag(false);
     982           0 :   mHasChanged = true;
     983           0 : }
     984             : 
     985             : nsresult
     986           0 : nsSMILAnimationFunction::SetAdditive(const nsAString& aAdditive,
     987             :                                      nsAttrValue& aResult)
     988             : {
     989           0 :   mHasChanged = true;
     990             :   bool parseResult
     991           0 :     = aResult.ParseEnumValue(aAdditive, sAdditiveTable, true);
     992           0 :   SetAdditiveErrorFlag(!parseResult);
     993           0 :   return parseResult ? NS_OK : NS_ERROR_FAILURE;
     994             : }
     995             : 
     996             : void
     997           0 : nsSMILAnimationFunction::UnsetAdditive()
     998             : {
     999           0 :   SetAdditiveErrorFlag(false);
    1000           0 :   mHasChanged = true;
    1001           0 : }
    1002             : 
    1003             : nsresult
    1004           0 : nsSMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode,
    1005             :                                      nsAttrValue& aResult)
    1006             : {
    1007           0 :   mHasChanged = true;
    1008             :   bool parseResult
    1009           0 :     = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, true);
    1010           0 :   SetCalcModeErrorFlag(!parseResult);
    1011           0 :   return parseResult ? NS_OK : NS_ERROR_FAILURE;
    1012             : }
    1013             : 
    1014             : void
    1015           0 : nsSMILAnimationFunction::UnsetCalcMode()
    1016             : {
    1017           0 :   SetCalcModeErrorFlag(false);
    1018           0 :   mHasChanged = true;
    1019           0 : }
    1020             : 
    1021             : nsresult
    1022           0 : nsSMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines,
    1023             :                                        nsAttrValue& aResult)
    1024             : {
    1025           0 :   mKeySplines.Clear();
    1026           0 :   aResult.SetTo(aKeySplines);
    1027             : 
    1028           0 :   mHasChanged = true;
    1029             : 
    1030           0 :   if (!nsSMILParserUtils::ParseKeySplines(aKeySplines, mKeySplines)) {
    1031           0 :     mKeySplines.Clear();
    1032           0 :     return NS_ERROR_FAILURE;
    1033             :   }
    1034             : 
    1035           0 :   return NS_OK;
    1036             : }
    1037             : 
    1038             : void
    1039           0 : nsSMILAnimationFunction::UnsetKeySplines()
    1040             : {
    1041           0 :   mKeySplines.Clear();
    1042           0 :   SetKeySplinesErrorFlag(false);
    1043           0 :   mHasChanged = true;
    1044           0 : }
    1045             : 
    1046             : nsresult
    1047           0 : nsSMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes,
    1048             :                                      nsAttrValue& aResult)
    1049             : {
    1050           0 :   mKeyTimes.Clear();
    1051           0 :   aResult.SetTo(aKeyTimes);
    1052             : 
    1053           0 :   mHasChanged = true;
    1054             : 
    1055           0 :   if (!nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, true,
    1056             :                                                               mKeyTimes)) {
    1057           0 :     mKeyTimes.Clear();
    1058           0 :     return NS_ERROR_FAILURE;
    1059             :   }
    1060             : 
    1061           0 :   return NS_OK;
    1062             : }
    1063             : 
    1064             : void
    1065           0 : nsSMILAnimationFunction::UnsetKeyTimes()
    1066             : {
    1067           0 :   mKeyTimes.Clear();
    1068           0 :   SetKeyTimesErrorFlag(false);
    1069           0 :   mHasChanged = true;
    1070           0 : }

Generated by: LCOV version 1.13