Bug 601299: Find RegExpStatics in cx->globalObject if necessary. (r=mrbkap)
[mozilla-central.git] / content / smil / nsSMILAnimationFunction.cpp
blobcf211922f046cd5406f728f78ca2a0aa45fa6393
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is the Mozilla SMIL module.
17 * The Initial Developer of the Original Code is Brian Birtles.
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
21 * Contributor(s):
22 * Brian Birtles <birtles@gmail.com>
23 * Chris Double <chris.double@double.co.nz>
24 * Daniel Holbert <dholbert@cs.stanford.edu>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 #include "nsSMILAnimationFunction.h"
41 #include "nsISMILAttr.h"
42 #include "nsSMILParserUtils.h"
43 #include "nsSMILNullType.h"
44 #include "nsISMILAnimationElement.h"
45 #include "nsSMILTimedElement.h"
46 #include "nsGkAtoms.h"
47 #include "nsCOMPtr.h"
48 #include "nsCOMArray.h"
49 #include "nsIContent.h"
50 #include "nsAutoPtr.h"
51 #include "nsContentUtils.h"
52 #include "nsReadableUtils.h"
53 #include "nsString.h"
54 #include <math.h>
56 //----------------------------------------------------------------------
57 // Static members
59 nsAttrValue::EnumTable nsSMILAnimationFunction::sAccumulateTable[] = {
60 {"none", PR_FALSE},
61 {"sum", PR_TRUE},
62 {nsnull, 0}
65 nsAttrValue::EnumTable nsSMILAnimationFunction::sAdditiveTable[] = {
66 {"replace", PR_FALSE},
67 {"sum", PR_TRUE},
68 {nsnull, 0}
71 nsAttrValue::EnumTable nsSMILAnimationFunction::sCalcModeTable[] = {
72 {"linear", CALC_LINEAR},
73 {"discrete", CALC_DISCRETE},
74 {"paced", CALC_PACED},
75 {"spline", CALC_SPLINE},
76 {nsnull, 0}
79 // Any negative number should be fine as a sentinel here,
80 // because valid distances are non-negative.
81 #define COMPUTE_DISTANCE_ERROR (-1)
83 //----------------------------------------------------------------------
84 // Constructors etc.
86 nsSMILAnimationFunction::nsSMILAnimationFunction()
87 : mIsActive(PR_FALSE),
88 mIsFrozen(PR_FALSE),
89 mSampleTime(-1),
90 mRepeatIteration(0),
91 mLastValue(PR_FALSE),
92 mHasChanged(PR_TRUE),
93 mValueNeedsReparsingEverySample(PR_FALSE),
94 mBeginTime(LL_MININT),
95 mAnimationElement(nsnull),
96 mErrorFlags(0)
100 void
101 nsSMILAnimationFunction::SetAnimationElement(
102 nsISMILAnimationElement* aAnimationElement)
104 mAnimationElement = aAnimationElement;
107 PRBool
108 nsSMILAnimationFunction::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
109 nsAttrValue& aResult, nsresult* aParseResult)
111 PRBool foundMatch = PR_TRUE;
112 nsresult parseResult = NS_OK;
114 // The attributes 'by', 'from', 'to', and 'values' may be parsed differently
115 // depending on the element & attribute we're animating. So instead of
116 // parsing them now we re-parse them at every sample.
117 if (aAttribute == nsGkAtoms::by ||
118 aAttribute == nsGkAtoms::from ||
119 aAttribute == nsGkAtoms::to ||
120 aAttribute == nsGkAtoms::values) {
121 // We parse to, from, by, values at sample time.
122 // XXX Need to flag which attribute has changed and then when we parse it at
123 // sample time, report any errors and reset the flag
124 mHasChanged = PR_TRUE;
125 aResult.SetTo(aValue);
126 } else if (aAttribute == nsGkAtoms::accumulate) {
127 parseResult = SetAccumulate(aValue, aResult);
128 } else if (aAttribute == nsGkAtoms::additive) {
129 parseResult = SetAdditive(aValue, aResult);
130 } else if (aAttribute == nsGkAtoms::calcMode) {
131 parseResult = SetCalcMode(aValue, aResult);
132 } else if (aAttribute == nsGkAtoms::keyTimes) {
133 parseResult = SetKeyTimes(aValue, aResult);
134 } else if (aAttribute == nsGkAtoms::keySplines) {
135 parseResult = SetKeySplines(aValue, aResult);
136 } else {
137 foundMatch = PR_FALSE;
140 if (foundMatch && aParseResult) {
141 *aParseResult = parseResult;
144 return foundMatch;
147 PRBool
148 nsSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute)
150 PRBool foundMatch = PR_TRUE;
152 if (aAttribute == nsGkAtoms::by ||
153 aAttribute == nsGkAtoms::from ||
154 aAttribute == nsGkAtoms::to ||
155 aAttribute == nsGkAtoms::values) {
156 mHasChanged = PR_TRUE;
157 } else if (aAttribute == nsGkAtoms::accumulate) {
158 UnsetAccumulate();
159 } else if (aAttribute == nsGkAtoms::additive) {
160 UnsetAdditive();
161 } else if (aAttribute == nsGkAtoms::calcMode) {
162 UnsetCalcMode();
163 } else if (aAttribute == nsGkAtoms::keyTimes) {
164 UnsetKeyTimes();
165 } else if (aAttribute == nsGkAtoms::keySplines) {
166 UnsetKeySplines();
167 } else {
168 foundMatch = PR_FALSE;
171 return foundMatch;
174 void
175 nsSMILAnimationFunction::SampleAt(nsSMILTime aSampleTime,
176 const nsSMILTimeValue& aSimpleDuration,
177 PRUint32 aRepeatIteration)
179 if (mHasChanged || mLastValue || mSampleTime != aSampleTime ||
180 mSimpleDuration != aSimpleDuration ||
181 mRepeatIteration != aRepeatIteration) {
182 mHasChanged = PR_TRUE;
185 mSampleTime = aSampleTime;
186 mSimpleDuration = aSimpleDuration;
187 mRepeatIteration = aRepeatIteration;
188 mLastValue = PR_FALSE;
191 void
192 nsSMILAnimationFunction::SampleLastValue(PRUint32 aRepeatIteration)
194 if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) {
195 mHasChanged = PR_TRUE;
198 mRepeatIteration = aRepeatIteration;
199 mLastValue = PR_TRUE;
202 void
203 nsSMILAnimationFunction::Activate(nsSMILTime aBeginTime)
205 mBeginTime = aBeginTime;
206 mIsActive = PR_TRUE;
207 mIsFrozen = PR_FALSE;
208 mFrozenValue = nsSMILValue();
209 mHasChanged = PR_TRUE;
212 void
213 nsSMILAnimationFunction::Inactivate(PRBool aIsFrozen)
215 mIsActive = PR_FALSE;
216 mIsFrozen = aIsFrozen;
217 mFrozenValue = nsSMILValue();
218 mHasChanged = PR_TRUE;
221 void
222 nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr,
223 nsSMILValue& aResult)
225 mHasChanged = PR_FALSE;
227 // Skip animations that are inactive or in error
228 if (!IsActiveOrFrozen() || mErrorFlags != 0)
229 return;
231 // Get the animation values
232 nsSMILValueArray values;
233 nsresult rv = GetValues(aSMILAttr, values);
234 if (NS_FAILED(rv))
235 return;
237 // Check that we have the right number of keySplines and keyTimes
238 CheckValueListDependentAttrs(values.Length());
239 if (mErrorFlags != 0)
240 return;
242 // If this interval is active, we must have a non-negative mSampleTime
243 NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive,
244 "Negative sample time for active animation");
245 NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() ||
246 mSimpleDuration.IsIndefinite() || mLastValue,
247 "Unresolved simple duration for active or frozen animation");
249 nsSMILValue result(aResult.mType);
251 if (mSimpleDuration.IsIndefinite() ||
252 (values.Length() == 1 && TreatSingleValueAsStatic())) {
253 // Indefinite duration or only one value set: Always set the first value
254 result = values[0];
256 } else if (mLastValue) {
258 // Sampling last value
259 const nsSMILValue& last = values[values.Length() - 1];
260 result = last;
262 // See comment in AccumulateResult: to-animation does not accumulate
263 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) {
264 // If the target attribute type doesn't support addition Add will
265 // fail leaving result = last
266 result.Add(last, mRepeatIteration);
269 } else if (!mFrozenValue.IsNull() && !mHasChanged) {
271 // Frozen to animation
272 result = mFrozenValue;
274 } else {
276 // Interpolation
277 if (NS_FAILED(InterpolateResult(values, result, aResult)))
278 return;
280 if (NS_FAILED(AccumulateResult(values, result)))
281 return;
283 if (IsToAnimation() && mIsFrozen) {
284 mFrozenValue = result;
288 // If additive animation isn't required or isn't supported, set the value.
289 if (!IsAdditive() || NS_FAILED(aResult.SandwichAdd(result))) {
290 aResult.Swap(result);
291 // Note: The old value of aResult is now in |result|, and it will get
292 // cleaned up when |result| goes out of scope, when this function returns.
296 PRInt8
297 nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const
299 NS_ENSURE_TRUE(aOther, 0);
301 NS_ASSERTION(aOther != this, "Trying to compare to self");
303 // Inactive animations sort first
304 if (!IsActiveOrFrozen() && aOther->IsActiveOrFrozen())
305 return -1;
307 if (IsActiveOrFrozen() && !aOther->IsActiveOrFrozen())
308 return 1;
310 // Sort based on begin time
311 if (mBeginTime != aOther->GetBeginTime())
312 return mBeginTime > aOther->GetBeginTime() ? 1 : -1;
314 // Next sort based on syncbase dependencies: the dependent element sorts after
315 // its syncbase
316 const nsSMILTimedElement& thisTimedElement =
317 mAnimationElement->TimedElement();
318 const nsSMILTimedElement& otherTimedElement =
319 aOther->mAnimationElement->TimedElement();
320 if (thisTimedElement.IsTimeDependent(otherTimedElement))
321 return 1;
322 if (otherTimedElement.IsTimeDependent(thisTimedElement))
323 return -1;
325 // Animations that appear later in the document sort after those earlier in
326 // the document
327 nsIContent& thisContent = mAnimationElement->AsElement();
328 nsIContent& otherContent = aOther->mAnimationElement->AsElement();
330 NS_ABORT_IF_FALSE(&thisContent != &otherContent,
331 "Two animations cannot have the same animation content element!");
333 return (nsContentUtils::PositionIsBefore(&thisContent, &otherContent))
334 ? -1 : 1;
337 PRBool
338 nsSMILAnimationFunction::WillReplace() const
341 * In IsAdditive() we don't consider to-animation to be additive as it is
342 * a special case that is dealt with differently in the compositing method but
343 * here we return false for to animation as it builds on the underlying value
344 * unless its a frozen to animation.
346 return !mErrorFlags && (!(IsAdditive() || IsToAnimation()) ||
347 (IsToAnimation() && mIsFrozen && !mHasChanged));
350 PRBool
351 nsSMILAnimationFunction::HasChanged() const
353 return mHasChanged || mValueNeedsReparsingEverySample;
356 PRBool
357 nsSMILAnimationFunction::UpdateCachedTarget(const nsSMILTargetIdentifier& aNewTarget)
359 if (!mLastTarget.Equals(aNewTarget)) {
360 mLastTarget = aNewTarget;
361 return PR_TRUE;
363 return PR_FALSE;
366 //----------------------------------------------------------------------
367 // Implementation helpers
369 nsresult
370 nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues,
371 nsSMILValue& aResult,
372 nsSMILValue& aBaseValue)
374 nsresult rv = NS_OK;
375 const nsSMILTime& dur = mSimpleDuration.GetMillis();
377 // Sanity Checks
378 NS_ABORT_IF_FALSE(mSampleTime >= 0.0f, "Sample time should not be negative");
379 NS_ABORT_IF_FALSE(dur >= 0.0f, "Simple duration should not be negative");
381 if (mSampleTime >= dur || mSampleTime < 0.0f) {
382 NS_ERROR("Animation sampled outside interval");
383 return NS_ERROR_FAILURE;
386 if ((!IsToAnimation() && aValues.Length() < 2) ||
387 (IsToAnimation() && aValues.Length() != 1)) {
388 NS_ERROR("Unexpected number of values");
389 return NS_ERROR_FAILURE;
391 // End Sanity Checks
393 // Get the normalised progress through the simple duration
394 const double simpleProgress = dur > 0.0 ? (double)mSampleTime / dur : 0.0;
396 nsSMILCalcMode calcMode = GetCalcMode();
397 if (calcMode != CALC_DISCRETE) {
398 // Get the normalised progress between adjacent values
399 const nsSMILValue* from = nsnull;
400 const nsSMILValue* to = nsnull;
401 // Init to -1 to make sure that if we ever forget to set this, the
402 // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail.
403 double intervalProgress = -1.f;
404 if (IsToAnimation()) {
405 from = &aBaseValue;
406 to = &aValues[0];
407 if (calcMode == CALC_PACED) {
408 // Note: key[Times/Splines/Points] are ignored for calcMode="paced"
409 intervalProgress = simpleProgress;
410 } else {
411 double scaledSimpleProgress =
412 ScaleSimpleProgress(simpleProgress, calcMode);
413 intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0);
415 } else {
416 if (calcMode == CALC_PACED) {
417 rv = ComputePacedPosition(aValues, simpleProgress,
418 intervalProgress, from, to);
419 // Note: If the above call fails, we'll skip the "from->Interpolate"
420 // call below, and we'll drop into the CALC_DISCRETE section
421 // instead. (as the spec says we should, because our failure was
422 // presumably due to the values being non-additive)
423 } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE
424 double scaledSimpleProgress =
425 ScaleSimpleProgress(simpleProgress, calcMode);
426 PRUint32 index = (PRUint32)floor(scaledSimpleProgress *
427 (aValues.Length() - 1));
428 from = &aValues[index];
429 to = &aValues[index + 1];
430 intervalProgress =
431 scaledSimpleProgress * (aValues.Length() - 1) - index;
432 intervalProgress = ScaleIntervalProgress(intervalProgress, index);
435 if (NS_SUCCEEDED(rv)) {
436 NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation");
437 NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation");
438 NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f,
439 "Interval progress should be in the range [0, 1)");
440 rv = from->Interpolate(*to, intervalProgress, aResult);
444 // Discrete-CalcMode case
445 // Note: If interpolation failed (isn't supported for this type), the SVG
446 // spec says to force discrete mode.
447 if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) {
448 if (IsToAnimation()) {
449 // SMIL 3, 12.6.4: Since a to animation has only 1 value, a discrete to
450 // animation will simply set the to value for the simple duration.
451 aResult = aValues[0];
452 } else {
453 double scaledSimpleProgress =
454 ScaleSimpleProgress(simpleProgress, CALC_DISCRETE);
455 PRUint32 index = (PRUint32)floor(scaledSimpleProgress * aValues.Length());
456 aResult = aValues[index];
458 rv = NS_OK;
460 return rv;
463 nsresult
464 nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues,
465 nsSMILValue& aResult)
467 if (!IsToAnimation() && GetAccumulate() && mRepeatIteration)
469 const nsSMILValue& lastValue = aValues[aValues.Length() - 1];
471 // If the target attribute type doesn't support addition, Add will
472 // fail and we leave aResult untouched.
473 aResult.Add(lastValue, mRepeatIteration);
476 return NS_OK;
480 * Given the simple progress for a paced animation, this method:
481 * - determines which two elements of the values array we're in between
482 * (returned as aFrom and aTo)
483 * - determines where we are between them
484 * (returned as aIntervalProgress)
486 * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance
487 * computation.
489 nsresult
490 nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues,
491 double aSimpleProgress,
492 double& aIntervalProgress,
493 const nsSMILValue*& aFrom,
494 const nsSMILValue*& aTo)
496 NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f,
497 "aSimpleProgress is out of bounds");
498 NS_ASSERTION(GetCalcMode() == CALC_PACED,
499 "Calling paced-specific function, but not in paced mode");
500 NS_ABORT_IF_FALSE(aValues.Length() >= 2, "Unexpected number of values");
502 // Trivial case: If we have just 2 values, then there's only one interval
503 // for us to traverse, and our progress across that interval is the exact
504 // same as our overall progress.
505 if (aValues.Length() == 2) {
506 aIntervalProgress = aSimpleProgress;
507 aFrom = &aValues[0];
508 aTo = &aValues[1];
509 return NS_OK;
512 double totalDistance = ComputePacedTotalDistance(aValues);
513 if (totalDistance == COMPUTE_DISTANCE_ERROR)
514 return NS_ERROR_FAILURE;
516 // total distance we should have moved at this point in time.
517 // (called 'remainingDist' due to how it's used in loop below)
518 double remainingDist = aSimpleProgress * totalDistance;
520 // Must be satisfied, because totalDistance is a sum of (non-negative)
521 // distances, and aSimpleProgress is non-negative
522 NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
524 // Find where remainingDist puts us in the list of values
525 // Note: We could optimize this next loop by caching the
526 // interval-distances in an array, but maybe that's excessive.
527 for (PRUint32 i = 0; i < aValues.Length() - 1; i++) {
528 // Note: The following assertion is valid because remainingDist should
529 // start out non-negative, and this loop never shaves off more than its
530 // current value.
531 NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative");
533 double curIntervalDist;
534 nsresult rv = aValues[i].ComputeDistance(aValues[i+1], curIntervalDist);
535 NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv),
536 "If we got through ComputePacedTotalDistance, we should "
537 "be able to recompute each sub-distance without errors");
539 NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative");
540 // Clamp distance value at 0, just in case ComputeDistance is evil.
541 curIntervalDist = NS_MAX(curIntervalDist, 0.0);
543 if (remainingDist >= curIntervalDist) {
544 remainingDist -= curIntervalDist;
545 } else {
546 // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why?
547 // Because this clause is only hit when remainingDist < curIntervalDist,
548 // and if curIntervalDist were 0, that would mean remainingDist would
549 // have to be < 0. But that can't happen, because remainingDist (as
550 // a distance) is non-negative by definition.
551 NS_ASSERTION(curIntervalDist != 0,
552 "We should never get here with this set to 0...");
554 // We found the right spot -- an interpolated position between
555 // values i and i+1.
556 aFrom = &aValues[i];
557 aTo = &aValues[i+1];
558 aIntervalProgress = remainingDist / curIntervalDist;
559 return NS_OK;
563 NS_NOTREACHED("shouldn't complete loop & get here -- if we do, "
564 "then aSimpleProgress was probably out of bounds");
565 return NS_ERROR_FAILURE;
569 * Computes the total distance to be travelled by a paced animation.
571 * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if
572 * our values don't support distance computation.
574 double
575 nsSMILAnimationFunction::ComputePacedTotalDistance(
576 const nsSMILValueArray& aValues) const
578 NS_ASSERTION(GetCalcMode() == CALC_PACED,
579 "Calling paced-specific function, but not in paced mode");
581 double totalDistance = 0.0;
582 for (PRUint32 i = 0; i < aValues.Length() - 1; i++) {
583 double tmpDist;
584 nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist);
585 if (NS_FAILED(rv)) {
586 return COMPUTE_DISTANCE_ERROR;
589 // Clamp distance value to 0, just in case we have an evil ComputeDistance
590 // implementation somewhere
591 NS_ABORT_IF_FALSE(tmpDist >= 0.0f, "distance values must be non-negative");
592 tmpDist = NS_MAX(tmpDist, 0.0);
594 totalDistance += tmpDist;
597 return totalDistance;
600 double
601 nsSMILAnimationFunction::ScaleSimpleProgress(double aProgress,
602 nsSMILCalcMode aCalcMode)
604 if (!HasAttr(nsGkAtoms::keyTimes))
605 return aProgress;
607 PRUint32 numTimes = mKeyTimes.Length();
609 if (numTimes < 2)
610 return aProgress;
612 PRUint32 i = 0;
613 for (; i < numTimes - 2 && aProgress >= mKeyTimes[i+1]; ++i);
615 if (aCalcMode == CALC_DISCRETE) {
616 // discrete calcMode behaviour differs in that each keyTime defines the time
617 // from when the corresponding value is set, and therefore the last value
618 // needn't be 1. So check if we're in the last 'interval', that is, the
619 // space between the final value and 1.0.
620 if (aProgress >= mKeyTimes[i+1]) {
621 NS_ABORT_IF_FALSE(i == numTimes - 2,
622 "aProgress is not in range of the current interval, yet the current"
623 " interval is not the last bounded interval either.");
624 ++i;
626 return (double)i / numTimes;
629 double& intervalStart = mKeyTimes[i];
630 double& intervalEnd = mKeyTimes[i+1];
632 double intervalLength = intervalEnd - intervalStart;
633 if (intervalLength <= 0.0)
634 return intervalStart;
636 return (i + (aProgress - intervalStart) / intervalLength) /
637 double(numTimes - 1);
640 double
641 nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress,
642 PRUint32 aIntervalIndex)
644 if (GetCalcMode() != CALC_SPLINE)
645 return aProgress;
647 if (!HasAttr(nsGkAtoms::keySplines))
648 return aProgress;
650 NS_ABORT_IF_FALSE(aIntervalIndex < mKeySplines.Length(),
651 "Invalid interval index");
653 nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex];
654 return spline.GetSplineValue(aProgress);
657 PRBool
658 nsSMILAnimationFunction::HasAttr(nsIAtom* aAttName) const
660 return mAnimationElement->HasAnimAttr(aAttName);
663 const nsAttrValue*
664 nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName) const
666 return mAnimationElement->GetAnimAttr(aAttName);
669 PRBool
670 nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const
672 return mAnimationElement->GetAnimAttr(aAttName, aResult);
676 * A utility function to make querying an attribute that corresponds to an
677 * nsSMILValue a little neater.
679 * @param aAttName The attribute name (in the global namespace).
680 * @param aSMILAttr The SMIL attribute to perform the parsing.
681 * @param[out] aResult The resulting nsSMILValue.
682 * @param[out] aPreventCachingOfSandwich
683 * If |aResult| contains dependencies on its context that
684 * should prevent the result of the animation sandwich from
685 * being cached and reused in future samples (as reported
686 * by nsISMILAttr::ValueFromString), then this outparam
687 * will be set to PR_TRUE. Otherwise it is left unmodified.
689 * Returns PR_FALSE if a parse error occurred, otherwise returns PR_TRUE.
691 PRBool
692 nsSMILAnimationFunction::ParseAttr(nsIAtom* aAttName,
693 const nsISMILAttr& aSMILAttr,
694 nsSMILValue& aResult,
695 PRBool& aPreventCachingOfSandwich) const
697 nsAutoString attValue;
698 if (GetAttr(aAttName, attValue)) {
699 PRBool preventCachingOfSandwich;
700 nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement,
701 aResult, preventCachingOfSandwich);
702 if (NS_FAILED(rv))
703 return PR_FALSE;
705 if (preventCachingOfSandwich) {
706 aPreventCachingOfSandwich = PR_TRUE;
709 return PR_TRUE;
713 * SMILANIM specifies the following rules for animation function values:
715 * (1) if values is set, it overrides everything
716 * (2) for from/to/by animation at least to or by must be specified, from on its
717 * own (or nothing) is an error--which we will ignore
718 * (3) if both by and to are specified only to will be used, by will be ignored
719 * (4) if by is specified without from (by animation), forces additive behaviour
720 * (5) if to is specified without from (to animation), special care needs to be
721 * taken when compositing animation as such animations are composited last.
723 * This helper method applies these rules to fill in the values list and to set
724 * some internal state.
726 nsresult
727 nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr,
728 nsSMILValueArray& aResult)
730 if (!mAnimationElement)
731 return NS_ERROR_FAILURE;
733 mValueNeedsReparsingEverySample = PR_FALSE;
734 nsSMILValueArray result;
736 // If "values" is set, use it
737 if (HasAttr(nsGkAtoms::values)) {
738 nsAutoString attValue;
739 GetAttr(nsGkAtoms::values, attValue);
740 PRBool preventCachingOfSandwich;
741 nsresult rv = nsSMILParserUtils::ParseValues(attValue, mAnimationElement,
742 aSMILAttr, result,
743 preventCachingOfSandwich);
744 if (NS_FAILED(rv))
745 return rv;
747 if (preventCachingOfSandwich) {
748 mValueNeedsReparsingEverySample = PR_TRUE;
750 // Else try to/from/by
751 } else {
752 PRBool preventCachingOfSandwich = PR_FALSE;
753 PRBool parseOk = PR_TRUE;
754 nsSMILValue to, from, by;
755 parseOk &= ParseAttr(nsGkAtoms::to, aSMILAttr, to,
756 preventCachingOfSandwich);
757 parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from,
758 preventCachingOfSandwich);
759 parseOk &= ParseAttr(nsGkAtoms::by, aSMILAttr, by,
760 preventCachingOfSandwich);
762 if (preventCachingOfSandwich) {
763 mValueNeedsReparsingEverySample = PR_TRUE;
766 if (!parseOk)
767 return NS_ERROR_FAILURE;
769 result.SetCapacity(2);
770 if (!to.IsNull()) {
771 if (!from.IsNull()) {
772 result.AppendElement(from);
773 result.AppendElement(to);
774 } else {
775 result.AppendElement(to);
777 } else if (!by.IsNull()) {
778 nsSMILValue effectiveFrom(by.mType);
779 if (!from.IsNull())
780 effectiveFrom = from;
781 // Set values to 'from; from + by'
782 result.AppendElement(effectiveFrom);
783 nsSMILValue effectiveTo(effectiveFrom);
784 if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) {
785 result.AppendElement(effectiveTo);
786 } else {
787 // Using by-animation with non-additive type or bad base-value
788 return NS_ERROR_FAILURE;
790 } else {
791 // No values, no to, no by -- call it a day
792 return NS_ERROR_FAILURE;
796 result.SwapElements(aResult);
798 return NS_OK;
801 void
802 nsSMILAnimationFunction::CheckValueListDependentAttrs(PRUint32 aNumValues)
804 CheckKeyTimes(aNumValues);
805 CheckKeySplines(aNumValues);
809 * Performs checks for the keyTimes attribute required by the SMIL spec but
810 * which depend on other attributes and therefore needs to be updated as
811 * dependent attributes are set.
813 void
814 nsSMILAnimationFunction::CheckKeyTimes(PRUint32 aNumValues)
816 if (!HasAttr(nsGkAtoms::keyTimes))
817 return;
819 nsSMILCalcMode calcMode = GetCalcMode();
821 // attribute is ignored for calcMode = paced
822 if (calcMode == CALC_PACED) {
823 SetKeyTimesErrorFlag(PR_FALSE);
824 return;
827 PRUint32 numKeyTimes = mKeyTimes.Length();
828 if (numKeyTimes < 1) {
829 // keyTimes isn't set or failed preliminary checks
830 SetKeyTimesErrorFlag(PR_TRUE);
831 return;
834 // no. keyTimes == no. values
835 // For to-animation the number of values is considered to be 2 unless it's
836 // discrete to-animation in which case either 1 or 2 is acceptable.
837 PRBool matchingNumOfValues = IsToAnimation() ?
838 calcMode == CALC_DISCRETE ? numKeyTimes <= 2 : numKeyTimes == 2 :
839 numKeyTimes == aNumValues;
840 if (!matchingNumOfValues) {
841 SetKeyTimesErrorFlag(PR_TRUE);
842 return;
845 // first value must be 0
846 if (mKeyTimes[0] != 0.0) {
847 SetKeyTimesErrorFlag(PR_TRUE);
848 return;
851 // last value must be 1 for linear or spline calcModes
852 if (calcMode != CALC_DISCRETE && numKeyTimes > 1 &&
853 mKeyTimes[numKeyTimes - 1] != 1.0) {
854 SetKeyTimesErrorFlag(PR_TRUE);
855 return;
858 SetKeyTimesErrorFlag(PR_FALSE);
861 void
862 nsSMILAnimationFunction::CheckKeySplines(PRUint32 aNumValues)
864 // attribute is ignored if calc mode is not spline
865 if (GetCalcMode() != CALC_SPLINE) {
866 SetKeySplinesErrorFlag(PR_FALSE);
867 return;
870 // calc mode is spline but the attribute is not set
871 if (!HasAttr(nsGkAtoms::keySplines)) {
872 SetKeySplinesErrorFlag(PR_FALSE);
873 return;
876 if (mKeySplines.Length() < 1) {
877 // keyTimes isn't set or failed preliminary checks
878 SetKeySplinesErrorFlag(PR_TRUE);
879 return;
882 // ignore splines if there's only one value
883 if (aNumValues == 1 && !IsToAnimation()) {
884 SetKeySplinesErrorFlag(PR_FALSE);
885 return;
888 // no. keySpline specs == no. values - 1
889 PRUint32 splineSpecs = mKeySplines.Length();
890 if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) ||
891 (IsToAnimation() && splineSpecs != 1)) {
892 SetKeySplinesErrorFlag(PR_TRUE);
893 return;
896 SetKeySplinesErrorFlag(PR_FALSE);
899 //----------------------------------------------------------------------
900 // Property getters
902 PRBool
903 nsSMILAnimationFunction::GetAccumulate() const
905 const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate);
906 if (!value)
907 return PR_FALSE;
909 return value->GetEnumValue();
912 PRBool
913 nsSMILAnimationFunction::GetAdditive() const
915 const nsAttrValue* value = GetAttr(nsGkAtoms::additive);
916 if (!value)
917 return PR_FALSE;
919 return value->GetEnumValue();
922 nsSMILAnimationFunction::nsSMILCalcMode
923 nsSMILAnimationFunction::GetCalcMode() const
925 const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode);
926 if (!value)
927 return CALC_LINEAR;
929 return nsSMILCalcMode(value->GetEnumValue());
932 //----------------------------------------------------------------------
933 // Property setters / un-setters:
935 nsresult
936 nsSMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate,
937 nsAttrValue& aResult)
939 mHasChanged = PR_TRUE;
940 PRBool parseResult =
941 aResult.ParseEnumValue(aAccumulate, sAccumulateTable, PR_TRUE);
942 SetAccumulateErrorFlag(!parseResult);
943 return parseResult ? NS_OK : NS_ERROR_FAILURE;
946 void
947 nsSMILAnimationFunction::UnsetAccumulate()
949 SetAccumulateErrorFlag(PR_FALSE);
950 mHasChanged = PR_TRUE;
953 nsresult
954 nsSMILAnimationFunction::SetAdditive(const nsAString& aAdditive,
955 nsAttrValue& aResult)
957 mHasChanged = PR_TRUE;
958 PRBool parseResult
959 = aResult.ParseEnumValue(aAdditive, sAdditiveTable, PR_TRUE);
960 SetAdditiveErrorFlag(!parseResult);
961 return parseResult ? NS_OK : NS_ERROR_FAILURE;
964 void
965 nsSMILAnimationFunction::UnsetAdditive()
967 SetAdditiveErrorFlag(PR_FALSE);
968 mHasChanged = PR_TRUE;
971 nsresult
972 nsSMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode,
973 nsAttrValue& aResult)
975 mHasChanged = PR_TRUE;
976 PRBool parseResult
977 = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, PR_TRUE);
978 SetCalcModeErrorFlag(!parseResult);
979 return parseResult ? NS_OK : NS_ERROR_FAILURE;
982 void
983 nsSMILAnimationFunction::UnsetCalcMode()
985 SetCalcModeErrorFlag(PR_FALSE);
986 mHasChanged = PR_TRUE;
989 nsresult
990 nsSMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines,
991 nsAttrValue& aResult)
993 mKeySplines.Clear();
994 aResult.SetTo(aKeySplines);
996 nsTArray<double> keySplines;
997 nsresult rv = nsSMILParserUtils::ParseKeySplines(aKeySplines, keySplines);
999 if (keySplines.Length() < 1 || keySplines.Length() % 4)
1000 rv = NS_ERROR_FAILURE;
1002 if (NS_SUCCEEDED(rv))
1004 mKeySplines.SetCapacity(keySplines.Length() % 4);
1005 for (PRUint32 i = 0; i < keySplines.Length() && NS_SUCCEEDED(rv); i += 4)
1007 if (!mKeySplines.AppendElement(nsSMILKeySpline(keySplines[i],
1008 keySplines[i+1],
1009 keySplines[i+2],
1010 keySplines[i+3]))) {
1011 rv = NS_ERROR_OUT_OF_MEMORY;
1016 mHasChanged = PR_TRUE;
1018 return rv;
1021 void
1022 nsSMILAnimationFunction::UnsetKeySplines()
1024 mKeySplines.Clear();
1025 SetKeySplinesErrorFlag(PR_FALSE);
1026 mHasChanged = PR_TRUE;
1029 nsresult
1030 nsSMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes,
1031 nsAttrValue& aResult)
1033 mKeyTimes.Clear();
1034 aResult.SetTo(aKeyTimes);
1036 nsresult rv =
1037 nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, PR_TRUE,
1038 mKeyTimes);
1040 if (NS_SUCCEEDED(rv) && mKeyTimes.Length() < 1)
1041 rv = NS_ERROR_FAILURE;
1043 if (NS_FAILED(rv))
1044 mKeyTimes.Clear();
1046 mHasChanged = PR_TRUE;
1048 return NS_OK;
1051 void
1052 nsSMILAnimationFunction::UnsetKeyTimes()
1054 mKeyTimes.Clear();
1055 SetKeyTimesErrorFlag(PR_FALSE);
1056 mHasChanged = PR_TRUE;