dom/smil/nsSMILAnimationFunction.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/smil/nsSMILAnimationFunction.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1059 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "mozilla/dom/SVGAnimationElement.h"
    1.10 +#include "nsSMILAnimationFunction.h"
    1.11 +#include "nsISMILAttr.h"
    1.12 +#include "nsSMILParserUtils.h"
    1.13 +#include "nsSMILNullType.h"
    1.14 +#include "nsSMILTimedElement.h"
    1.15 +#include "nsAttrValueInlines.h"
    1.16 +#include "nsGkAtoms.h"
    1.17 +#include "nsCOMPtr.h"
    1.18 +#include "nsCOMArray.h"
    1.19 +#include "nsIContent.h"
    1.20 +#include "nsAutoPtr.h"
    1.21 +#include "nsContentUtils.h"
    1.22 +#include "nsReadableUtils.h"
    1.23 +#include "nsString.h"
    1.24 +#include <math.h>
    1.25 +#include <algorithm>
    1.26 +
    1.27 +using namespace mozilla::dom;
    1.28 +
    1.29 +//----------------------------------------------------------------------
    1.30 +// Static members
    1.31 +
    1.32 +nsAttrValue::EnumTable nsSMILAnimationFunction::sAccumulateTable[] = {
    1.33 +      {"none", false},
    1.34 +      {"sum", true},
    1.35 +      {nullptr, 0}
    1.36 +};
    1.37 +
    1.38 +nsAttrValue::EnumTable nsSMILAnimationFunction::sAdditiveTable[] = {
    1.39 +      {"replace", false},
    1.40 +      {"sum", true},
    1.41 +      {nullptr, 0}
    1.42 +};
    1.43 +
    1.44 +nsAttrValue::EnumTable nsSMILAnimationFunction::sCalcModeTable[] = {
    1.45 +      {"linear", CALC_LINEAR},
    1.46 +      {"discrete", CALC_DISCRETE},
    1.47 +      {"paced", CALC_PACED},
    1.48 +      {"spline", CALC_SPLINE},
    1.49 +      {nullptr, 0}
    1.50 +};
    1.51 +
    1.52 +// Any negative number should be fine as a sentinel here,
    1.53 +// because valid distances are non-negative.
    1.54 +#define COMPUTE_DISTANCE_ERROR (-1)
    1.55 +
    1.56 +//----------------------------------------------------------------------
    1.57 +// Constructors etc.
    1.58 +
    1.59 +nsSMILAnimationFunction::nsSMILAnimationFunction()
    1.60 +  : mSampleTime(-1),
    1.61 +    mRepeatIteration(0),
    1.62 +    mBeginTime(INT64_MIN),
    1.63 +    mAnimationElement(nullptr),
    1.64 +    mErrorFlags(0),
    1.65 +    mIsActive(false),
    1.66 +    mIsFrozen(false),
    1.67 +    mLastValue(false),
    1.68 +    mHasChanged(true),
    1.69 +    mValueNeedsReparsingEverySample(false),
    1.70 +    mPrevSampleWasSingleValueAnimation(false),
    1.71 +    mWasSkippedInPrevSample(false)
    1.72 +{
    1.73 +}
    1.74 +
    1.75 +void
    1.76 +nsSMILAnimationFunction::SetAnimationElement(
    1.77 +    SVGAnimationElement* aAnimationElement)
    1.78 +{
    1.79 +  mAnimationElement = aAnimationElement;
    1.80 +}
    1.81 +
    1.82 +bool
    1.83 +nsSMILAnimationFunction::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
    1.84 +                                 nsAttrValue& aResult, nsresult* aParseResult)
    1.85 +{
    1.86 +  bool foundMatch = true;
    1.87 +  nsresult parseResult = NS_OK;
    1.88 +
    1.89 +  // The attributes 'by', 'from', 'to', and 'values' may be parsed differently
    1.90 +  // depending on the element & attribute we're animating.  So instead of
    1.91 +  // parsing them now we re-parse them at every sample.
    1.92 +  if (aAttribute == nsGkAtoms::by ||
    1.93 +      aAttribute == nsGkAtoms::from ||
    1.94 +      aAttribute == nsGkAtoms::to ||
    1.95 +      aAttribute == nsGkAtoms::values) {
    1.96 +    // We parse to, from, by, values at sample time.
    1.97 +    // XXX Need to flag which attribute has changed and then when we parse it at
    1.98 +    // sample time, report any errors and reset the flag
    1.99 +    mHasChanged = true;
   1.100 +    aResult.SetTo(aValue);
   1.101 +  } else if (aAttribute == nsGkAtoms::accumulate) {
   1.102 +    parseResult = SetAccumulate(aValue, aResult);
   1.103 +  } else if (aAttribute == nsGkAtoms::additive) {
   1.104 +    parseResult = SetAdditive(aValue, aResult);
   1.105 +  } else if (aAttribute == nsGkAtoms::calcMode) {
   1.106 +    parseResult = SetCalcMode(aValue, aResult);
   1.107 +  } else if (aAttribute == nsGkAtoms::keyTimes) {
   1.108 +    parseResult = SetKeyTimes(aValue, aResult);
   1.109 +  } else if (aAttribute == nsGkAtoms::keySplines) {
   1.110 +    parseResult = SetKeySplines(aValue, aResult);
   1.111 +  } else {
   1.112 +    foundMatch = false;
   1.113 +  }
   1.114 +
   1.115 +  if (foundMatch && aParseResult) {
   1.116 +    *aParseResult = parseResult;
   1.117 +  }
   1.118 +
   1.119 +  return foundMatch;
   1.120 +}
   1.121 +
   1.122 +bool
   1.123 +nsSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
   1.124 +{
   1.125 +  bool foundMatch = true;
   1.126 +
   1.127 +  if (aAttribute == nsGkAtoms::by ||
   1.128 +      aAttribute == nsGkAtoms::from ||
   1.129 +      aAttribute == nsGkAtoms::to ||
   1.130 +      aAttribute == nsGkAtoms::values) {
   1.131 +    mHasChanged = true;
   1.132 +  } else if (aAttribute == nsGkAtoms::accumulate) {
   1.133 +    UnsetAccumulate();
   1.134 +  } else if (aAttribute == nsGkAtoms::additive) {
   1.135 +    UnsetAdditive();
   1.136 +  } else if (aAttribute == nsGkAtoms::calcMode) {
   1.137 +    UnsetCalcMode();
   1.138 +  } else if (aAttribute == nsGkAtoms::keyTimes) {
   1.139 +    UnsetKeyTimes();
   1.140 +  } else if (aAttribute == nsGkAtoms::keySplines) {
   1.141 +    UnsetKeySplines();
   1.142 +  } else {
   1.143 +    foundMatch = false;
   1.144 +  }
   1.145 +
   1.146 +  return foundMatch;
   1.147 +}
   1.148 +
   1.149 +void
   1.150 +nsSMILAnimationFunction::SampleAt(nsSMILTime aSampleTime,
   1.151 +                                  const nsSMILTimeValue& aSimpleDuration,
   1.152 +                                  uint32_t aRepeatIteration)
   1.153 +{
   1.154 +  // * Update mHasChanged ("Might this sample be different from prev one?")
   1.155 +  // Were we previously sampling a fill="freeze" final val? (We're not anymore.)
   1.156 +  mHasChanged |= mLastValue;
   1.157 +
   1.158 +  // Are we sampling at a new point in simple duration? And does that matter?
   1.159 +  mHasChanged |=
   1.160 +    (mSampleTime != aSampleTime || mSimpleDuration != aSimpleDuration) &&
   1.161 +    !IsValueFixedForSimpleDuration();
   1.162 +
   1.163 +  // Are we on a new repeat and accumulating across repeats?
   1.164 +  if (!mErrorFlags) { // (can't call GetAccumulate() if we've had parse errors)
   1.165 +    mHasChanged |= (mRepeatIteration != aRepeatIteration) && GetAccumulate();
   1.166 +  }
   1.167 +
   1.168 +  mSampleTime       = aSampleTime;
   1.169 +  mSimpleDuration   = aSimpleDuration;
   1.170 +  mRepeatIteration  = aRepeatIteration;
   1.171 +  mLastValue        = false;
   1.172 +}
   1.173 +
   1.174 +void
   1.175 +nsSMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration)
   1.176 +{
   1.177 +  if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) {
   1.178 +    mHasChanged = true;
   1.179 +  }
   1.180 +
   1.181 +  mRepeatIteration  = aRepeatIteration;
   1.182 +  mLastValue        = true;
   1.183 +}
   1.184 +
   1.185 +void
   1.186 +nsSMILAnimationFunction::Activate(nsSMILTime aBeginTime)
   1.187 +{
   1.188 +  mBeginTime = aBeginTime;
   1.189 +  mIsActive = true;
   1.190 +  mIsFrozen = false;
   1.191 +  mHasChanged = true;
   1.192 +}
   1.193 +
   1.194 +void
   1.195 +nsSMILAnimationFunction::Inactivate(bool aIsFrozen)
   1.196 +{
   1.197 +  mIsActive = false;
   1.198 +  mIsFrozen = aIsFrozen;
   1.199 +  mHasChanged = true;
   1.200 +}
   1.201 +
   1.202 +void
   1.203 +nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
   1.204 +                                       nsSMILValue& aResult)
   1.205 +{
   1.206 +  mHasChanged = false;
   1.207 +  mPrevSampleWasSingleValueAnimation = false;
   1.208 +  mWasSkippedInPrevSample = false;
   1.209 +
   1.210 +  // Skip animations that are inactive or in error
   1.211 +  if (!IsActiveOrFrozen() || mErrorFlags != 0)
   1.212 +    return;
   1.213 +
   1.214 +  // Get the animation values
   1.215 +  nsSMILValueArray values;
   1.216 +  nsresult rv = GetValues(aSMILAttr, values);
   1.217 +  if (NS_FAILED(rv))
   1.218 +    return;
   1.219 +
   1.220 +  // Check that we have the right number of keySplines and keyTimes
   1.221 +  CheckValueListDependentAttrs(values.Length());
   1.222 +  if (mErrorFlags != 0)
   1.223 +    return;
   1.224 +
   1.225 +  // If this interval is active, we must have a non-negative mSampleTime
   1.226 +  NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive,
   1.227 +      "Negative sample time for active animation");
   1.228 +  NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mLastValue,
   1.229 +      "Unresolved simple duration for active or frozen animation");
   1.230 +
   1.231 +  // If we want to add but don't have a base value then just fail outright.
   1.232 +  // This can happen when we skipped getting the base value because there's an
   1.233 +  // animation function in the sandwich that should replace it but that function
   1.234 +  // failed unexpectedly.
   1.235 +  bool isAdditive = IsAdditive();
   1.236 +  if (isAdditive && aResult.IsNull())
   1.237 +    return;
   1.238 +
   1.239 +  nsSMILValue result;
   1.240 +
   1.241 +  if (values.Length() == 1 && !IsToAnimation()) {
   1.242 +
   1.243 +    // Single-valued animation
   1.244 +    result = values[0];
   1.245 +    mPrevSampleWasSingleValueAnimation = true;
   1.246 +
   1.247 +  } else if (mLastValue) {
   1.248 +
   1.249 +    // Sampling last value
   1.250 +    const nsSMILValue& last = values[values.Length() - 1];
   1.251 +    result = last;
   1.252 +
   1.253 +    // See comment in AccumulateResult: to-animation does not accumulate
   1.254 +    if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
   1.255 +      // If the target attribute type doesn't support addition Add will
   1.256 +      // fail leaving result = last
   1.257 +      result.Add(last, mRepeatIteration);
   1.258 +    }
   1.259 +
   1.260 +  } else {
   1.261 +
   1.262 +    // Interpolation
   1.263 +    if (NS_FAILED(InterpolateResult(values, result, aResult)))
   1.264 +      return;
   1.265 +
   1.266 +    if (NS_FAILED(AccumulateResult(values, result)))
   1.267 +      return;
   1.268 +  }
   1.269 +
   1.270 +  // If additive animation isn't required or isn't supported, set the value.
   1.271 +  if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) {
   1.272 +    aResult.Swap(result);
   1.273 +    // Note: The old value of aResult is now in |result|, and it will get
   1.274 +    // cleaned up when |result| goes out of scope, when this function returns.
   1.275 +  }
   1.276 +}
   1.277 +
   1.278 +int8_t
   1.279 +nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const
   1.280 +{
   1.281 +  NS_ENSURE_TRUE(aOther, 0);
   1.282 +
   1.283 +  NS_ASSERTION(aOther != this, "Trying to compare to self");
   1.284 +
   1.285 +  // Inactive animations sort first
   1.286 +  if (!IsActiveOrFrozen() && aOther->IsActiveOrFrozen())
   1.287 +    return -1;
   1.288 +
   1.289 +  if (IsActiveOrFrozen() && !aOther->IsActiveOrFrozen())
   1.290 +    return 1;
   1.291 +
   1.292 +  // Sort based on begin time
   1.293 +  if (mBeginTime != aOther->GetBeginTime())
   1.294 +    return mBeginTime > aOther->GetBeginTime() ? 1 : -1;
   1.295 +
   1.296 +  // Next sort based on syncbase dependencies: the dependent element sorts after
   1.297 +  // its syncbase
   1.298 +  const nsSMILTimedElement& thisTimedElement =
   1.299 +    mAnimationElement->TimedElement();
   1.300 +  const nsSMILTimedElement& otherTimedElement =
   1.301 +    aOther->mAnimationElement->TimedElement();
   1.302 +  if (thisTimedElement.IsTimeDependent(otherTimedElement))
   1.303 +    return 1;
   1.304 +  if (otherTimedElement.IsTimeDependent(thisTimedElement))
   1.305 +    return -1;
   1.306 +
   1.307 +  // Animations that appear later in the document sort after those earlier in
   1.308 +  // the document
   1.309 +  NS_ABORT_IF_FALSE(mAnimationElement != aOther->mAnimationElement,
   1.310 +      "Two animations cannot have the same animation content element!");
   1.311 +
   1.312 +  return (nsContentUtils::PositionIsBefore(mAnimationElement, aOther->mAnimationElement))
   1.313 +          ? -1 : 1;
   1.314 +}
   1.315 +
   1.316 +bool
   1.317 +nsSMILAnimationFunction::WillReplace() const
   1.318 +{
   1.319 +  /*
   1.320 +   * In IsAdditive() we don't consider to-animation to be additive as it is
   1.321 +   * a special case that is dealt with differently in the compositing method.
   1.322 +   * Here, however, we return FALSE for to-animation (i.e. it will NOT replace
   1.323 +   * the underlying value) as it builds on the underlying value.
   1.324 +   */
   1.325 +  return !mErrorFlags && !(IsAdditive() || IsToAnimation());
   1.326 +}
   1.327 +
   1.328 +bool
   1.329 +nsSMILAnimationFunction::HasChanged() const
   1.330 +{
   1.331 +  return mHasChanged || mValueNeedsReparsingEverySample;
   1.332 +}
   1.333 +
   1.334 +bool
   1.335 +nsSMILAnimationFunction::UpdateCachedTarget(
   1.336 +  const nsSMILTargetIdentifier& aNewTarget)
   1.337 +{
   1.338 +  if (!mLastTarget.Equals(aNewTarget)) {
   1.339 +    mLastTarget = aNewTarget;
   1.340 +    return true;
   1.341 +  }
   1.342 +  return false;
   1.343 +}
   1.344 +
   1.345 +//----------------------------------------------------------------------
   1.346 +// Implementation helpers
   1.347 +
   1.348 +nsresult
   1.349 +nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
   1.350 +                                           nsSMILValue& aResult,
   1.351 +                                           nsSMILValue& aBaseValue)
   1.352 +{
   1.353 +  // Sanity check animation values
   1.354 +  if ((!IsToAnimation() && aValues.Length() < 2) ||
   1.355 +      (IsToAnimation()  && aValues.Length() != 1)) {
   1.356 +    NS_ERROR("Unexpected number of values");
   1.357 +    return NS_ERROR_FAILURE;
   1.358 +  }
   1.359 +
   1.360 +  if (IsToAnimation() && aBaseValue.IsNull()) {
   1.361 +    return NS_ERROR_FAILURE;
   1.362 +  }
   1.363 +
   1.364 +  // Get the normalised progress through the simple duration.
   1.365 +  //
   1.366 +  // If we have an indefinite simple duration, just set the progress to be
   1.367 +  // 0 which will give us the expected behaviour of the animation being fixed at
   1.368 +  // its starting point.
   1.369 +  double simpleProgress = 0.0;
   1.370 +
   1.371 +  if (mSimpleDuration.IsDefinite()) {
   1.372 +    nsSMILTime dur = mSimpleDuration.GetMillis();
   1.373 +
   1.374 +    NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative");
   1.375 +    NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative");
   1.376 +
   1.377 +    if (mSampleTime >= dur || mSampleTime < 0) {
   1.378 +      NS_ERROR("Animation sampled outside interval");
   1.379 +      return NS_ERROR_FAILURE;
   1.380 +    }
   1.381 +
   1.382 +    if (dur > 0) {
   1.383 +      simpleProgress = (double)mSampleTime / dur;
   1.384 +    } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
   1.385 +  }
   1.386 +
   1.387 +  nsresult rv = NS_OK;
   1.388 +  nsSMILCalcMode calcMode = GetCalcMode();
   1.389 +  if (calcMode != CALC_DISCRETE) {
   1.390 +    // Get the normalised progress between adjacent values
   1.391 +    const nsSMILValue* from = nullptr;
   1.392 +    const nsSMILValue* to = nullptr;
   1.393 +    // Init to -1 to make sure that if we ever forget to set this, the
   1.394 +    // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail.
   1.395 +    double intervalProgress = -1.f;
   1.396 +    if (IsToAnimation()) {
   1.397 +      from = &aBaseValue;
   1.398 +      to = &aValues[0];
   1.399 +      if (calcMode == CALC_PACED) {
   1.400 +        // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
   1.401 +        intervalProgress = simpleProgress;
   1.402 +      } else {
   1.403 +        double scaledSimpleProgress =
   1.404 +          ScaleSimpleProgress(simpleProgress, calcMode);
   1.405 +        intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
   1.406 +      }
   1.407 +    } else if (calcMode == CALC_PACED) {
   1.408 +      rv = ComputePacedPosition(aValues, simpleProgress,
   1.409 +                                intervalProgress, from, to);
   1.410 +      // Note: If the above call fails, we'll skip the "from->Interpolate"
   1.411 +      // call below, and we'll drop into the CALC_DISCRETE section
   1.412 +      // instead. (as the spec says we should, because our failure was
   1.413 +      // presumably due to the values being non-additive)
   1.414 +    } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
   1.415 +      double scaledSimpleProgress =
   1.416 +        ScaleSimpleProgress(simpleProgress, calcMode);
   1.417 +      uint32_t index = (uint32_t)floor(scaledSimpleProgress *
   1.418 +                                       (aValues.Length() - 1));
   1.419 +      from = &aValues[index];
   1.420 +      to = &aValues[index + 1];
   1.421 +      intervalProgress =
   1.422 +        scaledSimpleProgress * (aValues.Length() - 1) - index;
   1.423 +      intervalProgress = ScaleIntervalProgress(intervalProgress, index);
   1.424 +    }
   1.425 +
   1.426 +    if (NS_SUCCEEDED(rv)) {
   1.427 +      NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation");
   1.428 +      NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation");
   1.429 +      NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f,
   1.430 +                      "Interval progress should be in the range [0, 1)");
   1.431 +      rv = from->Interpolate(*to, intervalProgress, aResult);
   1.432 +    }
   1.433 +  }
   1.434 +
   1.435 +  // Discrete-CalcMode case
   1.436 +  // Note: If interpolation failed (isn't supported for this type), the SVG
   1.437 +  // spec says to force discrete mode.
   1.438 +  if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) {
   1.439 +    double scaledSimpleProgress =
   1.440 +      ScaleSimpleProgress(simpleProgress, CALC_DISCRETE);
   1.441 +
   1.442 +    // Floating-point errors can mean that, for example, a sample time of 29s in
   1.443 +    // a 100s duration animation gives us a simple progress of 0.28999999999
   1.444 +    // instead of the 0.29 we'd expect. Normally this isn't a noticeable
   1.445 +    // problem, but when we have sudden jumps in animation values (such as is
   1.446 +    // the case here with discrete animation) we can get unexpected results.
   1.447 +    //
   1.448 +    // To counteract this, before we perform a floor() on the animation
   1.449 +    // progress, we add a tiny fudge factor to push us into the correct interval
   1.450 +    // in cases where floating-point errors might cause us to fall short.
   1.451 +    static const double kFloatingPointFudgeFactor = 1.0e-16;
   1.452 +    if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) {
   1.453 +      scaledSimpleProgress += kFloatingPointFudgeFactor;
   1.454 +    }
   1.455 +
   1.456 +    if (IsToAnimation()) {
   1.457 +      // We don't follow SMIL 3, 12.6.4, where discrete to animations
   1.458 +      // are the same as <set> animations.  Instead, we treat it as a
   1.459 +      // discrete animation with two values (the underlying value and
   1.460 +      // the to="" value), and honor keyTimes="" as well.
   1.461 +      uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2);
   1.462 +      aResult = index == 0 ? aBaseValue : aValues[0];
   1.463 +    } else {
   1.464 +      uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length());
   1.465 +      aResult = aValues[index];
   1.466 +    }
   1.467 +    rv = NS_OK;
   1.468 +  }
   1.469 +  return rv;
   1.470 +}
   1.471 +
   1.472 +nsresult
   1.473 +nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues,
   1.474 +                                          nsSMILValue& aResult)
   1.475 +{
   1.476 +  if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
   1.477 +    const nsSMILValue& lastValue = aValues[aValues.Length() - 1];
   1.478 +
   1.479 +    // If the target attribute type doesn't support addition, Add will
   1.480 +    // fail and we leave aResult untouched.
   1.481 +    aResult.Add(lastValue, mRepeatIteration);
   1.482 +  }
   1.483 +
   1.484 +  return NS_OK;
   1.485 +}
   1.486 +
   1.487 +/*
   1.488 + * Given the simple progress for a paced animation, this method:
   1.489 + *  - determines which two elements of the values array we're in between
   1.490 + *    (returned as aFrom and aTo)
   1.491 + *  - determines where we are between them
   1.492 + *    (returned as aIntervalProgress)
   1.493 + *
   1.494 + * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
   1.495 + * computation.
   1.496 + */
   1.497 +nsresult
   1.498 +nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues,
   1.499 +                                              double aSimpleProgress,
   1.500 +                                              double& aIntervalProgress,
   1.501 +                                              const nsSMILValue*& aFrom,
   1.502 +                                              const nsSMILValue*& aTo)
   1.503 +{
   1.504 +  NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
   1.505 +               "aSimpleProgress is out of bounds");
   1.506 +  NS_ASSERTION(GetCalcMode() == CALC_PACED,
   1.507 +               "Calling paced-specific function, but not in paced mode");
   1.508 +  NS_ABORT_IF_FALSE(aValues.Length() >= 2, "Unexpected number of values");
   1.509 +
   1.510 +  // Trivial case: If we have just 2 values, then there's only one interval
   1.511 +  // for us to traverse, and our progress across that interval is the exact
   1.512 +  // same as our overall progress.
   1.513 +  if (aValues.Length() == 2) {
   1.514 +    aIntervalProgress = aSimpleProgress;
   1.515 +    aFrom = &aValues[0];
   1.516 +    aTo = &aValues[1];
   1.517 +    return NS_OK;
   1.518 +  }
   1.519 +
   1.520 +  double totalDistance = ComputePacedTotalDistance(aValues);
   1.521 +  if (totalDistance == COMPUTE_DISTANCE_ERROR)
   1.522 +    return NS_ERROR_FAILURE;
   1.523 +
   1.524 +  // If we have 0 total distance, then it's unclear where our "paced" position
   1.525 +  // should be.  We can just fail, which drops us into discrete animation mode.
   1.526 +  // (That's fine, since our values are apparently indistinguishable anyway.)
   1.527 +  if (totalDistance == 0.0) {
   1.528 +    return NS_ERROR_FAILURE;
   1.529 +  }
   1.530 +
   1.531 +  // total distance we should have moved at this point in time.
   1.532 +  // (called 'remainingDist' due to how it's used in loop below)
   1.533 +  double remainingDist = aSimpleProgress * totalDistance;
   1.534 +
   1.535 +  // Must be satisfied, because totalDistance is a sum of (non-negative)
   1.536 +  // distances, and aSimpleProgress is non-negative
   1.537 +  NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
   1.538 +
   1.539 +  // Find where remainingDist puts us in the list of values
   1.540 +  // Note: We could optimize this next loop by caching the
   1.541 +  // interval-distances in an array, but maybe that's excessive.
   1.542 +  for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
   1.543 +    // Note: The following assertion is valid because remainingDist should
   1.544 +    // start out non-negative, and this loop never shaves off more than its
   1.545 +    // current value.
   1.546 +    NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
   1.547 +
   1.548 +    double curIntervalDist;
   1.549 +
   1.550 +#ifdef DEBUG
   1.551 +    nsresult rv =
   1.552 +#endif
   1.553 +      aValues[i].ComputeDistance(aValues[i+1], curIntervalDist);
   1.554 +    NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv),
   1.555 +                      "If we got through ComputePacedTotalDistance, we should "
   1.556 +                      "be able to recompute each sub-distance without errors");
   1.557 +
   1.558 +    NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
   1.559 +    // Clamp distance value at 0, just in case ComputeDistance is evil.
   1.560 +    curIntervalDist = std::max(curIntervalDist, 0.0);
   1.561 +
   1.562 +    if (remainingDist >= curIntervalDist) {
   1.563 +      remainingDist -= curIntervalDist;
   1.564 +    } else {
   1.565 +      // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
   1.566 +      // Because this clause is only hit when remainingDist < curIntervalDist,
   1.567 +      // and if curIntervalDist were 0, that would mean remainingDist would
   1.568 +      // have to be < 0.  But that can't happen, because remainingDist (as
   1.569 +      // a distance) is non-negative by definition.
   1.570 +      NS_ASSERTION(curIntervalDist != 0,
   1.571 +                   "We should never get here with this set to 0...");
   1.572 +
   1.573 +      // We found the right spot -- an interpolated position between
   1.574 +      // values i and i+1.
   1.575 +      aFrom = &aValues[i];
   1.576 +      aTo = &aValues[i+1];
   1.577 +      aIntervalProgress = remainingDist / curIntervalDist;
   1.578 +      return NS_OK;
   1.579 +    }
   1.580 +  }
   1.581 +
   1.582 +  NS_NOTREACHED("shouldn't complete loop & get here -- if we do, "
   1.583 +                "then aSimpleProgress was probably out of bounds");
   1.584 +  return NS_ERROR_FAILURE;
   1.585 +}
   1.586 +
   1.587 +/*
   1.588 + * Computes the total distance to be travelled by a paced animation.
   1.589 + *
   1.590 + * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
   1.591 + * our values don't support distance computation.
   1.592 + */
   1.593 +double
   1.594 +nsSMILAnimationFunction::ComputePacedTotalDistance(
   1.595 +    const nsSMILValueArray& aValues) const
   1.596 +{
   1.597 +  NS_ASSERTION(GetCalcMode() == CALC_PACED,
   1.598 +               "Calling paced-specific function, but not in paced mode");
   1.599 +
   1.600 +  double totalDistance = 0.0;
   1.601 +  for (uint32_t i = 0; i < aValues.Length() - 1; i++) {
   1.602 +    double tmpDist;
   1.603 +    nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist);
   1.604 +    if (NS_FAILED(rv)) {
   1.605 +      return COMPUTE_DISTANCE_ERROR;
   1.606 +    }
   1.607 +
   1.608 +    // Clamp distance value to 0, just in case we have an evil ComputeDistance
   1.609 +    // implementation somewhere
   1.610 +    NS_ABORT_IF_FALSE(tmpDist >= 0.0f, "distance values must be non-negative");
   1.611 +    tmpDist = std::max(tmpDist, 0.0);
   1.612 +
   1.613 +    totalDistance += tmpDist;
   1.614 +  }
   1.615 +
   1.616 +  return totalDistance;
   1.617 +}
   1.618 +
   1.619 +double
   1.620 +nsSMILAnimationFunction::ScaleSimpleProgress(double aProgress,
   1.621 +                                             nsSMILCalcMode aCalcMode)
   1.622 +{
   1.623 +  if (!HasAttr(nsGkAtoms::keyTimes))
   1.624 +    return aProgress;
   1.625 +
   1.626 +  uint32_t numTimes = mKeyTimes.Length();
   1.627 +
   1.628 +  if (numTimes < 2)
   1.629 +    return aProgress;
   1.630 +
   1.631 +  uint32_t i = 0;
   1.632 +  for (; i < numTimes - 2 && aProgress >= mKeyTimes[i+1]; ++i) { }
   1.633 +
   1.634 +  if (aCalcMode == CALC_DISCRETE) {
   1.635 +    // discrete calcMode behaviour differs in that each keyTime defines the time
   1.636 +    // from when the corresponding value is set, and therefore the last value
   1.637 +    // needn't be 1. So check if we're in the last 'interval', that is, the
   1.638 +    // space between the final value and 1.0.
   1.639 +    if (aProgress >= mKeyTimes[i+1]) {
   1.640 +      NS_ABORT_IF_FALSE(i == numTimes - 2,
   1.641 +          "aProgress is not in range of the current interval, yet the current"
   1.642 +          " interval is not the last bounded interval either.");
   1.643 +      ++i;
   1.644 +    }
   1.645 +    return (double)i / numTimes;
   1.646 +  }
   1.647 +
   1.648 +  double& intervalStart = mKeyTimes[i];
   1.649 +  double& intervalEnd   = mKeyTimes[i+1];
   1.650 +
   1.651 +  double intervalLength = intervalEnd - intervalStart;
   1.652 +  if (intervalLength <= 0.0)
   1.653 +    return intervalStart;
   1.654 +
   1.655 +  return (i + (aProgress - intervalStart) / intervalLength) /
   1.656 +         double(numTimes - 1);
   1.657 +}
   1.658 +
   1.659 +double
   1.660 +nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress,
   1.661 +                                               uint32_t aIntervalIndex)
   1.662 +{
   1.663 +  if (GetCalcMode() != CALC_SPLINE)
   1.664 +    return aProgress;
   1.665 +
   1.666 +  if (!HasAttr(nsGkAtoms::keySplines))
   1.667 +    return aProgress;
   1.668 +
   1.669 +  NS_ABORT_IF_FALSE(aIntervalIndex < mKeySplines.Length(),
   1.670 +                    "Invalid interval index");
   1.671 +
   1.672 +  nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
   1.673 +  return spline.GetSplineValue(aProgress);
   1.674 +}
   1.675 +
   1.676 +bool
   1.677 +nsSMILAnimationFunction::HasAttr(nsIAtom* aAttName) const
   1.678 +{
   1.679 +  return mAnimationElement->HasAnimAttr(aAttName);
   1.680 +}
   1.681 +
   1.682 +const nsAttrValue*
   1.683 +nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName) const
   1.684 +{
   1.685 +  return mAnimationElement->GetAnimAttr(aAttName);
   1.686 +}
   1.687 +
   1.688 +bool
   1.689 +nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const
   1.690 +{
   1.691 +  return mAnimationElement->GetAnimAttr(aAttName, aResult);
   1.692 +}
   1.693 +
   1.694 +/*
   1.695 + * A utility function to make querying an attribute that corresponds to an
   1.696 + * nsSMILValue a little neater.
   1.697 + *
   1.698 + * @param aAttName    The attribute name (in the global namespace).
   1.699 + * @param aSMILAttr   The SMIL attribute to perform the parsing.
   1.700 + * @param[out] aResult        The resulting nsSMILValue.
   1.701 + * @param[out] aPreventCachingOfSandwich
   1.702 + *                    If |aResult| contains dependencies on its context that
   1.703 + *                    should prevent the result of the animation sandwich from
   1.704 + *                    being cached and reused in future samples (as reported
   1.705 + *                    by nsISMILAttr::ValueFromString), then this outparam
   1.706 + *                    will be set to true. Otherwise it is left unmodified.
   1.707 + *
   1.708 + * Returns false if a parse error occurred, otherwise returns true.
   1.709 + */
   1.710 +bool
   1.711 +nsSMILAnimationFunction::ParseAttr(nsIAtom* aAttName,
   1.712 +                                   const nsISMILAttr& aSMILAttr,
   1.713 +                                   nsSMILValue& aResult,
   1.714 +                                   bool& aPreventCachingOfSandwich) const
   1.715 +{
   1.716 +  nsAutoString attValue;
   1.717 +  if (GetAttr(aAttName, attValue)) {
   1.718 +    bool preventCachingOfSandwich = false;
   1.719 +    nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement,
   1.720 +                                            aResult, preventCachingOfSandwich);
   1.721 +    if (NS_FAILED(rv))
   1.722 +      return false;
   1.723 +
   1.724 +    if (preventCachingOfSandwich) {
   1.725 +      aPreventCachingOfSandwich = true;
   1.726 +    }
   1.727 +  }
   1.728 +  return true;
   1.729 +}
   1.730 +
   1.731 +/*
   1.732 + * SMILANIM specifies the following rules for animation function values:
   1.733 + *
   1.734 + * (1) if values is set, it overrides everything
   1.735 + * (2) for from/to/by animation at least to or by must be specified, from on its
   1.736 + *     own (or nothing) is an error--which we will ignore
   1.737 + * (3) if both by and to are specified only to will be used, by will be ignored
   1.738 + * (4) if by is specified without from (by animation), forces additive behaviour
   1.739 + * (5) if to is specified without from (to animation), special care needs to be
   1.740 + *     taken when compositing animation as such animations are composited last.
   1.741 + *
   1.742 + * This helper method applies these rules to fill in the values list and to set
   1.743 + * some internal state.
   1.744 + */
   1.745 +nsresult
   1.746 +nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
   1.747 +                                   nsSMILValueArray& aResult)
   1.748 +{
   1.749 +  if (!mAnimationElement)
   1.750 +    return NS_ERROR_FAILURE;
   1.751 +
   1.752 +  mValueNeedsReparsingEverySample = false;
   1.753 +  nsSMILValueArray result;
   1.754 +
   1.755 +  // If "values" is set, use it
   1.756 +  if (HasAttr(nsGkAtoms::values)) {
   1.757 +    nsAutoString attValue;
   1.758 +    GetAttr(nsGkAtoms::values, attValue);
   1.759 +    bool preventCachingOfSandwich = false;
   1.760 +    if (!nsSMILParserUtils::ParseValues(attValue, mAnimationElement,
   1.761 +                                        aSMILAttr, result,
   1.762 +                                        preventCachingOfSandwich)) {
   1.763 +      return NS_ERROR_FAILURE;
   1.764 +    }
   1.765 +
   1.766 +    if (preventCachingOfSandwich) {
   1.767 +      mValueNeedsReparsingEverySample = true;
   1.768 +    }
   1.769 +  // Else try to/from/by
   1.770 +  } else {
   1.771 +    bool preventCachingOfSandwich = false;
   1.772 +    bool parseOk = true;
   1.773 +    nsSMILValue to, from, by;
   1.774 +    parseOk &= ParseAttr(nsGkAtoms::to,   aSMILAttr, to,
   1.775 +                         preventCachingOfSandwich);
   1.776 +    parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from,
   1.777 +                         preventCachingOfSandwich);
   1.778 +    parseOk &= ParseAttr(nsGkAtoms::by,   aSMILAttr, by,
   1.779 +                         preventCachingOfSandwich);
   1.780 +
   1.781 +    if (preventCachingOfSandwich) {
   1.782 +      mValueNeedsReparsingEverySample = true;
   1.783 +    }
   1.784 +
   1.785 +    if (!parseOk)
   1.786 +      return NS_ERROR_FAILURE;
   1.787 +
   1.788 +    result.SetCapacity(2);
   1.789 +    if (!to.IsNull()) {
   1.790 +      if (!from.IsNull()) {
   1.791 +        result.AppendElement(from);
   1.792 +        result.AppendElement(to);
   1.793 +      } else {
   1.794 +        result.AppendElement(to);
   1.795 +      }
   1.796 +    } else if (!by.IsNull()) {
   1.797 +      nsSMILValue effectiveFrom(by.mType);
   1.798 +      if (!from.IsNull())
   1.799 +        effectiveFrom = from;
   1.800 +      // Set values to 'from; from + by'
   1.801 +      result.AppendElement(effectiveFrom);
   1.802 +      nsSMILValue effectiveTo(effectiveFrom);
   1.803 +      if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
   1.804 +        result.AppendElement(effectiveTo);
   1.805 +      } else {
   1.806 +        // Using by-animation with non-additive type or bad base-value
   1.807 +        return NS_ERROR_FAILURE;
   1.808 +      }
   1.809 +    } else {
   1.810 +      // No values, no to, no by -- call it a day
   1.811 +      return NS_ERROR_FAILURE;
   1.812 +    }
   1.813 +  }
   1.814 +
   1.815 +  result.SwapElements(aResult);
   1.816 +
   1.817 +  return NS_OK;
   1.818 +}
   1.819 +
   1.820 +void
   1.821 +nsSMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues)
   1.822 +{
   1.823 +  CheckKeyTimes(aNumValues);
   1.824 +  CheckKeySplines(aNumValues);
   1.825 +}
   1.826 +
   1.827 +/**
   1.828 + * Performs checks for the keyTimes attribute required by the SMIL spec but
   1.829 + * which depend on other attributes and therefore needs to be updated as
   1.830 + * dependent attributes are set.
   1.831 + */
   1.832 +void
   1.833 +nsSMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues)
   1.834 +{
   1.835 +  if (!HasAttr(nsGkAtoms::keyTimes))
   1.836 +    return;
   1.837 +
   1.838 +  nsSMILCalcMode calcMode = GetCalcMode();
   1.839 +
   1.840 +  // attribute is ignored for calcMode = paced
   1.841 +  if (calcMode == CALC_PACED) {
   1.842 +    SetKeyTimesErrorFlag(false);
   1.843 +    return;
   1.844 +  }
   1.845 +
   1.846 +  uint32_t numKeyTimes = mKeyTimes.Length();
   1.847 +  if (numKeyTimes < 1) {
   1.848 +    // keyTimes isn't set or failed preliminary checks
   1.849 +    SetKeyTimesErrorFlag(true);
   1.850 +    return;
   1.851 +  }
   1.852 +
   1.853 +  // no. keyTimes == no. values
   1.854 +  // For to-animation the number of values is considered to be 2.
   1.855 +  bool matchingNumOfValues =
   1.856 +    numKeyTimes == (IsToAnimation() ? 2 : aNumValues);
   1.857 +  if (!matchingNumOfValues) {
   1.858 +    SetKeyTimesErrorFlag(true);
   1.859 +    return;
   1.860 +  }
   1.861 +
   1.862 +  // first value must be 0
   1.863 +  if (mKeyTimes[0] != 0.0) {
   1.864 +    SetKeyTimesErrorFlag(true);
   1.865 +    return;
   1.866 +  }
   1.867 +
   1.868 +  // last value must be 1 for linear or spline calcModes
   1.869 +  if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
   1.870 +      mKeyTimes[numKeyTimes - 1] != 1.0) {
   1.871 +    SetKeyTimesErrorFlag(true);
   1.872 +    return;
   1.873 +  }
   1.874 +
   1.875 +  SetKeyTimesErrorFlag(false);
   1.876 +}
   1.877 +
   1.878 +void
   1.879 +nsSMILAnimationFunction::CheckKeySplines(uint32_t aNumValues)
   1.880 +{
   1.881 +  // attribute is ignored if calc mode is not spline
   1.882 +  if (GetCalcMode() != CALC_SPLINE) {
   1.883 +    SetKeySplinesErrorFlag(false);
   1.884 +    return;
   1.885 +  }
   1.886 +
   1.887 +  // calc mode is spline but the attribute is not set
   1.888 +  if (!HasAttr(nsGkAtoms::keySplines)) {
   1.889 +    SetKeySplinesErrorFlag(false);
   1.890 +    return;
   1.891 +  }
   1.892 +
   1.893 +  if (mKeySplines.Length() < 1) {
   1.894 +    // keyTimes isn't set or failed preliminary checks
   1.895 +    SetKeySplinesErrorFlag(true);
   1.896 +    return;
   1.897 +  }
   1.898 +
   1.899 +  // ignore splines if there's only one value
   1.900 +  if (aNumValues == 1 && !IsToAnimation()) {
   1.901 +    SetKeySplinesErrorFlag(false);
   1.902 +    return;
   1.903 +  }
   1.904 +
   1.905 +  // no. keySpline specs == no. values - 1
   1.906 +  uint32_t splineSpecs = mKeySplines.Length();
   1.907 +  if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
   1.908 +      (IsToAnimation() && splineSpecs != 1)) {
   1.909 +    SetKeySplinesErrorFlag(true);
   1.910 +    return;
   1.911 +  }
   1.912 +
   1.913 +  SetKeySplinesErrorFlag(false);
   1.914 +}
   1.915 +
   1.916 +bool
   1.917 +nsSMILAnimationFunction::IsValueFixedForSimpleDuration() const
   1.918 +{
   1.919 +  return mSimpleDuration.IsIndefinite() ||
   1.920 +    (!mHasChanged && mPrevSampleWasSingleValueAnimation);
   1.921 +}
   1.922 +
   1.923 +//----------------------------------------------------------------------
   1.924 +// Property getters
   1.925 +
   1.926 +bool
   1.927 +nsSMILAnimationFunction::GetAccumulate() const
   1.928 +{
   1.929 +  const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate);
   1.930 +  if (!value)
   1.931 +    return false;
   1.932 +
   1.933 +  return value->GetEnumValue();
   1.934 +}
   1.935 +
   1.936 +bool
   1.937 +nsSMILAnimationFunction::GetAdditive() const
   1.938 +{
   1.939 +  const nsAttrValue* value = GetAttr(nsGkAtoms::additive);
   1.940 +  if (!value)
   1.941 +    return false;
   1.942 +
   1.943 +  return value->GetEnumValue();
   1.944 +}
   1.945 +
   1.946 +nsSMILAnimationFunction::nsSMILCalcMode
   1.947 +nsSMILAnimationFunction::GetCalcMode() const
   1.948 +{
   1.949 +  const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
   1.950 +  if (!value)
   1.951 +    return CALC_LINEAR;
   1.952 +
   1.953 +  return nsSMILCalcMode(value->GetEnumValue());
   1.954 +}
   1.955 +
   1.956 +//----------------------------------------------------------------------
   1.957 +// Property setters / un-setters:
   1.958 +
   1.959 +nsresult
   1.960 +nsSMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate,
   1.961 +                                       nsAttrValue& aResult)
   1.962 +{
   1.963 +  mHasChanged = true;
   1.964 +  bool parseResult =
   1.965 +    aResult.ParseEnumValue(aAccumulate, sAccumulateTable, true);
   1.966 +  SetAccumulateErrorFlag(!parseResult);
   1.967 +  return parseResult ? NS_OK : NS_ERROR_FAILURE;
   1.968 +}
   1.969 +
   1.970 +void
   1.971 +nsSMILAnimationFunction::UnsetAccumulate()
   1.972 +{
   1.973 +  SetAccumulateErrorFlag(false);
   1.974 +  mHasChanged = true;
   1.975 +}
   1.976 +
   1.977 +nsresult
   1.978 +nsSMILAnimationFunction::SetAdditive(const nsAString& aAdditive,
   1.979 +                                     nsAttrValue& aResult)
   1.980 +{
   1.981 +  mHasChanged = true;
   1.982 +  bool parseResult
   1.983 +    = aResult.ParseEnumValue(aAdditive, sAdditiveTable, true);
   1.984 +  SetAdditiveErrorFlag(!parseResult);
   1.985 +  return parseResult ? NS_OK : NS_ERROR_FAILURE;
   1.986 +}
   1.987 +
   1.988 +void
   1.989 +nsSMILAnimationFunction::UnsetAdditive()
   1.990 +{
   1.991 +  SetAdditiveErrorFlag(false);
   1.992 +  mHasChanged = true;
   1.993 +}
   1.994 +
   1.995 +nsresult
   1.996 +nsSMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode,
   1.997 +                                     nsAttrValue& aResult)
   1.998 +{
   1.999 +  mHasChanged = true;
  1.1000 +  bool parseResult
  1.1001 +    = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, true);
  1.1002 +  SetCalcModeErrorFlag(!parseResult);
  1.1003 +  return parseResult ? NS_OK : NS_ERROR_FAILURE;
  1.1004 +}
  1.1005 +
  1.1006 +void
  1.1007 +nsSMILAnimationFunction::UnsetCalcMode()
  1.1008 +{
  1.1009 +  SetCalcModeErrorFlag(false);
  1.1010 +  mHasChanged = true;
  1.1011 +}
  1.1012 +
  1.1013 +nsresult
  1.1014 +nsSMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines,
  1.1015 +                                       nsAttrValue& aResult)
  1.1016 +{
  1.1017 +  mKeySplines.Clear();
  1.1018 +  aResult.SetTo(aKeySplines);
  1.1019 +
  1.1020 +  mHasChanged = true;
  1.1021 +
  1.1022 +  if (!nsSMILParserUtils::ParseKeySplines(aKeySplines, mKeySplines)) {
  1.1023 +    mKeySplines.Clear();
  1.1024 +    return NS_ERROR_FAILURE;
  1.1025 +  }
  1.1026 +
  1.1027 +  return NS_OK;
  1.1028 +}
  1.1029 +
  1.1030 +void
  1.1031 +nsSMILAnimationFunction::UnsetKeySplines()
  1.1032 +{
  1.1033 +  mKeySplines.Clear();
  1.1034 +  SetKeySplinesErrorFlag(false);
  1.1035 +  mHasChanged = true;
  1.1036 +}
  1.1037 +
  1.1038 +nsresult
  1.1039 +nsSMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes,
  1.1040 +                                     nsAttrValue& aResult)
  1.1041 +{
  1.1042 +  mKeyTimes.Clear();
  1.1043 +  aResult.SetTo(aKeyTimes);
  1.1044 +
  1.1045 +  mHasChanged = true;
  1.1046 +
  1.1047 +  if (!nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, true,
  1.1048 +                                                              mKeyTimes)) {
  1.1049 +    mKeyTimes.Clear();
  1.1050 +    return NS_ERROR_FAILURE;
  1.1051 +  }
  1.1052 +
  1.1053 +  return NS_OK;
  1.1054 +}
  1.1055 +
  1.1056 +void
  1.1057 +nsSMILAnimationFunction::UnsetKeyTimes()
  1.1058 +{
  1.1059 +  mKeyTimes.Clear();
  1.1060 +  SetKeyTimesErrorFlag(false);
  1.1061 +  mHasChanged = true;
  1.1062 +}

mercurial