dom/smil/nsSMILAnimationFunction.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

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

mercurial