tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SMILAnimationFunction.cpp (35233B)


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