1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/smil/nsSMILAnimationFunction.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1059 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/dom/SVGAnimationElement.h" 1.10 +#include "nsSMILAnimationFunction.h" 1.11 +#include "nsISMILAttr.h" 1.12 +#include "nsSMILParserUtils.h" 1.13 +#include "nsSMILNullType.h" 1.14 +#include "nsSMILTimedElement.h" 1.15 +#include "nsAttrValueInlines.h" 1.16 +#include "nsGkAtoms.h" 1.17 +#include "nsCOMPtr.h" 1.18 +#include "nsCOMArray.h" 1.19 +#include "nsIContent.h" 1.20 +#include "nsAutoPtr.h" 1.21 +#include "nsContentUtils.h" 1.22 +#include "nsReadableUtils.h" 1.23 +#include "nsString.h" 1.24 +#include <math.h> 1.25 +#include <algorithm> 1.26 + 1.27 +using namespace mozilla::dom; 1.28 + 1.29 +//---------------------------------------------------------------------- 1.30 +// Static members 1.31 + 1.32 +nsAttrValue::EnumTable nsSMILAnimationFunction::sAccumulateTable[] = { 1.33 + {"none", false}, 1.34 + {"sum", true}, 1.35 + {nullptr, 0} 1.36 +}; 1.37 + 1.38 +nsAttrValue::EnumTable nsSMILAnimationFunction::sAdditiveTable[] = { 1.39 + {"replace", false}, 1.40 + {"sum", true}, 1.41 + {nullptr, 0} 1.42 +}; 1.43 + 1.44 +nsAttrValue::EnumTable nsSMILAnimationFunction::sCalcModeTable[] = { 1.45 + {"linear", CALC_LINEAR}, 1.46 + {"discrete", CALC_DISCRETE}, 1.47 + {"paced", CALC_PACED}, 1.48 + {"spline", CALC_SPLINE}, 1.49 + {nullptr, 0} 1.50 +}; 1.51 + 1.52 +// Any negative number should be fine as a sentinel here, 1.53 +// because valid distances are non-negative. 1.54 +#define COMPUTE_DISTANCE_ERROR (-1) 1.55 + 1.56 +//---------------------------------------------------------------------- 1.57 +// Constructors etc. 1.58 + 1.59 +nsSMILAnimationFunction::nsSMILAnimationFunction() 1.60 + : mSampleTime(-1), 1.61 + mRepeatIteration(0), 1.62 + mBeginTime(INT64_MIN), 1.63 + mAnimationElement(nullptr), 1.64 + mErrorFlags(0), 1.65 + mIsActive(false), 1.66 + mIsFrozen(false), 1.67 + mLastValue(false), 1.68 + mHasChanged(true), 1.69 + mValueNeedsReparsingEverySample(false), 1.70 + mPrevSampleWasSingleValueAnimation(false), 1.71 + mWasSkippedInPrevSample(false) 1.72 +{ 1.73 +} 1.74 + 1.75 +void 1.76 +nsSMILAnimationFunction::SetAnimationElement( 1.77 + SVGAnimationElement* aAnimationElement) 1.78 +{ 1.79 + mAnimationElement = aAnimationElement; 1.80 +} 1.81 + 1.82 +bool 1.83 +nsSMILAnimationFunction::SetAttr(nsIAtom* aAttribute, const nsAString& aValue, 1.84 + nsAttrValue& aResult, nsresult* aParseResult) 1.85 +{ 1.86 + bool foundMatch = true; 1.87 + nsresult parseResult = NS_OK; 1.88 + 1.89 + // The attributes 'by', 'from', 'to', and 'values' may be parsed differently 1.90 + // depending on the element & attribute we're animating. So instead of 1.91 + // parsing them now we re-parse them at every sample. 1.92 + if (aAttribute == nsGkAtoms::by || 1.93 + aAttribute == nsGkAtoms::from || 1.94 + aAttribute == nsGkAtoms::to || 1.95 + aAttribute == nsGkAtoms::values) { 1.96 + // We parse to, from, by, values at sample time. 1.97 + // XXX Need to flag which attribute has changed and then when we parse it at 1.98 + // sample time, report any errors and reset the flag 1.99 + mHasChanged = true; 1.100 + aResult.SetTo(aValue); 1.101 + } else if (aAttribute == nsGkAtoms::accumulate) { 1.102 + parseResult = SetAccumulate(aValue, aResult); 1.103 + } else if (aAttribute == nsGkAtoms::additive) { 1.104 + parseResult = SetAdditive(aValue, aResult); 1.105 + } else if (aAttribute == nsGkAtoms::calcMode) { 1.106 + parseResult = SetCalcMode(aValue, aResult); 1.107 + } else if (aAttribute == nsGkAtoms::keyTimes) { 1.108 + parseResult = SetKeyTimes(aValue, aResult); 1.109 + } else if (aAttribute == nsGkAtoms::keySplines) { 1.110 + parseResult = SetKeySplines(aValue, aResult); 1.111 + } else { 1.112 + foundMatch = false; 1.113 + } 1.114 + 1.115 + if (foundMatch && aParseResult) { 1.116 + *aParseResult = parseResult; 1.117 + } 1.118 + 1.119 + return foundMatch; 1.120 +} 1.121 + 1.122 +bool 1.123 +nsSMILAnimationFunction::UnsetAttr(nsIAtom* aAttribute) 1.124 +{ 1.125 + bool foundMatch = true; 1.126 + 1.127 + if (aAttribute == nsGkAtoms::by || 1.128 + aAttribute == nsGkAtoms::from || 1.129 + aAttribute == nsGkAtoms::to || 1.130 + aAttribute == nsGkAtoms::values) { 1.131 + mHasChanged = true; 1.132 + } else if (aAttribute == nsGkAtoms::accumulate) { 1.133 + UnsetAccumulate(); 1.134 + } else if (aAttribute == nsGkAtoms::additive) { 1.135 + UnsetAdditive(); 1.136 + } else if (aAttribute == nsGkAtoms::calcMode) { 1.137 + UnsetCalcMode(); 1.138 + } else if (aAttribute == nsGkAtoms::keyTimes) { 1.139 + UnsetKeyTimes(); 1.140 + } else if (aAttribute == nsGkAtoms::keySplines) { 1.141 + UnsetKeySplines(); 1.142 + } else { 1.143 + foundMatch = false; 1.144 + } 1.145 + 1.146 + return foundMatch; 1.147 +} 1.148 + 1.149 +void 1.150 +nsSMILAnimationFunction::SampleAt(nsSMILTime aSampleTime, 1.151 + const nsSMILTimeValue& aSimpleDuration, 1.152 + uint32_t aRepeatIteration) 1.153 +{ 1.154 + // * Update mHasChanged ("Might this sample be different from prev one?") 1.155 + // Were we previously sampling a fill="freeze" final val? (We're not anymore.) 1.156 + mHasChanged |= mLastValue; 1.157 + 1.158 + // Are we sampling at a new point in simple duration? And does that matter? 1.159 + mHasChanged |= 1.160 + (mSampleTime != aSampleTime || mSimpleDuration != aSimpleDuration) && 1.161 + !IsValueFixedForSimpleDuration(); 1.162 + 1.163 + // Are we on a new repeat and accumulating across repeats? 1.164 + if (!mErrorFlags) { // (can't call GetAccumulate() if we've had parse errors) 1.165 + mHasChanged |= (mRepeatIteration != aRepeatIteration) && GetAccumulate(); 1.166 + } 1.167 + 1.168 + mSampleTime = aSampleTime; 1.169 + mSimpleDuration = aSimpleDuration; 1.170 + mRepeatIteration = aRepeatIteration; 1.171 + mLastValue = false; 1.172 +} 1.173 + 1.174 +void 1.175 +nsSMILAnimationFunction::SampleLastValue(uint32_t aRepeatIteration) 1.176 +{ 1.177 + if (mHasChanged || !mLastValue || mRepeatIteration != aRepeatIteration) { 1.178 + mHasChanged = true; 1.179 + } 1.180 + 1.181 + mRepeatIteration = aRepeatIteration; 1.182 + mLastValue = true; 1.183 +} 1.184 + 1.185 +void 1.186 +nsSMILAnimationFunction::Activate(nsSMILTime aBeginTime) 1.187 +{ 1.188 + mBeginTime = aBeginTime; 1.189 + mIsActive = true; 1.190 + mIsFrozen = false; 1.191 + mHasChanged = true; 1.192 +} 1.193 + 1.194 +void 1.195 +nsSMILAnimationFunction::Inactivate(bool aIsFrozen) 1.196 +{ 1.197 + mIsActive = false; 1.198 + mIsFrozen = aIsFrozen; 1.199 + mHasChanged = true; 1.200 +} 1.201 + 1.202 +void 1.203 +nsSMILAnimationFunction::ComposeResult(const nsISMILAttr& aSMILAttr, 1.204 + nsSMILValue& aResult) 1.205 +{ 1.206 + mHasChanged = false; 1.207 + mPrevSampleWasSingleValueAnimation = false; 1.208 + mWasSkippedInPrevSample = false; 1.209 + 1.210 + // Skip animations that are inactive or in error 1.211 + if (!IsActiveOrFrozen() || mErrorFlags != 0) 1.212 + return; 1.213 + 1.214 + // Get the animation values 1.215 + nsSMILValueArray values; 1.216 + nsresult rv = GetValues(aSMILAttr, values); 1.217 + if (NS_FAILED(rv)) 1.218 + return; 1.219 + 1.220 + // Check that we have the right number of keySplines and keyTimes 1.221 + CheckValueListDependentAttrs(values.Length()); 1.222 + if (mErrorFlags != 0) 1.223 + return; 1.224 + 1.225 + // If this interval is active, we must have a non-negative mSampleTime 1.226 + NS_ABORT_IF_FALSE(mSampleTime >= 0 || !mIsActive, 1.227 + "Negative sample time for active animation"); 1.228 + NS_ABORT_IF_FALSE(mSimpleDuration.IsResolved() || mLastValue, 1.229 + "Unresolved simple duration for active or frozen animation"); 1.230 + 1.231 + // If we want to add but don't have a base value then just fail outright. 1.232 + // This can happen when we skipped getting the base value because there's an 1.233 + // animation function in the sandwich that should replace it but that function 1.234 + // failed unexpectedly. 1.235 + bool isAdditive = IsAdditive(); 1.236 + if (isAdditive && aResult.IsNull()) 1.237 + return; 1.238 + 1.239 + nsSMILValue result; 1.240 + 1.241 + if (values.Length() == 1 && !IsToAnimation()) { 1.242 + 1.243 + // Single-valued animation 1.244 + result = values[0]; 1.245 + mPrevSampleWasSingleValueAnimation = true; 1.246 + 1.247 + } else if (mLastValue) { 1.248 + 1.249 + // Sampling last value 1.250 + const nsSMILValue& last = values[values.Length() - 1]; 1.251 + result = last; 1.252 + 1.253 + // See comment in AccumulateResult: to-animation does not accumulate 1.254 + if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { 1.255 + // If the target attribute type doesn't support addition Add will 1.256 + // fail leaving result = last 1.257 + result.Add(last, mRepeatIteration); 1.258 + } 1.259 + 1.260 + } else { 1.261 + 1.262 + // Interpolation 1.263 + if (NS_FAILED(InterpolateResult(values, result, aResult))) 1.264 + return; 1.265 + 1.266 + if (NS_FAILED(AccumulateResult(values, result))) 1.267 + return; 1.268 + } 1.269 + 1.270 + // If additive animation isn't required or isn't supported, set the value. 1.271 + if (!isAdditive || NS_FAILED(aResult.SandwichAdd(result))) { 1.272 + aResult.Swap(result); 1.273 + // Note: The old value of aResult is now in |result|, and it will get 1.274 + // cleaned up when |result| goes out of scope, when this function returns. 1.275 + } 1.276 +} 1.277 + 1.278 +int8_t 1.279 +nsSMILAnimationFunction::CompareTo(const nsSMILAnimationFunction* aOther) const 1.280 +{ 1.281 + NS_ENSURE_TRUE(aOther, 0); 1.282 + 1.283 + NS_ASSERTION(aOther != this, "Trying to compare to self"); 1.284 + 1.285 + // Inactive animations sort first 1.286 + if (!IsActiveOrFrozen() && aOther->IsActiveOrFrozen()) 1.287 + return -1; 1.288 + 1.289 + if (IsActiveOrFrozen() && !aOther->IsActiveOrFrozen()) 1.290 + return 1; 1.291 + 1.292 + // Sort based on begin time 1.293 + if (mBeginTime != aOther->GetBeginTime()) 1.294 + return mBeginTime > aOther->GetBeginTime() ? 1 : -1; 1.295 + 1.296 + // Next sort based on syncbase dependencies: the dependent element sorts after 1.297 + // its syncbase 1.298 + const nsSMILTimedElement& thisTimedElement = 1.299 + mAnimationElement->TimedElement(); 1.300 + const nsSMILTimedElement& otherTimedElement = 1.301 + aOther->mAnimationElement->TimedElement(); 1.302 + if (thisTimedElement.IsTimeDependent(otherTimedElement)) 1.303 + return 1; 1.304 + if (otherTimedElement.IsTimeDependent(thisTimedElement)) 1.305 + return -1; 1.306 + 1.307 + // Animations that appear later in the document sort after those earlier in 1.308 + // the document 1.309 + NS_ABORT_IF_FALSE(mAnimationElement != aOther->mAnimationElement, 1.310 + "Two animations cannot have the same animation content element!"); 1.311 + 1.312 + return (nsContentUtils::PositionIsBefore(mAnimationElement, aOther->mAnimationElement)) 1.313 + ? -1 : 1; 1.314 +} 1.315 + 1.316 +bool 1.317 +nsSMILAnimationFunction::WillReplace() const 1.318 +{ 1.319 + /* 1.320 + * In IsAdditive() we don't consider to-animation to be additive as it is 1.321 + * a special case that is dealt with differently in the compositing method. 1.322 + * Here, however, we return FALSE for to-animation (i.e. it will NOT replace 1.323 + * the underlying value) as it builds on the underlying value. 1.324 + */ 1.325 + return !mErrorFlags && !(IsAdditive() || IsToAnimation()); 1.326 +} 1.327 + 1.328 +bool 1.329 +nsSMILAnimationFunction::HasChanged() const 1.330 +{ 1.331 + return mHasChanged || mValueNeedsReparsingEverySample; 1.332 +} 1.333 + 1.334 +bool 1.335 +nsSMILAnimationFunction::UpdateCachedTarget( 1.336 + const nsSMILTargetIdentifier& aNewTarget) 1.337 +{ 1.338 + if (!mLastTarget.Equals(aNewTarget)) { 1.339 + mLastTarget = aNewTarget; 1.340 + return true; 1.341 + } 1.342 + return false; 1.343 +} 1.344 + 1.345 +//---------------------------------------------------------------------- 1.346 +// Implementation helpers 1.347 + 1.348 +nsresult 1.349 +nsSMILAnimationFunction::InterpolateResult(const nsSMILValueArray& aValues, 1.350 + nsSMILValue& aResult, 1.351 + nsSMILValue& aBaseValue) 1.352 +{ 1.353 + // Sanity check animation values 1.354 + if ((!IsToAnimation() && aValues.Length() < 2) || 1.355 + (IsToAnimation() && aValues.Length() != 1)) { 1.356 + NS_ERROR("Unexpected number of values"); 1.357 + return NS_ERROR_FAILURE; 1.358 + } 1.359 + 1.360 + if (IsToAnimation() && aBaseValue.IsNull()) { 1.361 + return NS_ERROR_FAILURE; 1.362 + } 1.363 + 1.364 + // Get the normalised progress through the simple duration. 1.365 + // 1.366 + // If we have an indefinite simple duration, just set the progress to be 1.367 + // 0 which will give us the expected behaviour of the animation being fixed at 1.368 + // its starting point. 1.369 + double simpleProgress = 0.0; 1.370 + 1.371 + if (mSimpleDuration.IsDefinite()) { 1.372 + nsSMILTime dur = mSimpleDuration.GetMillis(); 1.373 + 1.374 + NS_ABORT_IF_FALSE(dur >= 0, "Simple duration should not be negative"); 1.375 + NS_ABORT_IF_FALSE(mSampleTime >= 0, "Sample time should not be negative"); 1.376 + 1.377 + if (mSampleTime >= dur || mSampleTime < 0) { 1.378 + NS_ERROR("Animation sampled outside interval"); 1.379 + return NS_ERROR_FAILURE; 1.380 + } 1.381 + 1.382 + if (dur > 0) { 1.383 + simpleProgress = (double)mSampleTime / dur; 1.384 + } // else leave simpleProgress at 0.0 (e.g. if mSampleTime == dur == 0) 1.385 + } 1.386 + 1.387 + nsresult rv = NS_OK; 1.388 + nsSMILCalcMode calcMode = GetCalcMode(); 1.389 + if (calcMode != CALC_DISCRETE) { 1.390 + // Get the normalised progress between adjacent values 1.391 + const nsSMILValue* from = nullptr; 1.392 + const nsSMILValue* to = nullptr; 1.393 + // Init to -1 to make sure that if we ever forget to set this, the 1.394 + // NS_ABORT_IF_FALSE that tests that intervalProgress is in range will fail. 1.395 + double intervalProgress = -1.f; 1.396 + if (IsToAnimation()) { 1.397 + from = &aBaseValue; 1.398 + to = &aValues[0]; 1.399 + if (calcMode == CALC_PACED) { 1.400 + // Note: key[Times/Splines/Points] are ignored for calcMode="paced" 1.401 + intervalProgress = simpleProgress; 1.402 + } else { 1.403 + double scaledSimpleProgress = 1.404 + ScaleSimpleProgress(simpleProgress, calcMode); 1.405 + intervalProgress = ScaleIntervalProgress(scaledSimpleProgress, 0); 1.406 + } 1.407 + } else if (calcMode == CALC_PACED) { 1.408 + rv = ComputePacedPosition(aValues, simpleProgress, 1.409 + intervalProgress, from, to); 1.410 + // Note: If the above call fails, we'll skip the "from->Interpolate" 1.411 + // call below, and we'll drop into the CALC_DISCRETE section 1.412 + // instead. (as the spec says we should, because our failure was 1.413 + // presumably due to the values being non-additive) 1.414 + } else { // calcMode == CALC_LINEAR or calcMode == CALC_SPLINE 1.415 + double scaledSimpleProgress = 1.416 + ScaleSimpleProgress(simpleProgress, calcMode); 1.417 + uint32_t index = (uint32_t)floor(scaledSimpleProgress * 1.418 + (aValues.Length() - 1)); 1.419 + from = &aValues[index]; 1.420 + to = &aValues[index + 1]; 1.421 + intervalProgress = 1.422 + scaledSimpleProgress * (aValues.Length() - 1) - index; 1.423 + intervalProgress = ScaleIntervalProgress(intervalProgress, index); 1.424 + } 1.425 + 1.426 + if (NS_SUCCEEDED(rv)) { 1.427 + NS_ABORT_IF_FALSE(from, "NULL from-value during interpolation"); 1.428 + NS_ABORT_IF_FALSE(to, "NULL to-value during interpolation"); 1.429 + NS_ABORT_IF_FALSE(0.0f <= intervalProgress && intervalProgress < 1.0f, 1.430 + "Interval progress should be in the range [0, 1)"); 1.431 + rv = from->Interpolate(*to, intervalProgress, aResult); 1.432 + } 1.433 + } 1.434 + 1.435 + // Discrete-CalcMode case 1.436 + // Note: If interpolation failed (isn't supported for this type), the SVG 1.437 + // spec says to force discrete mode. 1.438 + if (calcMode == CALC_DISCRETE || NS_FAILED(rv)) { 1.439 + double scaledSimpleProgress = 1.440 + ScaleSimpleProgress(simpleProgress, CALC_DISCRETE); 1.441 + 1.442 + // Floating-point errors can mean that, for example, a sample time of 29s in 1.443 + // a 100s duration animation gives us a simple progress of 0.28999999999 1.444 + // instead of the 0.29 we'd expect. Normally this isn't a noticeable 1.445 + // problem, but when we have sudden jumps in animation values (such as is 1.446 + // the case here with discrete animation) we can get unexpected results. 1.447 + // 1.448 + // To counteract this, before we perform a floor() on the animation 1.449 + // progress, we add a tiny fudge factor to push us into the correct interval 1.450 + // in cases where floating-point errors might cause us to fall short. 1.451 + static const double kFloatingPointFudgeFactor = 1.0e-16; 1.452 + if (scaledSimpleProgress + kFloatingPointFudgeFactor <= 1.0) { 1.453 + scaledSimpleProgress += kFloatingPointFudgeFactor; 1.454 + } 1.455 + 1.456 + if (IsToAnimation()) { 1.457 + // We don't follow SMIL 3, 12.6.4, where discrete to animations 1.458 + // are the same as <set> animations. Instead, we treat it as a 1.459 + // discrete animation with two values (the underlying value and 1.460 + // the to="" value), and honor keyTimes="" as well. 1.461 + uint32_t index = (uint32_t)floor(scaledSimpleProgress * 2); 1.462 + aResult = index == 0 ? aBaseValue : aValues[0]; 1.463 + } else { 1.464 + uint32_t index = (uint32_t)floor(scaledSimpleProgress * aValues.Length()); 1.465 + aResult = aValues[index]; 1.466 + } 1.467 + rv = NS_OK; 1.468 + } 1.469 + return rv; 1.470 +} 1.471 + 1.472 +nsresult 1.473 +nsSMILAnimationFunction::AccumulateResult(const nsSMILValueArray& aValues, 1.474 + nsSMILValue& aResult) 1.475 +{ 1.476 + if (!IsToAnimation() && GetAccumulate() && mRepeatIteration) { 1.477 + const nsSMILValue& lastValue = aValues[aValues.Length() - 1]; 1.478 + 1.479 + // If the target attribute type doesn't support addition, Add will 1.480 + // fail and we leave aResult untouched. 1.481 + aResult.Add(lastValue, mRepeatIteration); 1.482 + } 1.483 + 1.484 + return NS_OK; 1.485 +} 1.486 + 1.487 +/* 1.488 + * Given the simple progress for a paced animation, this method: 1.489 + * - determines which two elements of the values array we're in between 1.490 + * (returned as aFrom and aTo) 1.491 + * - determines where we are between them 1.492 + * (returned as aIntervalProgress) 1.493 + * 1.494 + * Returns NS_OK, or NS_ERROR_FAILURE if our values don't support distance 1.495 + * computation. 1.496 + */ 1.497 +nsresult 1.498 +nsSMILAnimationFunction::ComputePacedPosition(const nsSMILValueArray& aValues, 1.499 + double aSimpleProgress, 1.500 + double& aIntervalProgress, 1.501 + const nsSMILValue*& aFrom, 1.502 + const nsSMILValue*& aTo) 1.503 +{ 1.504 + NS_ASSERTION(0.0f <= aSimpleProgress && aSimpleProgress < 1.0f, 1.505 + "aSimpleProgress is out of bounds"); 1.506 + NS_ASSERTION(GetCalcMode() == CALC_PACED, 1.507 + "Calling paced-specific function, but not in paced mode"); 1.508 + NS_ABORT_IF_FALSE(aValues.Length() >= 2, "Unexpected number of values"); 1.509 + 1.510 + // Trivial case: If we have just 2 values, then there's only one interval 1.511 + // for us to traverse, and our progress across that interval is the exact 1.512 + // same as our overall progress. 1.513 + if (aValues.Length() == 2) { 1.514 + aIntervalProgress = aSimpleProgress; 1.515 + aFrom = &aValues[0]; 1.516 + aTo = &aValues[1]; 1.517 + return NS_OK; 1.518 + } 1.519 + 1.520 + double totalDistance = ComputePacedTotalDistance(aValues); 1.521 + if (totalDistance == COMPUTE_DISTANCE_ERROR) 1.522 + return NS_ERROR_FAILURE; 1.523 + 1.524 + // If we have 0 total distance, then it's unclear where our "paced" position 1.525 + // should be. We can just fail, which drops us into discrete animation mode. 1.526 + // (That's fine, since our values are apparently indistinguishable anyway.) 1.527 + if (totalDistance == 0.0) { 1.528 + return NS_ERROR_FAILURE; 1.529 + } 1.530 + 1.531 + // total distance we should have moved at this point in time. 1.532 + // (called 'remainingDist' due to how it's used in loop below) 1.533 + double remainingDist = aSimpleProgress * totalDistance; 1.534 + 1.535 + // Must be satisfied, because totalDistance is a sum of (non-negative) 1.536 + // distances, and aSimpleProgress is non-negative 1.537 + NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative"); 1.538 + 1.539 + // Find where remainingDist puts us in the list of values 1.540 + // Note: We could optimize this next loop by caching the 1.541 + // interval-distances in an array, but maybe that's excessive. 1.542 + for (uint32_t i = 0; i < aValues.Length() - 1; i++) { 1.543 + // Note: The following assertion is valid because remainingDist should 1.544 + // start out non-negative, and this loop never shaves off more than its 1.545 + // current value. 1.546 + NS_ASSERTION(remainingDist >= 0, "distance values must be non-negative"); 1.547 + 1.548 + double curIntervalDist; 1.549 + 1.550 +#ifdef DEBUG 1.551 + nsresult rv = 1.552 +#endif 1.553 + aValues[i].ComputeDistance(aValues[i+1], curIntervalDist); 1.554 + NS_ABORT_IF_FALSE(NS_SUCCEEDED(rv), 1.555 + "If we got through ComputePacedTotalDistance, we should " 1.556 + "be able to recompute each sub-distance without errors"); 1.557 + 1.558 + NS_ASSERTION(curIntervalDist >= 0, "distance values must be non-negative"); 1.559 + // Clamp distance value at 0, just in case ComputeDistance is evil. 1.560 + curIntervalDist = std::max(curIntervalDist, 0.0); 1.561 + 1.562 + if (remainingDist >= curIntervalDist) { 1.563 + remainingDist -= curIntervalDist; 1.564 + } else { 1.565 + // NOTE: If we get here, then curIntervalDist necessarily is not 0. Why? 1.566 + // Because this clause is only hit when remainingDist < curIntervalDist, 1.567 + // and if curIntervalDist were 0, that would mean remainingDist would 1.568 + // have to be < 0. But that can't happen, because remainingDist (as 1.569 + // a distance) is non-negative by definition. 1.570 + NS_ASSERTION(curIntervalDist != 0, 1.571 + "We should never get here with this set to 0..."); 1.572 + 1.573 + // We found the right spot -- an interpolated position between 1.574 + // values i and i+1. 1.575 + aFrom = &aValues[i]; 1.576 + aTo = &aValues[i+1]; 1.577 + aIntervalProgress = remainingDist / curIntervalDist; 1.578 + return NS_OK; 1.579 + } 1.580 + } 1.581 + 1.582 + NS_NOTREACHED("shouldn't complete loop & get here -- if we do, " 1.583 + "then aSimpleProgress was probably out of bounds"); 1.584 + return NS_ERROR_FAILURE; 1.585 +} 1.586 + 1.587 +/* 1.588 + * Computes the total distance to be travelled by a paced animation. 1.589 + * 1.590 + * Returns the total distance, or returns COMPUTE_DISTANCE_ERROR if 1.591 + * our values don't support distance computation. 1.592 + */ 1.593 +double 1.594 +nsSMILAnimationFunction::ComputePacedTotalDistance( 1.595 + const nsSMILValueArray& aValues) const 1.596 +{ 1.597 + NS_ASSERTION(GetCalcMode() == CALC_PACED, 1.598 + "Calling paced-specific function, but not in paced mode"); 1.599 + 1.600 + double totalDistance = 0.0; 1.601 + for (uint32_t i = 0; i < aValues.Length() - 1; i++) { 1.602 + double tmpDist; 1.603 + nsresult rv = aValues[i].ComputeDistance(aValues[i+1], tmpDist); 1.604 + if (NS_FAILED(rv)) { 1.605 + return COMPUTE_DISTANCE_ERROR; 1.606 + } 1.607 + 1.608 + // Clamp distance value to 0, just in case we have an evil ComputeDistance 1.609 + // implementation somewhere 1.610 + NS_ABORT_IF_FALSE(tmpDist >= 0.0f, "distance values must be non-negative"); 1.611 + tmpDist = std::max(tmpDist, 0.0); 1.612 + 1.613 + totalDistance += tmpDist; 1.614 + } 1.615 + 1.616 + return totalDistance; 1.617 +} 1.618 + 1.619 +double 1.620 +nsSMILAnimationFunction::ScaleSimpleProgress(double aProgress, 1.621 + nsSMILCalcMode aCalcMode) 1.622 +{ 1.623 + if (!HasAttr(nsGkAtoms::keyTimes)) 1.624 + return aProgress; 1.625 + 1.626 + uint32_t numTimes = mKeyTimes.Length(); 1.627 + 1.628 + if (numTimes < 2) 1.629 + return aProgress; 1.630 + 1.631 + uint32_t i = 0; 1.632 + for (; i < numTimes - 2 && aProgress >= mKeyTimes[i+1]; ++i) { } 1.633 + 1.634 + if (aCalcMode == CALC_DISCRETE) { 1.635 + // discrete calcMode behaviour differs in that each keyTime defines the time 1.636 + // from when the corresponding value is set, and therefore the last value 1.637 + // needn't be 1. So check if we're in the last 'interval', that is, the 1.638 + // space between the final value and 1.0. 1.639 + if (aProgress >= mKeyTimes[i+1]) { 1.640 + NS_ABORT_IF_FALSE(i == numTimes - 2, 1.641 + "aProgress is not in range of the current interval, yet the current" 1.642 + " interval is not the last bounded interval either."); 1.643 + ++i; 1.644 + } 1.645 + return (double)i / numTimes; 1.646 + } 1.647 + 1.648 + double& intervalStart = mKeyTimes[i]; 1.649 + double& intervalEnd = mKeyTimes[i+1]; 1.650 + 1.651 + double intervalLength = intervalEnd - intervalStart; 1.652 + if (intervalLength <= 0.0) 1.653 + return intervalStart; 1.654 + 1.655 + return (i + (aProgress - intervalStart) / intervalLength) / 1.656 + double(numTimes - 1); 1.657 +} 1.658 + 1.659 +double 1.660 +nsSMILAnimationFunction::ScaleIntervalProgress(double aProgress, 1.661 + uint32_t aIntervalIndex) 1.662 +{ 1.663 + if (GetCalcMode() != CALC_SPLINE) 1.664 + return aProgress; 1.665 + 1.666 + if (!HasAttr(nsGkAtoms::keySplines)) 1.667 + return aProgress; 1.668 + 1.669 + NS_ABORT_IF_FALSE(aIntervalIndex < mKeySplines.Length(), 1.670 + "Invalid interval index"); 1.671 + 1.672 + nsSMILKeySpline const &spline = mKeySplines[aIntervalIndex]; 1.673 + return spline.GetSplineValue(aProgress); 1.674 +} 1.675 + 1.676 +bool 1.677 +nsSMILAnimationFunction::HasAttr(nsIAtom* aAttName) const 1.678 +{ 1.679 + return mAnimationElement->HasAnimAttr(aAttName); 1.680 +} 1.681 + 1.682 +const nsAttrValue* 1.683 +nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName) const 1.684 +{ 1.685 + return mAnimationElement->GetAnimAttr(aAttName); 1.686 +} 1.687 + 1.688 +bool 1.689 +nsSMILAnimationFunction::GetAttr(nsIAtom* aAttName, nsAString& aResult) const 1.690 +{ 1.691 + return mAnimationElement->GetAnimAttr(aAttName, aResult); 1.692 +} 1.693 + 1.694 +/* 1.695 + * A utility function to make querying an attribute that corresponds to an 1.696 + * nsSMILValue a little neater. 1.697 + * 1.698 + * @param aAttName The attribute name (in the global namespace). 1.699 + * @param aSMILAttr The SMIL attribute to perform the parsing. 1.700 + * @param[out] aResult The resulting nsSMILValue. 1.701 + * @param[out] aPreventCachingOfSandwich 1.702 + * If |aResult| contains dependencies on its context that 1.703 + * should prevent the result of the animation sandwich from 1.704 + * being cached and reused in future samples (as reported 1.705 + * by nsISMILAttr::ValueFromString), then this outparam 1.706 + * will be set to true. Otherwise it is left unmodified. 1.707 + * 1.708 + * Returns false if a parse error occurred, otherwise returns true. 1.709 + */ 1.710 +bool 1.711 +nsSMILAnimationFunction::ParseAttr(nsIAtom* aAttName, 1.712 + const nsISMILAttr& aSMILAttr, 1.713 + nsSMILValue& aResult, 1.714 + bool& aPreventCachingOfSandwich) const 1.715 +{ 1.716 + nsAutoString attValue; 1.717 + if (GetAttr(aAttName, attValue)) { 1.718 + bool preventCachingOfSandwich = false; 1.719 + nsresult rv = aSMILAttr.ValueFromString(attValue, mAnimationElement, 1.720 + aResult, preventCachingOfSandwich); 1.721 + if (NS_FAILED(rv)) 1.722 + return false; 1.723 + 1.724 + if (preventCachingOfSandwich) { 1.725 + aPreventCachingOfSandwich = true; 1.726 + } 1.727 + } 1.728 + return true; 1.729 +} 1.730 + 1.731 +/* 1.732 + * SMILANIM specifies the following rules for animation function values: 1.733 + * 1.734 + * (1) if values is set, it overrides everything 1.735 + * (2) for from/to/by animation at least to or by must be specified, from on its 1.736 + * own (or nothing) is an error--which we will ignore 1.737 + * (3) if both by and to are specified only to will be used, by will be ignored 1.738 + * (4) if by is specified without from (by animation), forces additive behaviour 1.739 + * (5) if to is specified without from (to animation), special care needs to be 1.740 + * taken when compositing animation as such animations are composited last. 1.741 + * 1.742 + * This helper method applies these rules to fill in the values list and to set 1.743 + * some internal state. 1.744 + */ 1.745 +nsresult 1.746 +nsSMILAnimationFunction::GetValues(const nsISMILAttr& aSMILAttr, 1.747 + nsSMILValueArray& aResult) 1.748 +{ 1.749 + if (!mAnimationElement) 1.750 + return NS_ERROR_FAILURE; 1.751 + 1.752 + mValueNeedsReparsingEverySample = false; 1.753 + nsSMILValueArray result; 1.754 + 1.755 + // If "values" is set, use it 1.756 + if (HasAttr(nsGkAtoms::values)) { 1.757 + nsAutoString attValue; 1.758 + GetAttr(nsGkAtoms::values, attValue); 1.759 + bool preventCachingOfSandwich = false; 1.760 + if (!nsSMILParserUtils::ParseValues(attValue, mAnimationElement, 1.761 + aSMILAttr, result, 1.762 + preventCachingOfSandwich)) { 1.763 + return NS_ERROR_FAILURE; 1.764 + } 1.765 + 1.766 + if (preventCachingOfSandwich) { 1.767 + mValueNeedsReparsingEverySample = true; 1.768 + } 1.769 + // Else try to/from/by 1.770 + } else { 1.771 + bool preventCachingOfSandwich = false; 1.772 + bool parseOk = true; 1.773 + nsSMILValue to, from, by; 1.774 + parseOk &= ParseAttr(nsGkAtoms::to, aSMILAttr, to, 1.775 + preventCachingOfSandwich); 1.776 + parseOk &= ParseAttr(nsGkAtoms::from, aSMILAttr, from, 1.777 + preventCachingOfSandwich); 1.778 + parseOk &= ParseAttr(nsGkAtoms::by, aSMILAttr, by, 1.779 + preventCachingOfSandwich); 1.780 + 1.781 + if (preventCachingOfSandwich) { 1.782 + mValueNeedsReparsingEverySample = true; 1.783 + } 1.784 + 1.785 + if (!parseOk) 1.786 + return NS_ERROR_FAILURE; 1.787 + 1.788 + result.SetCapacity(2); 1.789 + if (!to.IsNull()) { 1.790 + if (!from.IsNull()) { 1.791 + result.AppendElement(from); 1.792 + result.AppendElement(to); 1.793 + } else { 1.794 + result.AppendElement(to); 1.795 + } 1.796 + } else if (!by.IsNull()) { 1.797 + nsSMILValue effectiveFrom(by.mType); 1.798 + if (!from.IsNull()) 1.799 + effectiveFrom = from; 1.800 + // Set values to 'from; from + by' 1.801 + result.AppendElement(effectiveFrom); 1.802 + nsSMILValue effectiveTo(effectiveFrom); 1.803 + if (!effectiveTo.IsNull() && NS_SUCCEEDED(effectiveTo.Add(by))) { 1.804 + result.AppendElement(effectiveTo); 1.805 + } else { 1.806 + // Using by-animation with non-additive type or bad base-value 1.807 + return NS_ERROR_FAILURE; 1.808 + } 1.809 + } else { 1.810 + // No values, no to, no by -- call it a day 1.811 + return NS_ERROR_FAILURE; 1.812 + } 1.813 + } 1.814 + 1.815 + result.SwapElements(aResult); 1.816 + 1.817 + return NS_OK; 1.818 +} 1.819 + 1.820 +void 1.821 +nsSMILAnimationFunction::CheckValueListDependentAttrs(uint32_t aNumValues) 1.822 +{ 1.823 + CheckKeyTimes(aNumValues); 1.824 + CheckKeySplines(aNumValues); 1.825 +} 1.826 + 1.827 +/** 1.828 + * Performs checks for the keyTimes attribute required by the SMIL spec but 1.829 + * which depend on other attributes and therefore needs to be updated as 1.830 + * dependent attributes are set. 1.831 + */ 1.832 +void 1.833 +nsSMILAnimationFunction::CheckKeyTimes(uint32_t aNumValues) 1.834 +{ 1.835 + if (!HasAttr(nsGkAtoms::keyTimes)) 1.836 + return; 1.837 + 1.838 + nsSMILCalcMode calcMode = GetCalcMode(); 1.839 + 1.840 + // attribute is ignored for calcMode = paced 1.841 + if (calcMode == CALC_PACED) { 1.842 + SetKeyTimesErrorFlag(false); 1.843 + return; 1.844 + } 1.845 + 1.846 + uint32_t numKeyTimes = mKeyTimes.Length(); 1.847 + if (numKeyTimes < 1) { 1.848 + // keyTimes isn't set or failed preliminary checks 1.849 + SetKeyTimesErrorFlag(true); 1.850 + return; 1.851 + } 1.852 + 1.853 + // no. keyTimes == no. values 1.854 + // For to-animation the number of values is considered to be 2. 1.855 + bool matchingNumOfValues = 1.856 + numKeyTimes == (IsToAnimation() ? 2 : aNumValues); 1.857 + if (!matchingNumOfValues) { 1.858 + SetKeyTimesErrorFlag(true); 1.859 + return; 1.860 + } 1.861 + 1.862 + // first value must be 0 1.863 + if (mKeyTimes[0] != 0.0) { 1.864 + SetKeyTimesErrorFlag(true); 1.865 + return; 1.866 + } 1.867 + 1.868 + // last value must be 1 for linear or spline calcModes 1.869 + if (calcMode != CALC_DISCRETE && numKeyTimes > 1 && 1.870 + mKeyTimes[numKeyTimes - 1] != 1.0) { 1.871 + SetKeyTimesErrorFlag(true); 1.872 + return; 1.873 + } 1.874 + 1.875 + SetKeyTimesErrorFlag(false); 1.876 +} 1.877 + 1.878 +void 1.879 +nsSMILAnimationFunction::CheckKeySplines(uint32_t aNumValues) 1.880 +{ 1.881 + // attribute is ignored if calc mode is not spline 1.882 + if (GetCalcMode() != CALC_SPLINE) { 1.883 + SetKeySplinesErrorFlag(false); 1.884 + return; 1.885 + } 1.886 + 1.887 + // calc mode is spline but the attribute is not set 1.888 + if (!HasAttr(nsGkAtoms::keySplines)) { 1.889 + SetKeySplinesErrorFlag(false); 1.890 + return; 1.891 + } 1.892 + 1.893 + if (mKeySplines.Length() < 1) { 1.894 + // keyTimes isn't set or failed preliminary checks 1.895 + SetKeySplinesErrorFlag(true); 1.896 + return; 1.897 + } 1.898 + 1.899 + // ignore splines if there's only one value 1.900 + if (aNumValues == 1 && !IsToAnimation()) { 1.901 + SetKeySplinesErrorFlag(false); 1.902 + return; 1.903 + } 1.904 + 1.905 + // no. keySpline specs == no. values - 1 1.906 + uint32_t splineSpecs = mKeySplines.Length(); 1.907 + if ((splineSpecs != aNumValues - 1 && !IsToAnimation()) || 1.908 + (IsToAnimation() && splineSpecs != 1)) { 1.909 + SetKeySplinesErrorFlag(true); 1.910 + return; 1.911 + } 1.912 + 1.913 + SetKeySplinesErrorFlag(false); 1.914 +} 1.915 + 1.916 +bool 1.917 +nsSMILAnimationFunction::IsValueFixedForSimpleDuration() const 1.918 +{ 1.919 + return mSimpleDuration.IsIndefinite() || 1.920 + (!mHasChanged && mPrevSampleWasSingleValueAnimation); 1.921 +} 1.922 + 1.923 +//---------------------------------------------------------------------- 1.924 +// Property getters 1.925 + 1.926 +bool 1.927 +nsSMILAnimationFunction::GetAccumulate() const 1.928 +{ 1.929 + const nsAttrValue* value = GetAttr(nsGkAtoms::accumulate); 1.930 + if (!value) 1.931 + return false; 1.932 + 1.933 + return value->GetEnumValue(); 1.934 +} 1.935 + 1.936 +bool 1.937 +nsSMILAnimationFunction::GetAdditive() const 1.938 +{ 1.939 + const nsAttrValue* value = GetAttr(nsGkAtoms::additive); 1.940 + if (!value) 1.941 + return false; 1.942 + 1.943 + return value->GetEnumValue(); 1.944 +} 1.945 + 1.946 +nsSMILAnimationFunction::nsSMILCalcMode 1.947 +nsSMILAnimationFunction::GetCalcMode() const 1.948 +{ 1.949 + const nsAttrValue* value = GetAttr(nsGkAtoms::calcMode); 1.950 + if (!value) 1.951 + return CALC_LINEAR; 1.952 + 1.953 + return nsSMILCalcMode(value->GetEnumValue()); 1.954 +} 1.955 + 1.956 +//---------------------------------------------------------------------- 1.957 +// Property setters / un-setters: 1.958 + 1.959 +nsresult 1.960 +nsSMILAnimationFunction::SetAccumulate(const nsAString& aAccumulate, 1.961 + nsAttrValue& aResult) 1.962 +{ 1.963 + mHasChanged = true; 1.964 + bool parseResult = 1.965 + aResult.ParseEnumValue(aAccumulate, sAccumulateTable, true); 1.966 + SetAccumulateErrorFlag(!parseResult); 1.967 + return parseResult ? NS_OK : NS_ERROR_FAILURE; 1.968 +} 1.969 + 1.970 +void 1.971 +nsSMILAnimationFunction::UnsetAccumulate() 1.972 +{ 1.973 + SetAccumulateErrorFlag(false); 1.974 + mHasChanged = true; 1.975 +} 1.976 + 1.977 +nsresult 1.978 +nsSMILAnimationFunction::SetAdditive(const nsAString& aAdditive, 1.979 + nsAttrValue& aResult) 1.980 +{ 1.981 + mHasChanged = true; 1.982 + bool parseResult 1.983 + = aResult.ParseEnumValue(aAdditive, sAdditiveTable, true); 1.984 + SetAdditiveErrorFlag(!parseResult); 1.985 + return parseResult ? NS_OK : NS_ERROR_FAILURE; 1.986 +} 1.987 + 1.988 +void 1.989 +nsSMILAnimationFunction::UnsetAdditive() 1.990 +{ 1.991 + SetAdditiveErrorFlag(false); 1.992 + mHasChanged = true; 1.993 +} 1.994 + 1.995 +nsresult 1.996 +nsSMILAnimationFunction::SetCalcMode(const nsAString& aCalcMode, 1.997 + nsAttrValue& aResult) 1.998 +{ 1.999 + mHasChanged = true; 1.1000 + bool parseResult 1.1001 + = aResult.ParseEnumValue(aCalcMode, sCalcModeTable, true); 1.1002 + SetCalcModeErrorFlag(!parseResult); 1.1003 + return parseResult ? NS_OK : NS_ERROR_FAILURE; 1.1004 +} 1.1005 + 1.1006 +void 1.1007 +nsSMILAnimationFunction::UnsetCalcMode() 1.1008 +{ 1.1009 + SetCalcModeErrorFlag(false); 1.1010 + mHasChanged = true; 1.1011 +} 1.1012 + 1.1013 +nsresult 1.1014 +nsSMILAnimationFunction::SetKeySplines(const nsAString& aKeySplines, 1.1015 + nsAttrValue& aResult) 1.1016 +{ 1.1017 + mKeySplines.Clear(); 1.1018 + aResult.SetTo(aKeySplines); 1.1019 + 1.1020 + mHasChanged = true; 1.1021 + 1.1022 + if (!nsSMILParserUtils::ParseKeySplines(aKeySplines, mKeySplines)) { 1.1023 + mKeySplines.Clear(); 1.1024 + return NS_ERROR_FAILURE; 1.1025 + } 1.1026 + 1.1027 + return NS_OK; 1.1028 +} 1.1029 + 1.1030 +void 1.1031 +nsSMILAnimationFunction::UnsetKeySplines() 1.1032 +{ 1.1033 + mKeySplines.Clear(); 1.1034 + SetKeySplinesErrorFlag(false); 1.1035 + mHasChanged = true; 1.1036 +} 1.1037 + 1.1038 +nsresult 1.1039 +nsSMILAnimationFunction::SetKeyTimes(const nsAString& aKeyTimes, 1.1040 + nsAttrValue& aResult) 1.1041 +{ 1.1042 + mKeyTimes.Clear(); 1.1043 + aResult.SetTo(aKeyTimes); 1.1044 + 1.1045 + mHasChanged = true; 1.1046 + 1.1047 + if (!nsSMILParserUtils::ParseSemicolonDelimitedProgressList(aKeyTimes, true, 1.1048 + mKeyTimes)) { 1.1049 + mKeyTimes.Clear(); 1.1050 + return NS_ERROR_FAILURE; 1.1051 + } 1.1052 + 1.1053 + return NS_OK; 1.1054 +} 1.1055 + 1.1056 +void 1.1057 +nsSMILAnimationFunction::UnsetKeyTimes() 1.1058 +{ 1.1059 + mKeyTimes.Clear(); 1.1060 + SetKeyTimesErrorFlag(false); 1.1061 + mHasChanged = true; 1.1062 +}