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