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