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"
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"
24 #include "nsContentUtils.h"
25 #include "nsGkAtoms.h"
26 #include "nsIContent.h"
27 #include "nsReadableUtils.h"
30 using namespace mozilla::dom
;
34 //----------------------------------------------------------------------
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
},
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 //----------------------------------------------------------------------
57 SMILAnimationFunction::SMILAnimationFunction()
60 mBeginTime(INT64_MIN
),
61 mAnimationElement(nullptr),
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
,
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
);
83 *aParseResult
= NS_OK
;
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
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
);
115 if (foundMatch
&& aParseResult
) {
116 *aParseResult
= parseResult
;
122 bool SMILAnimationFunction::UnsetAttr(nsAtom
* aAttribute
) {
123 if (IsDisallowedAttribute(aAttribute
)) {
127 bool foundMatch
= true;
129 if (aAttribute
== nsGkAtoms::by
|| aAttribute
== nsGkAtoms::from
||
130 aAttribute
== nsGkAtoms::to
|| aAttribute
== nsGkAtoms::values
) {
132 } else if (aAttribute
== nsGkAtoms::accumulate
) {
134 } else if (aAttribute
== nsGkAtoms::additive
) {
136 } else if (aAttribute
== nsGkAtoms::calcMode
) {
138 } else if (aAttribute
== nsGkAtoms::keyTimes
) {
140 } else if (aAttribute
== nsGkAtoms::keySplines
) {
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?
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
;
172 void SMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration
) {
173 if (mHasChanged
|| !mLastValue
|| mRepeatIteration
!= aRepeatIteration
) {
177 mRepeatIteration
= aRepeatIteration
;
181 void SMILAnimationFunction::Activate(SMILTime aBeginTime
) {
182 mBeginTime
= aBeginTime
;
188 void SMILAnimationFunction::Inactivate(bool aIsFrozen
) {
190 mIsFrozen
= aIsFrozen
;
194 void SMILAnimationFunction::ComposeResult(const SMILAttr
& aSMILAttr
,
195 SMILValue
& aResult
) {
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;
227 if (values
.Length() == 1 && !IsToAnimation()) {
228 // Single-valued animation
230 mPrevSampleWasSingleValueAnimation
= true;
232 } else if (mLastValue
) {
233 // Sampling last value
234 const SMILValue
& last
= values
[values
.Length() - 1];
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
);
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
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
282 MOZ_ASSERT(mAnimationElement
!= aOther
->mAnimationElement
,
283 "Two animations cannot have the same animation content element!");
285 return (nsContentUtils::PositionIsBefore(mAnimationElement
,
286 aOther
->mAnimationElement
))
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
;
314 //----------------------------------------------------------------------
315 // Implementation helpers
317 nsresult
SMILAnimationFunction::InterpolateResult(const SMILValueArray
& aValues
,
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
;
350 simpleProgress
= (double)mSampleTime
/ dur
;
351 } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0)
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
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()) {
375 if (calcMode
== CALC_PACED
) {
376 // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
377 intervalProgress
= simpleProgress
;
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
,
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
);
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];
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");
461 // Base the type of the zero value on the next element in the series.
462 SMILCSSValueType::FinalizeValue(aResult
, aValues
[index
+ 1]);
471 nsresult
SMILAnimationFunction::AccumulateResult(const SMILValueArray
& aValues
,
472 SMILValue
& aResult
) {
473 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration
) {
474 const SMILValue
& lastValue
= aValues
[aValues
.Length() - 1];
476 // If the target attribute type doesn't support addition, Add will
477 // fail and we leave aResult untouched.
478 aResult
.Add(lastValue
, mRepeatIteration
);
485 * Given the simple progress for a paced animation, this method:
486 * - determines which two elements of the values array we're in between
487 * (returned as aFrom and aTo)
488 * - determines where we are between them
489 * (returned as aIntervalProgress)
491 * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
494 nsresult
SMILAnimationFunction::ComputePacedPosition(
495 const SMILValueArray
& aValues
, double aSimpleProgress
,
496 double& aIntervalProgress
, const SMILValue
*& aFrom
, const SMILValue
*& aTo
) {
497 NS_ASSERTION(0.0f
<= aSimpleProgress
&& aSimpleProgress
< 1.0f
,
498 "aSimpleProgress is out of bounds");
499 NS_ASSERTION(GetCalcMode() == CALC_PACED
,
500 "Calling paced-specific function, but not in paced mode");
501 MOZ_ASSERT(aValues
.Length() >= 2, "Unexpected number of values");
503 // Trivial case: If we have just 2 values, then there's only one interval
504 // for us to traverse, and our progress across that interval is the exact
505 // same as our overall progress.
506 if (aValues
.Length() == 2) {
507 aIntervalProgress
= aSimpleProgress
;
513 double totalDistance
= ComputePacedTotalDistance(aValues
);
514 if (totalDistance
== COMPUTE_DISTANCE_ERROR
) return NS_ERROR_FAILURE
;
516 // If we have 0 total distance, then it's unclear where our "paced" position
517 // should be. We can just fail, which drops us into discrete animation mode.
518 // (That's fine, since our values are apparently indistinguishable anyway.)
519 if (totalDistance
== 0.0) {
520 return NS_ERROR_FAILURE
;
523 // total distance we should have moved at this point in time.
524 // (called 'remainingDist' due to how it's used in loop below)
525 double remainingDist
= aSimpleProgress
* totalDistance
;
527 // Must be satisfied, because totalDistance is a sum of (non-negative)
528 // distances, and aSimpleProgress is non-negative
529 NS_ASSERTION(remainingDist
>= 0, "distance values must be non-negative");
531 // Find where remainingDist puts us in the list of values
532 // Note: We could optimize this next loop by caching the
533 // interval-distances in an array, but maybe that's excessive.
534 for (uint32_t i
= 0; i
< aValues
.Length() - 1; i
++) {
535 // Note: The following assertion is valid because remainingDist should
536 // start out non-negative, and this loop never shaves off more than its
538 NS_ASSERTION(remainingDist
>= 0, "distance values must be non-negative");
540 double curIntervalDist
;
542 DebugOnly
<nsresult
> rv
=
543 aValues
[i
].ComputeDistance(aValues
[i
+ 1], curIntervalDist
);
544 MOZ_ASSERT(NS_SUCCEEDED(rv
),
545 "If we got through ComputePacedTotalDistance, we should "
546 "be able to recompute each sub-distance without errors");
548 NS_ASSERTION(curIntervalDist
>= 0, "distance values must be non-negative");
549 // Clamp distance value at 0, just in case ComputeDistance is evil.
550 curIntervalDist
= std::max(curIntervalDist
, 0.0);
552 if (remainingDist
>= curIntervalDist
) {
553 remainingDist
-= curIntervalDist
;
555 // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
556 // Because this clause is only hit when remainingDist < curIntervalDist,
557 // and if curIntervalDist were 0, that would mean remainingDist would
558 // have to be < 0. But that can't happen, because remainingDist (as
559 // a distance) is non-negative by definition.
560 NS_ASSERTION(curIntervalDist
!= 0,
561 "We should never get here with this set to 0...");
563 // We found the right spot -- an interpolated position between
566 aTo
= &aValues
[i
+ 1];
567 aIntervalProgress
= remainingDist
/ curIntervalDist
;
572 MOZ_ASSERT_UNREACHABLE(
573 "shouldn't complete loop & get here -- if we do, "
574 "then aSimpleProgress was probably out of bounds");
575 return NS_ERROR_FAILURE
;
579 * Computes the total distance to be travelled by a paced animation.
581 * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
582 * our values don't support distance computation.
584 double SMILAnimationFunction::ComputePacedTotalDistance(
585 const SMILValueArray
& aValues
) const {
586 NS_ASSERTION(GetCalcMode() == CALC_PACED
,
587 "Calling paced-specific function, but not in paced mode");
589 double totalDistance
= 0.0;
590 for (uint32_t i
= 0; i
< aValues
.Length() - 1; i
++) {
592 nsresult rv
= aValues
[i
].ComputeDistance(aValues
[i
+ 1], tmpDist
);
594 return COMPUTE_DISTANCE_ERROR
;
597 // Clamp distance value to 0, just in case we have an evil ComputeDistance
598 // implementation somewhere
599 MOZ_ASSERT(tmpDist
>= 0.0f
, "distance values must be non-negative");
600 tmpDist
= std::max(tmpDist
, 0.0);
602 totalDistance
+= tmpDist
;
605 return totalDistance
;
608 double SMILAnimationFunction::ScaleSimpleProgress(double aProgress
,
609 SMILCalcMode aCalcMode
) {
610 if (!HasAttr(nsGkAtoms::keyTimes
)) return aProgress
;
612 uint32_t numTimes
= mKeyTimes
.Length();
614 if (numTimes
< 2) return aProgress
;
617 for (; i
< numTimes
- 2 && aProgress
>= mKeyTimes
[i
+ 1]; ++i
) {
620 if (aCalcMode
== CALC_DISCRETE
) {
621 // discrete calcMode behaviour differs in that each keyTime defines the time
622 // from when the corresponding value is set, and therefore the last value
623 // needn't be 1. So check if we're in the last 'interval', that is, the
624 // space between the final value and 1.0.
625 if (aProgress
>= mKeyTimes
[i
+ 1]) {
626 MOZ_ASSERT(i
== numTimes
- 2,
627 "aProgress is not in range of the current interval, yet the "
628 "current interval is not the last bounded interval either.");
631 return (double)i
/ numTimes
;
634 double& intervalStart
= mKeyTimes
[i
];
635 double& intervalEnd
= mKeyTimes
[i
+ 1];
637 double intervalLength
= intervalEnd
- intervalStart
;
638 if (intervalLength
<= 0.0) return intervalStart
;
640 return (i
+ (aProgress
- intervalStart
) / intervalLength
) /
641 double(numTimes
- 1);
644 double SMILAnimationFunction::ScaleIntervalProgress(double aProgress
,
645 uint32_t aIntervalIndex
) {
646 if (GetCalcMode() != CALC_SPLINE
) return aProgress
;
648 if (!HasAttr(nsGkAtoms::keySplines
)) return aProgress
;
650 MOZ_ASSERT(aIntervalIndex
< mKeySplines
.Length(), "Invalid interval index");
652 SMILKeySpline
const& spline
= mKeySplines
[aIntervalIndex
];
653 return spline
.GetSplineValue(aProgress
);
656 bool SMILAnimationFunction::HasAttr(nsAtom
* aAttName
) const {
657 if (IsDisallowedAttribute(aAttName
)) {
660 return mAnimationElement
->HasAttr(aAttName
);
663 const nsAttrValue
* SMILAnimationFunction::GetAttr(nsAtom
* aAttName
) const {
664 if (IsDisallowedAttribute(aAttName
)) {
667 return mAnimationElement
->GetParsedAttr(aAttName
);
670 bool SMILAnimationFunction::GetAttr(nsAtom
* aAttName
,
671 nsAString
& aResult
) const {
672 if (IsDisallowedAttribute(aAttName
)) {
675 return mAnimationElement
->GetAttr(aAttName
, aResult
);
679 * A utility function to make querying an attribute that corresponds to an
680 * SMILValue a little neater.
682 * @param aAttName The attribute name (in the global namespace).
683 * @param aSMILAttr The SMIL attribute to perform the parsing.
684 * @param[out] aResult The resulting SMILValue.
685 * @param[out] aPreventCachingOfSandwich
686 * If |aResult| contains dependencies on its context that
687 * should prevent the result of the animation sandwich from
688 * being cached and reused in future samples (as reported
689 * by SMILAttr::ValueFromString), then this outparam
690 * will be set to true. Otherwise it is left unmodified.
692 * Returns false if a parse error occurred, otherwise returns true.
694 bool SMILAnimationFunction::ParseAttr(nsAtom
* aAttName
,
695 const SMILAttr
& aSMILAttr
,
697 bool& aPreventCachingOfSandwich
) const {
698 nsAutoString attValue
;
699 if (GetAttr(aAttName
, attValue
)) {
700 nsresult rv
= aSMILAttr
.ValueFromString(attValue
, mAnimationElement
,
701 aResult
, aPreventCachingOfSandwich
);
702 if (NS_FAILED(rv
)) return false;
708 * SMILANIM specifies the following rules for animation function values:
710 * (1) if values is set, it overrides everything
711 * (2) for from/to/by animation at least to or by must be specified, from on its
712 * own (or nothing) is an error--which we will ignore
713 * (3) if both by and to are specified only to will be used, by will be ignored
714 * (4) if by is specified without from (by animation), forces additive behaviour
715 * (5) if to is specified without from (to animation), special care needs to be
716 * taken when compositing animation as such animations are composited last.
718 * This helper method applies these rules to fill in the values list and to set
719 * some internal state.
721 nsresult
SMILAnimationFunction::GetValues(const SMILAttr
& aSMILAttr
,
722 SMILValueArray
& aResult
) {
723 if (!mAnimationElement
) return NS_ERROR_FAILURE
;
725 mValueNeedsReparsingEverySample
= false;
726 SMILValueArray result
;
728 // If "values" is set, use it
729 if (HasAttr(nsGkAtoms::values
)) {
730 nsAutoString attValue
;
731 GetAttr(nsGkAtoms::values
, attValue
);
732 bool preventCachingOfSandwich
= false;
733 if (!SMILParserUtils::ParseValues(attValue
, mAnimationElement
, aSMILAttr
,
734 result
, preventCachingOfSandwich
)) {
735 return NS_ERROR_FAILURE
;
738 if (preventCachingOfSandwich
) {
739 mValueNeedsReparsingEverySample
= true;
741 // Else try to/from/by
743 bool preventCachingOfSandwich
= false;
745 SMILValue to
, from
, by
;
747 ParseAttr(nsGkAtoms::to
, aSMILAttr
, to
, preventCachingOfSandwich
);
749 ParseAttr(nsGkAtoms::from
, aSMILAttr
, from
, preventCachingOfSandwich
);
751 ParseAttr(nsGkAtoms::by
, aSMILAttr
, by
, preventCachingOfSandwich
);
753 if (preventCachingOfSandwich
) {
754 mValueNeedsReparsingEverySample
= true;
757 if (!parseOk
|| !result
.SetCapacity(2, fallible
)) {
758 return NS_ERROR_FAILURE
;
761 // AppendElement() below must succeed, because SetCapacity() succeeded.
763 if (!from
.IsNull()) {
764 MOZ_ALWAYS_TRUE(result
.AppendElement(from
, fallible
));
765 MOZ_ALWAYS_TRUE(result
.AppendElement(to
, fallible
));
767 MOZ_ALWAYS_TRUE(result
.AppendElement(to
, fallible
));
769 } else if (!by
.IsNull()) {
770 SMILValue
effectiveFrom(by
.mType
);
771 if (!from
.IsNull()) effectiveFrom
= from
;
772 // Set values to 'from; from + by'
773 MOZ_ALWAYS_TRUE(result
.AppendElement(effectiveFrom
, fallible
));
774 SMILValue
effectiveTo(effectiveFrom
);
775 if (!effectiveTo
.IsNull() && NS_SUCCEEDED(effectiveTo
.Add(by
))) {
776 MOZ_ALWAYS_TRUE(result
.AppendElement(effectiveTo
, fallible
));
778 // Using by-animation with non-additive type or bad base-value
779 return NS_ERROR_FAILURE
;
782 // No values, no to, no by -- call it a day
783 return NS_ERROR_FAILURE
;
787 aResult
= std::move(result
);
792 void SMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues
) {
793 CheckKeyTimes(aNumValues
);
794 CheckKeySplines(aNumValues
);
798 * Performs checks for the keyTimes attribute required by the SMIL spec but
799 * which depend on other attributes and therefore needs to be updated as
800 * dependent attributes are set.
802 void SMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues
) {
803 if (!HasAttr(nsGkAtoms::keyTimes
)) return;
805 SMILCalcMode calcMode
= GetCalcMode();
807 // attribute is ignored for calcMode = paced
808 if (calcMode
== CALC_PACED
) {
809 SetKeyTimesErrorFlag(false);
813 uint32_t numKeyTimes
= mKeyTimes
.Length();
814 if (numKeyTimes
< 1) {
815 // keyTimes isn't set or failed preliminary checks
816 SetKeyTimesErrorFlag(true);
820 // no. keyTimes == no. values
821 // For to-animation the number of values is considered to be 2.
822 bool matchingNumOfValues
= numKeyTimes
== (IsToAnimation() ? 2 : aNumValues
);
823 if (!matchingNumOfValues
) {
824 SetKeyTimesErrorFlag(true);
828 // first value must be 0
829 if (mKeyTimes
[0] != 0.0) {
830 SetKeyTimesErrorFlag(true);
834 // last value must be 1 for linear or spline calcModes
835 if (calcMode
!= CALC_DISCRETE
&& numKeyTimes
> 1 &&
836 mKeyTimes
[numKeyTimes
- 1] != 1.0) {
837 SetKeyTimesErrorFlag(true);
841 SetKeyTimesErrorFlag(false);
844 void SMILAnimationFunction::CheckKeySplines(uint32_t aNumValues
) {
845 // attribute is ignored if calc mode is not spline
846 if (GetCalcMode() != CALC_SPLINE
) {
847 SetKeySplinesErrorFlag(false);
851 // calc mode is spline but the attribute is not set
852 if (!HasAttr(nsGkAtoms::keySplines
)) {
853 SetKeySplinesErrorFlag(false);
857 if (mKeySplines
.Length() < 1) {
858 // keyTimes isn't set or failed preliminary checks
859 SetKeySplinesErrorFlag(true);
863 // ignore splines if there's only one value
864 if (aNumValues
== 1 && !IsToAnimation()) {
865 SetKeySplinesErrorFlag(false);
869 // no. keySpline specs == no. values - 1
870 uint32_t splineSpecs
= mKeySplines
.Length();
871 if ((splineSpecs
!= aNumValues
- 1 && !IsToAnimation()) ||
872 (IsToAnimation() && splineSpecs
!= 1)) {
873 SetKeySplinesErrorFlag(true);
877 SetKeySplinesErrorFlag(false);
880 bool SMILAnimationFunction::IsValueFixedForSimpleDuration() const {
881 return mSimpleDuration
.IsIndefinite() ||
882 (!mHasChanged
&& mPrevSampleWasSingleValueAnimation
);
885 //----------------------------------------------------------------------
888 bool SMILAnimationFunction::GetAccumulate() const {
889 const nsAttrValue
* value
= GetAttr(nsGkAtoms::accumulate
);
890 if (!value
) return false;
892 return value
->GetEnumValue();
895 bool SMILAnimationFunction::GetAdditive() const {
896 const nsAttrValue
* value
= GetAttr(nsGkAtoms::additive
);
897 if (!value
) return false;
899 return value
->GetEnumValue();
902 SMILAnimationFunction::SMILCalcMode
SMILAnimationFunction::GetCalcMode() const {
903 const nsAttrValue
* value
= GetAttr(nsGkAtoms::calcMode
);
904 if (!value
) return CALC_LINEAR
;
906 return SMILCalcMode(value
->GetEnumValue());
909 //----------------------------------------------------------------------
910 // Property setters / un-setters:
912 nsresult
SMILAnimationFunction::SetAccumulate(const nsAString
& aAccumulate
,
913 nsAttrValue
& aResult
) {
916 aResult
.ParseEnumValue(aAccumulate
, sAccumulateTable
, true);
917 SetAccumulateErrorFlag(!parseResult
);
918 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
921 void SMILAnimationFunction::UnsetAccumulate() {
922 SetAccumulateErrorFlag(false);
926 nsresult
SMILAnimationFunction::SetAdditive(const nsAString
& aAdditive
,
927 nsAttrValue
& aResult
) {
929 bool parseResult
= aResult
.ParseEnumValue(aAdditive
, sAdditiveTable
, true);
930 SetAdditiveErrorFlag(!parseResult
);
931 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
934 void SMILAnimationFunction::UnsetAdditive() {
935 SetAdditiveErrorFlag(false);
939 nsresult
SMILAnimationFunction::SetCalcMode(const nsAString
& aCalcMode
,
940 nsAttrValue
& aResult
) {
942 bool parseResult
= aResult
.ParseEnumValue(aCalcMode
, sCalcModeTable
, true);
943 SetCalcModeErrorFlag(!parseResult
);
944 return parseResult
? NS_OK
: NS_ERROR_FAILURE
;
947 void SMILAnimationFunction::UnsetCalcMode() {
948 SetCalcModeErrorFlag(false);
952 nsresult
SMILAnimationFunction::SetKeySplines(const nsAString
& aKeySplines
,
953 nsAttrValue
& aResult
) {
955 aResult
.SetTo(aKeySplines
);
959 if (!SMILParserUtils::ParseKeySplines(aKeySplines
, mKeySplines
)) {
961 return NS_ERROR_FAILURE
;
967 void SMILAnimationFunction::UnsetKeySplines() {
969 SetKeySplinesErrorFlag(false);
973 nsresult
SMILAnimationFunction::SetKeyTimes(const nsAString
& aKeyTimes
,
974 nsAttrValue
& aResult
) {
976 aResult
.SetTo(aKeyTimes
);
980 if (!SMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes
, true,
983 return NS_ERROR_FAILURE
;
989 void SMILAnimationFunction::UnsetKeyTimes() {
991 SetKeyTimesErrorFlag(false);
995 } // namespace mozilla