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